From 1dcf9100ba1570c5a45a5e41bbdda6e17af8764c Mon Sep 17 00:00:00 2001 From: ShriprasadM Date: Wed, 5 May 2021 12:14:47 +0530 Subject: [PATCH 01/31] OTT-105: VCR - Video Event Trackers (#152) VCR - Video Event Trackers changes for PBS Module --- config/config.go | 3 +- endpoints/cookie_sync.go | 11 +- endpoints/events/vtrack.go | 235 ++++++++ endpoints/events/vtrack_test.go | 570 +++++++++++++++++- .../ctv/response/adpod_generator_test.go | 313 +++++++++- endpoints/openrtb2/ctv_auction.go | 47 ++ endpoints/openrtb2/ctv_auction_test.go | 93 +++ exchange/auction.go | 19 +- exchange/auction_test.go | 14 + exchange/events.go | 23 +- exchange/events_test.go | 107 ++++ exchange/exchange.go | 4 +- go.mod | 2 +- go.sum | 6 + openrtb_ext/request.go | 4 + 15 files changed, 1423 insertions(+), 28 deletions(-) diff --git a/config/config.go b/config/config.go index c1ad4426e28..d9d2b331bb9 100755 --- a/config/config.go +++ b/config/config.go @@ -79,7 +79,8 @@ type Configuration struct { // RequestValidation specifies the request validation options. RequestValidation RequestValidation `mapstructure:"request_validation"` // When true, PBS will assign a randomly generated UUID to req.Source.TID if it is empty - AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"` + AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"` + TrackerURL string `mapstructure:"tracker_url"` } const MIN_COOKIE_SIZE_BYTES = 500 diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index f32d823fe1d..aaa7a2425a3 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -5,11 +5,16 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" + "math/rand" + "net/http" + "strconv" + "github.com/PubMatic-OpenWrap/prebid-server/analytics" "github.com/PubMatic-OpenWrap/prebid-server/config" "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/PubMatic-OpenWrap/prebid-server/metrics" + "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/PubMatic-OpenWrap/prebid-server/privacy" "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" gdprPrivacy "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" @@ -17,10 +22,6 @@ import ( "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" - "io/ioutil" - "math/rand" - "net/http" - "strconv" ) func NewCookieSyncEndpoint( diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index 90e597ab7fc..8ef09f50b78 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -3,20 +3,27 @@ package events import ( "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" "net/http" + "net/url" "strings" "time" + "github.com/PubMatic-OpenWrap/openrtb" accountService "github.com/PubMatic-OpenWrap/prebid-server/account" "github.com/PubMatic-OpenWrap/prebid-server/adapters" "github.com/PubMatic-OpenWrap/prebid-server/analytics" "github.com/PubMatic-OpenWrap/prebid-server/config" "github.com/PubMatic-OpenWrap/prebid-server/errortypes" + "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + + // "github.com/beevik/etree" + "github.com/PubMatic-OpenWrap/etree" "github.com/golang/glog" "github.com/julienschmidt/httprouter" ) @@ -46,6 +53,40 @@ type CacheObject struct { UUID string `json:"uuid"` } +// 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-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]" +) + +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", +} + func NewVTrackEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, cache prebid_cache_client.Client, bidderInfos adapters.BidderInfos) httprouter.Handle { vte := &vtrackEndpoint{ Cfg: cfg, @@ -302,3 +343,197 @@ func ModifyVastXmlJSON(externalUrl string, data json.RawMessage, bidid, bidder, } return json.RawMessage(vast) } + +//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 *openrtb.Bid, bidder, accountID string, timestamp int64, bidRequest *openrtb.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]*openrtb.Imp) + for i := range bidRequest.Imp { + impMap[bidRequest.Imp[i].ID] = &bidRequest.Imp[i] + } + + eventURLMap := GetVideoEventTracking(trackerURL, bid, bidder, 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 openrtb.VideoLinearityLinearInStream: + creative.AddChild(doc.CreateElement("Linear")) + case openrtb.VideoLinearityNonLinearOverlay: + 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 *openrtb.Bid, bidder string, accountId string, timestamp int64, req *openrtb.BidRequest, doc *etree.Document, impMap map[string]*openrtb.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, req.Site.Domain) + eventURL = replaceMacro(eventURL, VASTPageURLMacro, req.Site.Page) + if nil != req.Site.Publisher { + eventURL = replaceMacro(eventURL, PBSAccountMacro, req.Site.Publisher.ID) + } + } + + if len(bid.ADomain) > 0 { + //eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, strings.Join(bid.ADomain, ",")) + domain, err := extractDomain(bid.ADomain[0]) + if nil == err { + eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, domain) + } else { + glog.Warningf("Unable to extract domain from '%s'. [%s]", bid.ADomain[0], err.Error()) + } + } + + eventURL = replaceMacro(eventURL, PBSBidderMacro, bidder) + eventURL = replaceMacro(eventURL, PBSBidIDMacro, 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) + } + eventURLMap[event] = eventURL + } + return eventURLMap +} + +func replaceMacro(trackerURL, macro, value string) string { + macro = strings.TrimSpace(macro) + if strings.HasPrefix(macro, "[") && strings.HasSuffix(macro, "]") && len(strings.TrimSpace(value)) > 0 { + trackerURL = strings.ReplaceAll(trackerURL, macro, url.QueryEscape(value)) + } 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 +} diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 52665e7736d..3d2bca3bb4d 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -5,15 +5,20 @@ import ( "context" "encoding/json" "fmt" + "io/ioutil" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/PubMatic-OpenWrap/etree" + // "github.com/beevik/etree" + "github.com/PubMatic-OpenWrap/openrtb" "github.com/PubMatic-OpenWrap/prebid-server/adapters" "github.com/PubMatic-OpenWrap/prebid-server/config" "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" "github.com/stretchr/testify/assert" - "io/ioutil" - "net/http/httptest" - "strings" - "testing" ) const ( @@ -690,3 +695,560 @@ func getVTrackRequestData(wi bool, wic bool) (db []byte, e error) { return data.Bytes(), e } + +func TestInjectVideoEventTrackers(t *testing.T) { + type args struct { + externalURL string + bid *openrtb.Bid + req *openrtb.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: &openrtb.Bid{ + AdM: ` + + + + http://example.com/tracking/midpoint + http://example.com/tracking/thirdQuartile + http://example.com/tracking/complete + http://partner.tracking.url + + + `, + }, + req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag + AdM: ` + + + http://something.com + + + `, + }, + req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag + AdM: ` + + + `, + }, + req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag + AdM: ` + + + iabtechlab + http://somevasturl + + + + + + `, + }, + req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag + // AdM: ``, + // }, + // req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag + // AdM: "", + // NURL: "nurl_contents", + // }, + // req: &openrtb.BidRequest{App: &openrtb.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 = []openrtb.Imp{{ID: "123", Video: &openrtb.Video{}}} + tc.args.bid.ImpID = tc.args.req.Imp[0].ID + accountID := "" + timestamp := int64(0) + biddername := "test_bidder" + injectedVast, injected, ierr := InjectVideoEventTrackers(tc.args.externalURL, vast, tc.args.bid, biddername, 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 *openrtb.Bid + bidder string + accountId string + timestamp int64 + req *openrtb.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: &openrtb.Bid{ + // AdM: vastXMLWith2Creatives, + }, + req: &openrtb.BidRequest{ + App: &openrtb.App{ + Bundle: "someappbundle", + }, + Imp: []openrtb.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: &openrtb.Bid{}, + req: &openrtb.BidRequest{ + App: &openrtb.App{}, // no app bundle value + Imp: []openrtb.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=[DOMAIN]", + "midpoint": "http://company.tracker.com?eventId=3&appbundle=[DOMAIN]", + "thirdQuartile": "http://company.tracker.com?eventId=5&appbundle=[DOMAIN]", + "start": "http://company.tracker.com?eventId=2&appbundle=[DOMAIN]", + "complete": "http://company.tracker.com?eventId=6&appbundle=[DOMAIN]"}, + }, + }, + { + name: "prefer_company_value_for_standard_macro", + args: args{ + trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", + req: &openrtb.BidRequest{ + App: &openrtb.App{ + Bundle: "myapp", // do not expect this value + }, + Imp: []openrtb.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: &openrtb.BidRequest{ + App: &openrtb.App{ + Bundle: "myapp123", + }, + Imp: []openrtb.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: &openrtb.BidRequest{ + Ext: []byte(`{"prebid":{ + "macros": { + "CUSTOM_MACRO": "my_custom_value" + } + }}`), + Imp: []openrtb.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: &openrtb.BidRequest{ + Ext: []byte(`{"prebid":{ + "macros": { + "": "my_custom_value" + } + }}`), + Imp: []openrtb.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: &openrtb.BidRequest{ + Ext: []byte(`{"prebid":{ + "macros": { + "": "my_custom_value" + } + }}`), + Imp: []openrtb.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: &openrtb.BidRequest{Imp: []openrtb.Imp{}}}, + want: want{trackerURLMap: make(map[string]string)}, + }, + { + name: "all_macros", // 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]", + req: &openrtb.BidRequest{ + App: &openrtb.App{Bundle: "com.someapp.com", Publisher: &openrtb.Publisher{ID: "5890"}}, + Ext: []byte(`{ + "prebid": { + "macros": { + "[PROFILE_ID]": "100", + "[PROFILE_VERSION]": "2", + "[UNIX_TIMESTAMP]": "1234567890", + "[PLATFORM]": "7", + "[WRAPPER_IMPRESSION_ID]": "abc~!@#$%^&&*()_+{}|:\"<>?[]\\;',./" + } + } + }`), + Imp: []openrtb.Imp{ + {TagID: "/testadunit/1", ID: "imp_1"}, + }, + }, + bid: &openrtb.Bid{ADomain: []string{"http://a.com/32?k=v", "b.com"}, ImpID: "imp_1", ID: "test_bid_id"}, + bidder: "test_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_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", + "midpoint": "https://company.tracker.com?operId=8&e=3&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", + "thirdQuartile": "https://company.tracker.com?operId=8&e=5&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", + "complete": "https://company.tracker.com?operId=8&e=6&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", + "start": "https://company.tracker.com?operId=8&e=2&p=5890&pid=100&v=2&ts=1234567890&pn=test_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"}, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + + if nil == tc.args.bid { + tc.args.bid = &openrtb.Bid{} + } + + impMap := map[string]*openrtb.Imp{} + + for _, imp := range tc.args.req.Imp { + impMap[imp.ID] = &imp + } + + eventURLMap := GetVideoEventTracking(tc.args.trackerURL, tc.args.bid, tc.args.bidder, 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=[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/openrtb2/ctv/response/adpod_generator_test.go b/endpoints/openrtb2/ctv/response/adpod_generator_test.go index ac18b316c85..932de32d6e0 100644 --- a/endpoints/openrtb2/ctv/response/adpod_generator_test.go +++ b/endpoints/openrtb2/ctv/response/adpod_generator_test.go @@ -1,12 +1,13 @@ package response import ( + "sort" "testing" - "github.com/stretchr/testify/assert" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/constant" "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types" + "github.com/stretchr/testify/assert" ) func Test_findUniqueCombinations(t *testing.T) { @@ -85,3 +86,311 @@ func Test_findUniqueCombinations(t *testing.T) { }) } } + +func TestAdPodGenerator_getMaxAdPodBid(t *testing.T) { + type fields struct { + request *openrtb.BidRequest + impIndex int + } + type args struct { + results []*highestCombination + } + tests := []struct { + name string + fields fields + args args + want *types.AdPodBid + }{ + { + name: `EmptyResults`, + fields: fields{ + request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: nil, + }, + want: nil, + }, + { + name: `AllBidsFiltered`, + fields: fields{ + request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + filteredBids: map[string]*filteredBid{ + `bid-1`: {bid: &types.Bid{Bid: &openrtb.Bid{ID: `bid-1`}}, reasonCode: constant.CTVRCCategoryExclusion}, + `bid-2`: {bid: &types.Bid{Bid: &openrtb.Bid{ID: `bid-2`}}, reasonCode: constant.CTVRCCategoryExclusion}, + `bid-3`: {bid: &types.Bid{Bid: &openrtb.Bid{ID: `bid-3`}}, reasonCode: constant.CTVRCCategoryExclusion}, + }, + }, + }, + }, + want: nil, + }, + { + name: `SingleResponse`, + fields: fields{ + request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-1`}}, + {Bid: &openrtb.Bid{ID: `bid-2`}}, + {Bid: &openrtb.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: &openrtb.Bid{ID: `bid-4`}}, reasonCode: constant.CTVRCCategoryExclusion}, + }, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-1`}}, + {Bid: &openrtb.Bid{ID: `bid-2`}}, + {Bid: &openrtb.Bid{ID: `bid-3`}}, + }, + Cat: []string{`cat-1`, `cat-2`}, + ADomain: []string{`domain-1`, `domain-2`}, + Price: 20, + }, + }, + { + name: `MultiResponse-AllNonDealBids`, + fields: fields{ + request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-11`}}, + }, + bidIDs: []string{`bid-11`}, + price: 10, + nDealBids: 0, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-21`}}, + }, + bidIDs: []string{`bid-21`}, + price: 20, + nDealBids: 0, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-31`}}, + }, + bidIDs: []string{`bid-31`}, + price: 10, + nDealBids: 0, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-41`}}, + }, + bidIDs: []string{`bid-41`}, + price: 15, + nDealBids: 0, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-21`}}, + }, + Cat: []string{}, + ADomain: []string{}, + Price: 20, + }, + }, + { + name: `MultiResponse-AllDealBids-SameCount`, + fields: fields{ + request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-11`}}, + }, + bidIDs: []string{`bid-11`}, + price: 10, + nDealBids: 1, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-21`}}, + }, + bidIDs: []string{`bid-21`}, + price: 20, + nDealBids: 1, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-31`}}, + }, + bidIDs: []string{`bid-31`}, + price: 10, + nDealBids: 1, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-41`}}, + }, + bidIDs: []string{`bid-41`}, + price: 15, + nDealBids: 1, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-21`}}, + }, + Cat: []string{}, + ADomain: []string{}, + Price: 20, + }, + }, + { + name: `MultiResponse-AllDealBids-DifferentCount`, + fields: fields{ + request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-11`}}, + }, + bidIDs: []string{`bid-11`}, + price: 10, + nDealBids: 2, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-21`}}, + }, + bidIDs: []string{`bid-21`}, + price: 20, + nDealBids: 1, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-31`}}, + }, + bidIDs: []string{`bid-31`}, + price: 10, + nDealBids: 3, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-41`}}, + }, + bidIDs: []string{`bid-41`}, + price: 15, + nDealBids: 2, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-31`}}, + }, + Cat: []string{}, + ADomain: []string{}, + Price: 10, + }, + }, + { + name: `MultiResponse-Mixed-DealandNonDealBids`, + fields: fields{ + request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, + impIndex: 0, + }, + args: args{ + results: []*highestCombination{ + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-11`}}, + }, + bidIDs: []string{`bid-11`}, + price: 10, + nDealBids: 2, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-21`}}, + }, + bidIDs: []string{`bid-21`}, + price: 20, + nDealBids: 0, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-31`}}, + }, + bidIDs: []string{`bid-31`}, + price: 10, + nDealBids: 3, + }, + { + bids: []*types.Bid{ + {Bid: &openrtb.Bid{ID: `bid-41`}}, + }, + bidIDs: []string{`bid-41`}, + price: 15, + nDealBids: 0, + }, + }, + }, + want: &types.AdPodBid{ + Bids: []*types.Bid{ + {Bid: &openrtb.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_auction.go b/endpoints/openrtb2/ctv_auction.go index 670bb4c438a..4e9d5b46438 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "net/http" + "net/url" "sort" "strconv" "strings" @@ -17,6 +18,7 @@ import ( accountService "github.com/PubMatic-OpenWrap/prebid-server/account" "github.com/PubMatic-OpenWrap/prebid-server/analytics" "github.com/PubMatic-OpenWrap/prebid-server/config" + "github.com/PubMatic-OpenWrap/prebid-server/endpoints/events" "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/combination" "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/constant" "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/impressions" @@ -828,6 +830,15 @@ func getAdPodBidCreative(video *openrtb.Video, adpod *types.AdPodBid) *string { continue } + // 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 @@ -853,6 +864,7 @@ func getAdPodBidCreative(video *openrtb.Video, adpod *types.AdPodBid) *string { } vast.CreateAttr(constant.VASTVersionAttribute, constant.VASTVersionsStr[int(version)]) + bidAdM, err := doc.WriteToString() if nil != err { fmt.Printf("ERROR, %v", err.Error()) @@ -907,3 +919,38 @@ func addTargetingKey(bid *openrtb.Bid, key openrtb_ext.TargetingKey, value strin } return err } + +func adjustBidIDInVideoEventTrackers(doc *etree.Document, bid *openrtb.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()) + } + } + } + } +} diff --git a/endpoints/openrtb2/ctv_auction_test.go b/endpoints/openrtb2/ctv_auction_test.go index 1e6a0907c72..db3438accfb 100644 --- a/endpoints/openrtb2/ctv_auction_test.go +++ b/endpoints/openrtb2/ctv_auction_test.go @@ -2,8 +2,12 @@ package openrtb2 import ( "encoding/json" + "fmt" + "net/url" + "strings" "testing" + "github.com/PubMatic-OpenWrap/etree" "github.com/PubMatic-OpenWrap/openrtb" "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" @@ -56,3 +60,92 @@ func TestAddTargetingKeys(t *testing.T) { } assert.Equal(t, "Invalid bid", addTargetingKey(nil, openrtb_ext.HbCategoryDurationKey, "some value").Error()) } + +func TestAdjustBidIDInVideoEventTrackers(t *testing.T) { + type args struct { + modifiedBid *openrtb.Bid + } + type want struct { + eventURLMap map[string]string + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "replace_with_custom_ctv_bid_id", + want: want{ + eventURLMap: map[string]string{ + "thirdQuartile": "https://thirdQuartile.com?operId=8&key1=value1&bidid=1-bid_123", + "complete": "https://complete.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", + "firstQuartile": "https://firstQuartile.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", + "midpoint": "https://midpoint.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", + "someevent": "https://othermacros?bidid=bid_123&abc=pqr", + }, + }, + args: args{ + modifiedBid: &openrtb.Bid{ + ID: "1-bid_123", + AdM: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + }, + }, + }, + } + for _, test := range tests { + doc := etree.NewDocument() + doc.ReadFromString(test.args.modifiedBid.AdM) + adjustBidIDInVideoEventTrackers(doc, test.args.modifiedBid) + events := doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/Linear/TrackingEvents/Tracking") + for _, event := range events { + evntName := event.SelectAttr("event").Value + expectedURL, _ := url.Parse(test.want.eventURLMap[evntName]) + expectedValues := expectedURL.Query() + actualURL, _ := url.Parse(event.Text()) + actualValues := actualURL.Query() + 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' [Event = %v]. but found %v", ev[i], k, evntName, av[i])) + } + } + + // check if operId=8 is first param + if evntName != "someevent" { + assert.True(t, strings.HasPrefix(actualURL.RawQuery, "operId=8"), "operId=8 must be first query param") + } + } + } +} diff --git a/exchange/auction.go b/exchange/auction.go index b8143d4e1a8..43fea247950 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -283,12 +283,19 @@ 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 *openrtb.Bid) string { - if bid.AdM == "" { - return `` + - `prebid.org wrapper` + - `` + - `` + - `` + wrapperVASTTemplate := `` + + `prebid.org wrapper` + + `` + + `` + + `` + adm := bid.AdM + + if adm == "" { + return fmt.Sprintf(wrapperVASTTemplate, bid.NURL) // set nurl as VASTAdTagURI + } + + if strings.HasPrefix(adm, "http") { // check if it contains URL + return fmt.Sprintf(wrapperVASTTemplate, adm) // set adm as VASTAdTagURI } return bid.AdM } diff --git a/exchange/auction_test.go b/exchange/auction_test.go index ba9f12077c9..a3ef7eab077 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -42,6 +42,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 := &openrtb.Bid{ + AdM: url, + } + vast := makeVAST(bid) + assert.Equal(t, expect, vast) +} + func TestBuildCacheString(t *testing.T) { testCases := []struct { description string diff --git a/exchange/events.go b/exchange/events.go index 99b494839df..128b6778364 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -4,6 +4,7 @@ import ( "encoding/json" "time" + "github.com/PubMatic-OpenWrap/openrtb" "github.com/PubMatic-OpenWrap/prebid-server/adapters" "github.com/PubMatic-OpenWrap/prebid-server/analytics" "github.com/PubMatic-OpenWrap/prebid-server/config" @@ -38,13 +39,13 @@ func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Ti } // modifyBidsForEvents adds bidEvents and modifies VAST AdM if necessary. -func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid) map[openrtb_ext.BidderName]*pbsOrtbSeatBid { +func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, req *openrtb.BidRequest, trackerURL string) map[openrtb_ext.BidderName]*pbsOrtbSeatBid { for bidderName, seatBid := range seatBids { - modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String()) + // modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String()) for _, pbsBid := range seatBid.bids { - if modifyingVastXMLAllowed { - ev.modifyBidVAST(pbsBid, bidderName) - } + // if modifyingVastXMLAllowed { + ev.modifyBidVAST(pbsBid, bidderName, req, trackerURL) + // } pbsBid.bidEvents = ev.makeBidExtEvents(pbsBid, bidderName) } } @@ -57,14 +58,20 @@ func (ev *eventTracking) isModifyingVASTXMLAllowed(bidderName string) bool { } // modifyBidVAST injects event Impression url if needed, otherwise returns original VAST string -func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName) { +func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName, req *openrtb.BidRequest, trackerURL string) { bid := pbsBid.bid if pbsBid.bidType != openrtb_ext.BidTypeVideo || len(bid.AdM) == 0 && len(bid.NURL) == 0 { return } vastXML := makeVAST(bid) - if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bid.ID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); ok { - bid.AdM = newVastXML + if ev.isModifyingVASTXMLAllowed(bidderName.String()) { // condition added for ow fork + if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bid.ID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); ok { + bid.AdM = newVastXML + } + } + // always inject event trackers without checkign isModifyingVASTXMLAllowed + if newVastXML, injected, _ := events.InjectVideoEventTrackers(trackerURL, vastXML, bid, bidderName.String(), ev.accountID, ev.auctionTimestampMs, req); injected { + bid.AdM = string(newVastXML) } } diff --git a/exchange/events_test.go b/exchange/events_test.go index 3315dfaae7b..a5e7d719510 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -1,9 +1,11 @@ package exchange import ( + "strings" "testing" "github.com/PubMatic-OpenWrap/openrtb" + "github.com/PubMatic-OpenWrap/prebid-server/adapters" "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -136,3 +138,108 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { }) } } + +func TestModifyBidVAST(t *testing.T) { + type args struct { + bidReq *openrtb.BidRequest + bid *openrtb.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: &openrtb.BidRequest{ + Imp: []openrtb.Imp{{ID: "123", Video: &openrtb.Video{}}}, + }, + bid: &openrtb.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: &openrtb.BidRequest{ + Imp: []openrtb.Imp{{ID: "123", Video: &openrtb.Video{}}}, + }, + bid: &openrtb.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: adapters.BidderInfos{ + "somebidder": adapters.BidderInfo{ + ModifyingVastXmlAllowed: false, + }, + }, + } + ev.modifyBidVAST(&pbsOrtbBid{ + bid: tc.args.bid, + bidType: openrtb_ext.BidTypeVideo, + }, "somebidder", tc.args.bidReq, "http://company.tracker.com?e=[EVENT_ID]") + validator(t, tc.args.bid, tc.want.tags) + }) + } +} + +func validator(t *testing.T, b *openrtb.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 43866d230cc..f474531523e 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -61,6 +61,7 @@ type exchange struct { UsersyncIfAmbiguous bool privacyConfig config.Privacy categoriesFetcher stored_requests.CategoryFetcher + trakerURL string } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -95,6 +96,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid GDPR: cfg.GDPR, LMT: cfg.LMT, }, + trakerURL: cfg.TrackerURL, } } @@ -191,7 +193,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } evTracking := getEventTracking(&requestExt.Prebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL) - adapterBids = evTracking.modifyBidsForEvents(adapterBids) + adapterBids = evTracking.modifyBidsForEvents(adapterBids, r.BidRequest, e.trakerURL) if targData != nil { // A non-nil auction is only needed if targeting is active. (It is used below this block to extract cache keys) diff --git a/go.mod b/go.mod index 92396ff96d3..b64f13db2a1 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/DATA-DOG/go-sqlmock v1.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/PubMatic-OpenWrap/etree v1.0.1 + github.com/PubMatic-OpenWrap/etree v1.0.2-0.20210129100623-8f30cfecf9f4 github.com/PubMatic-OpenWrap/openrtb v11.0.1-0.20200228131822-5216ebe65c0c+incompatible github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect diff --git a/go.sum b/go.sum index 9bc40d960c8..0b055532881 100644 --- a/go.sum +++ b/go.sum @@ -8,10 +8,16 @@ github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/PubMatic-OpenWrap/etree v1.0.1 h1:Q8sZ99MuXKmAx2v4XThKjwlstgadZffiRbNwUG0Ey1U= github.com/PubMatic-OpenWrap/etree v1.0.1/go.mod h1:5Y8qgcuDoy3XXG907UXkGnVTwihF16rXyJa4zRT7hOE= +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/openrtb v11.0.1-0.20200228131822-5216ebe65c0c+incompatible h1:BGwndVLu0ncwweHnofXzLo+SnRMe04Bq3KFfELLzif4= github.com/PubMatic-OpenWrap/openrtb v11.0.1-0.20200228131822-5216ebe65c0c+incompatible/go.mod h1:Ply/+GFe6FLkPMLV8Yh8xW0MpqclQyVf7m4PRsnaLDY= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +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/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c h1:uYq6BD31fkfeNKQmfLj7ODcEfkb5JLsKrXVSqgnfGg8= +github.com/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c/go.mod h1:0yGO2rna3S9DkITDWHY1bMtcY4IJ4w+4S+EooZUR0bE= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 3af4fe4eb9b..57bef59d711 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -33,6 +33,10 @@ type ExtRequestPrebid struct { // passing of personally identifiable information doesn't constitute a sale per CCPA law. // The array may contain a single sstar ('*') entry to represent all bidders. NoSale []string `json:"nosale,omitempty"` + + // Macros specifies list of custom macros along with the values. This is used while forming + // the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding + Macros map[string]string `json:"macros,omitempty"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains From c360511ffa6e22f754ee02328d857ccbf9f1d804 Mon Sep 17 00:00:00 2001 From: Pubmatic-Dhruv-Sonone <83747371+Pubmatic-Dhruv-Sonone@users.noreply.github.com> Date: Tue, 11 May 2021 18:53:14 +0530 Subject: [PATCH 02/31] OTT-172: Set default min ads to 1 from 2 (#153) * OTT-172: Set default min ads to 1 from 2 * Adding test cases for impression generation algorithm --- .../ctv/impressions/impression_generator.go | 2 +- .../impressions/maximize_for_duration_test.go | 25 ++++++++++++ .../ctv/impressions/min_max_algorithm_test.go | 40 +++++++++++++++---- .../ctv/impressions/testdata/input.go | 4 ++ .../ctv/impressions/testdata/output.go | 16 ++++++++ openrtb_ext/adpod.go | 2 +- 6 files changed, 80 insertions(+), 9 deletions(-) diff --git a/endpoints/openrtb2/ctv/impressions/impression_generator.go b/endpoints/openrtb2/ctv/impressions/impression_generator.go index eb195b39f56..5ad0ff404ea 100644 --- a/endpoints/openrtb2/ctv/impressions/impression_generator.go +++ b/endpoints/openrtb2/ctv/impressions/impression_generator.go @@ -147,7 +147,7 @@ func computeTimeForEachAdSlot(cfg generator, totalAds int64) int64 { // 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.slotMinDuration = %v. Hence, not computing multiples of %v value.", cfg.requested.slotMaxDuration, multipleOf) + util.Logf("requested.slotMinDuration = requested.slotMaxDuration = %v. Hence, not computing multiples of %v value.", cfg.requested.slotMaxDuration, multipleOf) return timeForEachSlot } diff --git a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go index 84f3304fb6d..6b7785638ee 100644 --- a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go +++ b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go @@ -384,6 +384,31 @@ var impressionsTests = []struct { 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, }}, } diff --git a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go index 5928b430924..01a062fab49 100644 --- a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go +++ b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go @@ -423,7 +423,34 @@ var impressionsTestsA2 = []struct { 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{}, @@ -470,7 +497,6 @@ var impressionsTestsA2 = []struct { // // 60, 60, 15, 45, 2, 2 // step5: [][2]int64{{30, 30}, {30, 30}}, // }}, - } func TestGetImpressionsA2(t *testing.T) { @@ -486,23 +512,23 @@ func TestGetImpressionsA2(t *testing.T) { 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 + } } diff --git a/endpoints/openrtb2/ctv/impressions/testdata/input.go b/endpoints/openrtb2/ctv/impressions/testdata/input.go index 8c7ae520f8c..3ee64544b95 100644 --- a/endpoints/openrtb2/ctv/impressions/testdata/input.go +++ b/endpoints/openrtb2/ctv/impressions/testdata/input.go @@ -54,4 +54,8 @@ var Input = map[string][]int{ "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 index 7b97c56f2bc..d7e854fc575 100644 --- a/endpoints/openrtb2/ctv/impressions/testdata/output.go +++ b/endpoints/openrtb2/ctv/impressions/testdata/output.go @@ -217,4 +217,20 @@ var Scenario = map[string]eout{ 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/openrtb_ext/adpod.go b/openrtb_ext/adpod.go index 03b973b6b5f..ac815cda224 100644 --- a/openrtb_ext/adpod.go +++ b/openrtb_ext/adpod.go @@ -184,7 +184,7 @@ func (ext *ExtVideoAdPod) Validate() (err []error) { func (pod *VideoAdPod) SetDefaultValue() { //pod.MinAds setting default value if nil == pod.MinAds { - pod.MinAds = getIntPtr(2) + pod.MinAds = getIntPtr(1) } //pod.MaxAds setting default value From d7f2b67ee6ea9d789919fe0300fd7a076f091bf1 Mon Sep 17 00:00:00 2001 From: PubMatic-OpenWrap Date: Wed, 12 May 2021 10:41:35 +0530 Subject: [PATCH 03/31] Revert "OTT-172: Set default min ads to 1 from 2 (#153)" (#154) This reverts commit c360511ffa6e22f754ee02328d857ccbf9f1d804. --- .../ctv/impressions/impression_generator.go | 2 +- .../impressions/maximize_for_duration_test.go | 25 ------------ .../ctv/impressions/min_max_algorithm_test.go | 40 ++++--------------- .../ctv/impressions/testdata/input.go | 4 -- .../ctv/impressions/testdata/output.go | 16 -------- openrtb_ext/adpod.go | 2 +- 6 files changed, 9 insertions(+), 80 deletions(-) diff --git a/endpoints/openrtb2/ctv/impressions/impression_generator.go b/endpoints/openrtb2/ctv/impressions/impression_generator.go index 5ad0ff404ea..eb195b39f56 100644 --- a/endpoints/openrtb2/ctv/impressions/impression_generator.go +++ b/endpoints/openrtb2/ctv/impressions/impression_generator.go @@ -147,7 +147,7 @@ func computeTimeForEachAdSlot(cfg generator, totalAds int64) int64 { // 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) + util.Logf("requested.slotMinDuration = requested.slotMinDuration = %v. Hence, not computing multiples of %v value.", cfg.requested.slotMaxDuration, multipleOf) return timeForEachSlot } diff --git a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go index 6b7785638ee..84f3304fb6d 100644 --- a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go +++ b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go @@ -384,31 +384,6 @@ var impressionsTests = []struct { 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, }}, } diff --git a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go index 01a062fab49..5928b430924 100644 --- a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go +++ b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go @@ -423,34 +423,7 @@ var impressionsTestsA2 = []struct { 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{}, @@ -497,6 +470,7 @@ var impressionsTestsA2 = []struct { // // 60, 60, 15, 45, 2, 2 // step5: [][2]int64{{30, 30}, {30, 30}}, // }}, + } func TestGetImpressionsA2(t *testing.T) { @@ -512,23 +486,23 @@ func TestGetImpressionsA2(t *testing.T) { 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 } } diff --git a/endpoints/openrtb2/ctv/impressions/testdata/input.go b/endpoints/openrtb2/ctv/impressions/testdata/input.go index 3ee64544b95..8c7ae520f8c 100644 --- a/endpoints/openrtb2/ctv/impressions/testdata/input.go +++ b/endpoints/openrtb2/ctv/impressions/testdata/input.go @@ -54,8 +54,4 @@ var Input = map[string][]int{ "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 index d7e854fc575..7b97c56f2bc 100644 --- a/endpoints/openrtb2/ctv/impressions/testdata/output.go +++ b/endpoints/openrtb2/ctv/impressions/testdata/output.go @@ -217,20 +217,4 @@ var Scenario = map[string]eout{ 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/openrtb_ext/adpod.go b/openrtb_ext/adpod.go index ac815cda224..03b973b6b5f 100644 --- a/openrtb_ext/adpod.go +++ b/openrtb_ext/adpod.go @@ -184,7 +184,7 @@ func (ext *ExtVideoAdPod) Validate() (err []error) { func (pod *VideoAdPod) SetDefaultValue() { //pod.MinAds setting default value if nil == pod.MinAds { - pod.MinAds = getIntPtr(1) + pod.MinAds = getIntPtr(2) } //pod.MaxAds setting default value From 41057252aed3f41ffa90fcf218d967c0ff2529f1 Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Fri, 14 May 2021 14:52:15 +0530 Subject: [PATCH 04/31] UOE-6319: OpenWrap S2S: Prebid Server Version Update to 0.157.0 (#146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove redundad struct (#1432) * Tcf2 id support (#1420) * Default TCF1 GVL in anticipation of IAB no longer hosting the v1 GVL (#1433) * update to the latest go-gdpr release (#1436) * Video endpoint bid selection enhancements (#1419) Co-authored-by: Veronika Solovei * [WIP] Bid deduplication enhancement (#1430) Co-authored-by: Veronika Solovei * Refactor rate converter separating scheduler from converter logic to improve testability (#1394) * Fix TCF1 Fetcher Fallback (#1438) * Eplanning adapter: Get domain from page (#1434) * Fix no bid debug log (#1375) * Update the fallback GVL to last version (#1440) * Enable geo activation of GDPR flag (#1427) * Validate External Cache Host (#1422) * first draft * Little tweaks * Scott's review part 1 * Scott's review corrections part 2 * Scotts refactor * correction in config_test.go * Correction and refactor * Multiple return statements * Test case refactor Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * Fixes bug (#1448) * Fixes bug * shortens list * Added adpod_id to request extension (#1444) * Added adpod_id to request -> ext -> appnexus and modified requests splitting based on pod * Unit test fix * Unit test fix * Minor unit test fixes * Code refactoring * Minor code and unit tests refactoring * Unit tests refactoring Co-authored-by: Veronika Solovei * Adform adapter: additional targeting params added (#1424) * Fix minor error message spelling mistake "vastml" -> "vastxml" (#1455) * Fixing comment for usage of deal priority field (#1451) * moving docs to website repo (#1443) * Fix bid dedup (#1456) Co-authored-by: Veronika Solovei * consumable: Correct width and height reported in response. (#1459) Prebid Server now responds with the width and height specified in the Bid Response from Consumable. Previously it would reuse the width and height specified in the Bid Request. That older behaviour was ported from an older version of the prebid.js adapter but is no longer valid. * Panics happen when left with zero length []Imp (#1462) * Add Scheme Option To External Cache URL (#1460) * Update gamma adapter (#1447) * Gamma SSP Adapter * Add Gamma SSP server adapter * increase coverage * Fix conflict with base master * Add check MediaType for Imp * Implement Multi Imps request * Changes requested * remove bad-request * increase coverage * Remove duplicate test file * Update gamma.go * Update gamma.go * Update gamma.go * Update config.go Remove Gamma User Sync Url from config * Gamma SSP Adapter * Add Gamma SSP server adapter * increase coverage * Fix conflict with base master * Add check MediaType for Imp * Implement Multi Imps request * Changes requested * remove bad-request * increase coverage * Remove duplicate test file * Update gamma.go * Update gamma.go * update gamma adapter * return nil when have No-Bid Signaling * add missing-adm.json * discard the bid that's missing adm * discard the bid that's missing adm * escape vast instead of encoded it * expand test coverage Co-authored-by: Easy Life * fix: avoid unexpected EOF on gz writer (#1449) * Smaato adapter: support for video mediaType (#1463) Co-authored-by: vikram * Rubicon liveramp param (#1466) Add liveramp mapping to user.ext should translate the "liveramp.com" id from the "user.ext.eids" array to "user.ext.liveramp_idl" as follows: ``` { "user": { "ext": { "eids": [{ "source": 'liveramp.com', "uids": [{ "id": "T7JiRRvsRAmh88" }] }] } } } ``` to XAPI: ``` { "user": { "ext": { "liveramp_idl": "T7JiRRvsRAmh88" } } } ``` * Consolidate StoredRequest configs, add validation for all data types (#1453) * Fix Test TestEventChannel_OutputFormat (#1468) * Add ability to randomly generate source.TID if empty and set publisher.ID to resolved account ID (#1439) * Add support for Account configuration (PBID-727, #1395) (#1426) * Minor changes to accounts test coverage (#1475) * Brightroll adapter - adding config support (#1461) * Refactor TCF 1/2 Vendor List Fetcher Tests (#1441) * Add validation checker for PRs and merges with github actions (#1476) * Cache refactor (#1431) Reason: Cache has Fetcher-like functionality to handle both requests and imps at a time. Internally, it still uses two caches configured and searched separately, causing some code repetition. Reusing this code to cache other objects like accounts is not easy. Keeping the req/imp repetition in fetcher and out of cache allows for a reusable simpler cache, preserving existing fetcher functionality. Changes in this set: Cache is now a simple generic id->RawMessage store fetcherWithCache handles the separate req and imp caches ComposedCache handles single caches - but it does not appear to be used Removed cache overlap tests since they do not apply now Slightly less code * Pass Through First Party Context Data (#1479) * Added new size 640x360 (Id: 198) (#1490) * Refactor: move getAccount to accounts package (from openrtb2) (#1483) * Fixed TCF2 Geo Only Enforcement (#1492) * New colossus adapter [Clean branch] (#1495) Co-authored-by: Aiholkin * New: InMobi Prebid Server Adapter (#1489) * Adding InMobi adapter * code review feedback, also explicitly working with Imp[0], as we don't support multiple impressions * less tolerant bidder params due to sneaky 1.13 -> 1.14+ change * Revert "Added new size 640x360 (Id: 198) (#1490)" (#1501) This reverts commit fa23f5c226df99a9a4ef318100fdb7d84d3e40fa. * CCPA Publisher No Sale Relationships (#1465) * Fix Merge Conflict (#1502) * Update conversant adapter for new prebid-server interface (#1484) * Implement returnCreative (#1493) * Working solution * clean-up * Test copy/paste error Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * ConnectAd S2S Adapter (#1505) * between adapter (#1437) Co-authored-by: Alexey Elymanov * Invibes adapter (#1469) Co-authored-by: aurel.vasile * Refactor postgres event producer so it will run either the full or de… (#1485) * Refactor postgres event producer so it will run either the full or delta query periodically * Minor cleanup, follow golang conventions, declare const slice, add test cases * Remove comments * Bidder Uniqueness Gatekeeping Test (#1506) * ucfunnel adapter update end point (#1511) * Refactor EEAC map to be more in line with the nonstandard publisher map (#1514) * Added bunch of new sizes (#1516) * New krushmedia bid adapter (#1504) * Invibes: Generic domainId parameter (#1512) * Smarty ads adapter (#1500) Co-authored-by: Kushneryk Pavlo Co-authored-by: user * Add vscode remote container development files (#1481) * First commit (#1510) Co-authored-by: Gus Carreon * Vtrack and event endpoints (#1467) * Rework pubstack module tests to remove race conditions (#1522) * Rework pubstack module tests to remove race conditions * PR feedback * Remove event count and add helper methods to assert events received on channel * Updating smartadserver endpoint configuration. (#1531) Co-authored-by: tadam * Add new size 500x1000 (ID: 548) (#1536) * Fix missing Request parameter for Adgeneration Adapter (#1525) * Fix endpoint url for TheMediaGrid Bid Adapter (#1541) * Add Account cache (#1519) * Add bidder name key support (#1496) * Simplifying exchange module: bidResponseExt gets built anyway (#1518) * first draft * Scott's feedback * stepping out real quick * add cache errors to bidResponseExt before marshalling * Removed vim's swp file Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * Correct GetCpmStringValue's second return value (#1520) * Add metrics to capture stored data fetch all/delta durations with fetch status (#1515) * Adds preferDeals support (#1528) * Emxd 3336 add app video ctv (#1529) * Adapter changes for app and video support * adding ctv devicetype test case * Adding whitespace * Updates based on feedback from Prebid team * protocol bug fix and testing * Modifying test cases to accomodate new imp.ext field * bidtype bug fix and additonal testcase for storeUrl Co-authored-by: Rakesh Balakrishnan Co-authored-by: Dan Bogdan * Add http api for fetching accounts (#1545) * Add missing postgres cache init config validation * Acuity ads adapter (#1537) Co-authored-by: Kushneryk Pavlo * Yieldmo app support in yaml file (#1542) Co-authored-by: Winston * Add metrics for account cache (#1543) * [Invibes] remove user sync for invibes (#1550) * [invibes] new bidder stub * [invibes] make request * [invibes] bid request parameters * [invibes] fix errors, add tests * [invibes] new version of MakeBids * cleaning code * [invibes] production urls, isamp flag * [invibes] fix parameters * [invibes] new test parameter * [invibes] change maintainer email * [invibes] PR fixes * [invibes] fix parameters test * [invibes] refactor endpoint template and bidVersion * [Invibes] fix tests * [invibes] resolve PR * [invibes] fix test * [invibes] fix test * [invibes] generic domainId parameter * [invibes] remove invibes cookie sync * [Invibes] comment missing Usersync Co-authored-by: aurel.vasile * Add Support For imp.ext.prebid For DealTiers (#1539) * Add Support For imp.ext.prebid For DealTiers * Remove Normalization * Add Accounts to http cache events (#1553) * Fix JSON tests ignore expected message field (#1450) * NoBid version 1.0. Initial commit. (#1547) Co-authored-by: Reda Guermas * Added dealTierSatisfied parameters in exchange.pbsOrtbBid and openrtb_ext.ExtBidPrebid and dealPriority in openrtb_ext.ExtBidPrebid (#1558) Co-authored-by: Shriprasad * Add client/AccountID support into Adoppler adapter. (#1535) * Optionally read IFA value and add it the the request url (Adhese) (#1563) * Add AMX RTB adapter (#1549) * update Datablocks usersync.go (#1572) * 33Across: Add video support in adapter (#1557) * SilverMob adapter (#1561) * SilverMob adapter * Fixes andchanges according to notes in PR * Remaining fixes: multibids, expectedMakeRequestsErrors * removed log * removed log * Multi-bid test * Removed unnesesary block Co-authored-by: Anton Nikityuk * Updated ePlanning GVL ID (#1574) * update adpone google vendor id (#1577) * ADtelligent gvlid (#1581) * Add account/ host GDPR enabled flags & account per request type GDPR enabled flags (#1564) * Add account level request type specific and general GDPR enabled flags * Clean up test TestAccountLevelGDPREnabled * Add host-level GDPR enabled flag * Move account GDPR enable check as receiver method on accountGDPR * Remove mapstructure annotations on account structs * Minor test updates * Re-add mapstructure annotations on account structs * Change RequestType to IntegrationType and struct annotation formatting * Update comment * Update account IntegrationType comments * Remove extra space in config/accounts.go via gofmt * DMX Bidfloor fix (#1579) * adform bidder video bid response support (#1573) * Fix Beachfront JSON tests (#1578) * Add account CCPA enabled and per-request-type enabled flags (#1566) * Add account level request-type-specific and general CCPA enabled flags * Remove mapstructure annotations on CCPA account structs and clean up CCPA tests * Adjust account/host CCPA enabled flag logic to incorporate feedback on similar GDPR feature * Add shared privacy policy account integration data structure * Refactor EnabledForIntegrationType methods on account privacy objects * Minor test refactor * Simplify logic in EnabledForIntegrationType methods * Refactored HoldAuction Arguments (#1570) * Fix bug in request.imp.ext Validation (#1575) * First draft * Brian's reivew * Removed leftover comments Co-authored-by: Gus Carreon * Bugfix: default admin port is 6060 (#1595) * Add timestamp to analytics and response.ext.prebid.auctiontimestamp l… (#1584) * Added app capabilities to VerizonMedia adapter (#1596) Co-authored-by: oath-jac * New Adapter Initialization Framework (#1532) * Fix 33Across App Handling (#1602) * Fix adapter JSON tests to have the right test structure (#1589) * Fix JSON EMX Digital * Fix JSON Brightroll * Fix JSON Beintoo * Fix JSON Gamoshi * Fix JSON Kubient * Fix JSON Marsmedia * Fix JSON Nanointeractive * Fix JSON Telaria * Fix JSON valueimpression * Fix JSON smartyads * Fix JSON rhythmone * Fix JSON krushmedia * Fix JSON cpmstar * Fix JSON acuityads * Fix JSON avocet * Rename wrongly named acuity ads test file * Fix JSON gamma * Add expected no bid responses * Fixed indentation and asesome-markup * Added usersync support to Between SSP adapter; Major fixes and refactor (#1587) Co-authored-by: Egor Skorokhodov * Removed Safari Metric (#1571) * Deepintent adapter (#1524) Co-authored-by: Sourabh Gandhe * update mobilefuse endpoint (#1606) Co-authored-by: Dan Barnett * Fix Missing Consumable Clock (#1610) * Remove Hook Scripts (#1614) * Add config gdpr.amp_exception deprecation warning (#1612) * Refactor Adapter Config To Its Own File (#1608) * RP adapter: use video placement parameter to set size ID (#1607) * Add a BidderRequest struct to hold bidder specific request info (#1611) * Add warning that gdpr checks will be skipped when gdpr.host_vendor_id… (#1615) * Add TLS Handshake connection metrics (#1613) * Improve GitHub Actions Validation (#1590) * Move SSL to Server directory (#1625) * Rename currencies to currency (#1626) * Deepintent: Params normalization (#1617) Co-authored-by: Sourabh Gandhe * Set Kubient email to prebid@kubient.com (#1629) * Rename pbsmetrics to metrics (#1624) * 33Across: Add support for multi-imp requests (#1609) * changed usersync endpoint (#1631) Co-authored-by: Sourabh Gandhe * Remove legacy GDPR AMP config flag used to prevent buyer ID scrub on AMP requests (#1565) * Updating contact info for adprime (#1640) * ucfunnel adapter update end point (#1639) * New Adapter: Mobfox (#1585) Co-authored-by: mobfox * IX: Implement Bidder interface, update endpoint. (#1569) Co-authored-by: Index Exchange Prebid Team * Fix GDPR consent assumption when gdpr req signal is unambiguous and s… (#1591) * Fix GDPR consent assumption when gdpr req signal is unambiguous and set to 1 and consent string is blank * Refactor TestAllowPersonalInfo to make it table-based and add empty consent string cases * Update PersonalInfoAllowed to allow PI if the request indicates GDPR does not apply * Update test descriptions * Update default vendor permissions to only allow PI based on UserSyncIfAmbiguous if request GDPR signal is ambiguous * Change GDPR request signal type name and other PR feedback code style changes * Rename GDPR package signal constants and consolidate gdprEnforced and gdprEnabled variables * Hoist GDPR signal/empty consent checks before vendor list check * Rename gdpr to gdprSignal in Permissions interface and implementations * Fix merge mistakes * Update gdpr logic to set the gdpr signal when ambiguous according to the config flag * Add userSyncIfAmbiguous to all test cases in TestAllowPersonalInfo * Simplify TestAllowPersonalInfo * Fix appnexus adapter not setting currency in the bid response (#1642) Co-authored-by: Gus * Add Adot adapter (#1605) Co-authored-by: Aurélien Giudici * Refactor AMP Param Parsing (#1627) * Refactor AMP Param Parsing * Added Tests * Enforce GDPR privacy if there's an error parsing consent (#1593) * Enforce GDPR privacy if there's an error parsing consent * Update test with consent string variables to improve readability * Fix test typo * Update test variable names to follow go conventions * MediaFuse adapter (#1635) * MediaFuse alias * Syncer and tests * gvlid * gvlid * new mail * New Adapter: Revcontent (#1622) * Fix Unruly Bidder Parmaters (#1616) * Implement EID Permissions (#1633) * Implement EID Permissions * Idsync removal (#1644) Co-authored-by: steve-a-districtm Co-authored-by: Steve Alliance * Audit beachfront tests and change some videoResponseType details (#1638) * Adding Events support in bid responses (#1597) * Fix Shared Memory Corruption In EMX_Digital (#1646) * Add gdpr.tcf1.fetch_gvl deprecation warning and update GVL subdomain (#1660) * Bubble up GDPR signal/consent errors while applying privacy policies (#1651) * Always sync when GDPR globally enabled and allow host cookie sync … (#1656) * Eplanning: new prioritization metric for adunit sizes (#1648) * Eplanning: new prioritization metric for adunit sizes * removing IX's usersync default URL (#1670) Co-authored-by: Index Exchange Prebid Team * AMX Bid Adapter: Loop Variable Bug (#1675) * requestheaders: new parameter inside debug.httpcalls. to log request header details (#1659) * Added support for logging requestheaders inside httpCalls.requestheaders * Reverterd test case change * Modified outgoing mock request for appnexus, to send some request header information. Modified sample mock response such that ext.debug.httpcalls.appnexus.requestheaders will return the information of passed request headers * Addressed code review comments given by SyntaxNode. Also Moved RequestHeaders next to RequestBidy in openrtb_ext.ExtHttpCall Co-authored-by: Shriprasad * Updating pulsepoint adapter (#1663) * Debug disable feature implementation: (#1677) Co-authored-by: Veronika Solovei * Always use fallback GVL for TCF1 (#1657) * Update TCF2 GVL subdomain and always use fallback GVL for TCF1 * Add config test coverage for invalid TCF1 FetchGVL and AMP Exception * Delete obselete test * Adform adapter: digitrust cleanup (#1690) * adform secure endpoint as default setting * digitrust cleanup * New Adapter: DecenterAds (#1669) Co-authored-by: vlad * Handle empty consent string during cookie sync and setuid (#1671) * Handle empty consent string during cookie sync and setuid * Remove todo comment * Make auction test table driven and convert GDPR impl normalize method to pass by value * Moved legacy auction endpoint signal parsing into its own method and removed unnecessary test cases * Fix SignalParse method to return nil for error when raw signal is empty and other PR feedback * Tappx User Syncer + Site Update (#1674) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * Beachfront Additional tests (#1679) * added place holder files for all but a couple of the intended new tests. I need to grok what those couple mean before being able to name a file. * This covers most of the suggested cases and a couple more that occured to me. I'll look at the couple that I held off on next. * added the unmarshal tests and found a couple problems to address in the process. * removed my __debug_bin. should be in gitignore. * A bit of clean up and commenting. Bumped version number. Added __debug_bin to .gitignore. This is the debugging binary created by Visual Studio Code, or at least version 1.52.1. * missed a bunch of version strings * removed IP faker * If IP is not included in an AdM request, an error is now thrown for the AdM imp instead of faking it. The AdM endpoint is the only one that requires an IP. Also, added several "no-ip" test cases. * Whent back to the fake IP solution instead of the error. Removed most of the "no-ip" test cases, leaving one. * changed ip in adm-video.json to not match the faker ip * removed a debugging comment * New Adapter: Onetag (#1695) * Pubmatic: Trimming publisher ID before passing (#1685) * Trimming publisher ID before passing * Fix typos in nobid.json (#1704) * Fix Typo In Adform Bidder Params (#1705) * Don't Load GVL v1 for TCF2 (+ TCF1 Cleanup) (#1693) * Typo fix for connectad bidder params (#1706) * Typo fix for invibes bidder params (#1707) * Typo fix nanointeractive bidder params (#1708) * Isolate /info/bidders Data Model + Add Uses HTTPS Flag (#1692) * Initial Commit * Merge Conflict Fixes * Removed Unncessary JSON Attributes * Removed Dev Notes * Add Missing validateDefaultAliases Test * Improved Reversed Test * Remove Var Scope Confusion * Proper Tests For Bidder Param Validator * Removed Unused Test Setup * New Adapter: Epom (#1680) Co-authored-by: Vasyl Zarva * New Adapter: Pangle (#1697) Co-authored-by: hcai * Fix Merge Conflict (#1714) * GumGum: adds pubId and irisid properties/parameters (#1664) * adds pubId and irisid properties * updates per naming convention & makes a video copy * updates when to copy banner, adds Publisher fallback and multiformat request * adds more json tests * rename the json file to remove whitespaces * Accommodate Apple iOS LMT bug (#1718) * New Adapter: jixie (#1698) * initial commit * added notes file for temp use * jixie adapter development work * jixie adaptor development work: mainly the test json files but also the jixie usersync code * added a test case with accountid. and cosmetic line changes in the banner*json test file * updated the jixie user sync: the endpoint and some params stuf * tks and fixing per comments on pull request 1698 * responding to guscarreon's comments: -more checking in makerequest of the bidder params (added 2 more test jsons) -removed blank lines, lines commented out -test_params: a case with unit alone -BadInput error * responding to review. put condition on jixie unit string in the bidder-params/jixie.json file. removed checking in jixie.go that has become unnecssary. removed unnec test cases. updated params-test * added one failed params test * removed a function that I no longer call! * renamed JixieAdapter to adapter * removed bidfloor from jixie explicit ext params * Fix Regs Nil Condition (#1723) * Mobfox: Add rout to adexcange (#1702) Co-authored-by: mobfox * New Adapter: TrustX (#1726) * New Adapter: UNICORN (#1719) * add bidder-info, bidder-params for UNICORN * Add adapter * Fixes GDPR bug about being overly strict on publisher restrictions (#1730) * 33Across: Updated exchange endpoint (#1738) * New Adapter: Adyoulike (#1700) Co-authored-by: Damien Dumas * Hoist GVL ID To Bidder Info (#1721) * Improve Digital adapter: add support for native ads (#1746) * Add Support For SkAdN + Refactor Split Imps (#1741) * No Longer Move bid.ext To bid.ext.bidder (#1742) * No Longer Move bid.ext To bid.ext.bidder * Remove Similar Behavior From seatbid.ext * Avoid Second Bid Copy * Removed Unused seatbid.ext * Typo fix: adyoulike bidder param debug description (#1755) * Aliases: Better Error Message For Disabled Bidder (#1751) * beachfront: Changes to support real 204 (#1737) * Fix race condition in 33across.go (#1757) Co-authored-by: Gus Carreon * Revert "Fix race condition in 33across.go (#1757)" (#1763) This reverts commit bdf1e7b3e13bdf87d3282bf74472fc66504537d5. * Replace TravisCI With GitHub Actions (#1754) * Initial Commit * Finished Configuration * Remove TravisCI * Remove TravisCI * Fix Go Version Badge * Correct Fix For Go Version Badge * Removed Custom Config File Name * Debug warnings (#1724) Co-authored-by: Veronika Solovei * Rubicon: Support sending segments to XAPI (#1752) Co-authored-by: Serhii Nahornyi * validateNativeContextTypes function test cases (#1743) * Applogy: Fix Shared Memory Overwriting (#1758) * Pubmatic: Fix Shared Memory Overwriting (#1759) * Beachfront: Fix Shared Memory Overwriting (#1762) * Fix race condition in Beachfront adapter * Removed nil check and simplified * FPD: Allow imp.ext.data To Passthrough To Adapters (#1765) * Smaato: Add support for app (#1767) Co-authored-by: Bernhard Pickenbrock * Update sync types (#1770) * 33across: Fix Shared Memory Overwriting (#1764) This reverts commit f7df258f061788ef7e72529115aa5fd554fa9f16. * Fix race condition in Yeahmobi adapter (#1761) Co-authored-by: Gus Carreon * Pubnative: Fix Shared Memory Overwriting (#1760) * Add request for registration (#1780) * Update OpenRTB Library (#1733) * Tappx changes - Backward compatible change of version (#1777) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * DMX: Enforcing w and h in imp (#1778) Co-authored-by: steve-a-districtm * Remove Authorization Headers From Debug Response (#1779) * Hide Authorization Headers In Debug Response * MakeExtHeaders Tests * Add Empty Test * Use http.Header Methods * Updates From Code Review * Fix Merge Conflict * New Adapter: Bidmachine (#1769) * New Adapter: Criteo (#1775) * Fix shared memory issue when stripping authorization header from bid requests (#1790) * RTB House: update parameters (#1785) * update parameters required by RTB House adapter * tabs to spaces Co-authored-by: Przemyslaw Iwanczak * Generate seatbid[].bid[].ext.prebid.bidid (#1772) * Basic GDPR enforcement for specific publisher-vendors. (#1782) * New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior * Zemanta: Rename Adapter To Outbrain (#1797) * Update openrtb library to v15 (#1802) * IX: Set bidVideo when category and duration is available (#1794) * Update IX defaults (#1799) Co-authored-by: Mike Burns * Update Adyoulike endpoint to hit production servers (#1805) * Openx: use bidfloor if set - prebid.js adapter behavior (#1795) * [ORBIDDER] add gvlVendorID and set bid response currency (#1798) * New Adapter: ADXCG (#1803) * Update kidoz properties to type string (#1808) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * Update bidmachine properties to type string (#1809) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * transform native eventtrackers to imptrackers and jstracker (#1811) * TheMediaGrid: Added processing of imp[].ext.data (#1807) * Renaming package github.com/PubMatic-OpenWrap/openrtb to github.com/mxmCherry/openrtb * Rename package github.com/PubMatic-OpenWrap/prebid-server to github.com/prebid/prebid-server * UOE-6196: OpenWrap S2S: Remove adpod_id from AppNexus adapter * Refactored code and fixed indentation * Fixed indentation for json files * Fixed indentation for json files * Fixed import in adapters/gumgum/gumgum.go * Reverted unwanted changes in test json files * Fixed unwanted git merge changes * Added missing field SkipDedup in ExtIncludeBrandCategory * Added missing Bidder field in ExtBid type * Exposing CookieSyncRequest for header-bidding * Temporary path change for static folder * Fixed static folder paths * Fixed default value in config for usersync_if_ambiguous * Fixed config after upgrade * Updated router.go to uncomment defaultRequest validation * Fixed path for accounts.filesystem.directorypath * Fixed diff with OW * Added DMX default usersync URL * Adding changes missed for UOE-5114 during prebid-server upgrade Co-authored-by: Adprime <64427228+Adprime@users.noreply.github.com> Co-authored-by: hhhjort <31041505+hhhjort@users.noreply.github.com> Co-authored-by: Veronika Solovei Co-authored-by: Veronika Solovei Co-authored-by: Brian Sardo <1168933+bsardo@users.noreply.github.com> Co-authored-by: Scott Kay Co-authored-by: chino117 Co-authored-by: Cameron Rice <37162584+camrice@users.noreply.github.com> Co-authored-by: guscarreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Jurij Sinickij Co-authored-by: Rob Hazan Co-authored-by: bretg Co-authored-by: Daniel Cassidy Co-authored-by: GammaSSP <35954362+gammassp@users.noreply.github.com> Co-authored-by: Easy Life Co-authored-by: gpolaert Co-authored-by: Stephan Brosinski Co-authored-by: vikram Co-authored-by: Dmitriy Co-authored-by: Laurentiu Badea Co-authored-by: Mansi Nahar Co-authored-by: smithaammassamveettil <39389834+smithaammassamveettil@users.noreply.github.com> Co-authored-by: hdeodhar <35999856+hdeodhar@users.noreply.github.com> Co-authored-by: Bill Newman Co-authored-by: Aiholkin Co-authored-by: Daniel Lawrence Co-authored-by: johnwier <49074029+johnwier@users.noreply.github.com> Co-authored-by: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Co-authored-by: Alexey Elymanov Co-authored-by: Alexey Elymanov Co-authored-by: invibes <51820283+invibes@users.noreply.github.com> Co-authored-by: aurel.vasile Co-authored-by: ucfunnel <39581136+ucfunnel@users.noreply.github.com> Co-authored-by: Krushmedia <71434282+Krushmedia@users.noreply.github.com> Co-authored-by: Kushneryk Pavel Co-authored-by: Kushneryk Pavlo Co-authored-by: user Co-authored-by: Gus Carreon Co-authored-by: Daniel Barrigas Co-authored-by: tadam75 Co-authored-by: tadam Co-authored-by: Andrea Cannuni <57228257+ACannuniRP@users.noreply.github.com> Co-authored-by: Ad Generation Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: Gus Carreon Co-authored-by: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: Rakesh Balakrishnan Co-authored-by: Dan Bogdan Co-authored-by: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Co-authored-by: Winston-Yieldmo <46379634+Winston-Yieldmo@users.noreply.github.com> Co-authored-by: Winston Co-authored-by: redaguermas Co-authored-by: Reda Guermas Co-authored-by: ShriprasadM Co-authored-by: Shriprasad Co-authored-by: Viacheslav Chimishuk Co-authored-by: Sander Co-authored-by: Nick Jacob Co-authored-by: htang555 Co-authored-by: Aparna Rao Co-authored-by: silvermob <73727464+silvermob@users.noreply.github.com> Co-authored-by: Anton Nikityuk Co-authored-by: Seba Perez Co-authored-by: Sergio Co-authored-by: Gena Co-authored-by: Steve Alliance Co-authored-by: Peter Fröhlich Co-authored-by: oath-jac <45564796+oath-jac@users.noreply.github.com> Co-authored-by: oath-jac Co-authored-by: egsk Co-authored-by: Egor Skorokhodov Co-authored-by: Sourabh Gandhe Co-authored-by: Sourabh Gandhe Co-authored-by: dtbarne <7635750+dtbarne@users.noreply.github.com> Co-authored-by: Dan Barnett Co-authored-by: Serhii Nahornyi Co-authored-by: Marsel Co-authored-by: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> Co-authored-by: Index Exchange 3 Prebid Team Co-authored-by: Index Exchange Prebid Team Co-authored-by: Giudici-a <34242194+Giudici-a@users.noreply.github.com> Co-authored-by: Aurélien Giudici Co-authored-by: jcamp-revc <68560678+jcamp-revc@users.noreply.github.com> Co-authored-by: steve-a-districtm Co-authored-by: Steve Alliance Co-authored-by: Jim Naumann Co-authored-by: Anand Venkatraman Co-authored-by: Vladyslav Laktionov Co-authored-by: vlad Co-authored-by: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Co-authored-by: ubuntu Co-authored-by: Albert Grandes Co-authored-by: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Co-authored-by: agilfix Co-authored-by: epomrnd Co-authored-by: Vasyl Zarva Co-authored-by: Hengsheng Cai Co-authored-by: hcai Co-authored-by: susyt Co-authored-by: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Co-authored-by: faithnh Co-authored-by: guiann Co-authored-by: Damien Dumas Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Gus Carreon Co-authored-by: Serhii Nahornyi Co-authored-by: el-chuck Co-authored-by: Bernhard Pickenbrock Co-authored-by: Pavel Dunyashev Co-authored-by: Benjamin Co-authored-by: Przemysław Iwańczak <36727380+piwanczak@users.noreply.github.com> Co-authored-by: Przemyslaw Iwanczak Co-authored-by: Rok Sušnik Co-authored-by: Rok Sušnik Co-authored-by: ixjohnny <75964135+ixjohnny@users.noreply.github.com> Co-authored-by: Michael Burns Co-authored-by: Mike Burns Co-authored-by: Arne Schulz Co-authored-by: adxcgcom <31470944+adxcgcom@users.noreply.github.com> --- .github/release-drafter.yml | 9 + .github/workflows/release.yml | 26 + .gitignore | 1 + .travis.yml | 30 - Makefile | 2 +- README.md | 7 +- account/account.go | 8 +- account/account_test.go | 8 +- adapters/33across/33across.go | 32 +- adapters/33across/33across_test.go | 6 +- adapters/33across/params_test.go | 2 +- adapters/33across/usersync.go | 6 +- adapters/33across/usersync_test.go | 7 +- adapters/acuityads/acuityads.go | 24 +- adapters/acuityads/acuityads_test.go | 6 +- .../acuityadstest/exemplary/banner-app.json | 2 +- .../acuityadstest/exemplary/native-app.json | 2 +- .../acuityadstest/exemplary/video-web.json | 2 +- .../invalid-smartyads-ext-object.json | 29 - adapters/acuityads/params_test.go | 2 +- adapters/acuityads/usersync.go | 6 +- adapters/acuityads/usersync_test.go | 7 +- adapters/adapterstest/adapter_test_util.go | 10 +- adapters/adapterstest/test_json.go | 27 +- adapters/adform/adform.go | 43 +- adapters/adform/adform_test.go | 78 +- .../adformtest/supplemental/regs-ext-nil.json | 53 ++ adapters/adform/params_test.go | 2 +- adapters/adform/usersync.go | 6 +- adapters/adform/usersync_test.go | 5 +- adapters/adgeneration/adgeneration.go | 32 +- adapters/adgeneration/adgeneration_test.go | 78 +- .../supplemental/204-bid-response.json | 2 +- .../supplemental/400-bid-response.json | 2 +- adapters/adgeneration/params_test.go | 2 +- adapters/adhese/adhese.go | 40 +- adapters/adhese/adhese_test.go | 6 +- .../adhesetest/exemplary/banner-internal.json | 2 +- adapters/adhese/params_test.go | 2 +- adapters/adhese/utils.go | 16 +- adapters/adkernel/adkernel.go | 44 +- adapters/adkernel/adkernel_test.go | 6 +- adapters/adkernel/usersync.go | 8 +- adapters/adkernel/usersync_test.go | 7 +- adapters/adkernelAdn/adkernelAdn.go | 44 +- adapters/adkernelAdn/adkernelAdn_test.go | 6 +- adapters/adkernelAdn/usersync.go | 8 +- adapters/adkernelAdn/usersync_test.go | 7 +- adapters/adman/adman.go | 22 +- adapters/adman/adman_test.go | 6 +- .../admantest/supplemental/bad_response.json | 2 +- .../admantest/supplemental/status-204.json | 2 +- adapters/adman/params_test.go | 2 +- adapters/adman/usersync.go | 6 +- adapters/adman/usersync_test.go | 7 +- adapters/admixer/admixer.go | 24 +- adapters/admixer/admixer_test.go | 6 +- adapters/admixer/params_test.go | 2 +- adapters/admixer/usersync.go | 7 +- adapters/admixer/usersync_test.go | 8 +- adapters/adocean/adocean.go | 38 +- adapters/adocean/adocean_test.go | 6 +- adapters/adocean/params_test.go | 2 +- adapters/adocean/usersync.go | 6 +- adapters/adocean/usersync_test.go | 5 +- adapters/adoppler/adoppler.go | 22 +- adapters/adoppler/adoppler_test.go | 6 +- .../supplemental/invalid-response.json | 2 +- adapters/adot/adot.go | 19 +- adapters/adot/adot_test.go | 21 +- .../supplemental/unmarshal_error.json | 2 +- adapters/adot/params_test.go | 2 +- adapters/adpone/adpone.go | 16 +- adapters/adpone/adpone_test.go | 6 +- .../adponetest/supplemental/bad_response.json | 2 +- adapters/adpone/usersync.go | 6 +- adapters/adpone/usersync_test.go | 3 +- adapters/adprime/adprime.go | 22 +- adapters/adprime/adprime_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/adprime/params_test.go | 2 +- adapters/adtarget/adtarget.go | 20 +- adapters/adtarget/adtarget_test.go | 6 +- .../adtargettest/exemplary/simple-banner.json | 2 +- .../adtargettest/exemplary/simple-video.json | 2 +- adapters/adtarget/params_test.go | 2 +- adapters/adtarget/usersync.go | 6 +- adapters/adtarget/usersync_test.go | 9 +- adapters/adtelligent/adtelligent.go | 20 +- adapters/adtelligent/adtelligent_test.go | 6 +- adapters/adtelligent/params_test.go | 2 +- adapters/adtelligent/usersync.go | 6 +- adapters/adtelligent/usersync_test.go | 7 +- adapters/advangelists/advangelists.go | 42 +- adapters/advangelists/advangelists_test.go | 6 +- adapters/advangelists/params_test.go | 2 +- adapters/advangelists/usersync.go | 6 +- adapters/advangelists/usersync_test.go | 5 +- adapters/adxcg/adxcg.go | 125 +++ adapters/adxcg/adxcg_test.go | 23 + .../adxcgtest/exemplary/simple-banner.json | 83 ++ .../adxcgtest/exemplary/simple-native.json | 73 ++ .../adxcgtest/exemplary/simple-video.json | 105 +++ .../adxcg/adxcgtest/params/race/banner.json | 1 + .../adxcgtest/supplemental/bad_response.json | 61 ++ .../adxcgtest/supplemental/status_204.json | 56 ++ .../adxcgtest/supplemental/status_400.json | 61 ++ .../adxcgtest/supplemental/status_418.json | 61 ++ adapters/adxcg/usersync.go | 12 + adapters/adxcg/usersync_test.go | 29 + adapters/adyoulike/adyoulike.go | 137 ++++ adapters/adyoulike/adyoulike_test.go | 22 + .../exemplary/multiformat-impression.json | 169 +++++ .../adyouliketest/params/race/banner.json | 3 + .../adyouliketest/params/race/video.json | 3 + .../supplemental/invalid-bid-response.json | 65 ++ .../supplemental/status-bad-request.json | 66 ++ .../supplemental/status-no-content.json | 66 ++ .../status-service-unavailable.json | 66 ++ .../supplemental/status-unknown.json | 66 ++ adapters/adyoulike/params_test.go | 62 ++ adapters/adyoulike/usersync.go | 12 + adapters/adyoulike/usersync_test.go | 35 + adapters/aja/aja.go | 20 +- adapters/aja/aja_test.go | 6 +- adapters/aja/usersync.go | 6 +- adapters/aja/usersync_test.go | 7 +- adapters/amx/amx.go | 22 +- adapters/amx/amx_test.go | 56 +- .../amx/amxtest/exemplary/video-simple.json | 2 +- .../amx/amxtest/exemplary/web-simple.json | 2 +- adapters/amx/params_test.go | 2 +- adapters/amx/usersync.go | 6 +- adapters/amx/usersync_test.go | 3 +- adapters/applogy/applogy.go | 32 +- adapters/applogy/applogy_test.go | 6 +- adapters/appnexus/appnexus.go | 41 +- adapters/appnexus/appnexus_test.go | 146 ++-- adapters/appnexus/params_test.go | 2 +- adapters/appnexus/usersync.go | 6 +- adapters/appnexus/usersync_test.go | 3 +- .../supplemental/no-bid-204.json | 2 +- adapters/audienceNetwork/facebook.go | 129 ++-- adapters/audienceNetwork/facebook_test.go | 8 +- adapters/audienceNetwork/usersync.go | 6 +- adapters/audienceNetwork/usersync_test.go | 3 +- adapters/avocet/avocet.go | 20 +- adapters/avocet/avocet/exemplary/banner.json | 106 --- adapters/avocet/avocet/exemplary/video.json | 104 --- adapters/avocet/avocet_test.go | 32 +- adapters/avocet/usersync.go | 6 +- adapters/avocet/usersync_test.go | 7 +- adapters/beachfront/beachfront.go | 177 ++--- adapters/beachfront/beachfront_test.go | 6 +- .../beachfronttest/exemplary/adm-video.json | 124 +++ .../beachfronttest/exemplary/banner.json | 3 +- .../adm-video-by-explicit.json} | 0 .../adm-video-no-ip.json} | 3 - .../supplemental/banner-204-with-body.json | 79 ++ ...r-empty_array-200.json => banner-204.json} | 11 +- .../banner-and-adm-video-by-explicit.json} | 12 +- ...video-expected-204-response-on-banner.json | 171 +++++ .../banner-and-adm-video.json} | 12 +- .../banner-and-nurl-video.json | 6 +- .../supplemental/banner-bad-request-400.json | 77 ++ .../supplemental/banner-no-appid.json | 34 + .../supplemental/banner-wrong-appids.json | 36 + ...er_response_unmarshal_error_adm_video.json | 80 ++ ...idder_response_unmarshal_error_banner.json | 78 ++ ...r_response_unmarshal_error_nurl_video.json | 82 ++ .../supplemental/bidfloor-below-min.json | 97 +++ .../internal-server-error-500.json | 78 ++ .../beachfronttest/supplemental/no-imps.json | 16 + ...dm-nurl--PASS.json => six-nine-combo.json} | 24 +- .../supplemental/two-four-combo.json | 3 +- .../supplemental/video-no-appid.json | 38 + .../supplemental/video-wrong-appids.json | 39 + adapters/beachfront/params_test.go | 2 +- adapters/beachfront/usersync.go | 7 +- adapters/beachfront/usersync_test.go | 7 +- adapters/beintoo/beintoo.go | 26 +- adapters/beintoo/beintoo_test.go | 6 +- .../invalid-response-unmarshall-error.json | 2 +- adapters/beintoo/params_test.go | 2 +- adapters/beintoo/usersync.go | 6 +- adapters/beintoo/usersync_test.go | 7 +- adapters/between/between.go | 28 +- adapters/between/between_test.go | 6 +- .../betweentest/exemplary/multi-request.json | 2 +- .../supplemental/bad-response-body.json | 4 +- adapters/between/params_test.go | 2 +- adapters/between/usersync.go | 6 +- adapters/between/usersync_test.go | 6 +- adapters/bidder.go | 26 +- adapters/bidmachine/bidmachine.go | 219 ++++++ adapters/bidmachine/bidmachine_test.go | 28 + .../rewarded_interstitial_no_battr.json | 128 ++++ .../rewarded_interstitial_w_battr.json | 128 ++++ .../exemplary/rewarded_video_no_battr.json | 132 ++++ .../exemplary/rewarded_video_w_battr.json | 132 ++++ .../exemplary/simple_banner.json | 118 +++ .../exemplary/simple_interstitial.json | 120 +++ .../exemplary/simple_video.json | 124 +++ .../bidmachinetest/params/race/banner.json | 5 + .../bidmachinetest/params/race/video.json | 5 + .../supplemental/empty-banner-format .json | 19 + .../supplemental/missing-banner-format.json | 26 + .../supplemental/status-204.json | 66 ++ .../supplemental/status-400.json | 69 ++ .../supplemental/status-403.json | 69 ++ .../supplemental/status-408.json | 69 ++ .../supplemental/status-500.json | 69 ++ .../supplemental/status-502.json | 69 ++ .../supplemental/status-503.json | 69 ++ .../supplemental/status-504.json | 69 ++ .../supplemental/wrong-host.json | 35 + adapters/bidmachine/params_test.go | 78 ++ adapters/brightroll/brightroll.go | 24 +- adapters/brightroll/brightroll_test.go | 6 +- adapters/brightroll/params_test.go | 2 +- adapters/brightroll/usersync.go | 6 +- adapters/brightroll/usersync_test.go | 3 +- adapters/colossus/colossus.go | 22 +- adapters/colossus/colossus_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/colossus/params_test.go | 2 +- adapters/colossus/usersync.go | 6 +- adapters/colossus/usersync_test.go | 7 +- adapters/connectad/connectad.go | 26 +- adapters/connectad/connectad_test.go | 6 +- .../supplemental/badresponse.json | 2 +- adapters/connectad/params_test.go | 2 +- adapters/connectad/usersync.go | 6 +- adapters/connectad/usersync_test.go | 7 +- adapters/consumable/adtypes.go | 7 +- adapters/consumable/consumable.go | 24 +- adapters/consumable/consumable_test.go | 8 +- adapters/consumable/params_test.go | 2 +- adapters/consumable/usersync.go | 8 +- adapters/consumable/usersync_test.go | 7 +- adapters/conversant/cnvr_legacy.go | 28 +- adapters/conversant/cnvr_legacy_test.go | 52 +- adapters/conversant/conversant.go | 32 +- adapters/conversant/conversant_test.go | 6 +- adapters/conversant/usersync.go | 6 +- adapters/conversant/usersync_test.go | 5 +- adapters/cpmstar/cpmstar.go | 22 +- adapters/cpmstar/cpmstar_test.go | 6 +- .../exemplary/banner-and-video.json | 2 +- .../supplemental/explicit-dimensions.json | 2 +- .../invalid-response-no-bids.json | 2 +- .../invalid-response-unmarshall-error.json | 2 +- .../supplemental/server-no-content.json | 2 +- adapters/cpmstar/params_test.go | 2 +- adapters/cpmstar/usersync.go | 6 +- adapters/cpmstar/usersync_test.go | 3 +- adapters/criteo/criteo.go | 114 +++ adapters/criteo/criteo_test.go | 28 + .../exemplary/simple-banner-cookie-uid.json | 115 +++ .../exemplary/simple-banner-inapp.json | 110 +++ .../exemplary/simple-banner-uid.json | 132 ++++ .../204-response-from-target.json | 81 ++ .../400-response-from-target.json | 86 +++ .../500-response-from-target.json | 86 +++ ...ccpa-with-consent-simple-banner-inapp.json | 115 +++ .../ccpa-with-consent-simple-banner-uid.json | 139 ++++ ...a-without-consent-simple-banner-inapp.json | 85 +++ ...cpa-without-consent-simple-banner-uid.json | 105 +++ ...gdpr-with-consent-simple-banner-inapp.json | 126 ++++ .../gdpr-with-consent-simple-banner-uid.json | 142 ++++ ...r-without-consent-simple-banner-inapp.json | 92 +++ ...dpr-without-consent-simple-banner-uid.json | 108 +++ .../multislots-simple-banner-inapp.json | 213 ++++++ ...mple-banner-uid-different-network-ids.json | 84 +++ .../multislots-simple-banner-uid.json | 233 ++++++ ...-direct-size-and-formats-not-included.json | 132 ++++ ...e-banner-with-direct-size-and-formats.json | 132 ++++ .../simple-banner-with-direct-size.json | 126 ++++ .../supplemental/simple-banner-with-ipv6.json | 126 ++++ adapters/criteo/generators.go | 36 + adapters/criteo/models.go | 299 ++++++++ adapters/criteo/models_test.go | 452 +++++++++++ adapters/criteo/params_test.go | 63 ++ adapters/criteo/usersync.go | 14 + adapters/criteo/usersync_test.go | 26 + adapters/datablocks/datablocks.go | 28 +- adapters/datablocks/datablocks_test.go | 6 +- .../supplemental/bad-response-body.json | 2 +- adapters/datablocks/usersync.go | 8 +- adapters/datablocks/usersync_test.go | 7 +- adapters/decenterads/decenterads.go | 20 +- adapters/decenterads/decenterads_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/decenterads/params_test.go | 2 +- adapters/deepintent/deepintent.go | 26 +- adapters/deepintent/deepintent_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/deepintent/params_test.go | 2 +- adapters/deepintent/usersync.go | 6 +- adapters/deepintent/usersync_test.go | 7 +- adapters/dmx/dmx.go | 84 ++- adapters/dmx/dmx_test.go | 261 ++++--- .../dmx/dmxtest/exemplary/idfa-to-app-id.json | 148 ++++ .../exemplary/imp-populated-banner.json | 2 +- .../exemplary/missing-width-height.json | 143 ++++ .../dmx/dmxtest/exemplary/simple-app.json | 2 +- adapters/dmx/params_test.go | 2 +- adapters/dmx/usersync.go | 6 +- adapters/dmx/usersync_test.go | 4 +- adapters/emx_digital/emx_digital.go | 34 +- adapters/emx_digital/emx_digital_test.go | 8 +- .../exemplary/banner-and-video-app.json | 2 +- .../exemplary/banner-and-video-site.json | 2 +- .../emx_digitaltest/exemplary/video-app.json | 2 +- .../emx_digitaltest/exemplary/video-ctv.json | 2 +- .../emx_digitaltest/exemplary/video-site.json | 2 +- .../invalid-response-unmarshall-error.json | 2 +- adapters/emx_digital/params_test.go | 2 +- adapters/emx_digital/usersync.go | 6 +- adapters/emx_digital/usersync_test.go | 7 +- adapters/engagebdr/engagebdr.go | 20 +- adapters/engagebdr/engagebdr_test.go | 6 +- adapters/engagebdr/params_test.go | 2 +- adapters/engagebdr/usersync.go | 7 +- adapters/engagebdr/usersync_test.go | 7 +- adapters/eplanning/eplanning.go | 30 +- adapters/eplanning/eplanning_test.go | 8 +- .../invalid-response-no-bids.json | 2 +- .../supplemental/server-no-content.json | 2 +- adapters/eplanning/usersync.go | 6 +- adapters/eplanning/usersync_test.go | 3 +- adapters/epom/epom.go | 127 ++++ adapters/epom/epom_test.go | 20 + .../epomtest/exemplary/simple-app-banner.json | 105 +++ .../epomtest/exemplary/simple-app-native.json | 91 +++ .../epomtest/exemplary/simple-app-video.json | 115 +++ .../exemplary/simple-site-banner.json | 105 +++ .../exemplary/simple-site-native.json | 92 +++ .../epomtest/exemplary/simple-site-video.json | 115 +++ .../epom/epomtest/params/race/banner.json | 2 + .../epom/epomtest/params/race/native.json | 2 + adapters/epom/epomtest/params/race/video.json | 2 + .../supplemental/bad-request-no-device.json | 27 + .../supplemental/bad-request-no-ip.json | 29 + .../bad-response-bad-request-error.json | 72 ++ .../bad-response-no-bid-obj-error.json | 75 ++ .../bad-response-no-seatbid-error.json | 74 ++ .../bad-response-server-internal-error.json | 72 ++ .../bad-response-unexpected-error.json | 72 ++ adapters/gamma/gamma.go | 42 +- adapters/gamma/gamma_test.go | 6 +- .../exemplary/banner-and-video-and-audio.json | 2 +- .../exemplary/valid-full-params.json | 2 +- adapters/gamma/params_test.go | 2 +- adapters/gamma/usersync.go | 6 +- adapters/gamma/usersync_test.go | 5 +- adapters/gamoshi/gamoshi.go | 18 +- adapters/gamoshi/gamoshi_test.go | 6 +- adapters/gamoshi/params_test.go | 2 +- adapters/gamoshi/usersync.go | 6 +- adapters/gamoshi/usersync_test.go | 5 +- adapters/grid/grid.go | 56 +- adapters/grid/grid_test.go | 6 +- .../grid/gridtest/exemplary/with_gpid.json | 100 +++ .../gridtest/supplemental/bad_response.json | 2 +- adapters/grid/usersync.go | 6 +- adapters/grid/usersync_test.go | 5 +- adapters/gumgum/gumgum.go | 93 ++- adapters/gumgum/gumgum_test.go | 6 +- .../supplemental/banner-only-in-format.json | 97 +++ .../supplemental/banner-with-pubId.json | 99 +++ .../supplemental/banner-with-site.json | 109 +++ .../supplemental/video-with-irisid.json | 111 +++ adapters/gumgum/params_test.go | 11 +- adapters/gumgum/usersync.go | 6 +- adapters/gumgum/usersync_test.go | 7 +- adapters/improvedigital/improvedigital.go | 22 +- .../improvedigital/improvedigital_test.go | 6 +- .../improvedigitaltest/exemplary/native.json | 161 ++++ .../params/race/native.json | 12 + .../supplemental/bad_response.json | 2 +- .../supplemental/native.json | 64 -- adapters/improvedigital/params_test.go | 2 +- adapters/improvedigital/usersync.go | 6 +- adapters/improvedigital/usersync_test.go | 7 +- adapters/info.go | 267 ------- adapters/infoawarebidder.go | 160 ++++ .../{info_test.go => infoawarebidder_test.go} | 137 ++-- adapters/inmobi/inmobi.go | 20 +- adapters/inmobi/inmobi_test.go | 6 +- .../inmobi/inmobitest/supplemental/204.json | 2 +- adapters/invibes/invibes.go | 44 +- adapters/invibes/invibes_test.go | 6 +- adapters/invibes/invibestest/amp/amp-ad.json | 2 +- .../invibes/invibestest/exemplary/no-ad.json | 2 +- .../invibestest/exemplary/test-ad.json | 2 +- adapters/invibes/params_test.go | 2 +- adapters/invibes/usersync.go | 5 +- adapters/invibes/usersync_test.go | 5 +- adapters/ix/ix.go | 63 +- adapters/ix/ix_test.go | 141 +++- adapters/ix/usersync.go | 6 +- adapters/ix/usersync_test.go | 3 +- adapters/jixie/jixie.go | 133 ++++ adapters/jixie/jixie_test.go | 20 + .../exemplary/banner-and-video-site.json | 230 ++++++ .../jixie/jixietest/params/race/banner.json | 4 + .../jixie/jixietest/params/race/video.json | 4 + .../jixietest/supplemental/add-accountid.json | 236 ++++++ .../jixietest/supplemental/add-extraprop.json | 233 ++++++ .../jixietest/supplemental/add-userid.json | 237 ++++++ adapters/jixie/params_test.go | 57 ++ adapters/jixie/usersync.go | 12 + adapters/jixie/usersync_test.go | 24 + adapters/kidoz/kidoz.go | 18 +- adapters/kidoz/kidoz_test.go | 30 +- .../kidoztest/exemplary/simple-banner.json | 2 +- .../kidoztest/exemplary/simple-video.json | 2 +- .../kidoztest/supplemental/status-204.json | 2 +- adapters/kidoz/params_test.go | 2 +- adapters/krushmedia/krushmedia.go | 24 +- adapters/krushmedia/krushmedia_test.go | 6 +- .../krushmediatest/exemplary/banner-app.json | 2 +- .../krushmediatest/exemplary/native-app.json | 2 +- .../krushmediatest/exemplary/video-web.json | 2 +- adapters/krushmedia/params_test.go | 2 +- adapters/krushmedia/usersync.go | 6 +- adapters/krushmedia/usersync_test.go | 7 +- adapters/kubient/kubient.go | 20 +- adapters/kubient/kubient_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/legacy.go | 4 +- adapters/lifestreet/lifestreet.go | 16 +- adapters/lifestreet/lifestreet_test.go | 46 +- adapters/lifestreet/usersync.go | 6 +- adapters/lifestreet/usersync_test.go | 5 +- adapters/lockerdome/lockerdome.go | 18 +- adapters/lockerdome/lockerdome_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/lockerdome/params_test.go | 2 +- adapters/lockerdome/usersync.go | 6 +- adapters/lockerdome/usersync_test.go | 3 +- adapters/logicad/logicad.go | 30 +- adapters/logicad/logicad_test.go | 6 +- adapters/logicad/params_test.go | 2 +- adapters/logicad/usersync.go | 6 +- adapters/logicad/usersync_test.go | 5 +- adapters/lunamedia/lunamedia.go | 38 +- adapters/lunamedia/lunamedia_test.go | 6 +- adapters/lunamedia/params_test.go | 2 +- adapters/lunamedia/usersync.go | 6 +- adapters/lunamedia/usersync_test.go | 5 +- adapters/marsmedia/marsmedia.go | 18 +- adapters/marsmedia/marsmedia_test.go | 6 +- adapters/marsmedia/params_test.go | 2 +- adapters/marsmedia/usersync.go | 6 +- adapters/marsmedia/usersync_test.go | 7 +- adapters/mediafuse/usersync.go | 6 +- adapters/mediafuse/usersync_test.go | 7 +- adapters/mgid/mgid.go | 20 +- adapters/mgid/mgid_test.go | 6 +- adapters/mgid/usersync.go | 6 +- adapters/mgid/usersync_test.go | 5 +- adapters/mobfoxpb/mobfoxpb.go | 73 +- adapters/mobfoxpb/mobfoxpb_test.go | 8 +- .../exemplary/simple-banner-direct-route.json | 128 ++++ .../exemplary/simple-banner-rtb-route.json | 128 ++++ .../mobfoxpbtest/exemplary/simple-banner.json | 132 ---- .../exemplary/simple-video-direct-route.json | 125 +++ .../exemplary/simple-video-rtb-route.json | 125 +++ .../mobfoxpbtest/exemplary/simple-video.json | 119 --- .../simple-web-banner-direct-route.json | 128 ++++ .../simple-web-banner-rtb-route.json | 128 ++++ .../exemplary/simple-web-banner.json | 130 ---- .../params/race/banner-direct-route.json | 3 + .../params/race/banner-rtb-route.json | 3 + .../mobfoxpbtest/params/race/banner.json | 3 - .../params/race/video-direct-route.json | 3 + .../params/race/video-rtb-route.json | 3 + .../mobfoxpbtest/params/race/video.json | 3 - .../supplemental/bad-imp-ext.json | 69 +- .../supplemental/bad_response.json | 152 ++-- .../supplemental/bad_status_code.json | 142 ++-- .../supplemental/imp_ext_empty_object.json | 63 +- .../supplemental/imp_ext_string.json | 63 +- .../supplemental/missmatch_bid_id.json | 109 +++ .../mobfoxpbtest/supplemental/status-204.json | 142 ++-- .../mobfoxpbtest/supplemental/status-404.json | 152 ++-- adapters/mobfoxpb/params_test.go | 6 +- adapters/mobilefuse/mobilefuse.go | 28 +- adapters/mobilefuse/mobilefuse_test.go | 6 +- adapters/mobilefuse/params_test.go | 2 +- adapters/nanointeractive/nanointeractive.go | 26 +- .../nanointeractive/nanointeractive_test.go | 6 +- adapters/nanointeractive/params_test.go | 2 +- adapters/nanointeractive/usersync.go | 6 +- adapters/nanointeractive/usersync_test.go | 7 +- adapters/ninthdecimal/ninthdecimal.go | 38 +- adapters/ninthdecimal/ninthdecimal_test.go | 6 +- adapters/ninthdecimal/params_test.go | 2 +- adapters/ninthdecimal/usersync.go | 6 +- adapters/ninthdecimal/usersync_test.go | 5 +- adapters/nobid/nobid.go | 20 +- adapters/nobid/nobid_test.go | 6 +- adapters/nobid/params_test.go | 2 +- adapters/nobid/usersync.go | 6 +- adapters/nobid/usersync_test.go | 6 +- adapters/onetag/onetag.go | 152 ++++ adapters/onetag/onetag_test.go | 26 + .../onetag/onetagtest/exemplary/no-bid.json | 78 ++ .../onetagtest/exemplary/simple-banner.json | 201 +++++ .../onetagtest/exemplary/simple-native.json | 121 +++ .../onetagtest/exemplary/simple-video.json | 115 +++ .../onetag/onetagtest/params/race/banner.json | 4 + .../onetag/onetagtest/params/race/native.json | 4 + .../onetag/onetagtest/params/race/video.json | 4 + .../supplemental/empty-publisher-id.json | 65 ++ .../supplemental/internal-server-error.json | 81 ++ .../supplemental/required-publisher-id.json | 66 ++ .../supplemental/unique-publisher-id.json | 69 ++ .../supplemental/wrong-impression-id.json | 121 +++ adapters/onetag/params_test.go | 54 ++ adapters/onetag/usersync.go | 12 + adapters/onetag/usersync_test.go | 23 + adapters/openrtb_util.go | 55 +- adapters/openrtb_util_test.go | 70 +- adapters/openx/openx.go | 34 +- adapters/openx/openx_test.go | 16 +- .../openxtest/exemplary/optional-params.json | 23 + adapters/openx/params_test.go | 2 +- adapters/openx/usersync.go | 6 +- adapters/openx/usersync_test.go | 3 +- adapters/orbidder/orbidder.go | 24 +- adapters/orbidder/orbidder_test.go | 6 +- adapters/orbidder/params_test.go | 2 +- adapters/outbrain/outbrain.go | 180 +++++ adapters/outbrain/outbrain_test.go | 20 + .../outbraintest/exemplary/banner.json | 133 ++++ .../outbraintest/exemplary/native.json | 121 +++ .../outbraintest/params/race/banner.json | 10 + .../outbraintest/params/race/native.json | 10 + .../supplemental/app_request.json | 144 ++++ .../supplemental/optional_params.json | 148 ++++ .../outbraintest/supplemental/status_204.json | 62 ++ .../outbraintest/supplemental/status_400.json | 67 ++ .../outbraintest/supplemental/status_418.json | 67 ++ adapters/outbrain/params_test.go | 50 ++ adapters/outbrain/usersync.go | 12 + adapters/outbrain/usersync_test.go | 33 + adapters/pangle/pangle.go | 197 +++++ adapters/pangle/pangle_test.go | 21 + .../pangletest/exemplary/app_banner.json | 127 ++++ .../exemplary/app_banner_instl.json | 129 ++++ .../pangletest/exemplary/app_native.json | 117 +++ .../pangletest/exemplary/app_video_instl.json | 141 ++++ .../exemplary/app_video_rewarded.json | 146 ++++ .../pangle/pangletest/params/race/banner.json | 3 + .../pangle/pangletest/params/race/native.json | 3 + .../pangle/pangletest/params/race/video.json | 3 + .../supplemental/pangle_ext_check.json | 107 +++ .../supplemental/response_code_204.json | 79 ++ .../supplemental/response_code_400.json | 84 +++ .../supplemental/response_code_non_200.json | 84 +++ .../supplemental/unrecognized_adtype.json | 110 +++ .../supplemental/unsupported_adtype.json | 48 ++ adapters/pangle/param_test.go | 45 ++ adapters/pubmatic/params_test.go | 2 +- adapters/pubmatic/pubmatic.go | 60 +- adapters/pubmatic/pubmatic_test.go | 60 +- .../supplemental/trimPublisherID.json | 259 +++---- adapters/pubmatic/usersync.go | 6 +- adapters/pubmatic/usersync_test.go | 7 +- adapters/pubnative/pubnative.go | 42 +- adapters/pubnative/pubnative_test.go | 6 +- adapters/pulsepoint/params_test.go | 2 +- adapters/pulsepoint/pulsepoint.go | 43 +- adapters/pulsepoint/pulsepoint_test.go | 32 +- .../supplemental/bad-bid-data.json | 2 +- adapters/pulsepoint/usersync.go | 6 +- adapters/pulsepoint/usersync_test.go | 3 +- adapters/revcontent/revcontent.go | 19 +- adapters/revcontent/revcontent_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/rhythmone/params_test.go | 2 +- adapters/rhythmone/rhythmone.go | 26 +- adapters/rhythmone/rhythmone_test.go | 6 +- adapters/rhythmone/usersync.go | 6 +- adapters/rhythmone/usersync_test.go | 7 +- adapters/rtbhouse/rtbhouse.go | 16 +- adapters/rtbhouse/rtbhouse_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/rtbhouse/usersync.go | 6 +- adapters/rtbhouse/usersync_test.go | 5 +- adapters/rubicon/rubicon.go | 100 ++- adapters/rubicon/rubicon_test.go | 237 +++--- .../rubicontest/exemplary/simple-video.json | 109 ++- adapters/rubicon/usersync.go | 6 +- adapters/rubicon/usersync_test.go | 5 +- adapters/sharethrough/butler.go | 26 +- adapters/sharethrough/butler_test.go | 86 +-- adapters/sharethrough/params_test.go | 2 +- adapters/sharethrough/sharethrough.go | 14 +- adapters/sharethrough/sharethrough_test.go | 44 +- adapters/sharethrough/usersync.go | 7 +- adapters/sharethrough/usersync_test.go | 5 +- adapters/sharethrough/utils.go | 25 +- adapters/sharethrough/utils_test.go | 67 +- adapters/silvermob/params_test.go | 2 +- adapters/silvermob/silvermob.go | 26 +- adapters/silvermob/silvermob_test.go | 6 +- .../silvermobtest/exemplary/native-app.json | 2 +- .../supplemental/invalid-response.json | 2 +- adapters/smaato/params_test.go | 2 +- adapters/smaato/smaato.go | 40 +- adapters/smaato/smaato_test.go | 6 +- .../exemplary/simple-banner-app.json | 225 ++++++ .../simple-banner-richMedia-app.json | 229 ++++++ .../exemplary/simple-banner-richMedia.json | 2 +- .../smaatotest/exemplary/simple-banner.json | 2 +- .../smaatotest/exemplary/video-app.json | 230 ++++++ .../smaato/smaatotest/exemplary/video.json | 2 +- .../supplemental/bad-adm-response.json | 2 +- .../bad-imp-banner-format-req.json | 2 +- .../supplemental/no-consent-info.json | 2 +- .../supplemental/status-code-204.json | 139 ++++ .../supplemental/status-code-400.json | 144 ++++ adapters/smartadserver/params_test.go | 2 +- adapters/smartadserver/smartadserver.go | 24 +- adapters/smartadserver/smartadserver_test.go | 6 +- adapters/smartadserver/usersync.go | 6 +- adapters/smartadserver/usersync_test.go | 7 +- adapters/smartrtb/smartrtb.go | 34 +- adapters/smartrtb/smartrtb_test.go | 6 +- .../smartrtbtest/supplemental/empty-imps.json | 2 +- .../supplemental/invalid-bid-json.json | 2 +- adapters/smartrtb/usersync.go | 6 +- adapters/smartrtb/usersync_test.go | 3 +- adapters/smartyads/params_test.go | 2 +- adapters/smartyads/smartyads.go | 24 +- adapters/smartyads/smartyads_test.go | 6 +- .../smartyadstest/exemplary/banner-app.json | 2 +- .../smartyadstest/exemplary/native-app.json | 2 +- adapters/smartyads/usersync.go | 6 +- adapters/smartyads/usersync_test.go | 7 +- adapters/somoaudience/params_test.go | 2 +- adapters/somoaudience/somoaudience.go | 35 +- adapters/somoaudience/somoaudience_test.go | 6 +- adapters/somoaudience/usersync.go | 6 +- adapters/somoaudience/usersync_test.go | 3 +- adapters/sonobi/params_test.go | 2 +- adapters/sonobi/sonobi.go | 22 +- adapters/sonobi/sonobi_test.go | 6 +- adapters/sonobi/usersync.go | 7 +- adapters/sonobi/usersync_test.go | 5 +- adapters/sovrn/sovrn.go | 24 +- adapters/sovrn/sovrn_test.go | 36 +- adapters/sovrn/usersync.go | 6 +- adapters/sovrn/usersync_test.go | 5 +- adapters/spotx/params_test.go | 2 +- adapters/spotx/spotx.go | 22 +- adapters/spotx/spotx_test.go | 10 +- adapters/synacormedia/params_test.go | 2 +- adapters/synacormedia/synacormedia.go | 26 +- adapters/synacormedia/synacormedia_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/synacormedia/usersync.go | 6 +- adapters/synacormedia/usersync_test.go | 3 +- adapters/syncer.go | 37 +- adapters/syncer_test.go | 6 +- adapters/tappx/params_test.go | 2 +- adapters/tappx/tappx.go | 27 +- adapters/tappx/tappx_test.go | 12 +- ...ngle-banner-impression-future-feature.json | 116 +++ .../exemplary/single-banner-impression.json | 10 +- .../exemplary/single-banner-site.json | 124 +++ .../exemplary/single-video-impression.json | 10 +- .../exemplary/single-video-site.json | 136 ++++ .../tappx/tappxtest/params/race/banner.json | 4 +- .../tappx/tappxtest/params/race/video.json | 4 +- .../tappxtest/supplemental/204status.json | 10 +- .../banner-impression-badhost.json | 4 +- .../banner-impression-noendpoint.json | 2 +- .../banner-impression-nohost.json | 2 +- .../supplemental/banner-impression-nokey.json | 4 +- .../tappxtest/supplemental/bidfloor.json | 10 +- .../supplemental/http-err-status.json | 10 +- .../supplemental/http-err-status2.json | 10 +- .../supplemental/wrong-imp-ext-1.json | 4 +- adapters/tappx/usersync.go | 12 + adapters/tappx/usersync_test.go | 34 + adapters/telaria/params_test.go | 2 +- adapters/telaria/telaria.go | 32 +- adapters/telaria/telaria_test.go | 6 +- adapters/telaria/usersync.go | 6 +- adapters/telaria/usersync_test.go | 5 +- adapters/triplelift/triplelift.go | 22 +- adapters/triplelift/triplelift_test.go | 6 +- adapters/triplelift/usersync.go | 6 +- adapters/triplelift/usersync_test.go | 3 +- .../triplelift_native/triplelift_native.go | 26 +- .../triplelift_native_test.go | 6 +- adapters/triplelift_native/usersync.go | 6 +- adapters/triplelift_native/usersync_test.go | 3 +- adapters/trustx/usersync.go | 12 + adapters/trustx/usersync_test.go | 29 + adapters/ucfunnel/params_test.go | 2 +- adapters/ucfunnel/ucfunnel.go | 22 +- adapters/ucfunnel/ucfunnel_test.go | 66 +- .../ucfunneltest/exemplary/ucfunnel.json | 2 +- adapters/ucfunnel/usersync.go | 6 +- adapters/ucfunnel/usersync_test.go | 5 +- adapters/unicorn/params_test.go | 61 ++ adapters/unicorn/unicorn.go | 244 ++++++ adapters/unicorn/unicorn_test.go | 20 + .../exemplary/banner-app-no-source.json | 228 ++++++ .../exemplary/banner-app-with-ip.json | 236 ++++++ .../exemplary/banner-app-with-ipv6.json | 236 ++++++ .../exemplary/banner-app-without-ext.json | 212 ++++++ .../banner-app-without-placementid.json | 231 ++++++ .../unicorntest/exemplary/banner-app.json | 232 ++++++ .../exemplary/banner-app_with_fpd.json | 244 ++++++ .../exemplary/banner-app_with_no_fpd.json | 238 ++++++ .../unicorntest/params/race/banner.json | 6 + .../unicorn/unicorntest/supplemental/204.json | 170 +++++ .../unicorn/unicorntest/supplemental/400.json | 175 +++++ .../unicorn/unicorntest/supplemental/500.json | 175 +++++ .../supplemental/ccpa-is-enabled.json | 80 ++ .../supplemental/coppa-is-enabled.json | 78 ++ .../supplemental/gdpr-is-enabled.json | 80 ++ .../supplemental/no-imp-ext-prebid.json | 67 ++ .../unicorntest/supplemental/no-imp-ext.json | 62 ++ .../supplemental/no-storedrequest-imp.json | 69 ++ adapters/unruly/params_test.go | 2 +- adapters/unruly/unruly.go | 28 +- adapters/unruly/unruly_test.go | 74 +- adapters/unruly/usersync.go | 6 +- adapters/unruly/usersync_test.go | 7 +- adapters/valueimpression/params_test.go | 2 +- adapters/valueimpression/usersync.go | 6 +- adapters/valueimpression/usersync_test.go | 7 +- adapters/valueimpression/valueimpression.go | 20 +- .../valueimpression/valueimpression_test.go | 6 +- .../exemplary/banner-and-video.json | 2 +- .../supplemental/explicit-dimensions.json | 2 +- .../invalid-response-no-bids.json | 2 +- .../invalid-response-unmarshall-error.json | 2 +- adapters/verizonmedia/params_test.go | 2 +- adapters/verizonmedia/usersync.go | 7 +- adapters/verizonmedia/usersync_test.go | 3 +- adapters/verizonmedia/verizonmedia.go | 28 +- adapters/verizonmedia/verizonmedia_test.go | 6 +- adapters/visx/params_test.go | 2 +- adapters/visx/usersync.go | 6 +- adapters/visx/usersync_test.go | 7 +- adapters/visx/visx.go | 20 +- adapters/visx/visx_test.go | 6 +- adapters/vrtcal/params_test.go | 2 +- adapters/vrtcal/usersync.go | 6 +- adapters/vrtcal/usersync_test.go | 5 +- adapters/vrtcal/vrtcal.go | 16 +- adapters/vrtcal/vrtcal_test.go | 6 +- adapters/yeahmobi/params_test.go | 2 +- adapters/yeahmobi/yeahmobi.go | 30 +- adapters/yeahmobi/yeahmobi_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/yieldlab/params_test.go | 2 +- adapters/yieldlab/usersync.go | 6 +- adapters/yieldlab/usersync_test.go | 5 +- adapters/yieldlab/yieldlab.go | 30 +- adapters/yieldlab/yieldlab_test.go | 6 +- adapters/yieldmo/params_test.go | 2 +- adapters/yieldmo/usersync.go | 6 +- adapters/yieldmo/usersync_test.go | 5 +- adapters/yieldmo/yieldmo.go | 22 +- adapters/yieldmo/yieldmo_test.go | 6 +- adapters/yieldone/params_test.go | 2 +- adapters/yieldone/usersync.go | 6 +- adapters/yieldone/usersync_test.go | 5 +- adapters/yieldone/yieldone.go | 22 +- adapters/yieldone/yieldone_test.go | 6 +- .../supplemental/bad_response.json | 2 +- adapters/zeroclickfraud/usersync.go | 6 +- adapters/zeroclickfraud/usersync_test.go | 6 +- adapters/zeroclickfraud/zeroclickfraud.go | 26 +- .../zeroclickfraud/zeroclickfraud_test.go | 6 +- .../supplemental/bad-response-body.json | 2 +- amp/parse.go | 22 +- amp/parse_test.go | 12 +- analytics/clients/http.go | 2 +- analytics/config/config.go | 10 +- analytics/config/config_test.go | 13 +- analytics/core.go | 20 +- analytics/filesystem/file_module.go | 2 +- analytics/filesystem/file_module_test.go | 11 +- analytics/pubstack/helpers/json.go | 2 +- analytics/pubstack/helpers/json_test.go | 9 +- analytics/pubstack/pubstack_module.go | 6 +- analytics/pubstack/pubstack_module_test.go | 8 +- cache/dummycache/dummycache.go | 2 +- cache/filecache/filecache.go | 2 +- cache/postgrescache/postgrescache.go | 4 +- config/accounts.go | 5 +- config/adapter.go | 2 +- config/bidderinfo.go | 101 +++ config/bidderinfo_test.go | 210 ++++++ config/bidderinfo_validate_test.go | 115 +++ config/config.go | 52 +- config/config_test.go | 5 +- .../test}/bidder-info/someBidder.yaml | 1 + currency/rate_converter.go | 4 +- currency/rate_converter_test.go | 2 +- docs/developers/automated-tests.md | 2 +- docs/developers/contributing.md | 2 +- endpoints/auction.go | 30 +- endpoints/auction_test.go | 36 +- endpoints/cookie_sync.go | 19 +- endpoints/cookie_sync_test.go | 43 +- endpoints/currency_rates.go | 2 +- endpoints/currency_rates_test.go | 2 +- endpoints/events/account_test.go | 12 +- endpoints/events/event.go | 10 +- endpoints/events/event_test.go | 6 +- endpoints/events/vtrack.go | 258 +------ endpoints/events/vtrack_test.go | 592 +-------------- endpoints/getuids.go | 4 +- endpoints/getuids_test.go | 2 +- endpoints/info/bidders.go | 116 +-- endpoints/info/bidders_detail.go | 185 +++++ endpoints/info/bidders_detail_test.go | 497 ++++++++++++ endpoints/info/bidders_test.go | 354 ++------- endpoints/openrtb2/amp_auction.go | 92 +-- endpoints/openrtb2/amp_auction_test.go | 322 ++++---- endpoints/openrtb2/auction.go | 366 ++++++--- endpoints/openrtb2/auction_benchmark_test.go | 19 +- endpoints/openrtb2/auction_test.go | 426 ++++++----- .../adslot_combination_generator.go | 2 +- .../adslot_combination_generator_test.go | 4 +- .../openrtb2/ctv/combination/combination.go | 4 +- .../ctv/combination/combination_test.go | 4 +- endpoints/openrtb2/ctv/impressions/helper.go | 4 +- .../ctv/impressions/impression_generator.go | 2 +- .../openrtb2/ctv/impressions/impressions.go | 4 +- .../ctv/impressions/maximize_for_duration.go | 4 +- .../impressions/maximize_for_duration_test.go | 4 +- .../ctv/impressions/min_max_algorithm.go | 4 +- .../ctv/impressions/min_max_algorithm_test.go | 2 +- .../ctv/response/adpod_generator copy.go.bak | 2 +- .../openrtb2/ctv/response/adpod_generator.go | 18 +- .../ctv/response/adpod_generator_test.go | 331 +------- endpoints/openrtb2/ctv/types/adpod_types.go | 12 +- endpoints/openrtb2/ctv/util/util.go | 10 +- endpoints/openrtb2/ctv/util/util_test.go | 10 +- endpoints/openrtb2/ctv_auction.go | 135 ++-- endpoints/openrtb2/ctv_auction_test.go | 101 +-- endpoints/openrtb2/interstitial.go | 26 +- endpoints/openrtb2/interstitial_test.go | 14 +- .../disabled/bad/bad-alias.json | 4 +- ...=> valid-fpd-allowed-with-ext-bidder.json} | 33 +- ...valid-fpd-allowed-with-prebid-bidder.json} | 33 +- ...ext-type-content-incompatible-subtype.json | 25 + ...ext-type-product-incompatible-subtype.json | 27 + ...text-type-social-incompatible-subtype.json | 27 + .../contextsubtype-greater-than-max.json | 26 + .../contextsubtype-invalid.json | 4 +- .../audio-maxbitrate-negative.json | 28 + .../invalid-whole/audio-maxseq-negative.json | 28 + .../audio-minbitrate-negative.json | 28 + .../audio-sequence-negative.json | 28 + .../invalid-whole/banner-h-negative.json | 23 + .../invalid-whole/banner-w-negative.json | 23 + .../device-geo-accuracy-negative.json | 30 + .../invalid-whole/device-h-negative.json | 27 + .../invalid-whole/device-ppi-negative.json | 28 + .../invalid-whole/device-w-negative.json | 27 + .../invalid-whole/format-h-negative.json | 20 + .../invalid-whole/format-hratio-negative.json | 21 + .../invalid-whole/format-w-negative.json | 20 + .../invalid-whole/format-wmin-negative.json | 21 + .../invalid-whole/format-wratio-negative.json | 21 + .../user-geo-accuracy-negative.json | 28 + .../invalid-whole/video-h-negative.json | 28 + .../video-maxbitrate-negative.json | 28 + .../video-minbitrate-negative.json | 28 + .../invalid-whole/video-w-negative.json | 28 + .../context-product-compatible-subtype.json | 41 + .../context-social-compatible-subtype.json | 41 + .../valid-whole/exemplary/skadn.json | 48 ++ .../supplementary/app-ios140-no-ifa.json | 34 + .../supplementary/us-privacy-invalid.json | 52 ++ endpoints/openrtb2/video_auction.go | 42 +- endpoints/openrtb2/video_auction_test.go | 152 ++-- endpoints/setuid.go | 12 +- endpoints/setuid_test.go | 23 +- errortypes/aggregate.go | 12 +- errortypes/aggregate_test.go | 4 +- errortypes/code.go | 3 +- errortypes/errortypes.go | 41 +- exchange/adapter_builders.go | 215 +++--- exchange/adapter_util.go | 38 +- exchange/adapter_util_test.go | 144 ++-- exchange/auction.go | 45 +- exchange/auction_test.go | 44 +- exchange/bidder.go | 113 +-- exchange/bidder_test.go | 471 ++++++++---- exchange/bidder_validate_bids.go | 12 +- exchange/bidder_validate_bids_test.go | 44 +- exchange/events.go | 48 +- exchange/events_test.go | 153 +--- exchange/exchange.go | 363 ++++----- exchange/exchange_test.go | 712 ++++++++++-------- .../exchangetest/append-bidder-names.json | 2 +- .../bid-ext-prebid-collision.json | 90 +++ exchange/exchangetest/bid-ext.json | 87 +++ exchange/exchangetest/bid-id-invalid.json | 161 ++++ exchange/exchangetest/bid-id-valid.json | 154 ++++ exchange/exchangetest/debuglog_disabled.json | 2 +- exchange/exchangetest/debuglog_enabled.json | 2 +- .../events-vast-account-off-request-on.json | 11 +- ...rstpartydata-imp-ext-multiple-bidders.json | 15 +- ...ata-imp-ext-multiple-prebid-bidders.json } | 17 +- .../firstpartydata-imp-ext-one-bidder.json | 10 +- ...stpartydata-imp-ext-one-prebid-bidder.json | 11 +- exchange/gdpr.go | 8 +- exchange/gdpr_test.go | 30 +- exchange/legacy.go | 42 +- exchange/legacy_test.go | 98 +-- exchange/price_granularity.go | 2 +- exchange/price_granularity_test.go | 2 +- exchange/seatbid.go | 8 - exchange/targeting.go | 10 +- exchange/targeting_test.go | 61 +- exchange/utils.go | 220 +++--- exchange/utils_test.go | 510 +++++++++++-- gdpr/gdpr.go | 12 +- gdpr/gdpr_test.go | 4 +- gdpr/impl.go | 51 +- gdpr/impl_test.go | 72 +- gdpr/vendorlist-fetching.go | 97 +-- gdpr/vendorlist-fetching_test.go | 178 ++--- go.mod | 18 +- go.sum | 98 ++- main.go | 16 +- 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 | 2 +- metrics/prometheus/prometheus.go | 7 +- metrics/prometheus/prometheus_test.go | 6 +- metrics/prometheus/type_conversion.go | 4 +- openrtb_ext/bid.go | 1 + openrtb_ext/bid_request_video.go | 18 +- openrtb_ext/bidders.go | 77 +- openrtb_ext/bidders_test.go | 181 ++--- openrtb_ext/bidders_validate_test.go | 99 +++ openrtb_ext/deal_tier.go | 4 +- openrtb_ext/deal_tier_test.go | 4 +- openrtb_ext/device.go | 76 +- openrtb_ext/device_test.go | 83 +- openrtb_ext/imp_adyoulike.go | 18 + openrtb_ext/imp_bidmachine.go | 7 + openrtb_ext/imp_criteo.go | 7 + openrtb_ext/imp_epom.go | 4 + openrtb_ext/imp_gumgum.go | 11 +- openrtb_ext/imp_jixie.go | 8 + openrtb_ext/imp_onetag.go | 10 + openrtb_ext/imp_outbrain.go | 15 + openrtb_ext/imp_pangle.go | 5 + openrtb_ext/imp_unicorn.go | 9 + openrtb_ext/request.go | 16 +- openrtb_ext/response.go | 13 +- openrtb_ext/site_test.go | 2 +- pbs/pbsrequest.go | 68 +- pbs/pbsrequest_test.go | 4 +- pbs/pbsresponse.go | 4 +- pbs/usersync.go | 10 +- prebid_cache_client/client.go | 6 +- prebid_cache_client/client_test.go | 6 +- prebid_cache_client/prebid_cache.go | 4 +- privacy/ccpa/consentwriter.go | 6 +- privacy/ccpa/consentwriter_test.go | 20 +- privacy/ccpa/parsedpolicy.go | 7 +- privacy/ccpa/parsedpolicy_test.go | 4 +- privacy/ccpa/policy.go | 20 +- privacy/ccpa/policy_test.go | 144 ++-- privacy/enforcement.go | 8 +- privacy/enforcement_test.go | 22 +- privacy/gdpr/consentwriter.go | 9 +- privacy/gdpr/consentwriter_test.go | 40 +- privacy/lmt/ios.go | 67 ++ privacy/lmt/ios_test.go | 276 +++++++ privacy/lmt/policy.go | 6 +- privacy/lmt/policy_test.go | 14 +- privacy/policies.go | 6 +- privacy/scrubber.go | 16 +- privacy/scrubber_test.go | 78 +- privacy/writer.go | 8 +- privacy/writer_test.go | 6 +- router/admin.go | 4 +- router/aspects/request_timeout_handler.go | 4 +- .../aspects/request_timeout_handler_test.go | 4 +- router/router.go | 200 ++--- router/router_test.go | 42 +- server/listener.go | 2 +- server/listener_test.go | 4 +- server/prometheus.go | 6 +- server/server.go | 6 +- server/server_test.go | 2 +- static/bidder-info/33across.yaml | 1 + static/bidder-info/acuityads.yaml | 1 + static/bidder-info/adform.yaml | 1 + static/bidder-info/adkernel.yaml | 1 + static/bidder-info/adkernelAdn.yaml | 1 + static/bidder-info/adman.yaml | 1 + static/bidder-info/admixer.yaml | 1 + static/bidder-info/adocean.yaml | 1 + static/bidder-info/adpone.yaml | 1 + static/bidder-info/adtelligent.yaml | 1 + static/bidder-info/adxcg.yaml | 13 + static/bidder-info/adyoulike.yaml | 10 + static/bidder-info/amx.yaml | 1 + static/bidder-info/appnexus.yaml | 1 + static/bidder-info/avocet.yaml | 1 + static/bidder-info/beachfront.yaml | 1 + static/bidder-info/beintoo.yaml | 1 + static/bidder-info/between.yaml | 1 + static/bidder-info/bidmachine.yaml | 8 + static/bidder-info/brightroll.yaml | 1 + static/bidder-info/connectad.yaml | 1 + static/bidder-info/consumable.yaml | 1 + static/bidder-info/conversant.yaml | 1 + static/bidder-info/criteo.yaml | 10 + static/bidder-info/deepintent.yaml | 1 + static/bidder-info/dmx.yaml | 1 + static/bidder-info/emx_digital.yaml | 1 + static/bidder-info/engagebdr.yaml | 1 + static/bidder-info/eplanning.yaml | 1 + static/bidder-info/epom.yaml | 14 + static/bidder-info/gamoshi.yaml | 1 + static/bidder-info/grid.yaml | 1 + static/bidder-info/gumgum.yaml | 1 + static/bidder-info/improvedigital.yaml | 3 + static/bidder-info/invibes.yaml | 1 + static/bidder-info/ix.yaml | 1 + static/bidder-info/jixie.yaml | 8 + static/bidder-info/lifestreet.yaml | 1 + static/bidder-info/mediafuse.yaml | 1 + static/bidder-info/mgid.yaml | 1 + static/bidder-info/nanointeractive.yaml | 1 + static/bidder-info/nobid.yaml | 1 + static/bidder-info/onetag.yaml | 14 + static/bidder-info/openx.yaml | 1 + static/bidder-info/orbidder.yaml | 4 +- static/bidder-info/outbrain.yaml | 12 + static/bidder-info/pangle.yaml | 9 + static/bidder-info/pubmatic.yaml | 1 + static/bidder-info/pulsepoint.yaml | 1 + static/bidder-info/rhythmone.yaml | 1 + static/bidder-info/rtbhouse.yaml | 1 + static/bidder-info/rubicon.yaml | 1 + static/bidder-info/sharethrough.yaml | 1 + static/bidder-info/smartadserver.yaml | 1 + static/bidder-info/somoaudience.yaml | 1 + static/bidder-info/sonobi.yaml | 1 + static/bidder-info/sovrn.yaml | 1 + static/bidder-info/tappx.yaml | 5 + static/bidder-info/telaria.yaml | 1 + static/bidder-info/triplelift.yaml | 1 + static/bidder-info/triplelift_native.yaml | 1 + static/bidder-info/trustx.yaml | 12 + static/bidder-info/ucfunnel.yaml | 1 + static/bidder-info/unicorn.yaml | 6 + static/bidder-info/unruly.yaml | 1 + static/bidder-info/verizonmedia.yaml | 1 + static/bidder-info/visx.yaml | 1 + static/bidder-info/yieldlab.yaml | 1 + static/bidder-info/yieldmo.yaml | 1 + static/bidder-params/adform.json | 2 +- static/bidder-params/adxcg.json | 14 + static/bidder-params/adyoulike.json | 33 + static/bidder-params/bidmachine.json | 28 + static/bidder-params/connectad.json | 4 +- static/bidder-params/criteo.json | 30 + static/bidder-params/dmx.json | 2 +- static/bidder-params/epom.json | 8 + static/bidder-params/gumgum.json | 21 +- static/bidder-params/invibes.json | 2 +- static/bidder-params/jixie.json | 27 + static/bidder-params/kidoz.json | 14 +- static/bidder-params/mobfoxpb.json | 42 +- static/bidder-params/nanointeractive.json | 4 +- static/bidder-params/nobid.json | 6 +- static/bidder-params/onetag.json | 20 + static/bidder-params/outbrain.json | 40 + static/bidder-params/pangle.json | 16 + static/bidder-params/rtbhouse.json | 17 +- static/bidder-params/trustx.json | 13 + static/bidder-params/unicorn.json | 25 + .../category-mapping/freewheel/freewheel.json | 2 +- .../backends/db_fetcher/fetcher.go | 2 +- .../backends/empty_fetcher/fetcher.go | 2 +- .../backends/file_fetcher/fetcher.go | 2 +- .../backends/file_fetcher/fetcher_test.go | 2 +- .../backends/http_fetcher/fetcher.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 | 14 +- stored_requests/events/api/api.go | 2 +- stored_requests/events/api/api_test.go | 6 +- stored_requests/events/events.go | 2 +- stored_requests/events/events_test.go | 4 +- stored_requests/events/http/http.go | 2 +- stored_requests/events/postgres/database.go | 8 +- .../events/postgres/database_test.go | 6 +- stored_requests/fetcher.go | 2 +- stored_requests/fetcher_test.go | 4 +- usersync/cookie.go | 9 +- usersync/cookie_test.go | 4 +- usersync/usersync.go | 12 +- usersync/usersyncers/syncer.go | 176 +++-- usersync/usersyncers/syncer_test.go | 45 +- util/httputil/httputil.go | 2 +- util/httputil/httputil_test.go | 2 +- util/iosutil/iosutil.go | 78 ++ util/iosutil/iosutil_test.go | 149 ++++ util/task/ticker_task_test.go | 2 +- 1131 files changed, 33290 insertions(+), 10146 deletions(-) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .travis.yml delete mode 100644 adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json create mode 100644 adapters/adform/adformtest/supplemental/regs-ext-nil.json create mode 100644 adapters/adxcg/adxcg.go create mode 100644 adapters/adxcg/adxcg_test.go create mode 100644 adapters/adxcg/adxcgtest/exemplary/simple-banner.json create mode 100644 adapters/adxcg/adxcgtest/exemplary/simple-native.json create mode 100644 adapters/adxcg/adxcgtest/exemplary/simple-video.json create mode 100644 adapters/adxcg/adxcgtest/params/race/banner.json create mode 100644 adapters/adxcg/adxcgtest/supplemental/bad_response.json create mode 100644 adapters/adxcg/adxcgtest/supplemental/status_204.json create mode 100644 adapters/adxcg/adxcgtest/supplemental/status_400.json create mode 100644 adapters/adxcg/adxcgtest/supplemental/status_418.json create mode 100644 adapters/adxcg/usersync.go create mode 100644 adapters/adxcg/usersync_test.go create mode 100644 adapters/adyoulike/adyoulike.go create mode 100644 adapters/adyoulike/adyoulike_test.go create mode 100644 adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json create mode 100644 adapters/adyoulike/adyouliketest/params/race/banner.json create mode 100644 adapters/adyoulike/adyouliketest/params/race/video.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/status-no-content.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json create mode 100644 adapters/adyoulike/adyouliketest/supplemental/status-unknown.json create mode 100644 adapters/adyoulike/params_test.go create mode 100644 adapters/adyoulike/usersync.go create mode 100644 adapters/adyoulike/usersync_test.go delete mode 100644 adapters/avocet/avocet/exemplary/banner.json delete mode 100644 adapters/avocet/avocet/exemplary/video.json create mode 100644 adapters/beachfront/beachfronttest/exemplary/adm-video.json rename adapters/beachfront/beachfronttest/{exemplary/adm-video-by-explicit-type.json => supplemental/adm-video-by-explicit.json} (100%) rename adapters/beachfront/beachfronttest/{exemplary/adm-video-by-default.json => supplemental/adm-video-no-ip.json} (98%) create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json rename adapters/beachfront/beachfronttest/supplemental/{banner-empty_array-200.json => banner-204.json} (90%) rename adapters/beachfront/beachfronttest/{exemplary/banner-and-adm-video-by-explicit-type.json => supplemental/banner-and-adm-video-by-explicit.json} (94%) create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json rename adapters/beachfront/beachfronttest/{exemplary/banner-and-adm-video-by-default.json => supplemental/banner-and-adm-video.json} (94%) rename adapters/beachfront/beachfronttest/{exemplary => supplemental}/banner-and-nurl-video.json (97%) create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/internal-server-error-500.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/no-imps.json rename adapters/beachfront/beachfronttest/supplemental/{six-nine-combo--response-order--banner-adm-nurl--PASS.json => six-nine-combo.json} (95%) create mode 100644 adapters/beachfront/beachfronttest/supplemental/video-no-appid.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/video-wrong-appids.json create mode 100644 adapters/bidmachine/bidmachine.go create mode 100644 adapters/bidmachine/bidmachine_test.go create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_no_battr.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/rewarded_interstitial_w_battr.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_no_battr.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/rewarded_video_w_battr.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/simple_banner.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/simple_interstitial.json create mode 100644 adapters/bidmachine/bidmachinetest/exemplary/simple_video.json create mode 100644 adapters/bidmachine/bidmachinetest/params/race/banner.json create mode 100644 adapters/bidmachine/bidmachinetest/params/race/video.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/empty-banner-format .json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/missing-banner-format.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-204.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-400.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-403.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-408.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-500.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-502.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-503.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/status-504.json create mode 100644 adapters/bidmachine/bidmachinetest/supplemental/wrong-host.json create mode 100644 adapters/bidmachine/params_test.go create mode 100644 adapters/criteo/criteo.go create mode 100644 adapters/criteo/criteo_test.go create mode 100755 adapters/criteo/criteotest/exemplary/simple-banner-cookie-uid.json create mode 100755 adapters/criteo/criteotest/exemplary/simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/exemplary/simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/204-response-from-target.json create mode 100755 adapters/criteo/criteotest/supplemental/400-response-from-target.json create mode 100755 adapters/criteo/criteotest/supplemental/500-response-from-target.json create mode 100755 adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/ccpa-with-consent-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/ccpa-without-consent-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/gdpr-with-consent-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/gdpr-without-consent-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/multislots-simple-banner-inapp.json create mode 100755 adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid-different-network-ids.json create mode 100755 adapters/criteo/criteotest/supplemental/multislots-simple-banner-uid.json create mode 100755 adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats-not-included.json create mode 100755 adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size-and-formats.json create mode 100755 adapters/criteo/criteotest/supplemental/simple-banner-with-direct-size.json create mode 100755 adapters/criteo/criteotest/supplemental/simple-banner-with-ipv6.json create mode 100644 adapters/criteo/generators.go create mode 100644 adapters/criteo/models.go create mode 100644 adapters/criteo/models_test.go create mode 100644 adapters/criteo/params_test.go create mode 100644 adapters/criteo/usersync.go create mode 100644 adapters/criteo/usersync_test.go create mode 100644 adapters/dmx/dmxtest/exemplary/idfa-to-app-id.json create mode 100644 adapters/dmx/dmxtest/exemplary/missing-width-height.json create mode 100644 adapters/epom/epom.go create mode 100644 adapters/epom/epom_test.go create mode 100644 adapters/epom/epomtest/exemplary/simple-app-banner.json create mode 100644 adapters/epom/epomtest/exemplary/simple-app-native.json create mode 100644 adapters/epom/epomtest/exemplary/simple-app-video.json create mode 100644 adapters/epom/epomtest/exemplary/simple-site-banner.json create mode 100644 adapters/epom/epomtest/exemplary/simple-site-native.json create mode 100644 adapters/epom/epomtest/exemplary/simple-site-video.json create mode 100644 adapters/epom/epomtest/params/race/banner.json create mode 100644 adapters/epom/epomtest/params/race/native.json create mode 100644 adapters/epom/epomtest/params/race/video.json create mode 100644 adapters/epom/epomtest/supplemental/bad-request-no-device.json create mode 100644 adapters/epom/epomtest/supplemental/bad-request-no-ip.json create mode 100644 adapters/epom/epomtest/supplemental/bad-response-bad-request-error.json create mode 100644 adapters/epom/epomtest/supplemental/bad-response-no-bid-obj-error.json create mode 100644 adapters/epom/epomtest/supplemental/bad-response-no-seatbid-error.json create mode 100644 adapters/epom/epomtest/supplemental/bad-response-server-internal-error.json create mode 100644 adapters/epom/epomtest/supplemental/bad-response-unexpected-error.json create mode 100644 adapters/grid/gridtest/exemplary/with_gpid.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/banner-only-in-format.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/banner-with-pubId.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/banner-with-site.json create mode 100644 adapters/gumgum/gumgumtest/supplemental/video-with-irisid.json create mode 100644 adapters/improvedigital/improvedigitaltest/exemplary/native.json create mode 100644 adapters/improvedigital/improvedigitaltest/params/race/native.json delete mode 100644 adapters/improvedigital/improvedigitaltest/supplemental/native.json delete mode 100644 adapters/info.go create mode 100644 adapters/infoawarebidder.go rename adapters/{info_test.go => infoawarebidder_test.go} (52%) create mode 100644 adapters/jixie/jixie.go create mode 100644 adapters/jixie/jixie_test.go create mode 100644 adapters/jixie/jixietest/exemplary/banner-and-video-site.json create mode 100644 adapters/jixie/jixietest/params/race/banner.json create mode 100644 adapters/jixie/jixietest/params/race/video.json create mode 100644 adapters/jixie/jixietest/supplemental/add-accountid.json create mode 100644 adapters/jixie/jixietest/supplemental/add-extraprop.json create mode 100644 adapters/jixie/jixietest/supplemental/add-userid.json create mode 100644 adapters/jixie/params_test.go create mode 100644 adapters/jixie/usersync.go create mode 100644 adapters/jixie/usersync_test.go create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json delete mode 100644 adapters/mobfoxpb/mobfoxpbtest/params/race/video.json create mode 100644 adapters/mobfoxpb/mobfoxpbtest/supplemental/missmatch_bid_id.json create mode 100644 adapters/onetag/onetag.go create mode 100644 adapters/onetag/onetag_test.go create mode 100644 adapters/onetag/onetagtest/exemplary/no-bid.json create mode 100644 adapters/onetag/onetagtest/exemplary/simple-banner.json create mode 100644 adapters/onetag/onetagtest/exemplary/simple-native.json create mode 100644 adapters/onetag/onetagtest/exemplary/simple-video.json create mode 100644 adapters/onetag/onetagtest/params/race/banner.json create mode 100644 adapters/onetag/onetagtest/params/race/native.json create mode 100644 adapters/onetag/onetagtest/params/race/video.json create mode 100644 adapters/onetag/onetagtest/supplemental/empty-publisher-id.json create mode 100644 adapters/onetag/onetagtest/supplemental/internal-server-error.json create mode 100644 adapters/onetag/onetagtest/supplemental/required-publisher-id.json create mode 100644 adapters/onetag/onetagtest/supplemental/unique-publisher-id.json create mode 100644 adapters/onetag/onetagtest/supplemental/wrong-impression-id.json create mode 100644 adapters/onetag/params_test.go create mode 100644 adapters/onetag/usersync.go create mode 100644 adapters/onetag/usersync_test.go create mode 100644 adapters/outbrain/outbrain.go create mode 100644 adapters/outbrain/outbrain_test.go create mode 100644 adapters/outbrain/outbraintest/exemplary/banner.json create mode 100644 adapters/outbrain/outbraintest/exemplary/native.json create mode 100644 adapters/outbrain/outbraintest/params/race/banner.json create mode 100644 adapters/outbrain/outbraintest/params/race/native.json create mode 100644 adapters/outbrain/outbraintest/supplemental/app_request.json create mode 100644 adapters/outbrain/outbraintest/supplemental/optional_params.json create mode 100644 adapters/outbrain/outbraintest/supplemental/status_204.json create mode 100644 adapters/outbrain/outbraintest/supplemental/status_400.json create mode 100644 adapters/outbrain/outbraintest/supplemental/status_418.json create mode 100644 adapters/outbrain/params_test.go create mode 100644 adapters/outbrain/usersync.go create mode 100644 adapters/outbrain/usersync_test.go create mode 100644 adapters/pangle/pangle.go create mode 100644 adapters/pangle/pangle_test.go create mode 100644 adapters/pangle/pangletest/exemplary/app_banner.json create mode 100644 adapters/pangle/pangletest/exemplary/app_banner_instl.json create mode 100644 adapters/pangle/pangletest/exemplary/app_native.json create mode 100644 adapters/pangle/pangletest/exemplary/app_video_instl.json create mode 100644 adapters/pangle/pangletest/exemplary/app_video_rewarded.json create mode 100644 adapters/pangle/pangletest/params/race/banner.json create mode 100644 adapters/pangle/pangletest/params/race/native.json create mode 100644 adapters/pangle/pangletest/params/race/video.json create mode 100644 adapters/pangle/pangletest/supplemental/pangle_ext_check.json create mode 100644 adapters/pangle/pangletest/supplemental/response_code_204.json create mode 100644 adapters/pangle/pangletest/supplemental/response_code_400.json create mode 100644 adapters/pangle/pangletest/supplemental/response_code_non_200.json create mode 100644 adapters/pangle/pangletest/supplemental/unrecognized_adtype.json create mode 100644 adapters/pangle/pangletest/supplemental/unsupported_adtype.json create mode 100644 adapters/pangle/param_test.go create mode 100644 adapters/smaato/smaatotest/exemplary/simple-banner-app.json create mode 100644 adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json create mode 100644 adapters/smaato/smaatotest/exemplary/video-app.json create mode 100644 adapters/smaato/smaatotest/supplemental/status-code-204.json create mode 100644 adapters/smaato/smaatotest/supplemental/status-code-400.json create mode 100644 adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json create mode 100644 adapters/tappx/tappxtest/exemplary/single-banner-site.json create mode 100644 adapters/tappx/tappxtest/exemplary/single-video-site.json create mode 100644 adapters/tappx/usersync.go create mode 100644 adapters/tappx/usersync_test.go create mode 100644 adapters/trustx/usersync.go create mode 100644 adapters/trustx/usersync_test.go create mode 100644 adapters/unicorn/params_test.go create mode 100644 adapters/unicorn/unicorn.go create mode 100644 adapters/unicorn/unicorn_test.go create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json create mode 100644 adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json create mode 100644 adapters/unicorn/unicorntest/params/race/banner.json create mode 100644 adapters/unicorn/unicorntest/supplemental/204.json create mode 100644 adapters/unicorn/unicorntest/supplemental/400.json create mode 100644 adapters/unicorn/unicorntest/supplemental/500.json create mode 100644 adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json create mode 100644 adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json create mode 100644 adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json create mode 100644 adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json create mode 100644 adapters/unicorn/unicorntest/supplemental/no-imp-ext.json create mode 100644 adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json create mode 100644 config/bidderinfo.go create mode 100644 config/bidderinfo_test.go create mode 100644 config/bidderinfo_validate_test.go rename {adapters/adapterstest => config/test}/bidder-info/someBidder.yaml (91%) create mode 100644 endpoints/info/bidders_detail.go create mode 100644 endpoints/info/bidders_detail_test.go rename endpoints/openrtb2/sample-requests/first-party-data/{valid-context-allowed-with-ext-bidder.json => valid-fpd-allowed-with-ext-bidder.json} (64%) rename endpoints/openrtb2/sample-requests/first-party-data/{valid-context-allowed-with-prebid-bidder.json => valid-fpd-allowed-with-prebid-bidder.json} (67%) create mode 100644 endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/audio-maxbitrate-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/audio-maxseq-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/audio-minbitrate-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/audio-sequence-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/banner-h-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/banner-w-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/device-geo-accuracy-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/device-h-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/device-ppi-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/device-w-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-h-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-hratio-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-w-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-wmin-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/format-wratio-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/user-geo-accuracy-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/video-h-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/video-maxbitrate-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/video-minbitrate-negative.json create mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/video-w-negative.json create mode 100644 endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/app-ios140-no-ifa.json create mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json create mode 100644 exchange/exchangetest/bid-ext-prebid-collision.json create mode 100644 exchange/exchangetest/bid-ext.json create mode 100644 exchange/exchangetest/bid-id-invalid.json create mode 100644 exchange/exchangetest/bid-id-valid.json rename exchange/exchangetest/{firstpartydata-imp-ext-multiple-prebid-bidders.json => firstpartydata-imp-ext-multiple-prebid-bidders.json } (90%) delete mode 100644 exchange/seatbid.go create mode 100644 openrtb_ext/bidders_validate_test.go create mode 100644 openrtb_ext/imp_adyoulike.go create mode 100644 openrtb_ext/imp_bidmachine.go create mode 100644 openrtb_ext/imp_criteo.go create mode 100644 openrtb_ext/imp_epom.go create mode 100644 openrtb_ext/imp_jixie.go create mode 100644 openrtb_ext/imp_onetag.go create mode 100644 openrtb_ext/imp_outbrain.go create mode 100644 openrtb_ext/imp_pangle.go create mode 100644 openrtb_ext/imp_unicorn.go create mode 100644 privacy/lmt/ios.go create mode 100644 privacy/lmt/ios_test.go create mode 100644 static/bidder-info/adxcg.yaml create mode 100644 static/bidder-info/adyoulike.yaml create mode 100644 static/bidder-info/bidmachine.yaml create mode 100644 static/bidder-info/criteo.yaml create mode 100644 static/bidder-info/epom.yaml create mode 100644 static/bidder-info/jixie.yaml create mode 100644 static/bidder-info/onetag.yaml create mode 100644 static/bidder-info/outbrain.yaml create mode 100644 static/bidder-info/pangle.yaml create mode 100644 static/bidder-info/trustx.yaml create mode 100644 static/bidder-info/unicorn.yaml create mode 100644 static/bidder-params/adxcg.json create mode 100644 static/bidder-params/adyoulike.json create mode 100644 static/bidder-params/bidmachine.json create mode 100644 static/bidder-params/criteo.json create mode 100644 static/bidder-params/epom.json create mode 100644 static/bidder-params/jixie.json create mode 100644 static/bidder-params/onetag.json create mode 100644 static/bidder-params/outbrain.json create mode 100644 static/bidder-params/pangle.json create mode 100644 static/bidder-params/trustx.json create mode 100644 static/bidder-params/unicorn.json mode change 100755 => 100644 usersync/usersyncers/syncer.go create mode 100644 util/iosutil/iosutil.go create mode 100644 util/iosutil/iosutil_test.go diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000000..720e2f043f0 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,9 @@ +change-template: '* $TITLE (#$NUMBER)' +template: | + ## Changes + + $CHANGES + + ## Contributors + + $CONTRIBUTORS \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..fb9b6592308 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,26 @@ +name: Release + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +jobs: + release: + name: Create Release + if: github.event.base_ref == 'refs/heads/master' + runs-on: ubuntu-latest + steps: + - name: Get Version + id: get_version + run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\//} + + - name: Create & Publish Release + uses: release-drafter/release-drafter@v5.12.1 + with: + name: ${{ steps.get_version.outputs.version }} + tag: ${{ steps.get_version.outputs.version }} + version: ${{ steps.get_version.outputs.version }} + publish: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 79076f9be84..514e217b46c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ vendor prebid-server build debug +__debug_bin # config files pbs.* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 655ea837eae..00000000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: go - -go: - - '1.14.2' - - '1.15' - -go_import_path: github.com/PubMatic-OpenWrap/prebid-server - -env: -- GO111MODULE=on - -script: - - "./validate.sh --nofmt --cov --race 10" - -before_deploy: - - go get github.com/mitchellh/gox - - gox -os="linux" -arch="386" -output="{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...; - -deploy: - provider: releases - skip_cleanup: true - api_key: - secure: TSJcbIpg2zTJuzUXwv0Un5DPztDTeIKQ2BuuO9KiWYY3Td/nKn0flTYE6B5O6iVqE96HKyj2j0W51rhnRTNDReRZv76L+YXLTJOTQEEQY/A+7XaUXRT0KIbr1EHaeU+4uPJe/8YXxq+nFNeqOjj+LY457WbvnQTIbraAmCgi4yNq4JR+J9BoCELkX0SnU7oq+brq9tJNL3V+7EHIVH6ZLa1lWOrapMnbrVils8gwzWR8XpbdaI+Sn30AGOFKZ0WE2ojZkZb8oZxyX0HKarIiykfZUUzRhlXlTJ0D81QOdc5AtPNR/2dqUXsUE8mRav9R3AJM2BCS2pnP29orCRQU/kxS/mRfx2oZhkr+OHPsNbJcGNSbqNKlM13bX2nL1ZJsJ6xL0VrkBFYlI01SWR12CT9DhZSqTmGPNEkt3fdzwuYtkJNfthb9e9obscnmJEHPSiZRv9dv/stP5LVJJHfFdrzM4+Qo3MCxLNOhmc+p93gsZPeuDGDlx8Tqv1KpN7sp0glbmOwyFAwbCVh5can/JPIAKsQi9VRyZAJvn+7sqqZCExN4TvFArq7pe0LjIVHUQZP9g/vS8HobQnPutmGxf8HqzVVEBnjMsXuiY4cVRecXVRM7crfJjLGr2e9ywIkUZMSD+bRkbRUZ0QQQPvWtcgRw5JmLKG9jDklj8BDkON8= - file: - - prebid-server_linux_386 - on: - repo: prebid/prebid-server - tags: true - branch: - - master diff --git a/Makefile b/Makefile index 8475ce8369b..8ffea91fe36 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ test: deps ifeq "$(adapter)" "" ./validate.sh else - go test github.com/PubMatic-OpenWrap/prebid-server/adapters/$(adapter) -bench=. + go test github.com/prebid/prebid-server/adapters/$(adapter) -bench=. endif # build will ensure all of our tests pass and then build the go binary diff --git a/README.md b/README.md index 529629e0e2a..9189421bd9d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ -[![Build Status](https://travis-ci.org/prebid/prebid-server.svg?branch=master)](https://travis-ci.org/prebid/prebid-server) -[![Go Report Card](https://goreportcard.com/badge/github.com/PubMatic-OpenWrap/prebid-server?style=flat-square)](https://goreportcard.com/report/github.com/PubMatic-OpenWrap/prebid-server) +[![Build](https://img.shields.io/github/workflow/status/prebid/prebid-server/Validate/master?style=flat-square)](https://github.com/prebid/prebid-server/actions/workflows/validate.yml) +[![Go Report Card](https://goreportcard.com/badge/github.com/prebid/prebid-server?style=flat-square)](https://goreportcard.com/report/github.com/prebid/prebid-server) +![Go Version](https://img.shields.io/github/go-mod/go-version/prebid/prebid-server?style=flat-square) # Prebid Server @@ -16,6 +17,8 @@ For more information, see: - [Prebid Server Overview](https://docs.prebid.org/prebid-server/overview/prebid-server-overview.html) - [Current Bidders](http://prebid.org/dev-docs/pbs-bidders.html) +Please consider [registering your Prebid Server](https://docs.prebid.org/prebid-server/hosting/pbs-hosting.html#optional-registration) to get on the mailing list for updates, etc. + ## Installation First install [Go](https://golang.org/doc/install) version 1.14 or newer. diff --git a/account/account.go b/account/account.go index d5d22f4a894..25a504ca23f 100644 --- a/account/account.go +++ b/account/account.go @@ -5,11 +5,11 @@ import ( "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" jsonpatch "github.com/evanphx/json-patch" + "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" ) // 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 0783ee0e134..75c48a02d89 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -6,10 +6,10 @@ import ( "fmt" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + "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/stretchr/testify/assert" ) diff --git a/adapters/33across/33across.go b/adapters/33across/33across.go index 7abd7ba72bd..fb329a76f21 100644 --- a/adapters/33across/33across.go +++ b/adapters/33across/33across.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 TtxAdapter struct { @@ -51,7 +51,7 @@ type bidTtxExt struct { } // MakeRequests create the object for TTX Reqeust. -func (a *TtxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -77,14 +77,14 @@ func (a *TtxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters return adapterRequests, errs } -func (a *TtxAdapter) makeRequest(request openrtb.BidRequest, imp openrtb.Imp) (*adapters.RequestData, error) { +func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) { impCopy, err := makeImps(imp) if err != nil { return nil, err } - request.Imp = []openrtb.Imp{*impCopy} + request.Imp = []openrtb2.Imp{*impCopy} // Last Step reqJSON, err := json.Marshal(request) @@ -103,7 +103,7 @@ func (a *TtxAdapter) makeRequest(request openrtb.BidRequest, imp openrtb.Imp) (* }, nil } -func makeImps(imp openrtb.Imp) (*openrtb.Imp, error) { +func makeImps(imp openrtb2.Imp) (*openrtb2.Imp, error) { if imp.Banner == nil && imp.Video == nil { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Imp ID %s must have at least one of [Banner, Video] defined", imp.ID), @@ -158,7 +158,7 @@ func makeImps(imp openrtb.Imp) (*openrtb.Imp, error) { return &imp, nil } -func makeReqExt(request *openrtb.BidRequest) ([]byte, error) { +func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) { var reqExt reqExt if len(request.Ext) > 0 { @@ -181,7 +181,7 @@ func makeReqExt(request *openrtb.BidRequest) ([]byte, error) { } // MakeBids make the bids for the bid response. -func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -198,7 +198,7 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -227,8 +227,8 @@ func (a *TtxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReque } -func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, error) { - videoCopy := video +func validateVideoParams(video *openrtb2.Video, prod string) (*openrtb2.Video, error) { + videoCopy := *video if videoCopy.W == 0 || videoCopy.H == 0 || videoCopy.Protocols == nil || @@ -248,11 +248,11 @@ func validateVideoParams(video *openrtb.Video, prod string) (*openrtb.Video, err videoCopy.Placement = 1 if videoCopy.StartDelay == nil { - videoCopy.StartDelay = openrtb.StartDelay.Ptr(0) + videoCopy.StartDelay = openrtb2.StartDelay.Ptr(0) } } - return videoCopy, nil + return &videoCopy, nil } func getBidType(ext bidExt) openrtb_ext.BidType { diff --git a/adapters/33across/33across_test.go b/adapters/33across/33across_test.go index 08c2ff614e9..97703735d9c 100644 --- a/adapters/33across/33across_test.go +++ b/adapters/33across/33across_test.go @@ -3,9 +3,9 @@ package ttx import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/33across/params_test.go b/adapters/33across/params_test.go index 19dfb22198c..0c7cde18216 100644 --- a/adapters/33across/params_test.go +++ b/adapters/33across/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/33across.json diff --git a/adapters/33across/usersync.go b/adapters/33across/usersync.go index 0bed70ee60d..df26f3b6325 100644 --- a/adapters/33across/usersync.go +++ b/adapters/33across/usersync.go @@ -3,10 +3,10 @@ package ttx import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func New33AcrossSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("33across", 58, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("33across", temp, adapters.SyncTypeIframe) } diff --git a/adapters/33across/usersync_test.go b/adapters/33across/usersync_test.go index 89cae0f3f19..e99b5965746 100644 --- a/adapters/33across/usersync_test.go +++ b/adapters/33across/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func Test33AcrossSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr=A&gdpr_consent=B&us_privacy=C&ru=%2Fsetuid%3Fbidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 58, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/acuityads/acuityads.go b/adapters/acuityads/acuityads.go index ac2c6ac7c4d..da1cda3da77 100644 --- a/adapters/acuityads/acuityads.go +++ b/adapters/acuityads/acuityads.go @@ -6,12 +6,12 @@ import ( "net/http" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 AcuityAdsAdapter struct { @@ -31,7 +31,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getHeaders(request *openrtb.BidRequest) http.Header { +func getHeaders(request *openrtb2.BidRequest) http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -55,7 +55,7 @@ func getHeaders(request *openrtb.BidRequest) http.Header { } func (a *AcuityAdsAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -87,7 +87,7 @@ func (a *AcuityAdsAdapter) MakeRequests( }}, nil } -func (a *AcuityAdsAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtAcuityAds, error) { +func (a *AcuityAdsAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtAcuityAds, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -136,7 +136,7 @@ func (a *AcuityAdsAdapter) checkResponseStatusCodes(response *adapters.ResponseD } func (a *AcuityAdsAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -153,7 +153,7 @@ func (a *AcuityAdsAdapter) MakeBids( } responseBody := bidderRawResponse.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", @@ -178,7 +178,7 @@ func (a *AcuityAdsAdapter) MakeBids( return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/acuityads/acuityads_test.go b/adapters/acuityads/acuityads_test.go index be12780a778..8cc50637374 100644 --- a/adapters/acuityads/acuityads_test.go +++ b/adapters/acuityads/acuityads_test.go @@ -3,9 +3,9 @@ package acuityads import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/acuityads/acuityadstest/exemplary/banner-app.json b/adapters/acuityads/acuityadstest/exemplary/banner-app.json index f4aedeb6788..526f1ea69ac 100644 --- a/adapters/acuityads/acuityadstest/exemplary/banner-app.json +++ b/adapters/acuityads/acuityadstest/exemplary/banner-app.json @@ -153,4 +153,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/adapters/acuityads/acuityadstest/exemplary/native-app.json b/adapters/acuityads/acuityadstest/exemplary/native-app.json index 6ada3926733..f10a0a9af4d 100644 --- a/adapters/acuityads/acuityadstest/exemplary/native-app.json +++ b/adapters/acuityads/acuityadstest/exemplary/native-app.json @@ -150,4 +150,4 @@ } ] } - + \ No newline at end of file diff --git a/adapters/acuityads/acuityadstest/exemplary/video-web.json b/adapters/acuityads/acuityadstest/exemplary/video-web.json index 60260aa5271..2efeffec320 100644 --- a/adapters/acuityads/acuityadstest/exemplary/video-web.json +++ b/adapters/acuityads/acuityadstest/exemplary/video-web.json @@ -160,4 +160,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json b/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json deleted file mode 100644 index 77752d01edf..00000000000 --- a/adapters/acuityads/acuityadstest/supplemental/invalid-smartyads-ext-object.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "expectedMakeRequestsErrors": [ - { - "value": "ext.bidder not provided", - "comparison": "literal" - } - ], - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "some-impression-id", - "tagid": "my-adcode", - "video": { - "mimes": ["video/mp4"], - "w": 640, - "h": 480, - "minduration": 120, - "maxduration": 150 - }, - "ext": "Awesome" - } - ], - "site": { - "page": "test.com" - } - }, - "httpCalls": [] -} diff --git a/adapters/acuityads/params_test.go b/adapters/acuityads/params_test.go index e1a47669796..892fe9a646d 100644 --- a/adapters/acuityads/params_test.go +++ b/adapters/acuityads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/acuityads/usersync.go b/adapters/acuityads/usersync.go index 90b610bb3cd..e2fc1f41961 100644 --- a/adapters/acuityads/usersync.go +++ b/adapters/acuityads/usersync.go @@ -3,10 +3,10 @@ package acuityads import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAcuityAdsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("acuityads", 231, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("acuityads", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/acuityads/usersync_test.go b/adapters/acuityads/usersync_test.go index 5c6f6a43677..b3ad10bdbb8 100644 --- a/adapters/acuityads/usersync_test.go +++ b/adapters/acuityads/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -29,6 +29,5 @@ func TestAcuityAdsSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://cs.admanmedia.com/sync/prebid?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dacuityads%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 231, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adapterstest/adapter_test_util.go b/adapters/adapterstest/adapter_test_util.go index 269eed087ed..25d43a138fb 100644 --- a/adapters/adapterstest/adapter_test_util.go +++ b/adapters/adapterstest/adapter_test_util.go @@ -8,13 +8,13 @@ import ( "net/http" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) // OrtbMockService Represents a scaffolded OpenRTB service. type OrtbMockService struct { Server *httptest.Server - LastBidRequest *openrtb.BidRequest + LastBidRequest *openrtb2.BidRequest LastHttpRequest *http.Request } @@ -30,8 +30,8 @@ func BidOnTags(tags string) map[string]bool { } // SampleBid Produces a sample bid based on params given. -func SampleBid(width *uint64, height *uint64, impId string, index int) openrtb.Bid { - return openrtb.Bid{ +func SampleBid(width *int64, height *int64, impId string, index int) openrtb2.Bid { + return openrtb2.Bid{ ID: "Bid-123", ImpID: fmt.Sprintf("div-adunit-%d", index), Price: 2.1, @@ -64,7 +64,7 @@ func VerifyBoolValue(value bool, expected bool, t *testing.T) { } // VerifyBannerSize helper function to assert banner size -func VerifyBannerSize(banner *openrtb.Banner, expectedWidth int, expectedHeight int, t *testing.T) { +func VerifyBannerSize(banner *openrtb2.Banner, expectedWidth int, expectedHeight int, t *testing.T) { VerifyIntValue(int(*(banner.W)), expectedWidth, t) VerifyIntValue(int(*(banner.H)), expectedHeight, t) } diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index b8c33064fc4..95319f1e328 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -7,8 +7,8 @@ import ( "regexp" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -123,12 +123,12 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd diffErrorLists(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors) for i := 0; i < len(spec.BidResponses); i++ { - diffBidLists(t, filename, bidResponses[i].Bids, spec.BidResponses[i].Bids) + diffBidLists(t, filename, bidResponses[i], spec.BidResponses[i].Bids) } } type testSpec struct { - BidRequest openrtb.BidRequest `json:"mockBidRequest"` + BidRequest openrtb2.BidRequest `json:"mockBidRequest"` HttpCalls []httpCall `json:"httpCalls"` BidResponses []expectedBidResponse `json:"expectedBidResponses"` MakeRequestErrors []testSpecExpectedError `json:"expectedMakeRequestsErrors"` @@ -227,9 +227,24 @@ func diffErrorLists(t *testing.T, description string, actual []error, expected [ } } -func diffBidLists(t *testing.T, filename string, actual []*adapters.TypedBid, expected []expectedBid) { +func diffBidLists(t *testing.T, filename string, response *adapters.BidderResponse, expected []expectedBid) { t.Helper() + if (response == nil || len(response.Bids) == 0) != (len(expected) == 0) { + if len(expected) == 0 { + t.Fatalf("%s: expectedBidResponses indicated a nil response, but mockResponses supplied a non-nil response", filename) + } + + t.Fatalf("%s: mockResponses included unexpected nil or empty response", filename) + } + + // Expected nil response - give diffBids something to work with. + if response == nil { + response = new(adapters.BidderResponse) + } + + actual := response.Bids + if len(actual) != len(expected) { t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(actual)) } @@ -266,7 +281,7 @@ func diffBids(t *testing.T, description string, actual *adapters.TypedBid, expec } // diffOrtbBids compares the actual Bid made by the adapter to the expectation from the JSON file. -func diffOrtbBids(t *testing.T, description string, actual *openrtb.Bid, expected json.RawMessage) { +func diffOrtbBids(t *testing.T, description string, actual *openrtb2.Bid, expected json.RawMessage) { if actual == nil { t.Errorf("Bidders cannot return nil Bids. %s was nil.", description) return diff --git a/adapters/adform/adform.go b/adapters/adform/adform.go index bb3f9f4d8a3..225c7af35d4 100644 --- a/adapters/adform/adform.go +++ b/adapters/adform/adform.go @@ -13,14 +13,13 @@ import ( "strconv" "strings" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - - "github.com/PubMatic-OpenWrap/openrtb" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/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/pbs" "golang.org/x/net/context/ctxhttp" ) @@ -235,8 +234,8 @@ func toPBSBidSlice(adformBids []*adformBid, r *adformRequest) pbs.PBSBidSlice { BidderCode: r.bidderCode, Price: bid.Price, Adm: adm, - Width: bid.Width, - Height: bid.Height, + Width: int64(bid.Width), + Height: int64(bid.Height), DealId: bid.DealId, Creative_id: bid.CreativeId, CreativeMediaType: string(bidType), @@ -374,7 +373,7 @@ func NewAdformLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, endpointURL } } -func (a *AdformAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdformAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { adformRequest, errors := openRtbToAdformRequest(request) if len(adformRequest.adUnits) == 0 { return nil, errors @@ -392,7 +391,7 @@ func (a *AdformAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt return requests, errors } -func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []error) { +func openRtbToAdformRequest(request *openrtb2.BidRequest) (*adformRequest, []error) { adUnits := make([]*adformAdUnit, 0, len(request.Imp)) errors := make([]error, 0, len(request.Imp)) secure := false @@ -453,7 +452,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro gdprApplies := "" var extRegs openrtb_ext.ExtRegs - if request.Regs != nil { + if request.Regs != nil && request.Regs.Ext != nil { if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil { errors = append(errors, &errortypes.BadInput{ Message: err.Error(), @@ -466,7 +465,7 @@ func openRtbToAdformRequest(request *openrtb.BidRequest) (*adformRequest, []erro eids := "" consent := "" - if request.User != nil { + if request.User != nil && request.User.Ext != nil { var extUser openrtb_ext.ExtUser if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { consent = extUser.Consent @@ -529,35 +528,35 @@ func encodeEids(eids []openrtb_ext.ExtUserEid) string { return encodedEids } -func getIPSafely(device *openrtb.Device) string { +func getIPSafely(device *openrtb2.Device) string { if device == nil { return "" } return device.IP } -func getIFASafely(device *openrtb.Device) string { +func getIFASafely(device *openrtb2.Device) string { if device == nil { return "" } return device.IFA } -func getUASafely(device *openrtb.Device) string { +func getUASafely(device *openrtb2.Device) string { if device == nil { return "" } return device.UA } -func getBuyerUIDSafely(user *openrtb.User) string { +func getBuyerUIDSafely(user *openrtb2.User) string { if user == nil { return "" } return user.BuyerUID } -func (a *AdformAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdformAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -584,7 +583,7 @@ func (a *AdformAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return bidResponse, nil } -func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb.BidRequest) *adapters.BidderResponse { +func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb2.BidRequest) *adapters.BidderResponse { bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(adformBids)) currency := bidResponse.Currency @@ -598,13 +597,13 @@ func toOpenRtbBidResponse(adformBids []*adformBid, r *openrtb.BidRequest) *adapt continue } - openRtbBid := openrtb.Bid{ + openRtbBid := openrtb2.Bid{ ID: r.Imp[i].ID, ImpID: r.Imp[i].ID, Price: bid.Price, AdM: adm, - W: bid.Width, - H: bid.Height, + W: int64(bid.Width), + H: int64(bid.Height), DealID: bid.DealId, CrID: bid.CreativeId, } diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go index 14663c32112..70cd6883a4d 100644 --- a/adapters/adform/adform_test.go +++ b/adapters/adform/adform_test.go @@ -10,17 +10,17 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -172,7 +172,7 @@ func TestAdformBasicResponse(t *testing.T) { if bid.Price != tag.price { t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) } - if bid.Width != adformTestData.width || bid.Height != adformTestData.height { + if bid.Width != int64(adformTestData.width) || bid.Height != int64(adformTestData.height) { t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, adformTestData.width, adformTestData.height) } if bid.Adm != tag.content { @@ -249,7 +249,7 @@ func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { // so User and Regs are copied from OpenRTB request, see legacy.go -> toLegacyRequest regs := getRegs() r.Regs = ®s - user := openrtb.User{ + user := openrtb2.User{ Ext: getUserExt(), } r.User = &user @@ -260,7 +260,7 @@ func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest { func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer { prebidRequest := pbs.PBSRequest{ AdUnits: make([]pbs.AdUnit, 4), - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: requestData.deviceUA, IP: requestData.deviceIP, IFA: requestData.deviceIFA, @@ -271,10 +271,10 @@ func preparePrebidRequestBody(requestData aBidInfo, t *testing.T) *bytes.Buffer for i, tag := range requestData.tags { prebidRequest.AdUnits[i] = pbs.AdUnit{ Code: tag.code, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { - W: requestData.width, - H: requestData.height, + W: int64(requestData.width), + H: int64(requestData.height), }, }, Bids: []pbs.Bids{ @@ -332,16 +332,16 @@ func TestOpenRTBRequest(t *testing.T) { func TestOpenRTBIncorrectRequest(t *testing.T) { bidder := new(AdformAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ {ID: "incorrect-bidder-field", Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`)}, {ID: "incorrect-adform-params", Ext: json.RawMessage(`{"bidder": { : "33" }}`)}, {ID: "mid-integer", Ext: json.RawMessage(`{"bidder": { "mid": 1.234 }}`)}, {ID: "mid-greater-then-zero", Ext: json.RawMessage(`{"bidder": { "mid": -1 }}`)}, }, - Device: &openrtb.Device{UA: "ua", IP: "ip"}, - User: &openrtb.User{BuyerUID: "buyerUID"}, + Device: &openrtb2.Device{UA: "ua", IP: "ip"}, + User: &openrtb2.User{BuyerUID: "buyerUID"}, } httpRequests, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -374,36 +374,36 @@ func createTestData(secure bool) aBidInfo { return testData } -func createOpenRtbRequest(testData *aBidInfo) *openrtb.BidRequest { +func createOpenRtbRequest(testData *aBidInfo) *openrtb2.BidRequest { secure := int8(0) if testData.secure { secure = int8(1) } - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: make([]openrtb.Imp, len(testData.tags)), - Site: &openrtb.Site{ + Imp: make([]openrtb2.Imp, len(testData.tags)), + Site: &openrtb2.Site{ Page: testData.referrer, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: testData.deviceUA, IP: testData.deviceIP, IFA: testData.deviceIFA, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: testData.tid, }, - User: &openrtb.User{ + User: &openrtb2.User{ BuyerUID: testData.buyerUID, }, } for i, tag := range testData.tags { - bidRequest.Imp[i] = openrtb.Imp{ + bidRequest.Imp[i] = openrtb2.Imp{ ID: tag.code, Secure: &secure, Ext: json.RawMessage(fmt.Sprintf("{\"bidder\": %s}", formatAdUnitJson(tag))), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, } } @@ -457,7 +457,7 @@ func TestOpenRTBStandardResponse(t *testing.T) { if bid.Price != tag.price { t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.price) } - if bid.W != testData.width || bid.H != testData.height { + if bid.W != int64(testData.width) || bid.H != int64(testData.height) { t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.W, bid.H, testData.width, testData.height) } if bid.AdM != tag.content { @@ -511,12 +511,12 @@ func TestAdformProperties(t *testing.T) { // helpers -func getRegs() openrtb.Regs { +func getRegs() openrtb2.Regs { var gdpr int8 = 1 regsExt := openrtb_ext.ExtRegs{ GDPR: &gdpr, } - regs := openrtb.Regs{} + regs := openrtb2.Regs{} regsExtData, err := json.Marshal(regsExt) if err == nil { regs.Ext = regsExtData @@ -689,37 +689,37 @@ func TestToOpenRtbBidResponse(t *testing.T) { expectedBids := 4 lastCurrency, anotherCurrency, emptyCurrency := "EUR", "USD", "" - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "banner-imp-no1", Ext: json.RawMessage(`{"bidder1": { "mid": "32341" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "banner-imp-no2", Ext: json.RawMessage(`{"bidder1": { "mid": "32342" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "banner-imp-no3", Ext: json.RawMessage(`{"bidder1": { "mid": "32343" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "banner-imp-no4", Ext: json.RawMessage(`{"bidder1": { "mid": "32344" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "video-imp-no4", Ext: json.RawMessage(`{"bidder1": { "mid": "32345" }}`), - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, }, - Device: &openrtb.Device{UA: "ua", IP: "ip"}, - User: &openrtb.User{BuyerUID: "buyerUID"}, + Device: &openrtb2.Device{UA: "ua", IP: "ip"}, + User: &openrtb2.User{BuyerUID: "buyerUID"}, } testAdformBids := []*adformBid{ diff --git a/adapters/adform/adformtest/supplemental/regs-ext-nil.json b/adapters/adform/adformtest/supplemental/regs-ext-nil.json new file mode 100644 index 00000000000..377c48b0445 --- /dev/null +++ b/adapters/adform/adformtest/supplemental/regs-ext-nil.json @@ -0,0 +1,53 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }], + "regs": {} + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx?CC=1&fd=1&gdpr=&gdpr_consent=&ip=&rp=4&stid=&bWlkPTEyMzQ1JnJjdXI9VVNE" + }, + "mockResponse": { + "status": 200, + "body": [{ + "response": "banner", + "banner": "", + "win_bid": 0.5, + "win_cur": "USD", + "width": 300, + "height": 250, + "deal_id": null, + "win_crid": "20078830" + }] + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "test-imp-id", + "impid": "test-imp-id", + "price": 0.5, + "adm": "", + "crid": "20078830", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/adform/params_test.go b/adapters/adform/params_test.go index c4ffb79399e..b392463f426 100644 --- a/adapters/adform/params_test.go +++ b/adapters/adform/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/adform.json diff --git a/adapters/adform/usersync.go b/adapters/adform/usersync.go index b98c1b6c0fc..6a237f794a6 100644 --- a/adapters/adform/usersync.go +++ b/adapters/adform/usersync.go @@ -3,10 +3,10 @@ package adform import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAdformSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adform", 50, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adform", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adform/usersync_test.go b/adapters/adform/usersync_test.go index 3a507331e85..f133be86583 100644 --- a/adapters/adform/usersync_test.go +++ b/adapters/adform/usersync_test.go @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/assert" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" ) func TestAdformSyncer(t *testing.T) { @@ -27,6 +27,5 @@ func TestAdformSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadform%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 50, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adgeneration/adgeneration.go b/adapters/adgeneration/adgeneration.go index 59a3ba5b6a2..110ec5ce98a 100644 --- a/adapters/adgeneration/adgeneration.go +++ b/adapters/adgeneration/adgeneration.go @@ -10,11 +10,11 @@ import ( "strconv" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 AdgenerationAdapter struct { @@ -41,7 +41,7 @@ type adgServerResponse struct { Results []interface{} `json:"results"` } -func (adg *AdgenerationAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adg *AdgenerationAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) var errs []error @@ -79,7 +79,7 @@ func (adg *AdgenerationAdapter) MakeRequests(request *openrtb.BidRequest, reqInf return bidRequestArray, errs } -func (adg *AdgenerationAdapter) getRequestUri(request *openrtb.BidRequest, index int) (string, error) { +func (adg *AdgenerationAdapter) getRequestUri(request *openrtb2.BidRequest, index int) (string, error) { imp := request.Imp[index] adgExt, err := unmarshalExtImpAdgeneration(&imp) if err != nil { @@ -98,7 +98,7 @@ func (adg *AdgenerationAdapter) getRequestUri(request *openrtb.BidRequest, index return uriObj.String(), err } -func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb.BidRequest, imp *openrtb.Imp) *url.Values { +func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb2.BidRequest, imp *openrtb2.Imp) *url.Values { v := url.Values{} v.Set("posall", "SSPLOC") v.Set("id", id) @@ -121,7 +121,7 @@ func (adg *AdgenerationAdapter) getRawQuery(id string, request *openrtb.BidReque return &v } -func unmarshalExtImpAdgeneration(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdgeneration, error) { +func unmarshalExtImpAdgeneration(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdgeneration, error) { var bidderExt adapters.ExtImpBidder var adgExt openrtb_ext.ExtImpAdgeneration if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { @@ -136,13 +136,13 @@ func unmarshalExtImpAdgeneration(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdgenerat return &adgExt, nil } -func getSizes(imp *openrtb.Imp) string { +func getSizes(imp *openrtb2.Imp) string { if imp.Banner == nil || len(imp.Banner.Format) == 0 { return "" } var sizeStr string for _, v := range imp.Banner.Format { - sizeStr += strconv.FormatUint(v.W, 10) + "x" + strconv.FormatUint(v.H, 10) + "," + sizeStr += strconv.FormatInt(v.W, 10) + "x" + strconv.FormatInt(v.H, 10) + "," } if len(sizeStr) > 0 && strings.LastIndex(sizeStr, ",") == len(sizeStr)-1 { sizeStr = sizeStr[:len(sizeStr)-1] @@ -150,7 +150,7 @@ func getSizes(imp *openrtb.Imp) string { return sizeStr } -func (adg *AdgenerationAdapter) getCurrency(request *openrtb.BidRequest) string { +func (adg *AdgenerationAdapter) getCurrency(request *openrtb2.BidRequest) string { if len(request.Cur) <= 0 { return adg.defaultCurrency } else { @@ -163,7 +163,7 @@ func (adg *AdgenerationAdapter) getCurrency(request *openrtb.BidRequest) string } } -func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -202,13 +202,13 @@ func (adg *AdgenerationAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex impId = v.ID bitType = openrtb_ext.BidTypeBanner adm = createAd(&bidResp, impId) - bid := openrtb.Bid{ + bid := openrtb2.Bid{ ID: bidResp.Locationid, ImpID: impId, AdM: adm, Price: bidResp.Cpm, - W: bidResp.W, - H: bidResp.H, + W: int64(bidResp.W), + H: int64(bidResp.H), CrID: bidResp.Creativeid, DealID: bidResp.Dealid, } diff --git a/adapters/adgeneration/adgeneration_test.go b/adapters/adgeneration/adgeneration_test.go index a4041a5a1d7..d5d93ac4e0b 100644 --- a/adapters/adgeneration/adgeneration_test.go +++ b/adapters/adgeneration/adgeneration_test.go @@ -4,11 +4,11 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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/stretchr/testify/assert" ) @@ -34,27 +34,27 @@ func TestgetRequestUri(t *testing.T) { bidderAdgeneration, _ := bidder.(*AdgenerationAdapter) // Test items - failedRequest := &openrtb.BidRequest{ + failedRequest := &openrtb2.BidRequest{ ID: "test-failed-bid-request", - Imp: []openrtb.Imp{ - {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{{ "id": "58278" }}`)}, - {ID: "extImpBidder-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"_bidder": { "id": "58278" }}`)}, - {ID: "extImpAdgeneration-failed-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "_id": "58278" }}`)}, + Imp: []openrtb2.Imp{ + {ID: "extImpBidder-failed-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{{ "id": "58278" }}`)}, + {ID: "extImpBidder-failed-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"_bidder": { "id": "58278" }}`)}, + {ID: "extImpAdgeneration-failed-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "_id": "58278" }}`)}, }, - Source: &openrtb.Source{TID: "SourceTID"}, - Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, - Site: &openrtb.Site{Page: "https://supership.com"}, - User: &openrtb.User{BuyerUID: "buyerID"}, + Source: &openrtb2.Source{TID: "SourceTID"}, + Device: &openrtb2.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb2.Site{Page: "https://supership.com"}, + User: &openrtb2.User{BuyerUID: "buyerID"}, } - successRequest := &openrtb.BidRequest{ + successRequest := &openrtb2.BidRequest{ ID: "test-success-bid-request", - Imp: []openrtb.Imp{ - {ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, + Imp: []openrtb2.Imp{ + {ID: "bidRequest-success-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, }, - Source: &openrtb.Source{TID: "SourceTID"}, - Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, - Site: &openrtb.Site{Page: "https://supership.com"}, - User: &openrtb.User{BuyerUID: "buyerID"}, + Source: &openrtb2.Source{TID: "SourceTID"}, + Device: &openrtb2.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb2.Site{Page: "https://supership.com"}, + User: &openrtb2.User{BuyerUID: "buyerID"}, } numRequests := len(failedRequest.Imp) @@ -108,23 +108,23 @@ func TestgetRequestUri(t *testing.T) { func TestGetSizes(t *testing.T) { // Test items - var request *openrtb.Imp + var request *openrtb2.Imp var size string - multiFormatBanner := &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 320, H: 50}}} - noFormatBanner := &openrtb.Banner{Format: []openrtb.Format{}} - nativeFormat := &openrtb.Native{} + multiFormatBanner := &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 320, H: 50}}} + noFormatBanner := &openrtb2.Banner{Format: []openrtb2.Format{}} + nativeFormat := &openrtb2.Native{} - request = &openrtb.Imp{Banner: multiFormatBanner} + request = &openrtb2.Imp{Banner: multiFormatBanner} size = getSizes(request) if size != "300x250,320x50" { t.Errorf("%v does not match size.", multiFormatBanner) } - request = &openrtb.Imp{Banner: noFormatBanner} + request = &openrtb2.Imp{Banner: noFormatBanner} size = getSizes(request) if size != "" { t.Errorf("%v does not match size.", noFormatBanner) } - request = &openrtb.Imp{Native: nativeFormat} + request = &openrtb2.Imp{Native: nativeFormat} size = getSizes(request) if size != "" { t.Errorf("%v does not match size.", nativeFormat) @@ -142,17 +142,17 @@ func TestGetCurrency(t *testing.T) { bidderAdgeneration, _ := bidder.(*AdgenerationAdapter) // Test items - var request *openrtb.BidRequest + var request *openrtb2.BidRequest var currency string innerDefaultCur := []string{"USD", "JPY"} usdCur := []string{"USD", "EUR"} - request = &openrtb.BidRequest{Cur: innerDefaultCur} + request = &openrtb2.BidRequest{Cur: innerDefaultCur} currency = bidderAdgeneration.getCurrency(request) if currency != "JPY" { t.Errorf("%v does not match currency.", innerDefaultCur) } - request = &openrtb.BidRequest{Cur: usdCur} + request = &openrtb2.BidRequest{Cur: usdCur} currency = bidderAdgeneration.getCurrency(request) if currency != "USD" { t.Errorf("%v does not match currency.", usdCur) @@ -212,14 +212,14 @@ func TestMakeBids(t *testing.T) { bidderAdgeneration, _ := bidder.(*AdgenerationAdapter) - internalRequest := &openrtb.BidRequest{ + internalRequest := &openrtb2.BidRequest{ ID: "test-success-bid-request", - Imp: []openrtb.Imp{ - {ID: "bidRequest-success-test", Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, + Imp: []openrtb2.Imp{ + {ID: "bidRequest-success-test", Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(`{"bidder": { "id": "58278" }}`)}, }, - Device: &openrtb.Device{UA: "testUA", IP: "testIP"}, - Site: &openrtb.Site{Page: "https://supership.com"}, - User: &openrtb.User{BuyerUID: "buyerID"}, + Device: &openrtb2.Device{UA: "testUA", IP: "testIP"}, + Site: &openrtb2.Site{Page: "https://supership.com"}, + User: &openrtb2.User{BuyerUID: "buyerID"}, } externalRequest := adapters.RequestData{} response := adapters.ResponseData{ @@ -254,8 +254,8 @@ func checkBidResponse(t *testing.T, bidderResponse *adapters.BidderResponse, exp var expectedID string = "58278" var expectedImpID = "bidRequest-success-test" var expectedPrice float64 = 30.0 - var expectedW uint64 = 300 - var expectedH uint64 = 250 + var expectedW int64 = 300 + var expectedH int64 = 250 var expectedCrID string = "Dummy_supership.jp" var extectedDealID string = "test-deal-id" diff --git a/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json index 8a4135c27af..b9a79688d40 100644 --- a/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json +++ b/adapters/adgeneration/adgenerationtest/supplemental/204-bid-response.json @@ -75,4 +75,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json b/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json index ebda348fa65..9744e8d2606 100644 --- a/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json +++ b/adapters/adgeneration/adgenerationtest/supplemental/400-bid-response.json @@ -80,4 +80,4 @@ "comparison": "literal" } ] -} +} \ No newline at end of file diff --git a/adapters/adgeneration/params_test.go b/adapters/adgeneration/params_test.go index 11a0dfe97c5..062d122ac08 100644 --- a/adapters/adgeneration/params_test.go +++ b/adapters/adgeneration/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adhese/adhese.go b/adapters/adhese/adhese.go index 96a936c2276..8b4d8c98afe 100644 --- a/adapters/adhese/adhese.go +++ b/adapters/adhese/adhese.go @@ -10,13 +10,13 @@ import ( "strings" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/golang/glog" + "github.com/mxmCherry/openrtb/v15/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 AdheseAdapter struct { @@ -57,7 +57,7 @@ func extractTargetParameters(parameters openrtb_ext.ExtImpAdhese) string { return parametersAsString } -func extractGdprParameter(request *openrtb.BidRequest) string { +func extractGdprParameter(request *openrtb2.BidRequest) string { if request.User != nil { var extUser openrtb_ext.ExtUser if err := json.Unmarshal(request.User.Ext, &extUser); err == nil { @@ -67,21 +67,21 @@ func extractGdprParameter(request *openrtb.BidRequest) string { return "" } -func extractRefererParameter(request *openrtb.BidRequest) string { +func extractRefererParameter(request *openrtb2.BidRequest) string { if request.Site != nil && request.Site.Page != "" { return "/xf" + url.QueryEscape(request.Site.Page) } return "" } -func extractIfaParameter(request *openrtb.BidRequest) string { +func extractIfaParameter(request *openrtb2.BidRequest) string { if request.Device != nil && request.Device.IFA != "" { return "/xz" + url.QueryEscape(request.Device.IFA) } return "" } -func (a *AdheseAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdheseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) var err error @@ -128,14 +128,14 @@ func (a *AdheseAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt }}, errs } -func (a *AdheseAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdheseAdapter) 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.StatusOK { return nil, []error{WrapServerError(fmt.Sprintf("Unexpected status code: %d.", response.StatusCode))} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse var adheseBidResponseArray []AdheseBid if err := json.Unmarshal(response.Body, &adheseBidResponseArray); err != nil { @@ -163,11 +163,11 @@ func (a *AdheseAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe if err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Price %v as float ", string(adheseBid.Extension.Prebid.Cpm.Amount)))} } - width, err := strconv.ParseUint(adheseBid.Width, 10, 64) + width, err := strconv.ParseInt(adheseBid.Width, 10, 64) if err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Width %v as int ", string(adheseBid.Width)))} } - height, err := strconv.ParseUint(adheseBid.Height, 10, 64) + height, err := strconv.ParseInt(adheseBid.Height, 10, 64) if err != nil { return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Height %v as int ", string(adheseBid.Height)))} } @@ -198,16 +198,16 @@ func (a *AdheseAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return bidderResponse, errs } -func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData AdheseOriginData) openrtb.BidResponse { +func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData AdheseOriginData) openrtb2.BidResponse { adheseExtJson, err := json.Marshal(adheseOriginData) if err != nil { glog.Error(fmt.Sprintf("Unable to parse adhese Origin Data as JSON due to %v", err)) adheseExtJson = make([]byte, 0) } - return openrtb.BidResponse{ + return openrtb2.BidResponse{ ID: adheseExt.Id, - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ DealID: adheseExt.OrderId, CrID: adheseExt.Id, AdM: getAdMarkup(adheseBid, adheseExt), @@ -218,8 +218,8 @@ func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData } } -func convertAdheseOpenRtbBid(adheseBid AdheseBid) openrtb.BidResponse { - var response openrtb.BidResponse = adheseBid.OriginData +func convertAdheseOpenRtbBid(adheseBid AdheseBid) openrtb2.BidResponse { + var response openrtb2.BidResponse = adheseBid.OriginData if len(response.SeatBid) > 0 && len(response.SeatBid[0].Bid) > 0 { response.SeatBid[0].Bid[0].AdM = adheseBid.Body } diff --git a/adapters/adhese/adhese_test.go b/adapters/adhese/adhese_test.go index 40b28887c20..3a9d28ee5e9 100644 --- a/adapters/adhese/adhese_test.go +++ b/adapters/adhese/adhese_test.go @@ -3,9 +3,9 @@ package adhese import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/adhese/adhesetest/exemplary/banner-internal.json b/adapters/adhese/adhesetest/exemplary/banner-internal.json index 225b37aa2f8..50efe4a656d 100644 --- a/adapters/adhese/adhesetest/exemplary/banner-internal.json +++ b/adapters/adhese/adhesetest/exemplary/banner-internal.json @@ -103,4 +103,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/adapters/adhese/params_test.go b/adapters/adhese/params_test.go index f9c3de6212e..45024749b2d 100644 --- a/adapters/adhese/params_test.go +++ b/adapters/adhese/params_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adhese/utils.go b/adapters/adhese/utils.go index 7b09ad02924..5a81bb83bd5 100644 --- a/adapters/adhese/utils.go +++ b/adapters/adhese/utils.go @@ -1,6 +1,6 @@ package adhese -import "github.com/PubMatic-OpenWrap/openrtb" +import "github.com/mxmCherry/openrtb/v15/openrtb2" type AdheseOriginData struct { Priority string `json:"priority"` @@ -22,13 +22,13 @@ type AdheseExt struct { } type AdheseBid struct { - Origin string `json:"origin"` - OriginData openrtb.BidResponse `json:"originData"` - OriginInstance string `json:"originInstance,omitempty"` - Body string `json:"body,omitempty"` - Height string `json:"height"` - Width string `json:"width"` - Extension Prebid `json:"extension"` + Origin string `json:"origin"` + OriginData openrtb2.BidResponse `json:"originData"` + OriginInstance string `json:"originInstance,omitempty"` + Body string `json:"body,omitempty"` + Height string `json:"height"` + Width string `json:"width"` + Extension Prebid `json:"extension"` } type Prebid struct { diff --git a/adapters/adkernel/adkernel.go b/adapters/adkernel/adkernel.go index f483ba7ce49..362307cce79 100644 --- a/adapters/adkernel/adkernel.go +++ b/adapters/adkernel/adkernel.go @@ -7,12 +7,12 @@ import ( "strconv" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 adkernelAdapter struct { @@ -20,7 +20,7 @@ type adkernelAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *adkernelAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *adkernelAdapter) 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, newBadInputError("No impression in the bid request")) @@ -52,10 +52,10 @@ func (adapter *adkernelAdapter) MakeRequests(request *openrtb.BidRequest, reqInf } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImpAdkernel, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) ([]openrtb2.Imp, []openrtb_ext.ExtImpAdkernel, []error) { impsCount := len(imps) errors := make([]error, 0, impsCount) - resImps := make([]openrtb.Imp, 0, impsCount) + resImps := make([]openrtb2.Imp, 0, impsCount) resImpExts := make([]openrtb_ext.ExtImpAdkernel, 0, impsCount) for _, imp := range imps { @@ -74,7 +74,7 @@ func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImp return resImps, resImpExts, errors } -func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdkernel) error { +func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdkernel) error { if impExt.ZoneId < 1 { return newBadInputError(fmt.Sprintf("Invalid zoneId value: %d. Ignoring imp id=%s", impExt.ZoneId, imp.ID)) } @@ -88,8 +88,8 @@ func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdkernel) er } //Group impressions by AdKernel-specific parameters `zoneId` & `host` -func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkernel) (map[openrtb_ext.ExtImpAdkernel][]openrtb.Imp, []error) { - res := make(map[openrtb_ext.ExtImpAdkernel][]openrtb.Imp) +func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkernel) (map[openrtb_ext.ExtImpAdkernel][]openrtb2.Imp, []error) { + res := make(map[openrtb_ext.ExtImpAdkernel][]openrtb2.Imp) errors := make([]error, 0) for idx := range imps { imp := imps[idx] @@ -100,7 +100,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkerne } impExt := impsExt[idx] if res[impExt] == nil { - res[impExt] = make([]openrtb.Imp, 0) + res[impExt] = make([]openrtb2.Imp, 0) } res[impExt] = append(res[impExt], imp) } @@ -108,7 +108,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkerne } //Alter impression info to comply with adkernel platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to adkernel platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -119,21 +119,21 @@ func compatImpression(imp *openrtb.Imp) error { return newBadInputError("Invalid impression") } -func compatBannerImpression(imp *openrtb.Imp) error { +func compatBannerImpression(imp *openrtb2.Imp) error { imp.Audio = nil imp.Video = nil imp.Native = nil return nil } -func compatVideoImpression(imp *openrtb.Imp) error { +func compatVideoImpression(imp *openrtb2.Imp) error { imp.Banner = nil imp.Audio = nil imp.Native = nil return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdkernel, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdkernel, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -149,7 +149,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdkernel, error) { return &adkernelExt, nil } -func (adapter *adkernelAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *adkernelAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -173,7 +173,7 @@ func (adapter *adkernelAdapter) buildAdapterRequest(prebidBidRequest *openrtb.Bi Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernel, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps if bidRequest.Site != nil { @@ -198,7 +198,7 @@ func (adapter *adkernelAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAdker } //MakeBids translates adkernel bid response to prebid-server specific format -func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -207,7 +207,7 @@ func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex newBadServerResponseError(fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)), } } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{ newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), @@ -233,7 +233,7 @@ func (adapter *adkernelAdapter) MakeBids(internalRequest *openrtb.BidRequest, ex } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Banner != nil { return openrtb_ext.BidTypeBanner diff --git a/adapters/adkernel/adkernel_test.go b/adapters/adkernel/adkernel_test.go index a85769f5565..e9fcaef1b0e 100644 --- a/adapters/adkernel/adkernel_test.go +++ b/adapters/adkernel/adkernel_test.go @@ -3,9 +3,9 @@ package adkernel import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/adkernel/usersync.go b/adapters/adkernel/usersync.go index 7622e1da3b7..2de82a00f6e 100644 --- a/adapters/adkernel/usersync.go +++ b/adapters/adkernel/usersync.go @@ -3,12 +3,10 @@ package adkernel import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) -const adkernelGDPRVendorID = uint16(14) - func NewAdkernelSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adkernel", 14, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adkernel", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adkernel/usersync_test.go b/adapters/adkernel/usersync_test.go index 7230fcbab9c..2ff81668d41 100644 --- a/adapters/adkernel/usersync_test.go +++ b/adapters/adkernel/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestAdkernelAdnSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.adkernel.com/user-sync?t=image&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, adkernelGDPRVendorID, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adkernelAdn/adkernelAdn.go b/adapters/adkernelAdn/adkernelAdn.go index 491bead4e8b..f6075f865e7 100644 --- a/adapters/adkernelAdn/adkernelAdn.go +++ b/adapters/adkernelAdn/adkernelAdn.go @@ -7,12 +7,12 @@ import ( "strconv" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const defaultDomain string = "tag.adkernel.com" @@ -22,7 +22,7 @@ type adkernelAdnAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *adkernelAdnAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *adkernelAdnAdapter) 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, newBadInputError("No impression in the bid request")) @@ -54,10 +54,10 @@ func (adapter *adkernelAdnAdapter) MakeRequests(request *openrtb.BidRequest, req } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImpAdkernelAdn, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) ([]openrtb2.Imp, []openrtb_ext.ExtImpAdkernelAdn, []error) { impsCount := len(imps) errors := make([]error, 0, impsCount) - resImps := make([]openrtb.Imp, 0, impsCount) + resImps := make([]openrtb2.Imp, 0, impsCount) resImpExts := make([]openrtb_ext.ExtImpAdkernelAdn, 0, impsCount) for _, imp := range imps { @@ -77,7 +77,7 @@ func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImp return resImps, resImpExts, errors } -func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdkernelAdn) error { +func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdkernelAdn) error { if impExt.PublisherID < 1 { return newBadInputError(fmt.Sprintf("Invalid pubId value. Ignoring imp id=%s", imp.ID)) } @@ -88,8 +88,8 @@ func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdkernelAdn) } //Group impressions by AdKernel-specific parameters `pubId` & `host` -func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkernelAdn) (map[openrtb_ext.ExtImpAdkernelAdn][]openrtb.Imp, []error) { - res := make(map[openrtb_ext.ExtImpAdkernelAdn][]openrtb.Imp) +func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdkernelAdn) (map[openrtb_ext.ExtImpAdkernelAdn][]openrtb2.Imp, []error) { + res := make(map[openrtb_ext.ExtImpAdkernelAdn][]openrtb2.Imp) errors := make([]error, 0) for idx := range imps { imp := imps[idx] @@ -100,7 +100,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkerne } impExt := impsExt[idx] if res[impExt] == nil { - res[impExt] = make([]openrtb.Imp, 0) + res[impExt] = make([]openrtb2.Imp, 0) } res[impExt] = append(res[impExt], imp) @@ -109,7 +109,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdkerne } //Alter impression info to comply with adkernel platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to adkernel platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -120,7 +120,7 @@ func compatImpression(imp *openrtb.Imp) error { return newBadInputError("Unsupported impression has been received") } -func compatBannerImpression(imp *openrtb.Imp) error { +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 @@ -143,14 +143,14 @@ func compatBannerImpression(imp *openrtb.Imp) error { return nil } -func compatVideoImpression(imp *openrtb.Imp) error { +func compatVideoImpression(imp *openrtb2.Imp) error { imp.Banner = nil imp.Audio = nil imp.Native = nil return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdkernelAdn, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdkernelAdn, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -166,7 +166,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdkernelAdn, error) return &adkernelAdnExt, nil } -func (adapter *adkernelAdnAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *adkernelAdnAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -190,7 +190,7 @@ func (adapter *adkernelAdnAdapter) buildAdapterRequest(prebidBidRequest *openrtb Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdkernelAdn, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps @@ -222,7 +222,7 @@ func (adapter *adkernelAdnAdapter) buildEndpointURL(params *openrtb_ext.ExtImpAd } //MakeBids translates adkernel bid response to prebid-server specific format -func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -231,7 +231,7 @@ func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb.BidRequest, newBadServerResponseError(fmt.Sprintf("Unexpected http status code: %d", response.StatusCode)), } } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{ newBadServerResponseError(fmt.Sprintf("Bad server response: %d", err)), @@ -257,7 +257,7 @@ func (adapter *adkernelAdnAdapter) MakeBids(internalRequest *openrtb.BidRequest, } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID && imp.Banner != nil { return openrtb_ext.BidTypeBanner diff --git a/adapters/adkernelAdn/adkernelAdn_test.go b/adapters/adkernelAdn/adkernelAdn_test.go index a4311d3e550..687f5a946c0 100644 --- a/adapters/adkernelAdn/adkernelAdn_test.go +++ b/adapters/adkernelAdn/adkernelAdn_test.go @@ -3,9 +3,9 @@ package adkernelAdn import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/adkernelAdn/usersync.go b/adapters/adkernelAdn/usersync.go index ab60edf2dc7..5a890e1565b 100644 --- a/adapters/adkernelAdn/usersync.go +++ b/adapters/adkernelAdn/usersync.go @@ -3,12 +3,10 @@ package adkernelAdn import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) -const adkernelGDPRVendorID = uint16(14) - func NewAdkernelAdnSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adkernelAdn", 14, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adkernelAdn", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adkernelAdn/usersync_test.go b/adapters/adkernelAdn/usersync_test.go index 9528579aa3d..17cf97ce779 100644 --- a/adapters/adkernelAdn/usersync_test.go +++ b/adapters/adkernelAdn/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestAdkernelAdnSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://tag.adkernel.com/syncr?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, adkernelGDPRVendorID, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adman/adman.go b/adapters/adman/adman.go index 3c0342fe24f..808951d3aba 100644 --- a/adapters/adman/adman.go +++ b/adapters/adman/adman.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) // AdmanAdapter struct @@ -30,7 +30,7 @@ type admanParams struct { } // MakeRequests create bid request for adman demand -func (a *AdmanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdmanAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var admanExt openrtb_ext.ExtImpAdman var err error @@ -39,7 +39,7 @@ func (a *AdmanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte reqCopy := *request for _, imp := range request.Imp { - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} var bidderExt adapters.ExtImpBidder if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { @@ -63,7 +63,7 @@ func (a *AdmanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte return adapterRequests, errs } -func (a *AdmanAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *AdmanAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error @@ -86,7 +86,7 @@ func (a *AdmanAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Reque } // MakeBids makes the bids -func (a *AdmanAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdmanAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -99,7 +99,7 @@ func (a *AdmanAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -124,7 +124,7 @@ func (a *AdmanAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/adman/adman_test.go b/adapters/adman/adman_test.go index 5dc10df8dad..ceee5f21bd6 100644 --- a/adapters/adman/adman_test.go +++ b/adapters/adman/adman_test.go @@ -3,9 +3,9 @@ package adman import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/adman/admantest/supplemental/bad_response.json b/adapters/adman/admantest/supplemental/bad_response.json index d5a28c74256..4431a328154 100644 --- a/adapters/adman/admantest/supplemental/bad_response.json +++ b/adapters/adman/admantest/supplemental/bad_response.json @@ -78,7 +78,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/adman/admantest/supplemental/status-204.json b/adapters/adman/admantest/supplemental/status-204.json index a659754e8b0..05f9d030832 100644 --- a/adapters/adman/admantest/supplemental/status-204.json +++ b/adapters/adman/admantest/supplemental/status-204.json @@ -79,4 +79,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/adman/params_test.go b/adapters/adman/params_test.go index 4cea67cc098..a80c2a44b8b 100644 --- a/adapters/adman/params_test.go +++ b/adapters/adman/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // TestValidParams makes sure that the adman schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/adman/usersync.go b/adapters/adman/usersync.go index f7edd8c5b70..2cb62fe7824 100644 --- a/adapters/adman/usersync.go +++ b/adapters/adman/usersync.go @@ -3,11 +3,11 @@ package adman import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) // NewAdmanSyncer returns adman syncer func NewAdmanSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adman", 149, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adman", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adman/usersync_test.go b/adapters/adman/usersync_test.go index db67499e91d..d0dc90c8c5d 100644 --- a/adapters/adman/usersync_test.go +++ b/adapters/adman/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestAdmanSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.admanmedia.com/pbs.gif?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadman%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 149, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index b16dc0073d4..ec49950a17e 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 AdmixerAdapter struct { @@ -28,7 +28,7 @@ type admixerImpExt struct { CustomParams map[string]interface{} `json:"customParams"` } -func (a *AdmixerAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) { +func (a *AdmixerAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) { rq, errs := a.makeRequest(request) if len(errs) > 0 { @@ -43,9 +43,9 @@ func (a *AdmixerAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return } -func (a *AdmixerAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *AdmixerAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ @@ -84,7 +84,7 @@ func (a *AdmixerAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Req }, errs } -func preprocess(imp *openrtb.Imp) error { +func preprocess(imp *openrtb2.Imp) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -126,7 +126,7 @@ func preprocess(imp *openrtb.Imp) error { return nil } -func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -149,7 +149,7 @@ func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -172,7 +172,7 @@ func (a *AdmixerAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR return bidResponse, nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { diff --git a/adapters/admixer/admixer_test.go b/adapters/admixer/admixer_test.go index 629d4df83cd..d994847c1ed 100644 --- a/adapters/admixer/admixer_test.go +++ b/adapters/admixer/admixer_test.go @@ -3,9 +3,9 @@ package admixer import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go index d25bdcecd68..71cccb6a3da 100644 --- a/adapters/admixer/params_test.go +++ b/adapters/admixer/params_test.go @@ -2,7 +2,7 @@ package admixer import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/admixer/usersync.go b/adapters/admixer/usersync.go index 1df22cc276d..89e162dff32 100644 --- a/adapters/admixer/usersync.go +++ b/adapters/admixer/usersync.go @@ -1,11 +1,12 @@ package admixer import ( - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAdmixerSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("admixer", 511, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("admixer", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/admixer/usersync_test.go b/adapters/admixer/usersync_test.go index 8a43b866804..6acc48453fb 100644 --- a/adapters/admixer/usersync_test.go +++ b/adapters/admixer/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,6 @@ func TestAdmixerSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://anyHost/anyPath", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 511, syncer.GDPRVendorID()) + assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index 8310626fcec..635cba8c9bc 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -13,12 +13,12 @@ import ( "strings" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const adapterVersion = "1.1.0" @@ -80,7 +80,7 @@ type AdOceanAdapter struct { measurementCode string } -func (a *AdOceanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdOceanAdapter) 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", @@ -119,8 +119,8 @@ func (a *AdOceanAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap func (a *AdOceanAdapter) addNewBid( requestsData []*requestData, - imp *openrtb.Imp, - request *openrtb.BidRequest, + imp *openrtb2.Imp, + request *openrtb2.BidRequest, consentString string, ) ([]*requestData, error) { var bidderExt adapters.ExtImpBidder @@ -177,7 +177,7 @@ func (a *AdOceanAdapter) addNewBid( return requestsData, nil } -func addToExistingRequest(requestsData []*requestData, newParams *openrtb_ext.ExtImpAdOcean, imp *openrtb.Imp, testImp bool) bool { +func addToExistingRequest(requestsData []*requestData, newParams *openrtb_ext.ExtImpAdOcean, imp *openrtb2.Imp, testImp bool) bool { auctionID := imp.ID for _, requestData := range requestsData { @@ -209,8 +209,8 @@ func addToExistingRequest(requestsData []*requestData, newParams *openrtb_ext.Ex func (a *AdOceanAdapter) makeURL( params *openrtb_ext.ExtImpAdOcean, - imp *openrtb.Imp, - request *openrtb.BidRequest, + imp *openrtb2.Imp, + request *openrtb2.BidRequest, slaveSizes map[string]string, consentString string, ) (*url.URL, error) { @@ -256,7 +256,7 @@ func (a *AdOceanAdapter) makeURL( return endpointURL, nil } -func getImpSizes(imp *openrtb.Imp) string { +func getImpSizes(imp *openrtb2.Imp) string { if imp.Banner == nil { return "" } @@ -264,14 +264,14 @@ func getImpSizes(imp *openrtb.Imp) string { if len(imp.Banner.Format) > 0 { sizes := make([]string, len(imp.Banner.Format)) for i, format := range imp.Banner.Format { - sizes[i] = strconv.FormatUint(format.W, 10) + "x" + strconv.FormatUint(format.H, 10) + sizes[i] = strconv.FormatInt(format.W, 10) + "x" + strconv.FormatInt(format.H, 10) } return strings.Join(sizes, "_") } if imp.Banner.W != nil && imp.Banner.H != nil { - return strconv.FormatUint(*imp.Banner.W, 10) + "x" + strconv.FormatUint(*imp.Banner.H, 10) + return strconv.FormatInt(*imp.Banner.W, 10) + "x" + strconv.FormatInt(*imp.Banner.H, 10) } return "" @@ -304,7 +304,7 @@ func setSlaveSizesParam(queryParams *url.Values, slaveSizes map[string]string, o } func (a *AdOceanAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { @@ -337,8 +337,8 @@ func (a *AdOceanAdapter) MakeBids( } price, _ := strconv.ParseFloat(bid.Price, 64) - width, _ := strconv.ParseUint(bid.Width, 10, 64) - height, _ := strconv.ParseUint(bid.Height, 10, 64) + width, _ := strconv.ParseInt(bid.Width, 10, 64) + height, _ := strconv.ParseInt(bid.Height, 10, 64) adCode, err := a.prepareAdCodeForBid(bid) if err != nil { errors = append(errors, err) @@ -346,7 +346,7 @@ func (a *AdOceanAdapter) MakeBids( } parsedResponses.Bids = append(parsedResponses.Bids, &adapters.TypedBid{ - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ ID: bid.ID, ImpID: auctionID, Price: price, diff --git a/adapters/adocean/adocean_test.go b/adapters/adocean/adocean_test.go index b75de2a9235..531204cdec3 100644 --- a/adapters/adocean/adocean_test.go +++ b/adapters/adocean/adocean_test.go @@ -3,9 +3,9 @@ package adocean import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/adocean/params_test.go b/adapters/adocean/params_test.go index 91e2fbdcb67..1a88c4716e0 100644 --- a/adapters/adocean/params_test.go +++ b/adapters/adocean/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/adocean/usersync.go b/adapters/adocean/usersync.go index 4bfe39e11e5..b189f822b46 100644 --- a/adapters/adocean/usersync.go +++ b/adapters/adocean/usersync.go @@ -3,10 +3,10 @@ package adocean import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAdOceanSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adocean", 328, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adocean", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/adocean/usersync_test.go b/adapters/adocean/usersync_test.go index aa0bcb77e21..5257017adfa 100644 --- a/adapters/adocean/usersync_test.go +++ b/adapters/adocean/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,5 +30,4 @@ func TestAdOceanSyncer(t *testing.T) { syncInfo.URL, ) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 328, syncer.GDPRVendorID()) } diff --git a/adapters/adoppler/adoppler.go b/adapters/adoppler/adoppler.go index 498bb4c7cc0..015f31c7d01 100644 --- a/adapters/adoppler/adoppler.go +++ b/adapters/adoppler/adoppler.go @@ -8,12 +8,12 @@ import ( "net/url" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const DefaultClient = "app" @@ -50,7 +50,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } func (ads *AdopplerAdapter) MakeRequests( - req *openrtb.BidRequest, + req *openrtb2.BidRequest, info *adapters.ExtraRequestInfo, ) ( []*adapters.RequestData, @@ -69,9 +69,9 @@ func (ads *AdopplerAdapter) MakeRequests( continue } - var r openrtb.BidRequest = *req + var r openrtb2.BidRequest = *req r.ID = req.ID + "-" + ext.AdUnit - r.Imp = []openrtb.Imp{imp} + r.Imp = []openrtb2.Imp{imp} body, err := json.Marshal(r) if err != nil { @@ -99,7 +99,7 @@ func (ads *AdopplerAdapter) MakeRequests( } func (ads *AdopplerAdapter) MakeBids( - intReq *openrtb.BidRequest, + intReq *openrtb2.BidRequest, extReq *adapters.RequestData, resp *adapters.ResponseData, ) ( @@ -119,7 +119,7 @@ func (ads *AdopplerAdapter) MakeBids( return nil, []error{err} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err := json.Unmarshal(resp.Body, &bidResp) if err != nil { err := &errortypes.BadServerResponse{ diff --git a/adapters/adoppler/adoppler_test.go b/adapters/adoppler/adoppler_test.go index eab0ac5708d..fb5cb22bab5 100644 --- a/adapters/adoppler/adoppler_test.go +++ b/adapters/adoppler/adoppler_test.go @@ -3,9 +3,9 @@ package adoppler import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/adoppler/adopplertest/supplemental/invalid-response.json b/adapters/adoppler/adopplertest/supplemental/invalid-response.json index d0a7d2ef84b..83aa35f0ec1 100644 --- a/adapters/adoppler/adopplertest/supplemental/invalid-response.json +++ b/adapters/adoppler/adopplertest/supplemental/invalid-response.json @@ -49,7 +49,7 @@ ], "expectedMakeBidsErrors":[ { - "value":"invalid body: json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value":"invalid body: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison":"literal" } ] diff --git a/adapters/adot/adot.go b/adapters/adot/adot.go index fcbb5a2906d..f41beed8d21 100644 --- a/adapters/adot/adot.go +++ b/adapters/adot/adot.go @@ -3,12 +3,13 @@ package adot import ( "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "net/http" + + "github.com/mxmCherry/openrtb/v15/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 { @@ -32,7 +33,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var reqJSON []byte var err error @@ -53,7 +54,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex // MakeBids unpacks the server's response into Bids. // The bidder return a status code 204 when it cannot delivery an ad. -func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -70,7 +71,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -96,7 +97,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest } // getMediaTypeForBid determines which type of bid. -func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) { +func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { if bid == nil { return "", fmt.Errorf("the bid request object is nil") } diff --git a/adapters/adot/adot_test.go b/adapters/adot/adot_test.go index 2e6e74861d4..fbc7def4a74 100644 --- a/adapters/adot/adot_test.go +++ b/adapters/adot/adot_test.go @@ -2,13 +2,14 @@ package adot import ( "encoding/json" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" "testing" + + "github.com/mxmCherry/openrtb/v15/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/stretchr/testify/assert" ) const testsBidderEndpoint = "https://dsp.adotmob.com/headerbidding/bidrequest" @@ -31,7 +32,7 @@ func TestMediaTypeError(t *testing.T) { assert.Error(t, err) byteInvalid, _ := json.Marshal(&adotBidExt{Adot: bidExt{"invalid"}}) - _, err = getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteInvalid)}) + _, err = getMediaTypeForBid(&openrtb2.Bid{Ext: json.RawMessage(byteInvalid)}) assert.Error(t, err) } @@ -59,17 +60,17 @@ func TestMediaTypeForBid(t *testing.T) { byteVideo, _ := json.Marshal(&adotBidExt{Adot: bidExt{"video"}}) byteNative, _ := json.Marshal(&adotBidExt{Adot: bidExt{"native"}}) - bidTypeBanner, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteBanner)}) + bidTypeBanner, _ := getMediaTypeForBid(&openrtb2.Bid{Ext: json.RawMessage(byteBanner)}) if bidTypeBanner != openrtb_ext.BidTypeBanner { t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeBanner, openrtb_ext.BidTypeBanner) } - bidTypeVideo, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteVideo)}) + bidTypeVideo, _ := getMediaTypeForBid(&openrtb2.Bid{Ext: json.RawMessage(byteVideo)}) if bidTypeVideo != openrtb_ext.BidTypeVideo { t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeVideo, openrtb_ext.BidTypeVideo) } - bidTypeNative, _ := getMediaTypeForBid(&openrtb.Bid{Ext: json.RawMessage(byteNative)}) + bidTypeNative, _ := getMediaTypeForBid(&openrtb2.Bid{Ext: json.RawMessage(byteNative)}) if bidTypeNative != openrtb_ext.BidTypeNative { t.Errorf("the type is not the valid one. actual: %v, expected: %v", bidTypeNative, openrtb_ext.BidTypeVideo) } diff --git a/adapters/adot/adottest/supplemental/unmarshal_error.json b/adapters/adot/adottest/supplemental/unmarshal_error.json index a87e1189a62..3094fa865e4 100644 --- a/adapters/adot/adottest/supplemental/unmarshal_error.json +++ b/adapters/adot/adottest/supplemental/unmarshal_error.json @@ -54,7 +54,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/adot/params_test.go b/adapters/adot/params_test.go index 2a6dc17d916..2f7b4b9af4e 100644 --- a/adapters/adot/params_test.go +++ b/adapters/adot/params_test.go @@ -2,7 +2,7 @@ package adot import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/adpone/adpone.go b/adapters/adpone/adpone.go index ec4cba75f87..972f1576ac1 100644 --- a/adapters/adpone/adpone.go +++ b/adapters/adpone/adpone.go @@ -5,12 +5,12 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" ) // Builder builds a new instance of the Adpone adapter for the given bidder with the given config. @@ -26,7 +26,7 @@ type adponeAdapter struct { } func (adapter *adponeAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -75,7 +75,7 @@ const unexpectedStatusCodeFormat = "" + "Unexpected status code: %d. Run with request.debug = 1 for more info" func (adapter *adponeAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -99,7 +99,7 @@ func (adapter *adponeAdapter) MakeBids( return nil, []error{err} } - var openRTBBidderResponse openrtb.BidResponse + var openRTBBidderResponse openrtb2.BidResponse if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{err} } diff --git a/adapters/adpone/adpone_test.go b/adapters/adpone/adpone_test.go index 69726e31d50..78ad51ba095 100644 --- a/adapters/adpone/adpone_test.go +++ b/adapters/adpone/adpone_test.go @@ -3,9 +3,9 @@ package adpone import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) const testsDir = "adponetest" diff --git a/adapters/adpone/adponetest/supplemental/bad_response.json b/adapters/adpone/adponetest/supplemental/bad_response.json index 9cbc8b5d9c2..68da7064b97 100644 --- a/adapters/adpone/adponetest/supplemental/bad_response.json +++ b/adapters/adpone/adponetest/supplemental/bad_response.json @@ -56,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/adpone/usersync.go b/adapters/adpone/usersync.go index 63f616091e2..38dec19bb72 100644 --- a/adapters/adpone/usersync.go +++ b/adapters/adpone/usersync.go @@ -3,17 +3,15 @@ package adpone import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) -const adponeGDPRVendorID = uint16(799) const adponeFamilyName = "adpone" func NewadponeSyncer(urlTemplate *template.Template) usersync.Usersyncer { return adapters.NewSyncer( adponeFamilyName, - adponeGDPRVendorID, urlTemplate, adapters.SyncTypeRedirect, ) diff --git a/adapters/adpone/usersync_test.go b/adapters/adpone/usersync_test.go index 7bc528b8f36..e744de5eb91 100644 --- a/adapters/adpone/usersync_test.go +++ b/adapters/adpone/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,5 +20,4 @@ func TestAdponeSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://usersync.adpone.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, adponeGDPRVendorID, syncer.GDPRVendorID()) } diff --git a/adapters/adprime/adprime.go b/adapters/adprime/adprime.go index 053999fd5d1..70fbd6b399b 100644 --- a/adapters/adprime/adprime.go +++ b/adapters/adprime/adprime.go @@ -5,12 +5,12 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/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" ) // AdprimeAdapter struct @@ -27,7 +27,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests create bid request for adprime demand -func (a *AdprimeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdprimeAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var err error var tagID string @@ -36,7 +36,7 @@ func (a *AdprimeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap reqCopy := *request for _, imp := range request.Imp { - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} tagID, err = jsonparser.GetString(reqCopy.Imp[0].Ext, "bidder", "TagID") if err != nil { @@ -55,7 +55,7 @@ func (a *AdprimeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return adapterRequests, errs } -func (a *AdprimeAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *AdprimeAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error @@ -78,7 +78,7 @@ func (a *AdprimeAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Req } // MakeBids makes the bids -func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -97,7 +97,7 @@ func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -122,7 +122,7 @@ func (a *AdprimeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/adprime/adprime_test.go b/adapters/adprime/adprime_test.go index cfcf255a5cc..ff12381d053 100644 --- a/adapters/adprime/adprime_test.go +++ b/adapters/adprime/adprime_test.go @@ -3,9 +3,9 @@ package adprime import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/adprime/adprimetest/supplemental/bad_response.json b/adapters/adprime/adprimetest/supplemental/bad_response.json index 329e9c7269f..435529a485c 100644 --- a/adapters/adprime/adprimetest/supplemental/bad_response.json +++ b/adapters/adprime/adprimetest/supplemental/bad_response.json @@ -78,7 +78,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/adprime/params_test.go b/adapters/adprime/params_test.go index bea13e32c13..05adad5c4ff 100644 --- a/adapters/adprime/params_test.go +++ b/adapters/adprime/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // TestValidParams makes sure that the adprime schema accepts all imp.ext fields which we intend to support. diff --git a/adapters/adtarget/adtarget.go b/adapters/adtarget/adtarget.go index 070ede40feb..24befbfec0f 100644 --- a/adapters/adtarget/adtarget.go +++ b/adapters/adtarget/adtarget.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 AdtargetAdapter struct { @@ -20,7 +20,7 @@ type adtargetImpExt struct { Adtarget openrtb_ext.ExtImpAdtarget `json:"adtarget"` } -func (a *AdtargetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdtargetAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { totalImps := len(request.Imp) errors := make([]error, 0, totalImps) @@ -55,7 +55,7 @@ func (a *AdtargetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada reqs := make([]*adapters.RequestData, 0, totalReqs) imps := request.Imp - request.Imp = make([]openrtb.Imp, 0, len(imps)) + request.Imp = make([]openrtb2.Imp, 0, len(imps)) for sourceId, impIndexes := range imp2source { request.Imp = request.Imp[:0] @@ -80,7 +80,7 @@ func (a *AdtargetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada return reqs, errors } -func (a *AdtargetAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdtargetAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { if httpRes.StatusCode == http.StatusNoContent { return nil, nil @@ -90,7 +90,7 @@ func (a *AdtargetAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters. Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", httpRes.StatusCode), }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("error while decoding response, err: %s", err), @@ -137,7 +137,7 @@ func (a *AdtargetAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters. return bidResponse, errors } -func validateImpressionAndSetExt(imp *openrtb.Imp) (int, error) { +func validateImpressionAndSetExt(imp *openrtb2.Imp) (int, error) { if imp.Banner == nil && imp.Video == nil { return 0, &errortypes.BadInput{ diff --git a/adapters/adtarget/adtarget_test.go b/adapters/adtarget/adtarget_test.go index bb20b40c286..ed21aef0828 100644 --- a/adapters/adtarget/adtarget_test.go +++ b/adapters/adtarget/adtarget_test.go @@ -3,9 +3,9 @@ package adtarget import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/adtarget/adtargettest/exemplary/simple-banner.json b/adapters/adtarget/adtargettest/exemplary/simple-banner.json index 3799612455f..0d925bacd9f 100644 --- a/adapters/adtarget/adtargettest/exemplary/simple-banner.json +++ b/adapters/adtarget/adtargettest/exemplary/simple-banner.json @@ -60,4 +60,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/adtarget/adtargettest/exemplary/simple-video.json b/adapters/adtarget/adtargettest/exemplary/simple-video.json index bf6de569496..5b47751749c 100644 --- a/adapters/adtarget/adtargettest/exemplary/simple-video.json +++ b/adapters/adtarget/adtargettest/exemplary/simple-video.json @@ -53,4 +53,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/adtarget/params_test.go b/adapters/adtarget/params_test.go index 61ed4885512..b128d11c9cf 100644 --- a/adapters/adtarget/params_test.go +++ b/adapters/adtarget/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/adtarget.json diff --git a/adapters/adtarget/usersync.go b/adapters/adtarget/usersync.go index 93e57b173f6..088de8fb2ad 100644 --- a/adapters/adtarget/usersync.go +++ b/adapters/adtarget/usersync.go @@ -3,10 +3,10 @@ package adtarget import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAdtargetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtarget", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adtarget", temp, adapters.SyncTypeIframe) } diff --git a/adapters/adtarget/usersync_test.go b/adapters/adtarget/usersync_test.go index 419a6cb037e..e66b7b8377b 100644 --- a/adapters/adtarget/usersync_test.go +++ b/adapters/adtarget/usersync_test.go @@ -5,10 +5,10 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -32,7 +32,6 @@ func TestAdtargetSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr=0&gdpr_consent=123&us_privacy=1-YY&redir=localhost%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D0%26gdpr_consent%3D123%26uid%3D%7Buid%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) + assert.Equal(t, "iframe", syncInfo.Type) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adtelligent/adtelligent.go b/adapters/adtelligent/adtelligent.go index 7d8f6099fbf..244742db504 100644 --- a/adapters/adtelligent/adtelligent.go +++ b/adapters/adtelligent/adtelligent.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 AdtelligentAdapter struct { @@ -20,7 +20,7 @@ type adtelligentImpExt struct { Adtelligent openrtb_ext.ExtImpAdtelligent `json:"adtelligent"` } -func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AdtelligentAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { totalImps := len(request.Imp) errors := make([]error, 0, totalImps) @@ -55,7 +55,7 @@ func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * reqs := make([]*adapters.RequestData, 0, totalReqs) imps := request.Imp - request.Imp = make([]openrtb.Imp, 0, len(imps)) + request.Imp = make([]openrtb2.Imp, 0, len(imps)) for sourceId, impIds := range imp2source { request.Imp = request.Imp[:0] @@ -85,13 +85,13 @@ func (a *AdtelligentAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * } -func (a *AdtelligentAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AdtelligentAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, httpRes *adapters.ResponseData) (*adapters.BidderResponse, []error) { if httpRes.StatusCode == http.StatusNoContent { return nil, nil } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(httpRes.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("error while decoding response, err: %s", err), @@ -138,7 +138,7 @@ func (a *AdtelligentAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapte return bidResponse, errors } -func validateImpression(imp *openrtb.Imp) (int, error) { +func validateImpression(imp *openrtb2.Imp) (int, error) { if imp.Banner == nil && imp.Video == nil { return 0, &errortypes.BadInput{ diff --git a/adapters/adtelligent/adtelligent_test.go b/adapters/adtelligent/adtelligent_test.go index 6a104aa7463..503b30b576a 100644 --- a/adapters/adtelligent/adtelligent_test.go +++ b/adapters/adtelligent/adtelligent_test.go @@ -3,9 +3,9 @@ package adtelligent import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/adtelligent/params_test.go b/adapters/adtelligent/params_test.go index 8d8eb6d13b3..eb2aab0d437 100644 --- a/adapters/adtelligent/params_test.go +++ b/adapters/adtelligent/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/adtelligent.json diff --git a/adapters/adtelligent/usersync.go b/adapters/adtelligent/usersync.go index 387c65bb46d..9198b30fe6f 100644 --- a/adapters/adtelligent/usersync.go +++ b/adapters/adtelligent/usersync.go @@ -3,10 +3,10 @@ package adtelligent import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAdtelligentSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adtelligent", 410, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adtelligent", temp, adapters.SyncTypeIframe) } diff --git a/adapters/adtelligent/usersync_test.go b/adapters/adtelligent/usersync_test.go index 7cc92eb4011..2ca0ddfc135 100644 --- a/adapters/adtelligent/usersync_test.go +++ b/adapters/adtelligent/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -24,7 +24,6 @@ func TestAdtelligentSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.adtelligent.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 410, syncer.GDPRVendorID()) + assert.Equal(t, "iframe", syncInfo.Type) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/advangelists/advangelists.go b/adapters/advangelists/advangelists.go index 249e3282481..b5b385f6c06 100644 --- a/adapters/advangelists/advangelists.go +++ b/adapters/advangelists/advangelists.go @@ -6,12 +6,12 @@ import ( "net/http" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 AdvangelistsAdapter struct { @@ -19,7 +19,7 @@ type AdvangelistsAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *AdvangelistsAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *AdvangelistsAdapter) 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"}) @@ -51,9 +51,9 @@ func (adapter *AdvangelistsAdapter) MakeRequests(request *openrtb.BidRequest, re } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImpAdvangelists, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) ([]openrtb2.Imp, []openrtb_ext.ExtImpAdvangelists, []error) { errors := make([]error, 0, len(imps)) - resImps := make([]openrtb.Imp, 0, len(imps)) + resImps := make([]openrtb2.Imp, 0, len(imps)) resImpExts := make([]openrtb_ext.ExtImpAdvangelists, 0, len(imps)) for _, imp := range imps { @@ -73,7 +73,7 @@ func getImpressionsInfo(imps []openrtb.Imp) ([]openrtb.Imp, []openrtb_ext.ExtImp return resImps, resImpExts, errors } -func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdvangelists) error { +func validateImpression(imp *openrtb2.Imp, impExt *openrtb_ext.ExtImpAdvangelists) error { if impExt.PublisherID == "" { return &errortypes.BadInput{Message: "No pubid value provided"} } @@ -81,8 +81,8 @@ func validateImpression(imp *openrtb.Imp, impExt *openrtb_ext.ExtImpAdvangelists } //Group impressions by advangelists-specific parameters `pubid -func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdvangelists) (map[openrtb_ext.ExtImpAdvangelists][]openrtb.Imp, []error) { - res := make(map[openrtb_ext.ExtImpAdvangelists][]openrtb.Imp) +func dispatchImpressions(imps []openrtb2.Imp, impsExt []openrtb_ext.ExtImpAdvangelists) (map[openrtb_ext.ExtImpAdvangelists][]openrtb2.Imp, []error) { + res := make(map[openrtb_ext.ExtImpAdvangelists][]openrtb2.Imp) errors := make([]error, 0) for idx, imp := range imps { err := compatImpression(&imp) @@ -92,7 +92,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdvange } impExt := impsExt[idx] if res[impExt] == nil { - res[impExt] = make([]openrtb.Imp, 0) + res[impExt] = make([]openrtb2.Imp, 0) } res[impExt] = append(res[impExt], imp) @@ -101,7 +101,7 @@ func dispatchImpressions(imps []openrtb.Imp, impsExt []openrtb_ext.ExtImpAdvange } //Alter impression info to comply with advangelists platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to advangelists platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -109,7 +109,7 @@ func compatImpression(imp *openrtb.Imp) error { return nil } -func compatBannerImpression(imp *openrtb.Imp) error { +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 @@ -127,7 +127,7 @@ func compatBannerImpression(imp *openrtb.Imp) error { return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdvangelists, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpAdvangelists, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -143,7 +143,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpAdvangelists, error) return &advangelistsExt, nil } -func (adapter *AdvangelistsAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *AdvangelistsAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -167,7 +167,7 @@ func (adapter *AdvangelistsAdapter) buildAdapterRequest(prebidBidRequest *openrt Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpAdvangelists, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps for idx := range bidRequest.Imp { @@ -197,7 +197,7 @@ func (adapter *AdvangelistsAdapter) buildEndpointURL(params *openrtb_ext.ExtImpA } //MakeBids translates advangelists bid response to prebid-server specific format -func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var msg = "" if response.StatusCode == http.StatusNoContent { return nil, nil @@ -206,7 +206,7 @@ func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb.BidRequest msg = fmt.Sprintf("Unexpected http status code: %d", response.StatusCode) return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - var bidResp openrtb.BidResponse + 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}} @@ -230,7 +230,7 @@ func (adapter *AdvangelistsAdapter) MakeBids(internalRequest *openrtb.BidRequest } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +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 diff --git a/adapters/advangelists/advangelists_test.go b/adapters/advangelists/advangelists_test.go index 49cca96a78a..4219c1a0237 100644 --- a/adapters/advangelists/advangelists_test.go +++ b/adapters/advangelists/advangelists_test.go @@ -3,9 +3,9 @@ package advangelists import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/advangelists/params_test.go b/adapters/advangelists/params_test.go index 2a94c782092..a58217a0ffd 100644 --- a/adapters/advangelists/params_test.go +++ b/adapters/advangelists/params_test.go @@ -2,7 +2,7 @@ package advangelists import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/advangelists/usersync.go b/adapters/advangelists/usersync.go index b1539d0093d..83930774773 100644 --- a/adapters/advangelists/usersync.go +++ b/adapters/advangelists/usersync.go @@ -3,10 +3,10 @@ package advangelists import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAdvangelistsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("advangelists", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("advangelists", temp, adapters.SyncTypeIframe) } diff --git a/adapters/advangelists/usersync_test.go b/adapters/advangelists/usersync_test.go index 5dae85b11a9..04ee7968d87 100644 --- a/adapters/advangelists/usersync_test.go +++ b/adapters/advangelists/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -26,6 +26,5 @@ func TestAdvangelistsSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=advangelists&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&uid=$UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/adxcg/adxcg.go b/adapters/adxcg/adxcg.go new file mode 100644 index 00000000000..e9f6c94bc40 --- /dev/null +++ b/adapters/adxcg/adxcg.go @@ -0,0 +1,125 @@ +package adxcg + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/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" +) + +// Builder builds a new instance of the Adxcg adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +type adapter struct { + endpoint string +} + +// MakeRequests prepares the HTTP requests which should be made to fetch bids. +func (adapter *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + 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") + requestToBidder := &adapters.RequestData{ + Method: "POST", + Uri: adapter.endpoint, + Body: openRTBRequestJSON, + Headers: headers, + } + requestsToBidder = append(requestsToBidder, requestToBidder) + + return requestsToBidder, errs +} + +const unexpectedStatusCodeFormat = "Unexpected status code: %d. Run with request.debug = 1 for more info" + +// MakeBids unpacks the server's response into Bids. +func (adapter *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + switch bidderRawResponse.StatusCode { + case http.StatusOK: + break + case http.StatusNoContent: + return nil, nil + case http.StatusBadRequest: + err := &errortypes.BadInput{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), + } + return nil, []error{err} + default: + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), + } + return nil, []error{err} + } + + var openRTBBidderResponse openrtb2.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { + return nil, []error{err} + } + + bidsCapacity := len(openRTBBidderResponse.SeatBid[0].Bid) + bidderResponse = adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + var typedBid *adapters.TypedBid + for _, seatBid := range openRTBBidderResponse.SeatBid { + for _, bid := range seatBid.Bid { + activeBid := bid + bidType, err := getMediaTypeForImp(activeBid.ImpID, openRTBRequest.Imp) + if err != nil { + errs = append(errs, err) + continue + } + + typedBid = &adapters.TypedBid{Bid: &activeBid, BidType: bidType} + bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + } + } + + return bidderResponse, nil + +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } else if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find native/banner/video impression \"%s\" ", impID), + } +} diff --git a/adapters/adxcg/adxcg_test.go b/adapters/adxcg/adxcg_test.go new file mode 100644 index 00000000000..d01e62f670c --- /dev/null +++ b/adapters/adxcg/adxcg_test.go @@ -0,0 +1,23 @@ +package adxcg + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const testsDir = "adxcgtest" +const testsBidderEndpoint = "http://localhost/prebid_server" + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdxcg, config.Adapter{ + Endpoint: testsBidderEndpoint}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, testsDir, bidder) +} diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-banner.json b/adapters/adxcg/adxcgtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..85ed18ff1a3 --- /dev/null +++ b/adapters/adxcg/adxcgtest/exemplary/simple-banner.json @@ -0,0 +1,83 @@ +{ + "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", + "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": "adxcg", + "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": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-native.json b/adapters/adxcg/adxcgtest/exemplary/simple-native.json new file mode 100644 index 00000000000..1a449e601a2 --- /dev/null +++ b/adapters/adxcg/adxcgtest/exemplary/simple-native.json @@ -0,0 +1,73 @@ +{ + "mockBidRequest": { + "id": "test-request-native-id", + "imp": [ + { + "id": "test-imp-native-id", + "native": { + "request": "test-native", + "ver": "1.2" + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-native-id", + "imp": [ + { + "id": "test-imp-native-id", + "native": { + "request": "test-native", + "ver": "1.2" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "adxcg", + "bid": [ + { + "id": "test-request-native-id", + "impid": "test-imp-native-id", + "price": 1.16, + "adm": "native-ad", + "w": 1, + "h": 1 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-native-id", + "impid": "test-imp-native-id", + "price": 1.16, + "adm": "native-ad", + "w": 1, + "h": 1 + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/adxcg/adxcgtest/exemplary/simple-video.json b/adapters/adxcg/adxcgtest/exemplary/simple-video.json new file mode 100644 index 00000000000..b5f54b9bc44 --- /dev/null +++ b/adapters/adxcg/adxcgtest/exemplary/simple-video.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "id": "test-request-video-id", + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "w": 300, + "h": 250, + "maxduration": 60, + "minduration": 1, + "api": [ + 1, + 2, + 5, + 6, + 7 + ], + "mimes": [ + "video\/mp4" + ], + "placement": 4, + "protocols": [ + 2 + ] + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://localhost/prebid_server", + "body": { + "id": "test-request-video-id", + "imp": [ + { + "id": "test-imp-video-id", + "video": { + "w": 300, + "h": 250, + "maxduration": 60, + "minduration": 1, + "api": [ + 1, + 2, + 5, + 6, + 7 + ], + "mimes": [ + "video\/mp4" + ], + "placement": 4, + "protocols": [ + 2 + ] + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "adxcg", + "bid": [ + { + "id": "test-request-video-id", + "impid": "test-imp-video-id", + "price": 1.16, + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-video-id", + "impid": "test-imp-video-id", + "price": 1.16, + "adm": "some-test-ad", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adxcg/adxcgtest/params/race/banner.json b/adapters/adxcg/adxcgtest/params/race/banner.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/adapters/adxcg/adxcgtest/params/race/banner.json @@ -0,0 +1 @@ +{} diff --git a/adapters/adxcg/adxcgtest/supplemental/bad_response.json b/adapters/adxcg/adxcgtest/supplemental/bad_response.json new file mode 100644 index 00000000000..f84f5555259 --- /dev/null +++ b/adapters/adxcg/adxcgtest/supplemental/bad_response.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "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", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "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/adxcg/adxcgtest/supplemental/status_204.json b/adapters/adxcg/adxcgtest/supplemental/status_204.json new file mode 100644 index 00000000000..0702c103332 --- /dev/null +++ b/adapters/adxcg/adxcgtest/supplemental/status_204.json @@ -0,0 +1,56 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "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", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/adxcg/adxcgtest/supplemental/status_400.json b/adapters/adxcg/adxcgtest/supplemental/status_400.json new file mode 100644 index 00000000000..65d21406bf0 --- /dev/null +++ b/adapters/adxcg/adxcgtest/supplemental/status_400.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "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", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adxcg/adxcgtest/supplemental/status_418.json b/adapters/adxcg/adxcgtest/supplemental/status_418.json new file mode 100644 index 00000000000..4c5dd576aa6 --- /dev/null +++ b/adapters/adxcg/adxcgtest/supplemental/status_418.json @@ -0,0 +1,61 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "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", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adxcg/usersync.go b/adapters/adxcg/usersync.go new file mode 100644 index 00000000000..c6627059703 --- /dev/null +++ b/adapters/adxcg/usersync.go @@ -0,0 +1,12 @@ +package adxcg + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdxcgSyncer(urlTemplate *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adxcg", urlTemplate, adapters.SyncTypeRedirect) +} diff --git a/adapters/adxcg/usersync_test.go b/adapters/adxcg/usersync_test.go new file mode 100644 index 00000000000..53a757f2732 --- /dev/null +++ b/adapters/adxcg/usersync_test.go @@ -0,0 +1,29 @@ +package adxcg + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestAdxcgSyncer(t *testing.T) { + syncURL := "https://app.adxcg.net/cma/cm-notify?pi=prebidsrvtst&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdxcgSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://app.adxcg.net/cma/cm-notify?pi=prebidsrvtst&gdpr=0&gdpr_consent=", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/adyoulike/adyoulike.go b/adapters/adyoulike/adyoulike.go new file mode 100644 index 00000000000..4cccd5dc5bc --- /dev/null +++ b/adapters/adyoulike/adyoulike.go @@ -0,0 +1,137 @@ +package adyoulike + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" +) + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + return &adapter{ + endpoint: config.Endpoint, + }, nil +} + +type adapter struct { + endpoint string +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + var err error + var tagID string + + reqCopy := *openRTBRequest + reqCopy.Imp = []openrtb2.Imp{} + for ind, imp := range openRTBRequest.Imp { + reqCopy.Imp = append(reqCopy.Imp, imp) + + tagID, err = jsonparser.GetString(reqCopy.Imp[ind].Ext, "bidder", "placement") + if err != nil { + errs = append(errs, err) + continue + } + + reqCopy.Imp[ind].TagID = tagID + } + + openRTBRequestJSON, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, err) + } + + if len(errs) > 0 { + 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") + + requestToBidder := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: openRTBRequestJSON, + Headers: headers, + } + requestsToBidder = append(requestsToBidder, requestToBidder) + + return requestsToBidder, errs +} + +const unexpectedStatusCodeFormat = "" + + "Unexpected status code: %d. Run with request.debug = 1 for more info" + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + switch bidderRawResponse.StatusCode { + case http.StatusOK: + break + case http.StatusNoContent: + return nil, []error{errors.New("MakeBids error: No Content")} + case http.StatusBadRequest: + err := &errortypes.BadInput{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), + } + return nil, []error{err} + default: + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode), + } + return nil, []error{err} + } + + var openRTBBidderResponse openrtb2.BidResponse + if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(openRTBRequest.Imp)) + bidResponse.Currency = openRTBBidderResponse.Cur + for _, seatBid := range openRTBBidderResponse.SeatBid { + for idx := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &seatBid.Bid[idx], + BidType: getMediaTypeForImp(seatBid.Bid[idx].ImpID, openRTBRequest.Imp), + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +// getMediaTypeForBid determines which type of bid. +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 && imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } else if imp.Banner == nil && imp.Native != nil { + mediaType = openrtb_ext.BidTypeNative + } + } + } + + return mediaType +} diff --git a/adapters/adyoulike/adyoulike_test.go b/adapters/adyoulike/adyoulike_test.go new file mode 100644 index 00000000000..9ab689f3c77 --- /dev/null +++ b/adapters/adyoulike/adyoulike_test.go @@ -0,0 +1,22 @@ +package adyoulike + +import ( + "testing" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const testsBidderEndpoint = "https://localhost/bid/4" + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdyoulike, config.Adapter{ + Endpoint: testsBidderEndpoint}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adyouliketest", bidder) +} diff --git a/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json b/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json new file mode 100644 index 00000000000..2ae99c04969 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/exemplary/multiformat-impression.json @@ -0,0 +1,169 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + }, + { + "id": "video-imp-id", + "video": { + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "placement": "54321" + } + } + }, + { + "id": "native-imp-id", + "native": { + "title": "required" + }, + "ext": { + "bidder": { + "placement": "123123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + }, + { + "ext": { + "bidder": { + "placement": "54321" + } + }, + "id": "video-imp-id", + "tagid": "54321", + "video": { + "h": 480, + "mimes": null, + "w": 640 + } + }, + { + "ext": { + "bidder": { + "placement": "123123" + } + }, + "id": "native-imp-id", + "native": { + "request": "" + }, + "tagid": "123123" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-response-id", + "seatbid": [{ + "seat": "1", + "bid": [{ + "id": "12340", + "impid": "banner-imp-id", + "price": 300, + "adm": "%3C%3Fxml%20version%3D%221.0%22%20encod%2Fhtml%3E", + "nurl": "http://example.com/winnoticeurl0" + }, + { + "id": "12341", + "impid": "video-imp-id", + "price": 301, + "adm": "%3C%3Fxml%20version%3D%221.0%22%20encod%2FVAST%3E", + "nurl": "http://example.com/winnoticeurl1" + }, + { + "id": "12342", + "impid": "native-imp-id", + "price": 302, + "adm": "{'json':'response','for':'native'}", + "nurl": "http://example.com/winnoticeurl2" + } + ] + }] + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "12340", + "impid": "banner-imp-id", + "price": 300, + "nurl": "http://example.com/winnoticeurl0", + "adm": "%3C%3Fxml%20version%3D%221.0%22%20encod%2Fhtml%3E" + }, + "type": "banner" + }, + { + "bid": { + "id": "12341", + "impid": "video-imp-id", + "price": 301, + "nurl": "http://example.com/winnoticeurl1", + "adm": "%3C%3Fxml%20version%3D%221.0%22%20encod%2FVAST%3E" + }, + "type": "video" + }, + { + "bid": { + "id": "12342", + "impid": "native-imp-id", + "price": 302, + "nurl": "http://example.com/winnoticeurl2", + "adm": "{'json':'response','for':'native'}" + }, + "type": "native" + } + ] + } + ] + } diff --git a/adapters/adyoulike/adyouliketest/params/race/banner.json b/adapters/adyoulike/adyouliketest/params/race/banner.json new file mode 100644 index 00000000000..726ca878c05 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "placement": "19f1b372c7548ec1fe734d2c9f8dc688" +} diff --git a/adapters/adyoulike/adyouliketest/params/race/video.json b/adapters/adyoulike/adyouliketest/params/race/video.json new file mode 100644 index 00000000000..d0883f5e04a --- /dev/null +++ b/adapters/adyoulike/adyouliketest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "placement": "19f1b372c7548ec1fe734d2c9f8dc688" +} diff --git a/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json b/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json new file mode 100644 index 00000000000..bda8d3e6640 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/invalid-bid-response.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + + "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/adyoulike/adyouliketest/supplemental/status-bad-request.json b/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json new file mode 100644 index 00000000000..83b5f3611d8 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/status-bad-request.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "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/adyoulike/adyouliketest/supplemental/status-no-content.json b/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json new file mode 100644 index 00000000000..9ef51e88a41 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/status-no-content.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": { + } + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [ + { + "value": "MakeBids error: No Content", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json b/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json new file mode 100644 index 00000000000..2b5872a16f5 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/status-service-unavailable.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 503, + "body": { + } + } + } + ], + + "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/adyoulike/adyouliketest/supplemental/status-unknown.json b/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json new file mode 100644 index 00000000000..0b9ce745dd9 --- /dev/null +++ b/adapters/adyoulike/adyouliketest/supplemental/status-unknown.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "banner-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://localhost/bid/4", + "body": { + "id": "test-request-id", + "imp": [ + { + "banner": { + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "placement": "12345" + } + }, + "id": "banner-imp-id", + "tagid": "12345" + } + ] + } + }, + "mockResponse": { + "status": 999, + "body": { + } + } + } + ], + + "expectedBidResponses": [], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 999. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adyoulike/params_test.go b/adapters/adyoulike/params_test.go new file mode 100644 index 00000000000..b57264b3dbd --- /dev/null +++ b/adapters/adyoulike/params_test.go @@ -0,0 +1,62 @@ +package adyoulike + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/adyoulike.json +// +// These also validate the format of the external API: request.imp[i].ext.adyoulike + +// TestValidParams makes sure that the adyoulike 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.BidderAdyoulike, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adyoulike params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adyoulike 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.BidderAdyoulike, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placement":"123"}`, + `{"placement":"123","campaign":"456"}`, + `{"placement":"123","campaign":"456","track":"789"}`, + `{"placement":"123","campaign":"456","track":"789","creative":"ABC"}`, + `{"placement":"123","campaign":"456","track":"789","creative":"ABC","source":"SSP"}`, + `{"placement":"123","campaign":"456","track":"789","creative":"ABC","source":"SSP","debug":"info"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"invalid":"123"}`, + `{"placement":123}`, + `{"placement":"123","campaign":123}`, +} diff --git a/adapters/adyoulike/usersync.go b/adapters/adyoulike/usersync.go new file mode 100644 index 00000000000..ffea6f69a27 --- /dev/null +++ b/adapters/adyoulike/usersync.go @@ -0,0 +1,12 @@ +package adyoulike + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdyoulikeSyncer(urlTemplate *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adyoulike", urlTemplate, adapters.SyncTypeRedirect) +} diff --git a/adapters/adyoulike/usersync_test.go b/adapters/adyoulike/usersync_test.go new file mode 100644 index 00000000000..72def4cf9b0 --- /dev/null +++ b/adapters/adyoulike/usersync_test.go @@ -0,0 +1,35 @@ +package adyoulike + +import ( + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" +) + +func TestAdyoulikeSyncer(t *testing.T) { + syncURL := "//visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr_consent_string={{.GDPRConsent}}&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdyoulikeSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", + }, + CCPA: ccpa.Policy{ + Consent: "1-YY", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr_consent_string=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&gdpr=1&us_privacy=1-YY", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/aja/aja.go b/adapters/aja/aja.go index afd9c6d7131..6e77fdd5685 100644 --- a/adapters/aja/aja.go +++ b/adapters/aja/aja.go @@ -5,21 +5,21 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 AJAAdapter struct { endpoint string } -func (a *AJAAdapter) MakeRequests(bidReq *openrtb.BidRequest, extraInfo *adapters.ExtraRequestInfo) (adapterReqs []*adapters.RequestData, errs []error) { +func (a *AJAAdapter) MakeRequests(bidReq *openrtb2.BidRequest, extraInfo *adapters.ExtraRequestInfo) (adapterReqs []*adapters.RequestData, errs []error) { // split imps by tagid tagIDs := []string{} - impsByTagID := map[string][]openrtb.Imp{} + impsByTagID := map[string][]openrtb2.Imp{} for _, imp := range bidReq.Imp { extAJA, err := parseExtAJA(imp) if err != nil { @@ -54,7 +54,7 @@ func (a *AJAAdapter) MakeRequests(bidReq *openrtb.BidRequest, extraInfo *adapter return } -func parseExtAJA(imp openrtb.Imp) (openrtb_ext.ExtImpAJA, error) { +func parseExtAJA(imp openrtb2.Imp) (openrtb_ext.ExtImpAJA, error) { var ( extImp adapters.ExtImpBidder extAJA openrtb_ext.ExtImpAJA @@ -75,7 +75,7 @@ func parseExtAJA(imp openrtb.Imp) (openrtb_ext.ExtImpAJA, error) { return extAJA, nil } -func (a *AJAAdapter) MakeBids(bidReq *openrtb.BidRequest, adapterReq *adapters.RequestData, adapterResp *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AJAAdapter) MakeBids(bidReq *openrtb2.BidRequest, adapterReq *adapters.RequestData, adapterResp *adapters.ResponseData) (*adapters.BidderResponse, []error) { if adapterResp.StatusCode != http.StatusOK { if adapterResp.StatusCode == http.StatusNoContent { return nil, nil @@ -90,7 +90,7 @@ func (a *AJAAdapter) MakeBids(bidReq *openrtb.BidRequest, adapterReq *adapters.R }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(adapterResp.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Failed to unmarshal bid response: %s", err.Error()), diff --git a/adapters/aja/aja_test.go b/adapters/aja/aja_test.go index d2d9d7fa7c1..bab5419d889 100644 --- a/adapters/aja/aja_test.go +++ b/adapters/aja/aja_test.go @@ -3,9 +3,9 @@ package aja import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) const testsBidderEndpoint = "https://localhost/bid/4" diff --git a/adapters/aja/usersync.go b/adapters/aja/usersync.go index deddbabb1d9..6a9fad74e32 100644 --- a/adapters/aja/usersync.go +++ b/adapters/aja/usersync.go @@ -3,10 +3,10 @@ package aja import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAJASyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("aja", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("aja", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/aja/usersync_test.go b/adapters/aja/usersync_test.go index bf03f47af19..dabd5e190b9 100644 --- a/adapters/aja/usersync_test.go +++ b/adapters/aja/usersync_test.go @@ -4,10 +4,10 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -31,6 +31,5 @@ func TestAJASyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr=1&us_privacy=C&redir=localhost/setuid?bidder=aja&gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&uid=%s", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/amx/amx.go b/adapters/amx/amx.go index ddd0c0373da..737fac421f0 100644 --- a/adapters/amx/amx.go +++ b/adapters/amx/amx.go @@ -7,11 +7,11 @@ import ( "net/url" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const vastImpressionFormat = "" @@ -49,9 +49,9 @@ type amxExt struct { Bidder openrtb_ext.ExtImpAMX `json:"bidder"` } -func ensurePublisherWithID(pub *openrtb.Publisher, publisherID string) openrtb.Publisher { +func ensurePublisherWithID(pub *openrtb2.Publisher, publisherID string) openrtb2.Publisher { if pub == nil { - return openrtb.Publisher{ID: publisherID} + return openrtb2.Publisher{ID: publisherID} } pubCopy := *pub @@ -60,7 +60,7 @@ func ensurePublisherWithID(pub *openrtb.Publisher, publisherID string) openrtb.P } // MakeRequests creates AMX adapter requests -func (adapter *AMXAdapter) MakeRequests(request *openrtb.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { +func (adapter *AMXAdapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) { reqCopy := *request var publisherID string @@ -119,7 +119,7 @@ type amxBidExt struct { } // MakeBids will parse the bids from the AMX server -func (adapter *AMXAdapter) MakeBids(request *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *AMXAdapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if http.StatusNoContent == response.StatusCode { @@ -140,7 +140,7 @@ func (adapter *AMXAdapter) MakeBids(request *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -196,7 +196,7 @@ func pixelToImpression(pixel string) string { return fmt.Sprintf(vastImpressionFormat, pixel) } -func interpolateImpressions(bid openrtb.Bid, ext amxBidExt) string { +func interpolateImpressions(bid openrtb2.Bid, ext amxBidExt) string { var buffer strings.Builder if bid.NURL != "" { buffer.WriteString(pixelToImpression(bid.NURL)) diff --git a/adapters/amx/amx_test.go b/adapters/amx/amx_test.go index 6fc850cb2cc..229889db7cf 100644 --- a/adapters/amx/amx_test.go +++ b/adapters/amx/amx_test.go @@ -6,13 +6,13 @@ import ( "regexp" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/adapters/adapterstest" ) const ( @@ -48,7 +48,7 @@ func TestEndpointQueryStringMalformed(t *testing.T) { func TestMakeRequestsTagID(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ Endpoint: amxTestEndpoint}) @@ -75,12 +75,12 @@ func TestMakeRequestsTagID(t *testing.T) { } for _, tc := range tests { - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "sample_imp_1", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} @@ -94,16 +94,16 @@ func TestMakeRequestsTagID(t *testing.T) { imp1.TagID = tc.tagID } - inputRequest := openrtb.BidRequest{ - User: &openrtb.User{}, - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{}, + inputRequest := openrtb2.BidRequest{ + User: &openrtb2.User{}, + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{}, } actualAdapterRequests, err := bidder.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) assert.Len(t, actualAdapterRequests, 1) assert.Empty(t, err) - var body openrtb.BidRequest + var body openrtb2.BidRequest assert.Nil(t, json.Unmarshal(actualAdapterRequests[0].Body, &body)) assert.Equal(t, tc.expectedTagID, body.Imp[0].TagID) } @@ -111,7 +111,7 @@ func TestMakeRequestsTagID(t *testing.T) { func TestMakeRequestsPublisherId(t *testing.T) { var w, h int = 300, 250 - var width, height uint64 = uint64(w), uint64(h) + var width, height int64 = int64(w), int64(h) bidder, buildErr := Builder(openrtb_ext.BidderAMX, config.Adapter{ Endpoint: amxTestEndpoint}) @@ -137,12 +137,12 @@ func TestMakeRequestsPublisherId(t *testing.T) { } for _, tc := range tests { - imp1 := openrtb.Imp{ + imp1 := openrtb2.Imp{ ID: "sample_imp_1", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &width, H: &height, - Format: []openrtb.Format{ + Format: []openrtb2.Format{ {W: 300, H: 250}, }, }} @@ -152,15 +152,15 @@ func TestMakeRequestsPublisherId(t *testing.T) { fmt.Sprintf(`{"bidder":{"tagId":"%s"}}`, tc.extTagID)) } - inputRequest := openrtb.BidRequest{ - User: &openrtb.User{ID: "example_user_id"}, - Imp: []openrtb.Imp{imp1}, - Site: &openrtb.Site{}, + inputRequest := openrtb2.BidRequest{ + User: &openrtb2.User{ID: "example_user_id"}, + Imp: []openrtb2.Imp{imp1}, + Site: &openrtb2.Site{}, ID: "1234", } if tc.publisherID != "" || !tc.blankNil { - inputRequest.Site.Publisher = &openrtb.Publisher{ + inputRequest.Site.Publisher = &openrtb2.Publisher{ ID: tc.publisherID, } } @@ -168,7 +168,7 @@ func TestMakeRequestsPublisherId(t *testing.T) { actualAdapterRequests, err := bidder.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) assert.Len(t, actualAdapterRequests, 1) assert.Empty(t, err) - var body openrtb.BidRequest + var body openrtb2.BidRequest assert.Nil(t, json.Unmarshal(actualAdapterRequests[0].Body, &body)) assert.Equal(t, tc.expectedPublisherID, body.Site.Publisher.ID) } @@ -182,7 +182,7 @@ func countImpressionPixels(vast string) int { } func TestVideoImpInsertion(t *testing.T) { - markup := interpolateImpressions(openrtb.Bid{ + markup := interpolateImpressions(openrtb2.Bid{ AdM: sampleVastADM, NURL: "https://example2.com/nurl", }, amxBidExt{Himp: []string{"https://example.com/pixel.png"}}) @@ -191,14 +191,14 @@ func TestVideoImpInsertion(t *testing.T) { assert.Equal(t, 3, countImpressionPixels(markup), "should have 3 Impression pixels") // make sure that a blank NURL won't result in a blank impression tag - markup = interpolateImpressions(openrtb.Bid{ + markup = interpolateImpressions(openrtb2.Bid{ AdM: sampleVastADM, NURL: "", }, amxBidExt{}) assert.Equal(t, 1, countImpressionPixels(markup), "should have 1 impression pixels") // we should also ignore blank ext.Himp pixels - markup = interpolateImpressions(openrtb.Bid{ + markup = interpolateImpressions(openrtb2.Bid{ AdM: sampleVastADM, NURL: "https://example-nurl.com/nurl", }, amxBidExt{Himp: []string{"", "", ""}}) @@ -206,7 +206,7 @@ func TestVideoImpInsertion(t *testing.T) { } func TestNoDisplayImpInsertion(t *testing.T) { - data := interpolateImpressions(openrtb.Bid{ + data := interpolateImpressions(openrtb2.Bid{ AdM: sampleDisplayADM, NURL: "https://example2.com/nurl", }, amxBidExt{Himp: []string{"https://example.com/pixel.png"}}) diff --git a/adapters/amx/amxtest/exemplary/video-simple.json b/adapters/amx/amxtest/exemplary/video-simple.json index f8fe587ff63..678722adf8c 100644 --- a/adapters/amx/amxtest/exemplary/video-simple.json +++ b/adapters/amx/amxtest/exemplary/video-simple.json @@ -242,4 +242,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/adapters/amx/amxtest/exemplary/web-simple.json b/adapters/amx/amxtest/exemplary/web-simple.json index 92fc4afc018..f75bd85acb0 100644 --- a/adapters/amx/amxtest/exemplary/web-simple.json +++ b/adapters/amx/amxtest/exemplary/web-simple.json @@ -243,4 +243,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/adapters/amx/params_test.go b/adapters/amx/params_test.go index ef177644b21..89e9a3adeb4 100644 --- a/adapters/amx/params_test.go +++ b/adapters/amx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/adapters/amx/usersync.go b/adapters/amx/usersync.go index d9ff10df562..17ad04d5cfb 100644 --- a/adapters/amx/usersync.go +++ b/adapters/amx/usersync.go @@ -3,11 +3,11 @@ package amx import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) // NewAMXSyncer produces an AMX RTB usersyncer func NewAMXSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("amx", 737, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("amx", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/amx/usersync_test.go b/adapters/amx/usersync_test.go index e6020b27570..b6b6e6babe8 100644 --- a/adapters/amx/usersync_test.go +++ b/adapters/amx/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -18,6 +18,5 @@ func TestAMXSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://pbs.amxrtb.com/cchain/0?gdpr=&gdpr_consent=&cb=localhost%2Fsetuid%3Fbidder%3Damx%26uid%3D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 737, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/applogy/applogy.go b/adapters/applogy/applogy.go index cdeafa0f426..b144c6b836f 100644 --- a/adapters/applogy/applogy.go +++ b/adapters/applogy/applogy.go @@ -6,18 +6,18 @@ import ( "net/http" "strconv" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +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") @@ -25,7 +25,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E result := make([]*adapters.RequestData, 0, len(impressions)) errs := make([]error, 0, len(impressions)) - for i, impression := range 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", @@ -33,17 +33,17 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E continue } if impression.Banner != nil { - banner := impression.Banner - if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { - if len(banner.Format) == 0 { + 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 } - format := banner.Format[0] - banner.W = &format.W - banner.H = &format.H + 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 { @@ -70,7 +70,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E errs = append(errs, errors.New("Applogy token required")) continue } - request.Imp = impressions[i : i+1] + request.Imp = []openrtb2.Imp{impression} body, err := json.Marshal(request) if err != nil { errs = append(errs, err) @@ -92,7 +92,7 @@ func (a *ApplogyAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.E return result, errs } -func (a *ApplogyAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *ApplogyAdapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error switch responseData.StatusCode { @@ -110,7 +110,7 @@ func (a *ApplogyAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.Reque }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse err := json.Unmarshal(responseData.Body, &bidResponse) if err != nil { return nil, []error{&errortypes.BadServerResponse{ diff --git a/adapters/applogy/applogy_test.go b/adapters/applogy/applogy_test.go index 63e99ed5895..d86c5cacd68 100644 --- a/adapters/applogy/applogy_test.go +++ b/adapters/applogy/applogy_test.go @@ -3,9 +3,9 @@ package applogy import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/appnexus/appnexus.go b/adapters/appnexus/appnexus.go index 847c11a25ce..8993c7249dc 100644 --- a/adapters/appnexus/appnexus.go +++ b/adapters/appnexus/appnexus.go @@ -11,17 +11,17 @@ import ( "strconv" "strings" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/pbs" "golang.org/x/net/context/ctxhttp" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) const defaultPlatformID int = 5 @@ -163,9 +163,9 @@ func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder } if anReq.Imp[i].Banner != nil && params.Position != "" { if params.Position == "above" { - anReq.Imp[i].Banner.Pos = openrtb.AdPositionAboveTheFold.Ptr() + anReq.Imp[i].Banner.Pos = openrtb2.AdPositionAboveTheFold.Ptr() } else if params.Position == "below" { - anReq.Imp[i].Banner.Pos = openrtb.AdPositionBelowTheFold.Ptr() + anReq.Imp[i].Banner.Pos = openrtb2.AdPositionBelowTheFold.Ptr() } } @@ -245,7 +245,7 @@ func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder debug.ResponseBody = responseBody } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return nil, err @@ -288,7 +288,7 @@ func (a *AppNexusAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder return bids, nil } -func (a *AppNexusAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AppNexusAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { memberIds := make(map[string]bool) errs := make([]error, 0, len(request.Imp)) @@ -391,9 +391,9 @@ func generatePodId() string { return fmt.Sprint(val) } -func groupByPods(imps []openrtb.Imp) map[string]([]openrtb.Imp) { +func groupByPods(imps []openrtb2.Imp) map[string]([]openrtb2.Imp) { // find number of pods in response - podImps := make(map[string][]openrtb.Imp) + podImps := make(map[string][]openrtb2.Imp) for _, imp := range imps { pod := strings.Split(imp.ID, "_")[0] podImps[pod] = append(podImps[pod], imp) @@ -401,7 +401,7 @@ func groupByPods(imps []openrtb.Imp) map[string]([]openrtb.Imp) { return podImps } -func marshalAndSetRequestExt(request *openrtb.BidRequest, requestExtension appnexusReqExt, errs []error) { +func marshalAndSetRequestExt(request *openrtb2.BidRequest, requestExtension appnexusReqExt, errs []error) { var err error request.Ext, err = json.Marshal(requestExtension) if err != nil { @@ -409,7 +409,7 @@ func marshalAndSetRequestExt(request *openrtb.BidRequest, requestExtension appne } } -func splitRequests(imps []openrtb.Imp, request *openrtb.BidRequest, requestExtension appnexusReqExt, uri string, errs []error) ([]*adapters.RequestData, []error) { +func splitRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExtension appnexusReqExt, uri string, errs []error) ([]*adapters.RequestData, []error) { // Initial capacity for future array of requests, memory optimization. // Let's say there are 35 impressions and limit impressions per request equals to 10. @@ -465,7 +465,7 @@ func keys(m map[string]bool) []string { // preprocess mutates the imp to get it ready to send to appnexus. // // It returns the member param, if it exists, and an error if anything went wrong during the preprocessing. -func preprocess(imp *openrtb.Imp, defaultDisplayManagerVer string) (string, error) { +func preprocess(imp *openrtb2.Imp, defaultDisplayManagerVer string) (string, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return "", err @@ -503,9 +503,9 @@ func preprocess(imp *openrtb.Imp, defaultDisplayManagerVer string) (string, erro if imp.Banner != nil { bannerCopy := *imp.Banner if appnexusExt.Position == "above" { - bannerCopy.Pos = openrtb.AdPositionAboveTheFold.Ptr() + bannerCopy.Pos = openrtb2.AdPositionAboveTheFold.Ptr() } else if appnexusExt.Position == "below" { - bannerCopy.Pos = openrtb.AdPositionBelowTheFold.Ptr() + bannerCopy.Pos = openrtb2.AdPositionBelowTheFold.Ptr() } // Fixes #307 @@ -552,7 +552,7 @@ func makeKeywordStr(keywords []*openrtb_ext.ExtImpAppnexusKeyVal) string { return strings.Join(kvs, ",") } -func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -567,7 +567,7 @@ func (a *AppNexusAdapter) MakeBids(internalRequest *openrtb.BidRequest, external return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -690,5 +690,6 @@ func loadCategoryMapFromFileSystem() map[string]string { return adapterOptions.IabCategories } } + return nil } diff --git a/adapters/appnexus/appnexus_test.go b/adapters/appnexus/appnexus_test.go index 635563a14d3..3386c430fe1 100644 --- a/adapters/appnexus/appnexus_test.go +++ b/adapters/appnexus/appnexus_test.go @@ -10,17 +10,17 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" ) func TestJsonSamples(t *testing.T) { @@ -63,16 +63,16 @@ func TestMemberQueryParam(t *testing.T) { var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = "video" - var req openrtb.BidRequest + var req openrtb2.BidRequest req.ID = "test_id" reqExt := `{"prebid":{}}` impExt := `{"bidder":{"placementId":123}}` req.Ext = []byte(reqExt) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_2", Ext: []byte(impExt)}) result, err := a.MakeRequests(&req, &reqInfo) @@ -80,7 +80,7 @@ func TestMemberQueryParam(t *testing.T) { assert.Len(t, result, 1, "Only one request should be returned") var error error - var reqData *openrtb.BidRequest + var reqData *openrtb2.BidRequest error = json.Unmarshal(result[0].Body, &reqData) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -101,28 +101,28 @@ func TestVideoSinglePodManyImps(t *testing.T) { var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = "video" - var req openrtb.BidRequest + var req openrtb2.BidRequest req.ID = "test_id" reqExt := `{"prebid":{}}` impExt := `{"bidder":{"placementId":123}}` req.Ext = []byte(reqExt) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_3", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_4", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_5", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_6", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_7", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_8", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_9", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_10", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_11", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_12", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_13", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_14", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_3", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_4", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_5", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_6", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_7", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_8", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_9", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_10", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_11", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_12", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_13", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_14", Ext: []byte(impExt)}) res, err := a.MakeRequests(&req, &reqInfo) @@ -130,7 +130,7 @@ func TestVideoSinglePodManyImps(t *testing.T) { assert.Len(t, res, 2, "Two requests should be returned") var error error - var reqData1 *openrtb.BidRequest + var reqData1 *openrtb2.BidRequest error = json.Unmarshal(res[0].Body, &reqData1) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -140,7 +140,7 @@ func TestVideoSinglePodManyImps(t *testing.T) { adPodId1 := reqDataExt1.Appnexus.AdPodId - var reqData2 *openrtb.BidRequest + var reqData2 *openrtb2.BidRequest error = json.Unmarshal(res[1].Body, &reqData2) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -161,20 +161,20 @@ func TestVideoTwoPods(t *testing.T) { var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = "video" - var req openrtb.BidRequest + var req openrtb2.BidRequest req.ID = "test_id" reqExt := `{"prebid":{}}` impExt := `{"bidder":{"placementId":123}}` req.Ext = []byte(reqExt) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_2", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_2", Ext: []byte(impExt)}) res, err := a.MakeRequests(&req, &reqInfo) @@ -182,7 +182,7 @@ func TestVideoTwoPods(t *testing.T) { assert.Len(t, res, 2, "Two request should be returned") var error error - var reqData1 *openrtb.BidRequest + var reqData1 *openrtb2.BidRequest error = json.Unmarshal(res[0].Body, &reqData1) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -192,7 +192,7 @@ func TestVideoTwoPods(t *testing.T) { adPodId1 := reqDataExt1.Appnexus.AdPodId - var reqData2 *openrtb.BidRequest + var reqData2 *openrtb2.BidRequest error = json.Unmarshal(res[1].Body, &reqData2) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -213,32 +213,32 @@ func TestVideoTwoPodsManyImps(t *testing.T) { var reqInfo adapters.ExtraRequestInfo reqInfo.PbsEntryPoint = "video" - var req openrtb.BidRequest + var req openrtb2.BidRequest req.ID = "test_id" reqExt := `{"prebid":{}}` impExt := `{"bidder":{"placementId":123}}` req.Ext = []byte(reqExt) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "1_2", Ext: []byte(impExt)}) - - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_0", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_1", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_2", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_3", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_4", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_5", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_6", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_7", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_8", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_9", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_10", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_11", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_12", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_13", Ext: []byte(impExt)}) - req.Imp = append(req.Imp, openrtb.Imp{ID: "2_14", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "1_2", Ext: []byte(impExt)}) + + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_0", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_1", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_2", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_3", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_4", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_5", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_6", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_7", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_8", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_9", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_10", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_11", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_12", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_13", Ext: []byte(impExt)}) + req.Imp = append(req.Imp, openrtb2.Imp{ID: "2_14", Ext: []byte(impExt)}) res, err := a.MakeRequests(&req, &reqInfo) @@ -246,7 +246,7 @@ func TestVideoTwoPodsManyImps(t *testing.T) { assert.Len(t, res, 3, "Three requests should be returned") var error error - var reqData1 *openrtb.BidRequest + var reqData1 *openrtb2.BidRequest error = json.Unmarshal(res[0].Body, &reqData1) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -254,7 +254,7 @@ func TestVideoTwoPodsManyImps(t *testing.T) { error = json.Unmarshal(reqData1.Ext, &reqDataExt1) assert.NoError(t, error, "Response ext unmarshalling error should be nil") - var reqData2 *openrtb.BidRequest + var reqData2 *openrtb2.BidRequest error = json.Unmarshal(res[1].Body, &reqData2) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -262,7 +262,7 @@ func TestVideoTwoPodsManyImps(t *testing.T) { error = json.Unmarshal(reqData2.Ext, &reqDataExt2) assert.NoError(t, error, "Response ext unmarshalling error should be nil") - var reqData3 *openrtb.BidRequest + var reqData3 *openrtb2.BidRequest error = json.Unmarshal(res[2].Body, &reqData3) assert.NoError(t, error, "Response body unmarshalling error should be nil") @@ -284,7 +284,7 @@ func TestVideoTwoPodsManyImps(t *testing.T) { // ---------------------------------------------------------------------------- // Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb. +// clean up the existing code and make everything openrtb2. type anTagInfo struct { code string @@ -323,7 +323,7 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -336,14 +336,14 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { return } - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: breq.ID, BidID: "a-random-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "Buyer Member ID", - Bid: make([]openrtb.Bid, 0, 2), + Bid: make([]openrtb2.Bid, 0, 2), }, }, } @@ -413,11 +413,11 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Empty imp.banner.format array"), http.StatusInternalServerError) return } - if andata.tags[i].position == "above" && *imp.Banner.Pos != openrtb.AdPosition(1) { + if andata.tags[i].position == "above" && *imp.Banner.Pos != openrtb2.AdPosition(1) { http.Error(w, fmt.Sprintf("Mismatch in position - expected 1 for atf"), http.StatusInternalServerError) return } - if andata.tags[i].position == "below" && *imp.Banner.Pos != openrtb.AdPosition(3) { + if andata.tags[i].position == "below" && *imp.Banner.Pos != openrtb2.AdPosition(3) { http.Error(w, fmt.Sprintf("Mismatch in position - expected 3 for btf"), http.StatusInternalServerError) return } @@ -440,7 +440,7 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { } } - resBid := openrtb.Bid{ + resBid := openrtb2.Bid{ ID: "random-id", ImpID: imp.ID, Price: andata.tags[i].bid, @@ -449,7 +449,7 @@ func DummyAppNexusServer(w http.ResponseWriter, r *http.Request) { } if imp.Video != nil { - resBid.Attr = []openrtb.CreativeAttribute{openrtb.CreativeAttribute(6)} + resBid.Attr = []openrtb2.CreativeAttribute{openrtb2.CreativeAttribute(6)} } resp.SeatBid[0].Bid = append(resp.SeatBid[0].Bid, resBid) } @@ -566,7 +566,7 @@ func TestAppNexusLegacyBasicResponse(t *testing.T) { pbin.AdUnits[i] = pbs.AdUnit{ Code: tag.code, MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 600, diff --git a/adapters/appnexus/params_test.go b/adapters/appnexus/params_test.go index c30f5cf3e2a..f84cccc9a4c 100644 --- a/adapters/appnexus/params_test.go +++ b/adapters/appnexus/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/appnexus.json diff --git a/adapters/appnexus/usersync.go b/adapters/appnexus/usersync.go index 16ffdfa3338..d29f0e3cb6b 100644 --- a/adapters/appnexus/usersync.go +++ b/adapters/appnexus/usersync.go @@ -3,10 +3,10 @@ package appnexus import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAppnexusSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("adnxs", 32, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("adnxs", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/appnexus/usersync_test.go b/adapters/appnexus/usersync_test.go index 6796ce13b96..d01e5704e28 100644 --- a/adapters/appnexus/usersync_test.go +++ b/adapters/appnexus/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestAppNexusSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ib.adnxs.com/getuid?https%3A%2F%2Fprebid.adnxs.com%2Fpbs%2Fv1%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 32, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json index 8c97f3e9098..c857ba2c83f 100644 --- a/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json +++ b/adapters/audienceNetwork/audienceNetworktest/supplemental/no-bid-204.json @@ -83,4 +83,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go index a74c51d9ecb..5cbdbc90561 100644 --- a/adapters/audienceNetwork/facebook.go +++ b/adapters/audienceNetwork/facebook.go @@ -10,19 +10,18 @@ import ( "net/http" "strings" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/util/maputil" - - "github.com/PubMatic-OpenWrap/openrtb" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/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" ) -var supportedBannerHeights = map[uint64]bool{ - 50: true, - 250: true, +var supportedBannerHeights = map[int64]struct{}{ + 50: {}, + 250: {}, } type FacebookAdapter struct { @@ -40,7 +39,7 @@ type facebookReqExt struct { AuthID string `json:"authentication_id"` } -func (this *FacebookAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (this *FacebookAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ Message: "No impressions provided", @@ -62,7 +61,7 @@ func (this *FacebookAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * return this.buildRequests(request) } -func (this *FacebookAdapter) buildRequests(request *openrtb.BidRequest) ([]*adapters.RequestData, []error) { +func (this *FacebookAdapter) buildRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { // Documentation suggests bid request splitting by impression so that each // request only represents a single impression reqs := make([]*adapters.RequestData, 0, len(request.Imp)) @@ -77,7 +76,7 @@ func (this *FacebookAdapter) buildRequests(request *openrtb.BidRequest) ([]*adap // Make a copy of the request so that we don't change the original request which // is shared across multiple threads fbreq := *request - fbreq.Imp = []openrtb.Imp{imp} + fbreq.Imp = []openrtb2.Imp{imp} if err := this.modifyRequest(&fbreq); err != nil { errs = append(errs, err) @@ -109,14 +108,14 @@ func (this *FacebookAdapter) buildRequests(request *openrtb.BidRequest) ([]*adap // The authentication ID is a sha256 hmac hash encoded as a hex string, based on // the app secret and the ID of the bid request -func (this *FacebookAdapter) makeAuthID(req *openrtb.BidRequest) string { +func (this *FacebookAdapter) makeAuthID(req *openrtb2.BidRequest) string { h := hmac.New(sha256.New, []byte(this.appSecret)) h.Write([]byte(req.ID)) return hex.EncodeToString(h.Sum(nil)) } -func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { +func (this *FacebookAdapter) modifyRequest(out *openrtb2.BidRequest) error { if len(out.Imp) != 1 { panic("each bid request to facebook should only have a single impression") } @@ -146,7 +145,7 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { if out.App != nil { app := *out.App - app.Publisher = &openrtb.Publisher{ID: pubId} + app.Publisher = &openrtb2.Publisher{ID: pubId} out.App = &app } @@ -157,13 +156,8 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb.BidRequest) error { return nil } -func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { - impType, ok := resolveImpType(out) - if !ok { - return &errortypes.BadInput{ - Message: fmt.Sprintf("imp #%s with invalid type", out.ID), - } - } +func (this *FacebookAdapter) modifyImp(out *openrtb2.Imp) error { + impType := resolveImpType(out) if out.Instl == 1 && impType != openrtb_ext.BidTypeBanner { return &errortypes.BadInput{ @@ -176,10 +170,9 @@ func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { out.Banner = &bannerCopy if out.Instl == 1 { - out.Banner.W = openrtb.Uint64Ptr(0) - out.Banner.H = openrtb.Uint64Ptr(0) + out.Banner.W = openrtb2.Int64Ptr(0) + out.Banner.H = openrtb2.Int64Ptr(0) out.Banner.Format = nil - return nil } @@ -204,15 +197,14 @@ func (this *FacebookAdapter) modifyImp(out *openrtb.Imp) error { } } - /* This will get overwritten post-serialization */ - out.Banner.W = openrtb.Uint64Ptr(0) + out.Banner.W = openrtb2.Int64Ptr(-1) out.Banner.Format = nil } return nil } -func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb.Imp) (string, string, error) { +func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb2.Imp) (string, string, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(out.Ext, &bidderExt); err != nil { return "", "", &errortypes.BadInput{ @@ -237,9 +229,9 @@ func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb.Imp) (str publisherID := fbExt.PublisherId // Support the legacy path with the caller was expected to pass in just placementId - // which was an underscore concantenated string with the publisherId and placementId. + // which was an underscore concatenated string with the publisherId and placementId. // The new path for callers is to pass in the placementId and publisherId independently - // and the below code will prefix the placementId that we pass to FAN with the publsiherId + // and the below code will prefix the placementId that we pass to FAN with the publisherId // so that we can abstract the implementation details from the caller toks := strings.Split(placementID, "_") if len(toks) == 1 { @@ -262,17 +254,17 @@ func (this *FacebookAdapter) extractPlacementAndPublisher(out *openrtb.Imp) (str return placementID, publisherID, nil } -// XXX: This entire function is just a hack to get around mxmCherry 11.0.0 limitations, without -// having to fork the library and maintain our own branch -func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { - impType, ok := resolveImpType(imp) - if ok == false { - panic("processing an invalid impression") +// modifyImpCustom modifies the impression after it's marshalled to get around mxmCherry 14.0.0 limitations. +func modifyImpCustom(jsonData []byte, imp *openrtb2.Imp) ([]byte, error) { + impType := resolveImpType(imp) + + // we only need to modify video and native impressions + if impType != openrtb_ext.BidTypeVideo && impType != openrtb_ext.BidTypeNative { + return jsonData, nil } var jsonMap map[string]interface{} - err := json.Unmarshal(jsonData, &jsonMap) - if err != nil { + if err := json.Unmarshal(jsonData, &jsonMap); err != nil { return jsonData, err } @@ -286,28 +278,16 @@ func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { } switch impType { - case openrtb_ext.BidTypeBanner: - // The current version of mxmCherry (11.0.0) represents banner.w as an unsigned - // integer, so setting a value of -1 is not possible which is why we have to do it - // post-serialization - isInterstitial := imp.Instl == 1 - if !isInterstitial { - if bannerMap, ok := maputil.ReadEmbeddedMap(impMap, "banner"); ok { - bannerMap["w"] = json.RawMessage("-1") - } else { - return jsonData, errors.New("unable to find imp[0].banner in json data") - } + case openrtb_ext.BidTypeVideo: + videoMap, ok := maputil.ReadEmbeddedMap(impMap, "video") + if !ok { + return jsonData, errors.New("unable to find imp[0].video in json data") } - case openrtb_ext.BidTypeVideo: // mxmCherry omits video.w/h if set to zero, so we need to force set those // fields to zero post-serialization for the time being - if videoMap, ok := maputil.ReadEmbeddedMap(impMap, "video"); ok { - videoMap["w"] = json.RawMessage("0") - videoMap["h"] = json.RawMessage("0") - } else { - return jsonData, errors.New("unable to find imp[0].video in json data") - } + videoMap["w"] = json.RawMessage("0") + videoMap["h"] = json.RawMessage("0") case openrtb_ext.BidTypeNative: nativeMap, ok := maputil.ReadEmbeddedMap(impMap, "native") @@ -316,8 +296,8 @@ func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { } // Set w/h to -1 for native impressions based on the facebook native spec. - // We have to set this post-serialization since the OpenRTB protocol doesn't - // actually support w/h in the native object + // We have to set this post-serialization since these fields are not included + // in the OpenRTB 2.5 spec. nativeMap["w"] = json.RawMessage("-1") nativeMap["h"] = json.RawMessage("-1") @@ -335,13 +315,11 @@ func modifyImpCustom(jsonData []byte, imp *openrtb.Imp) ([]byte, error) { } } -func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - /* No bid response */ +func (this *FacebookAdapter) MakeBids(request *openrtb2.BidRequest, adapterRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } - /* Any other http status codes outside of 200 and 204 should be treated as errors */ if response.StatusCode != http.StatusOK { msg := response.Headers.Get("x-fb-an-errors") return nil, []error{&errortypes.BadInput{ @@ -349,7 +327,7 @@ func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterReques }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -358,7 +336,9 @@ func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterReques var errs []error for _, seatbid := range bidResp.SeatBid { - for _, bid := range seatbid.Bid { + for i := range seatbid.Bid { + bid := seatbid.Bid[i] + if bid.AdM == "" { errs = append(errs, &errortypes.BadServerResponse{ Message: fmt.Sprintf("Bid %s missing 'adm'", bid.ID), @@ -394,38 +374,35 @@ func (this *FacebookAdapter) MakeBids(request *openrtb.BidRequest, adapterReques return out, errs } -func resolveBidType(bid *openrtb.Bid, req *openrtb.BidRequest) openrtb_ext.BidType { +func resolveBidType(bid *openrtb2.Bid, req *openrtb2.BidRequest) openrtb_ext.BidType { for _, imp := range req.Imp { if bid.ImpID == imp.ID { - if typ, ok := resolveImpType(&imp); ok { - return typ - } - - panic("Processing an invalid impression; cannot resolve impression type") + return resolveImpType(&imp) } } panic(fmt.Sprintf("Invalid bid imp ID %s does not match any imp IDs from the original bid request", bid.ImpID)) } -func resolveImpType(imp *openrtb.Imp) (openrtb_ext.BidType, bool) { +func resolveImpType(imp *openrtb2.Imp) openrtb_ext.BidType { if imp.Banner != nil { - return openrtb_ext.BidTypeBanner, true + return openrtb_ext.BidTypeBanner } if imp.Video != nil { - return openrtb_ext.BidTypeVideo, true + return openrtb_ext.BidTypeVideo } if imp.Audio != nil { - return openrtb_ext.BidTypeAudio, true + return openrtb_ext.BidTypeAudio } if imp.Native != nil { - return openrtb_ext.BidTypeNative, true + return openrtb_ext.BidTypeNative } - return openrtb_ext.BidTypeBanner, false + // Required to satisfy compiler. Not reachable in practice due to validations performed in PBS-Core. + return openrtb_ext.BidTypeBanner } // Builder builds a new instance of Facebook's Audience Network adapter for the given bidder with the given config. diff --git a/adapters/audienceNetwork/facebook_test.go b/adapters/audienceNetwork/facebook_test.go index 596529dbb9a..a2e17b71ca8 100644 --- a/adapters/audienceNetwork/facebook_test.go +++ b/adapters/audienceNetwork/facebook_test.go @@ -4,10 +4,10 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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/stretchr/testify/assert" ) diff --git a/adapters/audienceNetwork/usersync.go b/adapters/audienceNetwork/usersync.go index 6b8ba3ba7a3..4dd0b68ccff 100644 --- a/adapters/audienceNetwork/usersync.go +++ b/adapters/audienceNetwork/usersync.go @@ -3,10 +3,10 @@ package audienceNetwork import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewFacebookSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("audienceNetwork", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("audienceNetwork", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/audienceNetwork/usersync_test.go b/adapters/audienceNetwork/usersync_test.go index e11fb194dec..591ea74f7ba 100644 --- a/adapters/audienceNetwork/usersync_test.go +++ b/adapters/audienceNetwork/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestFacebookSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://www.facebook.com/audiencenetwork/idsync/?partner=partnerId&callback=localhost%2Fsetuid%3Fbidder%3DaudienceNetwork%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/avocet/avocet.go b/adapters/avocet/avocet.go index ef2314e7ebb..7ec4788e858 100644 --- a/adapters/avocet/avocet.go +++ b/adapters/avocet/avocet.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) // AvocetAdapter implements a adapters.Bidder compatible with the Avocet advertising platform. @@ -18,7 +18,7 @@ type AvocetAdapter struct { Endpoint string } -func (a *AvocetAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *AvocetAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, nil } @@ -50,7 +50,7 @@ type avocetBidExtension struct { DealPriority int `json:"deal_priority"` } -func (a *AvocetAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *AvocetAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -68,7 +68,7 @@ func (a *AvocetAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe }} } - var br openrtb.BidResponse + var br openrtb2.BidResponse err := json.Unmarshal(response.Body, &br) if err != nil { return nil, []error{&errortypes.BadServerResponse{ @@ -105,12 +105,12 @@ func (a *AvocetAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe } // getBidType returns the openrtb_ext.BidType for the provided bid. -func getBidType(bid openrtb.Bid, ext avocetBidExt) openrtb_ext.BidType { +func getBidType(bid openrtb2.Bid, ext avocetBidExt) openrtb_ext.BidType { if ext.Avocet.Duration != 0 { return openrtb_ext.BidTypeVideo } switch bid.API { - case openrtb.APIFrameworkVPAID10, openrtb.APIFrameworkVPAID20: + case openrtb2.APIFrameworkVPAID10, openrtb2.APIFrameworkVPAID20: return openrtb_ext.BidTypeVideo default: return openrtb_ext.BidTypeBanner diff --git a/adapters/avocet/avocet/exemplary/banner.json b/adapters/avocet/avocet/exemplary/banner.json deleted file mode 100644 index b5e308ea725..00000000000 --- a/adapters/avocet/avocet/exemplary/banner.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "placement": "5ea9601ac865f911007f1b6a" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "ext": { - "bidder": { - "placement": "5ea9601ac865f911007f1b6a" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "bidid": "dd87f80c-16a0-43c8-a673-b94b3ea4d417", - "id": "test-request-id", - "seatbid": [ - { - "bid": [ - { - "adm": "", - "adomain": ["avocet.io"], - "cid": "5b51e2d689654741306813a4", - "crid": "5b51e49634f2021f127ff7c9", - "h": 250, - "id": "bc708396-9202-437b-b726-08b9864cb8b8", - "impid": "test-imp-id", - "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", - "language": "en", - "price": 15.64434783, - "w": 300 - } - ], - "seat": "TEST_SEAT_ID" - } - ] - } - } - } - ], - - "expectedBids": [ - { - "bid": { - "adm": "", - "adomain": ["avocet.io"], - "cid": "5b51e2d689654741306813a4", - "crid": "5b51e49634f2021f127ff7c9", - "h": 250, - "id": "bc708396-9202-437b-b726-08b9864cb8b8", - "impid": "test-imp-id", - "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5b51e49634f2021f127ff7c9.jpeg", - "language": "en", - "price": 15.64434783, - "w": 300 - }, - "type": "banner" - } - ] -} diff --git a/adapters/avocet/avocet/exemplary/video.json b/adapters/avocet/avocet/exemplary/video.json deleted file mode 100644 index 2398256b0dd..00000000000 --- a/adapters/avocet/avocet/exemplary/video.json +++ /dev/null @@ -1,104 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1920, - "h": 1080 - }, - "ext": { - "bidder": { - "placement": "5ea9601ac865f911007f1b6a" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "https://bid.staging.avct.cloud/ortb/bid/5e722ee9bd6df11d063a8013", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1920, - "h": 1080 - }, - "ext": { - "bidder": { - "placement": "5ea9601ac865f911007f1b6a" - } - } - } - ] - } - }, - "mockResponse": { - "status": 200, - "body": { - "bidid": "a0eec3aa-f9f6-42fb-9aa4-f1b5656d4f42", - "id": "749d36d7-c993-455f-aefd-ffd8a7e3ccf", - "seatbid": [ - { - "bid": [ - { - "adm": "Avocet", - "adomain": ["avocet.io"], - "cid": "5b51e2d689654741306813a4", - "crid": "5ec530e32d57fe1100f17d87", - "h": 396, - "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", - "impid": "dfp-ad--top-above-nav", - "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", - "language": "en", - "price": 15.64434783, - "w": 600, - "ext": { - "avocet": { - "duration": 30 - } - } - } - ], - "seat": "TEST_SEAT_ID" - } - ] - } - } - } - ], - - "expectedBids": [ - { - "bid": { - "adm": "Avocet", - "adomain": ["avocet.io"], - "cid": "5b51e2d689654741306813a4", - "crid": "5ec530e32d57fe1100f17d87", - "h": 396, - "id": "3d4c2d45-5a8c-43b8-9e15-4f48ac45204f", - "impid": "dfp-ad--top-above-nav", - "iurl": "https://cdn.staging.avocet.io/snapshots/5b51dd1634f2021f127ff7c0/5ec530e32d57fe1100f17d87.jpeg", - "language": "en", - "price": 15.64434783, - "w": 600, - "ext": { - "avocet": { - "duration": 30 - } - } - }, - "type": "video" - } - ] -} diff --git a/adapters/avocet/avocet_test.go b/adapters/avocet/avocet_test.go index f669e34492f..8dc3c333147 100644 --- a/adapters/avocet/avocet_test.go +++ b/adapters/avocet/avocet_test.go @@ -6,12 +6,12 @@ import ( "reflect" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) func TestJsonSamples(t *testing.T) { @@ -30,7 +30,7 @@ func TestAvocetAdapter_MakeRequests(t *testing.T) { Endpoint string } type args struct { - request *openrtb.BidRequest + request *openrtb2.BidRequest reqInfo *adapters.ExtraRequestInfo } type reqData []*adapters.RequestData @@ -45,7 +45,7 @@ func TestAvocetAdapter_MakeRequests(t *testing.T) { name: "return nil if zero imps", fields: fields{Endpoint: "https://bid.avct.cloud"}, args: args{ - &openrtb.BidRequest{}, + &openrtb2.BidRequest{}, nil, }, want: nil, @@ -55,7 +55,7 @@ func TestAvocetAdapter_MakeRequests(t *testing.T) { name: "makes POST request with JSON content", fields: fields{Endpoint: "https://bid.avct.cloud"}, args: args{ - &openrtb.BidRequest{Imp: []openrtb.Imp{{}}}, + &openrtb2.BidRequest{Imp: []openrtb2.Imp{{}}}, nil, }, want: reqData{ @@ -100,7 +100,7 @@ func TestAvocetAdapter_MakeBids(t *testing.T) { Endpoint string } type args struct { - internalRequest *openrtb.BidRequest + internalRequest *openrtb2.BidRequest externalRequest *adapters.RequestData response *adapters.ResponseData } @@ -195,7 +195,7 @@ func TestAvocetAdapter_MakeBids(t *testing.T) { func Test_getBidType(t *testing.T) { type args struct { - bid openrtb.Bid + bid openrtb2.Bid ext avocetBidExt } tests := []struct { @@ -205,17 +205,17 @@ func Test_getBidType(t *testing.T) { }{ { name: "VPAID 1.0", - args: args{openrtb.Bid{API: openrtb.APIFrameworkVPAID10}, avocetBidExt{}}, + args: args{openrtb2.Bid{API: openrtb2.APIFrameworkVPAID10}, avocetBidExt{}}, want: openrtb_ext.BidTypeVideo, }, { name: "VPAID 2.0", - args: args{openrtb.Bid{API: openrtb.APIFrameworkVPAID20}, avocetBidExt{}}, + args: args{openrtb2.Bid{API: openrtb2.APIFrameworkVPAID20}, avocetBidExt{}}, want: openrtb_ext.BidTypeVideo, }, { name: "other", - args: args{openrtb.Bid{}, avocetBidExt{}}, + args: args{openrtb2.Bid{}, avocetBidExt{}}, want: openrtb_ext.BidTypeBanner, }, } @@ -228,7 +228,7 @@ func Test_getBidType(t *testing.T) { } } -var validBannerBid = openrtb.Bid{ +var validBannerBid = openrtb2.Bid{ AdM: "", ADomain: []string{"avocet.io"}, CID: "5b51e2d689654741306813a4", @@ -267,7 +267,7 @@ var validBannerBidResponseBody = []byte(`{ ] }`) -var validVideoBid = openrtb.Bid{ +var validVideoBid = openrtb2.Bid{ AdM: "Avocet", ADomain: []string{"avocet.io"}, CID: "5b51e2d689654741306813a4", diff --git a/adapters/avocet/usersync.go b/adapters/avocet/usersync.go index ec4f25dd952..0cfa055ae86 100644 --- a/adapters/avocet/usersync.go +++ b/adapters/avocet/usersync.go @@ -3,10 +3,10 @@ package avocet import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewAvocetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("avocet", 63, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("avocet", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/avocet/usersync_test.go b/adapters/avocet/usersync_test.go index 12b7901cc90..bd4cd4145a2 100644 --- a/adapters/avocet/usersync_test.go +++ b/adapters/avocet/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestAvocetSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ads.avct.cloud/getuid?&gdpr=1&gdpr_consent=ConsentString&us_privacy=PrivacyString&url=%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D1%26gdpr_consent%3DConsentString%26uid%3D%7B%7BUUID%7D%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 63, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/beachfront/beachfront.go b/adapters/beachfront/beachfront.go index 8aca4ca0427..6bcabf4a39c 100644 --- a/adapters/beachfront/beachfront.go +++ b/adapters/beachfront/beachfront.go @@ -5,15 +5,14 @@ import ( "errors" "fmt" "net/http" - "reflect" "strconv" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const Seat = "beachfront" @@ -24,12 +23,13 @@ const defaultVideoEndpoint = "https://reachms.bfmio.com/bid.json?exchange_id" const nurlVideoEndpointSuffix = "&prebidserver" const beachfrontAdapterName = "BF_PREBID_S2S" -const beachfrontAdapterVersion = "0.9.1" +const beachfrontAdapterVersion = "0.9.2" const minBidFloor = 0.01 -const DefaultVideoWidth = 300 -const DefaultVideoHeight = 250 +const defaultVideoWidth = 300 +const defaultVideoHeight = 250 +const fakeIP = "255.255.255.255" type BeachfrontAdapter struct { bannerEndpoint string @@ -51,9 +51,9 @@ type beachfrontRequests struct { // --------------------------------------------------- type beachfrontVideoRequest struct { - AppId string `json:"appId"` - VideoResponseType string `json:"videoResponseType"` - Request openrtb.BidRequest `json:"request"` + AppId string `json:"appId"` + VideoResponseType string `json:"videoResponseType"` + Request openrtb2.BidRequest `json:"request"` } // --------------------------------------------------- @@ -71,11 +71,12 @@ type beachfrontBannerRequest struct { IsMobile int8 `json:"isMobile"` UA string `json:"ua"` Dnt int8 `json:"dnt"` - User openrtb.User `json:"user"` + User openrtb2.User `json:"user"` AdapterName string `json:"adapterName"` AdapterVersion string `json:"adapterVersion"` IP string `json:"ip"` RequestID string `json:"requestId"` + Real204 bool `json:"real204"` } type beachfrontSlot struct { @@ -107,7 +108,7 @@ type beachfrontVideoBidExtension struct { Duration int `json:"duration"` } -func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *BeachfrontAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { beachfrontRequests, errs := preprocess(request) headers := http.Header{} @@ -197,9 +198,9 @@ func (a *BeachfrontAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a return reqs, errs } -func preprocess(request *openrtb.BidRequest) (beachfrontReqs beachfrontRequests, errs []error) { - var videoImps = make([]openrtb.Imp, 0) - var bannerImps = make([]openrtb.Imp, 0) +func preprocess(request *openrtb2.BidRequest) (beachfrontReqs beachfrontRequests, errs []error) { + var videoImps = make([]openrtb2.Imp, 0) + var bannerImps = make([]openrtb2.Imp, 0) for i := 0; i < len(request.Imp); i++ { if request.Imp[i].Banner != nil && request.Imp[i].Banner.Format != nil && @@ -249,16 +250,12 @@ func getAppId(ext openrtb_ext.ExtImpBeachfront, media openrtb_ext.BidType) (stri var appid string var error error - if fmt.Sprintf("%s", reflect.TypeOf(ext.AppId)) == "string" && - ext.AppId != "" { - + if ext.AppId != "" { appid = ext.AppId - } else if fmt.Sprintf("%s", reflect.TypeOf(ext.AppIds)) == "openrtb_ext.ExtImpBeachfrontAppIds" { - if media == openrtb_ext.BidTypeVideo && ext.AppIds.Video != "" { - appid = ext.AppIds.Video - } else if media == openrtb_ext.BidTypeBanner && ext.AppIds.Banner != "" { - appid = ext.AppIds.Banner - } + } else if media == openrtb_ext.BidTypeVideo && ext.AppIds.Video != "" { + appid = ext.AppIds.Video + } else if media == openrtb_ext.BidTypeBanner && ext.AppIds.Banner != "" { + appid = ext.AppIds.Banner } else { error = errors.New("unable to determine the appId(s) from the supplied extension") } @@ -270,7 +267,7 @@ func getAppId(ext openrtb_ext.ExtImpBeachfront, media openrtb_ext.BidType) (stri getBannerRequest, singular. A "Slot" is an "imp," and each Slot can have an AppId, so just one request to the beachfront banner endpoint gets all banner Imps. */ -func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []error) { +func getBannerRequest(request *openrtb2.BidRequest) (beachfrontBannerRequest, []error) { var bfr beachfrontBannerRequest var errs = make([]error, 0, len(request.Imp)) @@ -304,8 +301,8 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e for j := 0; j < len(request.Imp[i].Banner.Format); j++ { slot.Sizes = append(slot.Sizes, beachfrontSize{ - H: request.Imp[i].Banner.Format[j].H, - W: request.Imp[i].Banner.Format[j].W, + H: uint64(request.Imp[i].Banner.Format[j].H), + W: uint64(request.Imp[i].Banner.Format[j].W), }) } @@ -317,7 +314,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e } if request.Device != nil { - bfr.IP = getIP(request.Device.IP) + bfr.IP = request.Device.IP bfr.DeviceModel = request.Device.Model bfr.DeviceOs = request.Device.OS if request.Device.DNT != nil { @@ -330,7 +327,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e var t = fallBackDeviceType(request) - if t == openrtb.DeviceTypeMobileTablet { + if t == openrtb2.DeviceTypeMobileTablet { bfr.Page = request.App.Bundle if request.App.Domain == "" { bfr.Domain = getDomain(request.App.Domain) @@ -339,7 +336,7 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e } bfr.IsMobile = 1 - } else if t == openrtb.DeviceTypePersonalComputer { + } else if t == openrtb2.DeviceTypePersonalComputer { bfr.Page = request.Site.Page if request.Site.Domain == "" { bfr.Domain = getDomain(request.Site.Page) @@ -371,19 +368,20 @@ func getBannerRequest(request *openrtb.BidRequest) (beachfrontBannerRequest, []e if request.Imp[0].Secure != nil { bfr.Secure = *request.Imp[0].Secure } + bfr.Real204 = true return bfr, errs } -func fallBackDeviceType(request *openrtb.BidRequest) openrtb.DeviceType { +func fallBackDeviceType(request *openrtb2.BidRequest) openrtb2.DeviceType { if request.Site != nil { - return openrtb.DeviceTypePersonalComputer + return openrtb2.DeviceTypePersonalComputer } - return openrtb.DeviceTypeMobileTablet + return openrtb2.DeviceTypeMobileTablet } -func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, []error) { +func getVideoRequests(request *openrtb2.BidRequest) ([]beachfrontVideoRequest, []error) { var bfReqs = make([]beachfrontVideoRequest, len(request.Imp)) var errs = make([]error, 0, len(request.Imp)) var failedRequestIndicies = make([]int, 0) @@ -399,6 +397,7 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } appid, err := getAppId(beachfrontExt, openrtb_ext.BidTypeVideo) + bfReqs[i].AppId = appid if err != nil { // Failed to get an appid, so this request is junk. @@ -407,20 +406,30 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] continue } - bfReqs[i].AppId = appid + bfReqs[i].Request = *request + var secure int8 - if beachfrontExt.VideoResponseType != "" { - bfReqs[i].VideoResponseType = beachfrontExt.VideoResponseType + var deviceCopy openrtb2.Device + if bfReqs[i].Request.Device == nil { + deviceCopy = openrtb2.Device{} } else { - bfReqs[i].VideoResponseType = "adm" + deviceCopy = *bfReqs[i].Request.Device } - bfReqs[i].Request = *request - var secure int8 + if beachfrontExt.VideoResponseType == "nurl" { + bfReqs[i].VideoResponseType = "nurl" + } else { + bfReqs[i].VideoResponseType = "adm" - if bfReqs[i].Request.Site != nil && bfReqs[i].Request.Site.Domain == "" && bfReqs[i].Request.Site.Page != "" { - bfReqs[i].Request.Site.Domain = getDomain(bfReqs[i].Request.Site.Page) + if deviceCopy.IP == "" { + deviceCopy.IP = fakeIP + } + } + if bfReqs[i].Request.Site != nil && bfReqs[i].Request.Site.Domain == "" && bfReqs[i].Request.Site.Page != "" { + siteCopy := *bfReqs[i].Request.Site + siteCopy.Domain = getDomain(bfReqs[i].Request.Site.Page) + bfReqs[i].Request.Site = &siteCopy secure = isSecure(bfReqs[i].Request.Site.Page) } @@ -433,13 +442,13 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] fmt.Sprintf("%s.%s", chunks[len(chunks)-(len(chunks)-1)], chunks[0]) } } - } - if bfReqs[i].Request.Device != nil && bfReqs[i].Request.Device.DeviceType == 0 { + if deviceCopy.DeviceType == 0 { // More fine graned deviceType methods will be added in the future - bfReqs[i].Request.Device.DeviceType = fallBackDeviceType(request) + deviceCopy.DeviceType = fallBackDeviceType(request) } + bfReqs[i].Request.Device = &deviceCopy imp := request.Imp[i] @@ -454,8 +463,8 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } if imp.Video.H == 0 && imp.Video.W == 0 { - imp.Video.W = DefaultVideoWidth - imp.Video.H = DefaultVideoHeight + imp.Video.W = defaultVideoWidth + imp.Video.H = defaultVideoHeight } if len(bfReqs[i].Request.Cur) == 0 { @@ -464,12 +473,9 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] } bfReqs[i].Request.Imp = nil - bfReqs[i].Request.Imp = make([]openrtb.Imp, 1, 1) + bfReqs[i].Request.Imp = make([]openrtb2.Imp, 1) bfReqs[i].Request.Imp[0] = imp - if bfReqs[i].Request.Device != nil && bfReqs[i].Request.Device.IP != "" { - bfReqs[i].Request.Device.IP = getIP(bfReqs[i].Request.Device.IP) - } } // Strip out any failed requests @@ -482,17 +488,20 @@ func getVideoRequests(request *openrtb.BidRequest) ([]beachfrontVideoRequest, [] return bfReqs, errs } -func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { - var bids []openrtb.Bid - var errs []error - - if response.StatusCode == http.StatusNoContent || (response.StatusCode == http.StatusOK && len(response.Body) <= 2) { +func (a *BeachfrontAdapter) 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 { + if response.StatusCode >= http.StatusInternalServerError { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("server error status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), + }} + } + + if response.StatusCode >= http.StatusBadRequest { return nil, []error{&errortypes.BadInput{ - Message: fmt.Sprintf("bad request status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), + Message: fmt.Sprintf("request error status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri), }} } @@ -500,7 +509,9 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return nil, []error{fmt.Errorf("unexpected status code %d from %s. Run with request.debug = 1 for more info", response.StatusCode, externalRequest.Uri)} } - var xtrnal openrtb.BidRequest + var bids []openrtb2.Bid + var errs = make([]error, 0) + var xtrnal openrtb2.BidRequest // For video, which uses RTB for the external request, this will unmarshal as expected. For banner, it will // only get the User struct and everything else will be nil @@ -516,6 +527,7 @@ func (a *BeachfrontAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern var dur beachfrontVideoBidExtension bidResponse := adapters.NewBidderResponseWithBidsCapacity(BidCapacity) + for i := 0; i < len(bids); i++ { // If we unmarshal without an error, this is an AdM video @@ -548,20 +560,17 @@ func (a *BeachfrontAdapter) getBidType(externalRequest *adapters.RequestData) op return openrtb_ext.BidTypeBanner } -func postprocess(response *adapters.ResponseData, xtrnal openrtb.BidRequest, uri string, id string) ([]openrtb.Bid, []error) { +func postprocess(response *adapters.ResponseData, xtrnal openrtb2.BidRequest, uri string, id string) ([]openrtb2.Bid, []error) { var beachfrontResp []beachfrontResponseSlot - var errs = make([]error, 0) - var openrtbResp openrtb.BidResponse + var openrtbResp openrtb2.BidResponse - // try it as a video - if err := json.Unmarshal(response.Body, &openrtbResp); err != nil { - errs = append(errs, err) + if err := json.Unmarshal(response.Body, &openrtbResp); err != nil || len(openrtbResp.SeatBid) == 0 { - // try it as a banner if err := json.Unmarshal(response.Body, &beachfrontResp); err != nil { - errs = append(errs, err) - return nil, errs + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprint("server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info"), + }} } else { return postprocessBanner(beachfrontResp, id) } @@ -570,27 +579,27 @@ func postprocess(response *adapters.ResponseData, xtrnal openrtb.BidRequest, uri return postprocessVideo(openrtbResp.SeatBid[0].Bid, xtrnal, uri, id) } -func postprocessBanner(beachfrontResp []beachfrontResponseSlot, id string) ([]openrtb.Bid, []error) { +func postprocessBanner(beachfrontResp []beachfrontResponseSlot, id string) ([]openrtb2.Bid, []error) { - var bids = make([]openrtb.Bid, len(beachfrontResp)) + var bids = make([]openrtb2.Bid, len(beachfrontResp)) var errs = make([]error, 0) for i := 0; i < len(beachfrontResp); i++ { - bids[i] = openrtb.Bid{ + bids[i] = openrtb2.Bid{ CrID: beachfrontResp[i].CrID, ImpID: beachfrontResp[i].Slot, Price: beachfrontResp[i].Price, ID: fmt.Sprintf("%sBanner", beachfrontResp[i].Slot), AdM: beachfrontResp[i].Adm, - H: beachfrontResp[i].H, - W: beachfrontResp[i].W, + H: int64(beachfrontResp[i].H), + W: int64(beachfrontResp[i].W), } } return bids, errs } -func postprocessVideo(bids []openrtb.Bid, xtrnal openrtb.BidRequest, uri string, id string) ([]openrtb.Bid, []error) { +func postprocessVideo(bids []openrtb2.Bid, xtrnal openrtb2.BidRequest, uri string, id string) ([]openrtb2.Bid, []error) { var errs = make([]error, 0) @@ -624,7 +633,7 @@ func extractNurlVideoCrid(nurl string) string { return "" } -func getBeachfrontExtension(imp openrtb.Imp) (openrtb_ext.ExtImpBeachfront, error) { +func getBeachfrontExtension(imp openrtb2.Imp) (openrtb_ext.ExtImpBeachfront, error) { var err error var bidderExt adapters.ExtImpBidder var beachfrontExt openrtb_ext.ExtImpBeachfront @@ -669,18 +678,12 @@ func isSecure(page string) int8 { } -func getIP(ip string) string { - // This will only effect testing. The backend will return "" for localhost IPs, - // and seems not to know what IPv6 is, so just setting it to one that is not likely to - // be used. - if ip == "" || ip == "::1" || ip == "127.0.0.1" { - return "192.168.255.255" +func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideoRequest { + if len(slice) >= s+1 { + return append(slice[:s], slice[s+1:]...) } - return ip -} -func removeVideoElement(slice []beachfrontVideoRequest, s int) []beachfrontVideoRequest { - return append(slice[:s], slice[s+1:]...) + return []beachfrontVideoRequest{} } // Builder builds a new instance of the Beachfront adapter for the given bidder with the given config. diff --git a/adapters/beachfront/beachfront_test.go b/adapters/beachfront/beachfront_test.go index aace3224534..1c1c4ff4469 100644 --- a/adapters/beachfront/beachfront_test.go +++ b/adapters/beachfront/beachfront_test.go @@ -3,9 +3,9 @@ package beachfront import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video.json b/adapters/beachfront/beachfronttest/exemplary/adm-video.json new file mode 100644 index 00000000000..edde1301dcc --- /dev/null +++ b/adapters/beachfront/beachfronttest/exemplary/adm-video.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"192.168.168.168" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"192.168.168.168" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "adm-video", + "seatBid": [ + { + "bid": [ + { + "id": "5fd7c8a6ff2f1f0d42ee6427", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + } + ], + "seat": "bfio-s-1" + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "video1AdmVideo", + "impid": "video1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250, + "ext": { + "duration": 30 + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/exemplary/banner.json b/adapters/beachfront/beachfronttest/exemplary/banner.json index 1eb17286e9c..1a8e830521d 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner.json +++ b/adapters/beachfront/beachfronttest/exemplary/banner.json @@ -45,6 +45,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, @@ -56,7 +57,7 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "user": {} } }, diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json similarity index 100% rename from adapters/beachfront/beachfronttest/exemplary/adm-video-by-explicit-type.json rename to adapters/beachfront/beachfronttest/supplemental/adm-video-by-explicit.json diff --git a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json similarity index 98% rename from adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json rename to adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json index d3fa41a23c5..842e0f3777e 100644 --- a/adapters/beachfront/beachfronttest/exemplary/adm-video-by-default.json +++ b/adapters/beachfront/beachfronttest/supplemental/adm-video-no-ip.json @@ -22,9 +22,6 @@ ], "site": { "page": "https://some.domain.us/some/page.html" - }, - "device":{ - "ip":"255.255.255.255" } }, diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json b/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json new file mode 100644 index 00000000000..edba490e814 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-204-with-body.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "id": "dudImp", + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.02, + "appId": "dudAppId1" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "dudImp", + "id": "dudAppId1", + "bidfloor": 0.02, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "real204": true, + "referrer": "", + "search": "", + "secure": 1, + "requestId": "some_test_ad", + "isMobile": 0, + "ip": "", + "deviceModel": "", + "deviceOs": "", + "dnt": 0, + "ua": "", + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "user": { + } + } + }, + "mockResponse": { + "status": 204, + "body": [ + { + "something": "where nothing should be" + } + ] + } + } + ], + + "expectedBidResponses": [] +} + diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json b/adapters/beachfront/beachfronttest/supplemental/banner-204.json similarity index 90% rename from adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json rename to adapters/beachfront/beachfronttest/supplemental/banner-204.json index c90c1215f9e..69aa0b882a9 100644 --- a/adapters/beachfront/beachfronttest/supplemental/banner-empty_array-200.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-204.json @@ -46,6 +46,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, @@ -57,17 +58,19 @@ "dnt": 0, "ua": "", "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "user": { } } }, "mockResponse": { - "status": 200, - "body": [] + "status": 204, + "body": "" } } ], - "expectedBidResponses": [] + "expectedBidResponses": [ + + ] } diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json similarity index 94% rename from adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json rename to adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json index 3482f4ef81e..b060efe6f03 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-explicit-type.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-by-explicit.json @@ -34,6 +34,9 @@ ], "site": { "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" } }, @@ -57,6 +60,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, @@ -64,11 +68,11 @@ "deviceModel": "", "isMobile": 0, "ua": "", - "ip": "", + "ip": "255.255.255.255", "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "requestId": "banner-and-video" } }, @@ -109,6 +113,10 @@ "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html" }, + "device": { + "devicetype": 2, + "ip": "255.255.255.255" + }, "cur": [ "USD" ] diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json new file mode 100644 index 00000000000..240994bb370 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video-expected-204-response-on-banner.json @@ -0,0 +1,171 @@ +{ + "mockBidRequest": { + "id": "banner-and-video", + "imp": [ + { + "id": "mix1", + "ext": { + "bidder": { + "bidfloor": 0.41, + "appIds": { + "banner": "bannerAppId1", + "video": "videoAppId1" + } + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "mix1", + "id": "bannerAppId1", + "bidfloor": 0.41, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "real204": true, + "referrer": "", + "search": "", + "secure": 1, + "deviceOs": "", + "deviceModel": "", + "isMobile": 0, + "ua": "", + "ip": "255.255.255.255", + "dnt": 0, + "user": {}, + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "requestId": "banner-and-video" + } + }, + "mockResponse": { + "status": 204, + "body": "" + } + }, + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "banner-and-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 0.41, + "secure": 1, + "id": "mix1" + } + ], + "site": { + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html" + }, + "device": { + "devicetype": 2, + "ip": "255.255.255.255" + }, + "cur": [ + "USD" + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "61b87329-8790-47b7-90dd-c53ae7ce1723", + "seatbid": [ + { + "bid": [ + { + "id": "5d839458f73decdc1572b7f6", + "impid": "mix1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250 + } + ], + "seat": "bfb-io-s1" + } + ], + "bidid": "5d839458f73decdc1572b7f6", + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + {"bids":[]}, + { + "bids": [ + { + "bid": { + "id": "mix1AdmVideo", + "impid": "mix1", + "price": 20, + "adm": "http://example.com/vast.xml", + "adid": "1088", + "adomain": [ + "beachfront.io" + ], + "cid": "277", + "crid": "532", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json similarity index 94% rename from adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json rename to adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json index bff1b76a688..18e8ca7a1bf 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner-and-adm-video-by-default.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-adm-video.json @@ -33,6 +33,9 @@ ], "site": { "page": "https://some.domain.us/some/page.html" + }, + "device": { + "ip": "255.255.255.255" } }, @@ -56,6 +59,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, @@ -63,11 +67,11 @@ "deviceModel": "", "isMobile": 0, "ua": "", - "ip": "", + "ip": "255.255.255.255", "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "requestId": "banner-and-video" } }, @@ -108,6 +112,10 @@ "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html" }, + "device": { + "devicetype": 2, + "ip": "255.255.255.255" + }, "cur": [ "USD" ] diff --git a/adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json similarity index 97% rename from adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json rename to adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json index 9db714f41a1..2ef8ca585e1 100644 --- a/adapters/beachfront/beachfronttest/exemplary/banner-and-nurl-video.json +++ b/adapters/beachfront/beachfronttest/supplemental/banner-and-nurl-video.json @@ -57,6 +57,7 @@ ], "domain": "some.domain.us", "page": "https://some.domain.us/some/page.html", + "real204": true, "referrer": "", "search": "", "secure": 1, @@ -68,7 +69,7 @@ "dnt": 0, "user": {}, "adapterName": "BF_PREBID_S2S", - "adapterVersion": "0.9.1", + "adapterVersion": "0.9.2", "requestId": "banner-and-video" } }, @@ -110,6 +111,9 @@ "page": "https://some.domain.us/some/page.html" }, "isPrebid": true, + "device": { + "devicetype": 2 + }, "cur": [ "USD" ] diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json new file mode 100644 index 00000000000..35926b3c943 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-bad-request-400.json @@ -0,0 +1,77 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 5.02, + "appId": "dudAppId" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "", + "id": "dudAppId", + "bidfloor": 5.02, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "real204": true, + "referrer": "", + "search": "", + "secure": 1, + "requestId": "some_test_ad", + "isMobile": 0, + "ip": "", + "deviceModel": "", + "deviceOs": "", + "dnt": 0, + "ua": "", + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "user": {} + } + }, + "mockResponse": { + "status": 400, + "body": "" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "request error status code 400 from https://qa.beachrtb.com/prebid_display. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json b/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json new file mode 100644 index 00000000000..a53dd8ce1fc --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-no-appid.json @@ -0,0 +1,34 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.02, + "appId": "" + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "unable to determine the appId(s) from the supplied extension", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json b/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json new file mode 100644 index 00000000000..3b582648bd5 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/banner-wrong-appids.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.02, + "appIds": { + "video": "theWrongAppId" + } + } + } + } + ] + }, + + "expectedMakeRequestsErrors": [ + { + "value": "unable to determine the appId(s) from the supplied extension", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json new file mode 100644 index 00000000000..51c676f5076 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_adm_video.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"255.255.255.255" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "wrong": "very very wrong.", + "alsoWrong": "not even close to right." + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json new file mode 100644 index 00000000000..e7869d36d6d --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_banner.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "bidfloor": 0.02, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.02, + "appId": "bannerAppId1" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "", + "id": "bannerAppId1", + "bidfloor": 0.02, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "real204": true, + "referrer": "", + "search": "", + "secure": 1, + "requestId": "some_test_ad", + "isMobile": 0, + "ip": "", + "deviceModel": "", + "deviceOs": "", + "dnt": 0, + "ua": "", + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "user": {} + } + }, + "mockResponse": { + "status": 200, + "body": { + "wrong": "very very wrong.", + "alsoWrong": "not even close to right." + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json new file mode 100644 index 00000000000..7990b0a1bb1 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/bidder_response_unmarshal_error_nurl_video.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "adm-video", + "imp": [ + { + "id": "video1", + "ext": { + "bidder": { + "bidfloor": 3.01, + "appId": "videoAppId1", + "videoResponseType": "nurl" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "context": "instream", + "w": 300, + "h": 250 + } + } + ], + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "device":{ + "ip":"255.255.255.255" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/bid.json?exchange_id=videoAppId1&prebidserver", + "body": { + "id": "adm-video", + "imp": [ + { + "video": { + "w": 300, + "h": 250, + "mimes": [ + "video/mp4" + ] + }, + "bidfloor": 3.01, + "id": "video1", + "secure": 1 + } + ], + "isPrebid": true, + "site": { + "page": "https://some.domain.us/some/page.html", + "domain": "some.domain.us" + }, + "cur": [ + "USD" + ], + "device":{ + "devicetype": 2, + "ip":"255.255.255.255" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "wrong": "very very wrong.", + "alsoWrong": "not even close to right." + } + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "server response failed to unmarshal as valid rtb. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json new file mode 100644 index 00000000000..7ebe6cf4aa0 --- /dev/null +++ b/adapters/beachfront/beachfronttest/supplemental/bidfloor-below-min.json @@ -0,0 +1,97 @@ +{ + "mockBidRequest": { + "id": "some_test_ad", + "site": { + "page": "https://some.domain.us/some/page.html" + }, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "bidfloor": 0.002, + "appId": "bannerAppId1" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://qa.beachrtb.com/prebid_display", + "body": { + "slots": [ + { + "slot": "", + "id": "bannerAppId1", + "bidfloor": 0, + "sizes": [ + { + "w": 300, + "h": 250 + } + ] + } + ], + "domain": "some.domain.us", + "page": "https://some.domain.us/some/page.html", + "real204": true, + "referrer": "", + "search": "", + "secure": 1, + "requestId": "some_test_ad", + "isMobile": 0, + "ip": "", + "deviceModel": "", + "deviceOs": "", + "dnt": 0, + "ua": "", + "adapterName": "BF_PREBID_S2S", + "adapterVersion": "0.9.2", + "user": {} + } + }, + "mockResponse": { + "status": 200, + "body": [ + { + "crid": "crid_1", + "price": 2.942808, + "w": 300, + "h": 250, + "slot": "div-gpt-ad-1460505748561-0", + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "ttl": 300, + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + }, + "type": "video" + }] + } + ] +} + diff --git a/adapters/jixie/jixietest/params/race/banner.json b/adapters/jixie/jixietest/params/race/banner.json new file mode 100644 index 00000000000..a0523d34c3f --- /dev/null +++ b/adapters/jixie/jixietest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "unit": "1000008-AbCdEf123400", + "bidfloor": "0.02" +} diff --git a/adapters/jixie/jixietest/params/race/video.json b/adapters/jixie/jixietest/params/race/video.json new file mode 100644 index 00000000000..b6d11e8fc4e --- /dev/null +++ b/adapters/jixie/jixietest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "unit": "1000008-AbCdEf2345", + "bidfloor": "0.03" +} diff --git a/adapters/jixie/jixietest/supplemental/add-accountid.json b/adapters/jixie/jixietest/supplemental/add-accountid.json new file mode 100644 index 00000000000..d7fe7d864a6 --- /dev/null +++ b/adapters/jixie/jixietest/supplemental/add-accountid.json @@ -0,0 +1,236 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, { + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234", + "accountid": "accountJX1234" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345", + "accountid": "accountJX1234" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456", + "accountid": "accountJX1234" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://hb.jixie.io/v2/hbsvrpost", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "111.222.333.444" + ], + "Referer": [ + "http://www.publisher.com/today/site?param1=a¶m2=b" + ], + "User-Agent": [ + "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + },{ + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234", + "accountid": "accountJX1234" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345", + "accountid": "accountJX1234" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456", + "accountid": "accountJX1234" + } + } + }], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "ttl": 300, + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + }, + "type": "video" + }] + } + ] +} + \ No newline at end of file diff --git a/adapters/jixie/jixietest/supplemental/add-extraprop.json b/adapters/jixie/jixietest/supplemental/add-extraprop.json new file mode 100644 index 00000000000..85c55a3620e --- /dev/null +++ b/adapters/jixie/jixietest/supplemental/add-extraprop.json @@ -0,0 +1,233 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, { + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234", + "jxprop1": "abc" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345", + "jxprop1": "def" } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456" + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://hb.jixie.io/v2/hbsvrpost", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "111.222.333.444" + ], + "Referer": [ + "http://www.publisher.com/today/site?param1=a¶m2=b" + ], + "User-Agent": [ + "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + },{ + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234", + "jxprop1": "abc" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345", + "jxprop1": "def" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456" + } + } + }], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "ttl": 300, + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + }, + "type": "video" + }] + } + ] +} + \ No newline at end of file diff --git a/adapters/jixie/jixietest/supplemental/add-userid.json b/adapters/jixie/jixietest/supplemental/add-userid.json new file mode 100644 index 00000000000..3ed30da4676 --- /dev/null +++ b/adapters/jixie/jixietest/supplemental/add-userid.json @@ -0,0 +1,237 @@ +{ + "mockBidRequest": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, { + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456" + } + } + } + ], + "user": { + "buyeruid": "awesome-buyeruid", + "id": "awesome-id" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://hb.jixie.io/v2/hbsvrpost", + "headers": { + "Accept": [ + "application/json" + ], + "Content-Type": [ + "application/json;charset=utf-8" + ], + "X-Forwarded-For": [ + "111.222.333.444" + ], + "Referer": [ + "http://www.publisher.com/today/site?param1=a¶m2=b" + ], + "User-Agent": [ + "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + ] + }, + "body": { + "id": "some_test_auction", + "imp": [{ + "id": "some_test_ad_id_1", + "banner": { + "format": [{ + "w": 300, + "h": 250 + },{ + "w": 300, + "h": 600 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf1234" + } + } + }, + { + "id": "some_test_ad_id_2", + "banner": { + "format": [{ + "w": 300, + "h": 50 + }], + "w": 300, + "h": 50 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf2345" + } + } + }, + { + "id": "some_test_ad_id_3", + "video":{ + "mimes": [ + "video/mp4", + "application/javascript" + ], + "protocols":[ + 2, + 3, + 5, + 6 + ], + "w":640, + "h":480 + }, + "ext": { + "bidder": { + "unit": "1000008-AbCdEf3456" + } + } + }], + "user": { + "buyeruid": "awesome-buyeruid", + "id": "awesome-id" + }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/today/site?param1=a¶m2=b" + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36", + "ip": "111.222.333.444" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "some_test_auction", + "seatbid": [{ + "seat": "12356", + "bid": [{ + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "ttl": 300, + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }] + }, + { + "seat": "45678", + "bid": [{ + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "ttl": 300, + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + } + ] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "bids": [{ + "bid": { + "adm": "
", + "id": "some_test_ad_id_1", + "impid": "some_test_ad_id_1", + "crid": "94395500", + "w": 300, + "price": 2.942808, + "adid": "94395500", + "h": 250 + }, + "type": "banner" + }, + { + "bid": { + "adm": " JXADSERVERDEMOin-stream 00:00:10", + "id": "some_test_ad_id_3", + "impid": "some_test_ad_id_3", + "crid": "9999999", + "w": 640, + "price": 1, + "adid": "9999999", + "h": 360 + }, + "type": "video" + }] + } + ] +} + \ No newline at end of file diff --git a/adapters/jixie/params_test.go b/adapters/jixie/params_test.go new file mode 100644 index 00000000000..4bc2e3080c1 --- /dev/null +++ b/adapters/jixie/params_test.go @@ -0,0 +1,57 @@ +package jixie + +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.BidderJixie, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected jixie 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.BidderJixie, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"unit": "1000008-AA77BB88CC" }`, + `{"unit": "1000008-AA77BB88CC", "accountid": "9988776655", "jxprop1": "somethingimportant" }`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `[]`, + `true`, + `{}`, + `{"unit":12345678}`, + `{"Unit":"12345678"}`, + `{"Unit": 12345678}`, + `{"AdUnit": "1"}`, + `{"adUnit": 1}`, + `{"unit": ""}`, + `{"unit": "12345678901234567"}`, + `{"unit":"1000008-AA77BB88CC", "accountid", "jxprop1": "somethingimportant" }`, + `{"unit":"1000008-AA77BB88CC", malformed, }`, +} diff --git a/adapters/jixie/usersync.go b/adapters/jixie/usersync.go new file mode 100644 index 00000000000..137d78f2859 --- /dev/null +++ b/adapters/jixie/usersync.go @@ -0,0 +1,12 @@ +package jixie + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewJixieSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("jixie", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/jixie/usersync_test.go b/adapters/jixie/usersync_test.go new file mode 100644 index 00000000000..575482435ff --- /dev/null +++ b/adapters/jixie/usersync_test.go @@ -0,0 +1,24 @@ +package jixie + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestJixieSyncer(t *testing.T) { + syncURL := "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewJixieSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + + assert.NoError(t, err) + assert.Equal(t, "https://id.jixie.io/api/sync?pid=&gdpr=&gdpr_consent=&us_privacy=&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D%26gdpr_consent%3D%26uid%3D%25%25JXUID%25%25", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/kidoz/kidoz.go b/adapters/kidoz/kidoz.go index 755ffd24d63..43372dc2f39 100644 --- a/adapters/kidoz/kidoz.go +++ b/adapters/kidoz/kidoz.go @@ -6,18 +6,18 @@ import ( "net/http" "strconv" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 KidozAdapter struct { endpoint string } -func (a *KidozAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *KidozAdapter) 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") @@ -102,7 +102,7 @@ func (a *KidozAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.Ext return result, errs } -func (a *KidozAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *KidozAdapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error switch responseData.StatusCode { @@ -129,7 +129,7 @@ func (a *KidozAdapter) MakeBids(request *openrtb.BidRequest, _ *adapters.Request }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse err := json.Unmarshal(responseData.Body, &bidResponse) if err != nil { return nil, []error{&errortypes.BadServerResponse{ @@ -169,7 +169,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters const UndefinedMediaType = openrtb_ext.BidType("") -func GetMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func GetMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { var bidType openrtb_ext.BidType = UndefinedMediaType for _, impression := range imps { if impression.ID != impID { diff --git a/adapters/kidoz/kidoz_test.go b/adapters/kidoz/kidoz_test.go index b3365ac70a9..3fd807ea454 100644 --- a/adapters/kidoz/kidoz_test.go +++ b/adapters/kidoz/kidoz_test.go @@ -5,11 +5,11 @@ import ( "net/http" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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/stretchr/testify/assert" ) @@ -24,14 +24,14 @@ func TestJsonSamples(t *testing.T) { adapterstest.RunJSONBidderTest(t, "kidoztest", bidder) } -func makeBidRequest() *openrtb.BidRequest { - request := &openrtb.BidRequest{ +func makeBidRequest() *openrtb2.BidRequest { + request := &openrtb2.BidRequest{ ID: "test-req-id-0", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "test-imp-id-0", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ { W: 320, H: 50, @@ -89,22 +89,22 @@ func TestMakeBids(t *testing.T) { } func TestGetMediaTypeForImp(t *testing.T) { - imps := []openrtb.Imp{ + imps := []openrtb2.Imp{ { ID: "1", - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, }, { ID: "2", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, }, { ID: "3", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, }, { ID: "4", - Audio: &openrtb.Audio{}, + Audio: &openrtb2.Audio{}, }, } diff --git a/adapters/kidoz/kidoztest/exemplary/simple-banner.json b/adapters/kidoz/kidoztest/exemplary/simple-banner.json index 6b2d6dbc56f..81b5f1e5227 100644 --- a/adapters/kidoz/kidoztest/exemplary/simple-banner.json +++ b/adapters/kidoz/kidoztest/exemplary/simple-banner.json @@ -69,4 +69,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/exemplary/simple-video.json b/adapters/kidoz/kidoztest/exemplary/simple-video.json index f50ed907bc0..7c012f95527 100644 --- a/adapters/kidoz/kidoztest/exemplary/simple-video.json +++ b/adapters/kidoz/kidoztest/exemplary/simple-video.json @@ -67,4 +67,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/kidoz/kidoztest/supplemental/status-204.json b/adapters/kidoz/kidoztest/supplemental/status-204.json index 03e337c9fda..add65e7d666 100644 --- a/adapters/kidoz/kidoztest/supplemental/status-204.json +++ b/adapters/kidoz/kidoztest/supplemental/status-204.json @@ -55,4 +55,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/kidoz/params_test.go b/adapters/kidoz/params_test.go index 43c5a68d69d..073d7382d68 100644 --- a/adapters/kidoz/params_test.go +++ b/adapters/kidoz/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/krushmedia/krushmedia.go b/adapters/krushmedia/krushmedia.go index 1f2e6b636c6..f1b80da701f 100644 --- a/adapters/krushmedia/krushmedia.go +++ b/adapters/krushmedia/krushmedia.go @@ -7,12 +7,12 @@ import ( "strconv" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 KrushmediaAdapter struct { @@ -32,7 +32,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getHeaders(request *openrtb.BidRequest) *http.Header { +func getHeaders(request *openrtb2.BidRequest) *http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -60,7 +60,7 @@ func getHeaders(request *openrtb.BidRequest) *http.Header { } func (a *KrushmediaAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -107,7 +107,7 @@ func (a *KrushmediaAdapter) MakeRequests( }}, nil } -func (a *KrushmediaAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtKrushmedia, error) { +func (a *KrushmediaAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtKrushmedia, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -129,7 +129,7 @@ func (a *KrushmediaAdapter) buildEndpointURL(params *openrtb_ext.ExtKrushmedia) } func (a *KrushmediaAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -157,7 +157,7 @@ func (a *KrushmediaAdapter) MakeBids( } responseBody := bidderRawResponse.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", @@ -176,7 +176,7 @@ func (a *KrushmediaAdapter) MakeBids( return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/krushmedia/krushmedia_test.go b/adapters/krushmedia/krushmedia_test.go index 0fc5c93fed2..7bdea503569 100644 --- a/adapters/krushmedia/krushmedia_test.go +++ b/adapters/krushmedia/krushmedia_test.go @@ -3,9 +3,9 @@ package krushmedia import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/krushmedia/krushmediatest/exemplary/banner-app.json b/adapters/krushmedia/krushmediatest/exemplary/banner-app.json index ac5be1b6ff0..75defaa0712 100644 --- a/adapters/krushmedia/krushmediatest/exemplary/banner-app.json +++ b/adapters/krushmedia/krushmediatest/exemplary/banner-app.json @@ -156,4 +156,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/adapters/krushmedia/krushmediatest/exemplary/native-app.json b/adapters/krushmedia/krushmediatest/exemplary/native-app.json index a2bb393727b..bd27d2dc69e 100644 --- a/adapters/krushmedia/krushmediatest/exemplary/native-app.json +++ b/adapters/krushmedia/krushmediatest/exemplary/native-app.json @@ -154,4 +154,4 @@ } ] } - + \ No newline at end of file diff --git a/adapters/krushmedia/krushmediatest/exemplary/video-web.json b/adapters/krushmedia/krushmediatest/exemplary/video-web.json index 48eedb29f48..1e2d8069407 100644 --- a/adapters/krushmedia/krushmediatest/exemplary/video-web.json +++ b/adapters/krushmedia/krushmediatest/exemplary/video-web.json @@ -163,4 +163,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/adapters/krushmedia/params_test.go b/adapters/krushmedia/params_test.go index 0f912513b2c..26daa56e10b 100644 --- a/adapters/krushmedia/params_test.go +++ b/adapters/krushmedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/krushmedia/usersync.go b/adapters/krushmedia/usersync.go index 0a4f664e56f..860cb2204e9 100644 --- a/adapters/krushmedia/usersync.go +++ b/adapters/krushmedia/usersync.go @@ -3,10 +3,10 @@ package krushmedia import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewKrushmediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("krushmedia", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("krushmedia", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/krushmedia/usersync_test.go b/adapters/krushmedia/usersync_test.go index a5908b8061d..765b0faa18b 100644 --- a/adapters/krushmedia/usersync_test.go +++ b/adapters/krushmedia/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -29,6 +29,5 @@ func TestKrushmediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr=0&gdpr_consent=allGdpr&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/kubient/kubient.go b/adapters/kubient/kubient.go index fd6c4ef13e8..a99e9005105 100644 --- a/adapters/kubient/kubient.go +++ b/adapters/kubient/kubient.go @@ -5,12 +5,12 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" + "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. @@ -28,7 +28,7 @@ type KubientAdapter struct { // MakeRequests prepares the HTTP requests which should be made to fetch bids. func (adapter *KubientAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ([]*adapters.RequestData, []error) { if len(openRTBRequest.Imp) == 0 { @@ -65,7 +65,7 @@ func (adapter *KubientAdapter) MakeRequests( return requestsToBidder, errs } -func checkImpExt(impObj openrtb.Imp) error { +func checkImpExt(impObj openrtb2.Imp) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(impObj.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -87,7 +87,7 @@ func checkImpExt(impObj openrtb.Imp) error { } // MakeBids makes the bids -func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -106,7 +106,7 @@ func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb.BidRequest, ext }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -131,7 +131,7 @@ func (adapter *KubientAdapter) MakeBids(internalRequest *openrtb.BidRequest, ext return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/kubient/kubient_test.go b/adapters/kubient/kubient_test.go index e7f8a9ee2fc..19eb3e8ff13 100644 --- a/adapters/kubient/kubient_test.go +++ b/adapters/kubient/kubient_test.go @@ -3,9 +3,9 @@ package kubient import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/kubient/kubienttest/supplemental/bad_response.json b/adapters/kubient/kubienttest/supplemental/bad_response.json index 076acf29058..832dc975088 100644 --- a/adapters/kubient/kubienttest/supplemental/bad_response.json +++ b/adapters/kubient/kubienttest/supplemental/bad_response.json @@ -54,7 +54,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/legacy.go b/adapters/legacy.go index c3fde16d764..8b2221fe0ca 100644 --- a/adapters/legacy.go +++ b/adapters/legacy.go @@ -6,8 +6,8 @@ import ( "net/http" "time" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/server/ssl" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/server/ssl" ) // This file contains some deprecated, legacy types. diff --git a/adapters/lifestreet/lifestreet.go b/adapters/lifestreet/lifestreet.go index ca9b9688943..14f6931751a 100644 --- a/adapters/lifestreet/lifestreet.go +++ b/adapters/lifestreet/lifestreet.go @@ -9,12 +9,12 @@ import ( "net/http" "strings" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/pbs" "golang.org/x/net/context/ctxhttp" ) @@ -63,7 +63,7 @@ func (a *LifestreetAdapter) callOne(ctx context.Context, req *pbs.PBSRequest, re return } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return @@ -98,11 +98,11 @@ func (a *LifestreetAdapter) callOne(ctx context.Context, req *pbs.PBSRequest, re return } -func (a *LifestreetAdapter) MakeOpenRtbBidRequest(req *pbs.PBSRequest, bidder *pbs.PBSBidder, slotTag string, mtype pbs.MediaType, unitInd int) (openrtb.BidRequest, error) { +func (a *LifestreetAdapter) MakeOpenRtbBidRequest(req *pbs.PBSRequest, bidder *pbs.PBSBidder, slotTag string, mtype pbs.MediaType, unitInd int) (openrtb2.BidRequest, error) { lsReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), []pbs.MediaType{mtype}) if err != nil { - return openrtb.BidRequest{}, err + return openrtb2.BidRequest{}, err } if lsReq.Imp != nil && len(lsReq.Imp) > 0 { diff --git a/adapters/lifestreet/lifestreet_test.go b/adapters/lifestreet/lifestreet_test.go index 39712192da6..5c4f47fdff9 100644 --- a/adapters/lifestreet/lifestreet_test.go +++ b/adapters/lifestreet/lifestreet_test.go @@ -10,15 +10,15 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" ) type lsTagInfo struct { @@ -53,7 +53,7 @@ func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -84,7 +84,7 @@ func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Model '%s' doesn't match '%s", breq.Device.Model, lsdata.deviceModel), http.StatusInternalServerError) return } - if *breq.Device.ConnectionType != openrtb.ConnectionType(lsdata.deviceConnectiontype) { + if *breq.Device.ConnectionType != openrtb2.ConnectionType(lsdata.deviceConnectiontype) { http.Error(w, fmt.Sprintf("Connectiontype '%d' doesn't match '%d", breq.Device.ConnectionType, lsdata.deviceConnectiontype), http.StatusInternalServerError) return } @@ -96,24 +96,24 @@ func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { http.Error(w, fmt.Sprintf("Wrong number of imp objects sent: %d", len(breq.Imp)), http.StatusInternalServerError) return } - var bid *openrtb.Bid + var bid *openrtb2.Bid for _, tag := range lsdata.tags { if breq.Imp[0].Banner == nil { http.Error(w, fmt.Sprintf("No banner object sent"), http.StatusInternalServerError) return } - if *breq.Imp[0].Banner.W != lsdata.width || *breq.Imp[0].Banner.H != lsdata.height { + if *breq.Imp[0].Banner.W != int64(lsdata.width) || *breq.Imp[0].Banner.H != int64(lsdata.height) { http.Error(w, fmt.Sprintf("Size '%dx%d' doesn't match '%dx%d", breq.Imp[0].Banner.W, breq.Imp[0].Banner.H, lsdata.width, lsdata.height), http.StatusInternalServerError) return } if breq.Imp[0].TagID == tag.slotTag { - bid = &openrtb.Bid{ + bid = &openrtb2.Bid{ ID: "random-id", ImpID: breq.Imp[0].ID, Price: tag.bid, AdM: tag.content, - W: lsdata.width, - H: lsdata.height, + W: int64(lsdata.width), + H: int64(lsdata.height), } } } @@ -122,14 +122,14 @@ func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { return } - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "2345676337", BidID: "975537589956", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "LSM", - Bid: []openrtb.Bid{*bid}, + Bid: []openrtb2.Bid{*bid}, }, }, } @@ -181,25 +181,25 @@ func TestLifestreetBasicResponse(t *testing.T) { pbin := pbs.PBSRequest{ AdUnits: make([]pbs.AdUnit, 2), - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: lsdata.appBundle, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: lsdata.deviceUA, IP: lsdata.deviceIP, Make: lsdata.deviceMake, Model: lsdata.deviceModel, - ConnectionType: openrtb.ConnectionType(lsdata.deviceConnectiontype).Ptr(), + ConnectionType: openrtb2.ConnectionType(lsdata.deviceConnectiontype).Ptr(), IFA: lsdata.deviceIfa, }, } for i, tag := range lsdata.tags { pbin.AdUnits[i] = pbs.AdUnit{ Code: tag.code, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { - W: lsdata.width, - H: lsdata.height, + W: int64(lsdata.width), + H: int64(lsdata.height), }, }, Bids: []pbs.Bids{ @@ -266,7 +266,7 @@ func TestLifestreetBasicResponse(t *testing.T) { if bid.Price != tag.bid { t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) } - if bid.Width != lsdata.width || bid.Height != lsdata.height { + if bid.Width != int64(lsdata.width) || bid.Height != int64(lsdata.height) { t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, lsdata.width, lsdata.height) } if bid.Adm != tag.content { diff --git a/adapters/lifestreet/usersync.go b/adapters/lifestreet/usersync.go index 4f18854e54a..f5300ebaa90 100644 --- a/adapters/lifestreet/usersync.go +++ b/adapters/lifestreet/usersync.go @@ -3,10 +3,10 @@ package lifestreet import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewLifestreetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lifestreet", 67, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("lifestreet", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/lifestreet/usersync_test.go b/adapters/lifestreet/usersync_test.go index 134af2f5b88..e41217fe10f 100644 --- a/adapters/lifestreet/usersync_test.go +++ b/adapters/lifestreet/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,5 @@ func TestLifestreetSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl=localhost%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24%24visitor_cookie%24%24", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 67, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/lockerdome/lockerdome.go b/adapters/lockerdome/lockerdome.go index ec9ebe8c281..28c966be6de 100644 --- a/adapters/lockerdome/lockerdome.go +++ b/adapters/lockerdome/lockerdome.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const unexpectedStatusCodeMessage = "Unexpected status code: %d. Run with request.debug = 1 for more info" @@ -20,7 +20,7 @@ type LockerDomeAdapter struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids [from the bidder, in this case, LockerDome] -func (adapter *LockerDomeAdapter) MakeRequests(openRTBRequest *openrtb.BidRequest, extraReqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { +func (adapter *LockerDomeAdapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, extraReqInfo *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) { numberOfImps := len(openRTBRequest.Imp) @@ -70,7 +70,7 @@ func (adapter *LockerDomeAdapter) MakeRequests(openRTBRequest *openrtb.BidReques indexesOfValidImps = append(indexesOfValidImps, i) } if numberOfImps > len(indexesOfValidImps) { - var validImps []openrtb.Imp + var validImps []openrtb2.Imp for j := 0; j < len(indexesOfValidImps); j++ { validImps = append(validImps, openRTBRequest.Imp[j]) } @@ -109,7 +109,7 @@ func (adapter *LockerDomeAdapter) MakeRequests(openRTBRequest *openrtb.BidReques } // MakeBids unpacks the server's response into Bids. -func (adapter *LockerDomeAdapter) MakeBids(openRTBRequest *openrtb.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) { +func (adapter *LockerDomeAdapter) MakeBids(openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData) (bidderResponse *adapters.BidderResponse, errs []error) { if bidderRawResponse.StatusCode == http.StatusNoContent { return nil, nil @@ -127,7 +127,7 @@ func (adapter *LockerDomeAdapter) MakeBids(openRTBRequest *openrtb.BidRequest, r }} } - var openRTBBidderResponse openrtb.BidResponse + var openRTBBidderResponse openrtb2.BidResponse if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{ fmt.Errorf("Error unmarshaling LockerDome bid response - %s", err.Error()), diff --git a/adapters/lockerdome/lockerdome_test.go b/adapters/lockerdome/lockerdome_test.go index ca3da099eaa..6ac495d5d7c 100644 --- a/adapters/lockerdome/lockerdome_test.go +++ b/adapters/lockerdome/lockerdome_test.go @@ -3,9 +3,9 @@ package lockerdome import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/lockerdome/lockerdometest/supplemental/bad_response.json b/adapters/lockerdome/lockerdometest/supplemental/bad_response.json index 8df8a0e1633..d119a47da80 100644 --- a/adapters/lockerdome/lockerdometest/supplemental/bad_response.json +++ b/adapters/lockerdome/lockerdometest/supplemental/bad_response.json @@ -56,7 +56,7 @@ "expectedMakeBidsErrors": [ { - "value": "Error unmarshaling LockerDome bid response - json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "Error unmarshaling LockerDome bid response - json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/lockerdome/params_test.go b/adapters/lockerdome/params_test.go index 61ee259a437..815246571e3 100644 --- a/adapters/lockerdome/params_test.go +++ b/adapters/lockerdome/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file tests static/bidder-params/lockerdome.json diff --git a/adapters/lockerdome/usersync.go b/adapters/lockerdome/usersync.go index 67c4e15dd2d..d5cce16804a 100644 --- a/adapters/lockerdome/usersync.go +++ b/adapters/lockerdome/usersync.go @@ -3,10 +3,10 @@ package lockerdome import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewLockerDomeSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lockerdome", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("lockerdome", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/lockerdome/usersync_test.go b/adapters/lockerdome/usersync_test.go index acfa788e5f7..3a2bd7f325b 100644 --- a/adapters/lockerdome/usersync_test.go +++ b/adapters/lockerdome/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestLockerDomeSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://lockerdome.com/usync/prebidserver?pid=&gdpr=&gdpr_consent=&us_privacy=&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D%26gdpr_consent%3D%26uid%3D%7B%7Buid%7D%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/logicad/logicad.go b/adapters/logicad/logicad.go index dd316321381..982723d0d0a 100644 --- a/adapters/logicad/logicad.go +++ b/adapters/logicad/logicad.go @@ -5,18 +5,18 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 LogicadAdapter struct { endpoint string } -func (adapter *LogicadAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *LogicadAdapter) 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"}} } @@ -38,10 +38,10 @@ func (adapter *LogicadAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo return result, errs } -func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLogicad][]openrtb.Imp, []openrtb.Imp, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpLogicad][]openrtb2.Imp, []openrtb2.Imp, []error) { errors := make([]error, 0, len(imps)) - resImps := make([]openrtb.Imp, 0, len(imps)) - res := make(map[openrtb_ext.ExtImpLogicad][]openrtb.Imp) + resImps := make([]openrtb2.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpLogicad][]openrtb2.Imp) for _, imp := range imps { impExt, err := getImpressionExt(&imp) @@ -55,7 +55,7 @@ func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLogicad][]ope } if res[impExt] == nil { - res[impExt] = make([]openrtb.Imp, 0) + res[impExt] = make([]openrtb2.Imp, 0) } res[impExt] = append(res[impExt], imp) resImps = append(resImps, imp) @@ -70,7 +70,7 @@ func validateImpression(impExt *openrtb_ext.ExtImpLogicad) error { return nil } -func getImpressionExt(imp *openrtb.Imp) (openrtb_ext.ExtImpLogicad, error) { +func getImpressionExt(imp *openrtb2.Imp) (openrtb_ext.ExtImpLogicad, error) { var bidderExt adapters.ExtImpBidder var logicadExt openrtb_ext.ExtImpLogicad @@ -87,7 +87,7 @@ func getImpressionExt(imp *openrtb.Imp) (openrtb_ext.ExtImpLogicad, error) { return logicadExt, nil } -func (adapter *LogicadAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *LogicadAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -105,7 +105,7 @@ func (adapter *LogicadAdapter) buildAdapterRequest(prebidBidRequest *openrtb.Bid Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLogicad, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps for idx := range bidRequest.Imp { @@ -117,7 +117,7 @@ func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext. } //MakeBids translates Logicad bid response to prebid-server specific format -func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -126,7 +126,7 @@ func (adapter *LogicadAdapter) MakeBids(internalRequest *openrtb.BidRequest, ext return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - var bidResp openrtb.BidResponse + 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}} diff --git a/adapters/logicad/logicad_test.go b/adapters/logicad/logicad_test.go index 5da8e56e6c2..820aad9751d 100644 --- a/adapters/logicad/logicad_test.go +++ b/adapters/logicad/logicad_test.go @@ -3,9 +3,9 @@ package logicad import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/logicad/params_test.go b/adapters/logicad/params_test.go index 8b5d296bd1f..eb34452811b 100644 --- a/adapters/logicad/params_test.go +++ b/adapters/logicad/params_test.go @@ -2,7 +2,7 @@ package logicad import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/logicad/usersync.go b/adapters/logicad/usersync.go index 34d29a25543..e685cc985fc 100644 --- a/adapters/logicad/usersync.go +++ b/adapters/logicad/usersync.go @@ -3,10 +3,10 @@ package logicad import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewLogicadSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("logicad", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("logicad", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/logicad/usersync_test.go b/adapters/logicad/usersync_test.go index aeb029d3380..e8b10c665fe 100644 --- a/adapters/logicad/usersync_test.go +++ b/adapters/logicad/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -26,6 +26,5 @@ func TestLogicadSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://localhost/cookiesender?r=true&gdpr=1&gdpr_consent=A&ru=localhost%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/lunamedia/lunamedia.go b/adapters/lunamedia/lunamedia.go index c5e0ef8a3ee..899269e661f 100644 --- a/adapters/lunamedia/lunamedia.go +++ b/adapters/lunamedia/lunamedia.go @@ -6,12 +6,12 @@ import ( "net/http" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 LunaMediaAdapter struct { @@ -19,7 +19,7 @@ type LunaMediaAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *LunaMediaAdapter) 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"}) @@ -49,10 +49,10 @@ func (adapter *LunaMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqIn } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp, []openrtb.Imp, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpLunaMedia][]openrtb2.Imp, []openrtb2.Imp, []error) { errors := make([]error, 0, len(imps)) - resImps := make([]openrtb.Imp, 0, len(imps)) - res := make(map[openrtb_ext.ExtImpLunaMedia][]openrtb.Imp) + resImps := make([]openrtb2.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpLunaMedia][]openrtb2.Imp) for _, imp := range imps { impExt, err := getImpressionExt(&imp) @@ -71,7 +71,7 @@ func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpLunaMedia][]o continue } if res[*impExt] == nil { - res[*impExt] = make([]openrtb.Imp, 0) + res[*impExt] = make([]openrtb2.Imp, 0) } res[*impExt] = append(res[*impExt], imp) resImps = append(resImps, imp) @@ -87,7 +87,7 @@ func validateImpression(impExt *openrtb_ext.ExtImpLunaMedia) error { } //Alter impression info to comply with LunaMedia platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to LunaMedia platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -95,7 +95,7 @@ func compatImpression(imp *openrtb.Imp) error { return nil } -func compatBannerImpression(imp *openrtb.Imp) error { +func compatBannerImpression(imp *openrtb2.Imp) error { // Create a copy of the banner, since imp is a shallow copy of the original. bannerCopy := *imp.Banner @@ -114,7 +114,7 @@ func compatBannerImpression(imp *openrtb.Imp) error { return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpLunaMedia, error) { +func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpLunaMedia, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -130,7 +130,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpLunaMedia, error) { return &LunaMediaExt, nil } -func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) (*adapters.RequestData, error) { +func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb2.Imp) (*adapters.RequestData, error) { newBidRequest := createBidRequest(prebidBidRequest, params, imps) reqJSON, err := json.Marshal(newBidRequest) if err != nil { @@ -154,7 +154,7 @@ func (adapter *LunaMediaAdapter) buildAdapterRequest(prebidBidRequest *openrtb.B Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpLunaMedia, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps for idx := range bidRequest.Imp { @@ -184,7 +184,7 @@ func (adapter *LunaMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpLuna } //MakeBids translates LunaMedia bid response to prebid-server specific format -func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var msg = "" if response.StatusCode == http.StatusNoContent { return nil, nil @@ -194,7 +194,7 @@ func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, e return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - var bidResp openrtb.BidResponse + 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}} @@ -218,7 +218,7 @@ func (adapter *LunaMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, e } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +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 diff --git a/adapters/lunamedia/lunamedia_test.go b/adapters/lunamedia/lunamedia_test.go index 6d0952cdd9c..4149060c809 100644 --- a/adapters/lunamedia/lunamedia_test.go +++ b/adapters/lunamedia/lunamedia_test.go @@ -3,9 +3,9 @@ package lunamedia import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/lunamedia/params_test.go b/adapters/lunamedia/params_test.go index 2f21ea45510..b4faeea1f77 100644 --- a/adapters/lunamedia/params_test.go +++ b/adapters/lunamedia/params_test.go @@ -2,7 +2,7 @@ package lunamedia import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/lunamedia/usersync.go b/adapters/lunamedia/usersync.go index 194c4b77dbe..39c9a808040 100644 --- a/adapters/lunamedia/usersync.go +++ b/adapters/lunamedia/usersync.go @@ -3,10 +3,10 @@ package lunamedia import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewLunaMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lunamedia", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("lunamedia", temp, adapters.SyncTypeIframe) } diff --git a/adapters/lunamedia/usersync_test.go b/adapters/lunamedia/usersync_test.go index 3a549aec5f7..24cd740d600 100644 --- a/adapters/lunamedia/usersync_test.go +++ b/adapters/lunamedia/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -26,6 +26,5 @@ func TestLunaMediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://api.lunamedia.io/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=lunamedia&gdpr=1&gdpr_consent=A&uid=$UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/marsmedia/marsmedia.go b/adapters/marsmedia/marsmedia.go index f1cffac2274..a63db09e208 100644 --- a/adapters/marsmedia/marsmedia.go +++ b/adapters/marsmedia/marsmedia.go @@ -6,18 +6,18 @@ import ( "net/http" "strconv" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 MarsmediaAdapter struct { URI string } -func (a *MarsmediaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *MarsmediaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { request := *requestIn @@ -106,7 +106,7 @@ func (a *MarsmediaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo * }}, []error{} } -func (a *MarsmediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *MarsmediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -124,7 +124,7 @@ func (a *MarsmediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa }} } - var bidResp openrtb.BidResponse + 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), @@ -150,7 +150,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } // getMediaTypeForImp figures out which media type this bid is for. -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner //default type for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/marsmedia/marsmedia_test.go b/adapters/marsmedia/marsmedia_test.go index 03e46312ca2..ab87bf773a4 100644 --- a/adapters/marsmedia/marsmedia_test.go +++ b/adapters/marsmedia/marsmedia_test.go @@ -3,9 +3,9 @@ package marsmedia import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/marsmedia/params_test.go b/adapters/marsmedia/params_test.go index 2e3b483824d..43cd49c2ed3 100644 --- a/adapters/marsmedia/params_test.go +++ b/adapters/marsmedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/marsmedia.json diff --git a/adapters/marsmedia/usersync.go b/adapters/marsmedia/usersync.go index 63d06d9dcc5..4ac76d1f5f2 100644 --- a/adapters/marsmedia/usersync.go +++ b/adapters/marsmedia/usersync.go @@ -3,10 +3,10 @@ package marsmedia import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewMarsmediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("marsmedia", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("marsmedia", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/marsmedia/usersync_test.go b/adapters/marsmedia/usersync_test.go index cc4f0b819f9..975af65fcf5 100644 --- a/adapters/marsmedia/usersync_test.go +++ b/adapters/marsmedia/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,7 +30,6 @@ func TestMarsmediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr=A&gdpr_consent=B&us_privacy=C&redirect=localhost:8000%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/mediafuse/usersync.go b/adapters/mediafuse/usersync.go index ca299c724f6..d482ad774bd 100644 --- a/adapters/mediafuse/usersync.go +++ b/adapters/mediafuse/usersync.go @@ -3,10 +3,10 @@ package mediafuse import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewMediafuseSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("mediafuse", 411, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("mediafuse", temp, adapters.SyncTypeIframe) } diff --git a/adapters/mediafuse/usersync_test.go b/adapters/mediafuse/usersync_test.go index 95e6a7cf93f..95045689a87 100644 --- a/adapters/mediafuse/usersync_test.go +++ b/adapters/mediafuse/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -24,7 +24,6 @@ func TestMediafuseSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.hbmp.mediafuse.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 411, syncer.GDPRVendorID()) + assert.Equal(t, "iframe", syncInfo.Type) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/mgid/mgid.go b/adapters/mgid/mgid.go index 9754ba8088e..95ede0ab5c4 100644 --- a/adapters/mgid/mgid.go +++ b/adapters/mgid/mgid.go @@ -6,11 +6,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 MgidAdapter struct { @@ -26,7 +26,7 @@ type RespBidExt struct { CreativeType openrtb_ext.BidType `json:"crtype"` } -func (a *MgidAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) (adapterRequests []*adapters.RequestData, errs []error) { +func (a *MgidAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) (adapterRequests []*adapters.RequestData, errs []error) { adapterReq, errs := a.makeRequest(request) if adapterReq != nil && len(errs) == 0 { @@ -36,7 +36,7 @@ func (a *MgidAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapter return } -func (a *MgidAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *MgidAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error path, err := preprocess(request) @@ -65,7 +65,7 @@ func (a *MgidAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Reques } // Mutate the request to get it ready to send to yieldmo. -func preprocess(request *openrtb.BidRequest) (path string, err error) { +func preprocess(request *openrtb2.BidRequest) (path string, err error) { if request.TMax == 0 { request.TMax = 200 } @@ -123,7 +123,7 @@ func preprocess(request *openrtb.BidRequest) (path string, err error) { return } -func (a *MgidAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *MgidAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -140,7 +140,7 @@ func (a *MgidAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.Requ }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} diff --git a/adapters/mgid/mgid_test.go b/adapters/mgid/mgid_test.go index 11f5596ed9a..7d30045168d 100644 --- a/adapters/mgid/mgid_test.go +++ b/adapters/mgid/mgid_test.go @@ -3,9 +3,9 @@ package mgid import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/mgid/usersync.go b/adapters/mgid/usersync.go index 3eb77025d4d..94cf12e119d 100644 --- a/adapters/mgid/usersync.go +++ b/adapters/mgid/usersync.go @@ -3,10 +3,10 @@ package mgid import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewMgidSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("mgid", 358, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("mgid", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/mgid/usersync_test.go b/adapters/mgid/usersync_test.go index b918dabfe0b..3fa5a151bd8 100644 --- a/adapters/mgid/usersync_test.go +++ b/adapters/mgid/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,5 @@ func TestMgidSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://cm.mgid.com/m?cdsp=363893&adu=https%3A//external.com%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Bmuidn%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 358, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/mobfoxpb/mobfoxpb.go b/adapters/mobfoxpb/mobfoxpb.go index 42ac3dc02d4..7fcf416a480 100644 --- a/adapters/mobfoxpb/mobfoxpb.go +++ b/adapters/mobfoxpb/mobfoxpb.go @@ -4,13 +4,24 @@ import ( "encoding/json" "fmt" "net/http" + "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/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" +) + +const ( + ROUTE_NATIVE = "o" + ROUTE_RTB = "rtb" + METHOD_NATIVE = "ortb" + METHOD_RTB = "req" + MACROS_ROUTE = "__route__" + MACROS_METHOD = "__method__" + MACROS_KEY = "__key__" ) type adapter struct { @@ -26,23 +37,38 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests create bid request for mobfoxpb demand -func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var err error - var tagID string - + var route string + var method string var adapterRequests []*adapters.RequestData + requestURI := a.URI reqCopy := *request imp := request.Imp[0] - tagID, err = jsonparser.GetString(imp.Ext, "bidder", "TagID") - if err != nil { - errs = append(errs, err) + tagID, errTag := jsonparser.GetString(imp.Ext, "bidder", "TagID") + key, errKey := jsonparser.GetString(imp.Ext, "bidder", "key") + if errTag != nil && errKey != nil { + errs = append(errs, &errortypes.BadInput{ + Message: fmt.Sprintf("Invalid or non existing key and tagId, atleast one should be present"), + }) return nil, errs } - imp.TagID = tagID - reqCopy.Imp = []openrtb.Imp{imp} - adapterReq, err := a.makeRequest(&reqCopy) + + if key != "" { + route = ROUTE_RTB + method = METHOD_RTB + requestURI = strings.Replace(requestURI, MACROS_KEY, key, 1) + } else if tagID != "" { + method = METHOD_NATIVE + route = ROUTE_NATIVE + } + + requestURI = strings.Replace(requestURI, MACROS_ROUTE, route, 1) + requestURI = strings.Replace(requestURI, MACROS_METHOD, method, 1) + + reqCopy.Imp = []openrtb2.Imp{imp} + adapterReq, err := a.makeRequest(&reqCopy, requestURI) if err != nil { errs = append(errs, err) } @@ -52,7 +78,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex return adapterRequests, errs } -func (a *adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) { +func (a *adapter) makeRequest(request *openrtb2.BidRequest, requestURI string) (*adapters.RequestData, error) { reqJSON, err := json.Marshal(request) if err != nil { @@ -64,14 +90,14 @@ func (a *adapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestDat headers.Add("Accept", "application/json") return &adapters.RequestData{ Method: "POST", - Uri: a.URI, + Uri: requestURI, Body: reqJSON, Headers: headers, }, nil } // MakeBids makes the bids -func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -84,7 +110,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -98,18 +124,17 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest if err != nil { errs = append(errs, err) } else { - b := &adapters.TypedBid{ + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ Bid: &bid, BidType: bidType, - } - bidResponse.Bids = append(bidResponse.Bids, b) + }) } } } return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { @@ -126,6 +151,6 @@ func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, // This shouldnt happen. Lets handle it just incase by returning an error. return "", &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Failed to find impression \"%s\" ", impID), + Message: fmt.Sprintf("Failed to find impression \"%s\"", impID), } } diff --git a/adapters/mobfoxpb/mobfoxpb_test.go b/adapters/mobfoxpb/mobfoxpb_test.go index 23bdb28118c..56ad948bcde 100644 --- a/adapters/mobfoxpb/mobfoxpb_test.go +++ b/adapters/mobfoxpb/mobfoxpb_test.go @@ -3,14 +3,14 @@ package mobfoxpb import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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.BidderMobfoxpb, config.Adapter{ - Endpoint: "http://example.com/?c=o&m=ortb"}) + Endpoint: "http://example.com/?c=__route__&m=__method__&key=__key__"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) } diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-direct-route.json new file mode 100644 index 00000000000..fb6bd260f74 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-direct-route.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "key": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=rtb&m=req&key=6", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "key": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "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": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "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, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-rtb-route.json new file mode 100644 index 00000000000..7b38008536d --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner-rtb-route.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "6" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } + } + }, + "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": { + "prebid": { + "type": "banner" + } + } + } + ] + } + ] + } + } + } + ], + "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, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json deleted file mode 100644 index b1936661a71..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-banner.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "6", - "ext": { - "bidder": { - "TagID": "6" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } -}, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "6", - "ext": { - "bidder": { - "TagID": "6" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - } - }, - "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": { - "prebid": { - "type": "banner" - } - } - } - ] - } - ] - } - } - } - ], - - "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, - "ext": { - "prebid": { - "type": "banner" - } - } - }, - "type": "banner" - } - ] - } - ] -} diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-direct-route.json new file mode 100644 index 00000000000..a949fdb1527 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-direct-route.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "key": "7" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=rtb&m=req&key=7", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "key": "7" + } + } + } + ] + } + }, + "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", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "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", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-rtb-route.json new file mode 100644 index 00000000000..a33f0e62fc7 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video-rtb-route.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "TagID": "7" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "TagID": "7" + } + } + } + ] + } + }, + "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", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "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", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json deleted file mode 100644 index 6cdcdc5a6cc..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-video.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "ext": { - "bidder": { - "TagID": "7" - } - } - } - ] - }, - - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - }, - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "imp": [ - { - "id": "test-imp-id", - "video": { - "mimes": ["video/mp4"], - "protocols": [2, 5], - "w": 1024, - "h": 576 - }, - "tagid": "7", - "ext": { - "bidder": { - "TagID": "7" - } - } - } - ] - } - }, - "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", - "ext": { - "prebid": { - "type": "video" - } - } - } - ], - "seat": "mobfoxpb" - } - ], - "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", - "ext": { - "prebid": { - "type": "video" - } - } - }, - "type": "video" - } - ] - } - ] -} diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-direct-route.json new file mode 100644 index 00000000000..d8727226723 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-direct-route.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "key": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=rtb&m=req&key=8", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "key": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "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, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "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, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-rtb-route.json new file mode 100644 index 00000000000..adbb7173848 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner-rtb-route.json @@ -0,0 +1,128 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "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, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "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, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json b/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json deleted file mode 100644 index bba728ac1e9..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/exemplary/simple-web-banner.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "8", - "ext": { - "bidder": { - "TagID": "8" - } - } - } - ], - "site": { - "id": "1", - "domain": "test.com" - }, - "device": { - "ip": "123.123.123.123" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "8", - "ext": { - "bidder": { - "TagID": "8" - } - } - } - ], - "site": { - "id": "1", - "domain": "test.com" - }, - "device": { - "ip": "123.123.123.123" - } - } - }, - "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, - "ext": { - "prebid": { - "type": "banner" - } - } - } - ], - "seat": "mobfoxpb" - } - ], - "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, - "ext": { - "prebid": { - "type": "banner" - } - } - }, - "type": "banner" - } - ] - } - ] -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json new file mode 100644 index 00000000000..3ffcd9bf63c --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-direct-route.json @@ -0,0 +1,3 @@ +{ + "TagID": "6" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json new file mode 100644 index 00000000000..3ce815613d1 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner-rtb-route.json @@ -0,0 +1,3 @@ +{ + "key": "6" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json deleted file mode 100644 index dbdac1ad995..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "6" -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json new file mode 100644 index 00000000000..1e42cfc4a05 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-direct-route.json @@ -0,0 +1,3 @@ +{ + "TagID": "7" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json new file mode 100644 index 00000000000..8c4421b65ef --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/params/race/video-rtb-route.json @@ -0,0 +1,3 @@ +{ + "key": "7" +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json b/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json deleted file mode 100644 index 6e2e0b3803b..00000000000 --- a/adapters/mobfoxpb/mobfoxpbtest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "TagID": "7" -} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad-imp-ext.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad-imp-ext.json index ac3dce598eb..6f2b95a8c54 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad-imp-ext.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad-imp-ext.json @@ -1,42 +1,41 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "mobfoxpb": { + "TagID": "6" + } + } } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - "tagid": "6", - "ext": { - "mobfoxpb": { - "TagID": "6" - } + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] + "expectedMakeRequestsErrors": [ + { + "value": "Invalid or non existing key and tagId, atleast one should be present", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json index 2f834c92be7..d61cb8837c4 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_response.json @@ -1,87 +1,85 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "17", - "ext": { - "bidder": { - "TagID": "17" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "17", - "ext": { - "bidder": { - "TagID": "17" + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "17" + } } - } } - ], - "app": { + ], + "app": { "id": "1", "bundle": "com.wls.testwlsapplication" - }, - "device": { + }, + "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 openrtb.BidResponse", - "comparison": "literal" - } - ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "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" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_status_code.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_status_code.json index 96d3a649109..60ee36e48e3 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_status_code.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/bad_status_code.json @@ -1,81 +1,79 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "100000000" + } + } } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - "tagid": "100000000", - "ext": { - "bidder": { - "TagID": "100000000" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" + "device": {} }, - "device": {} - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "100000000", - "ext": { - "bidder": { - "TagID": "100000000" + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": {} } - } + }, + "mockResponse": { + "status": 400, + "body": {} } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": {} } - }, - "mockResponse": { - "status": 400, - "body": {} - } - } - ], - "expectedMakeBidsErrors": [ - { - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] + ], + "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/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_empty_object.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_empty_object.json index cc56fa25c2c..9179916e922 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_empty_object.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_empty_object.json @@ -1,38 +1,37 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": {} } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - "tagid": "6", - "ext": {} - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] + "expectedMakeRequestsErrors": [ + { + "value": "Invalid or non existing key and tagId, atleast one should be present", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_string.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_string.json index 464c9e31e39..45c32a1aa63 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_string.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/imp_ext_string.json @@ -1,38 +1,37 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": "" } - ] + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" }, - "tagid": "6", - "ext": "" - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + } }, - "device": { - "ip": "123.123.123.123", - "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] + "expectedMakeRequestsErrors": [ + { + "value": "Invalid or non existing key and tagId, atleast one should be present", + "comparison": "literal" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/missmatch_bid_id.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/missmatch_bid_id.json new file mode 100644 index 00000000000..28a1b6c72d4 --- /dev/null +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/missmatch_bid_id.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "8" + } + } + } + ], + "site": { + "id": "1", + "domain": "test.com" + }, + "device": { + "ip": "123.123.123.123" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id-not", + "price": 0.27543, + "adm": "", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "w": 468, + "h": 60, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "mobfoxpb" + } + ], + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find impression \"test-imp-id-not\"", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-204.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-204.json index c1091969991..e69b248d2a1 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-204.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-204.json @@ -1,82 +1,80 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "17", - "ext": { - "bidder": { - "TagID": "17" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "17", - "ext": { - "bidder": { - "TagID": "17" + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "17" + } } - } } - ], - "app": { + ], + "app": { "id": "1", "bundle": "com.wls.testwlsapplication" - }, - "device": { + }, + "device": { "ip": "123.123.123.123", "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } } - }, - "expectedBidResponses": [], - "mockResponse": { - "status": 204, - "body": {} - } - } - ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "17" + } + } + } + ], + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "device": { + "ip": "123.123.123.123", + "ifa": "sdjfksdf-dfsds-dsdg-dsgg" + } + } + }, + "expectedBidResponses": [], + "mockResponse": { + "status": 204, + "body": {} + } + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-404.json b/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-404.json index d9ef7108017..987b9daf980 100644 --- a/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-404.json +++ b/adapters/mobfoxpb/mobfoxpbtest/supplemental/status-404.json @@ -1,87 +1,85 @@ { - "mockBidRequest": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "100000000", - "ext": { - "bidder": { - "TagID": "100000000" - } - } - } - ], - "app": { - "id": "1", - "bundle": "com.wls.testwlsapplication" - }, - "device": { - "ip": "123.123.123.123", - "ifa": "sdjfksdf-dfsds-dsdg-dsgg" - } - }, - "httpCalls": [ - { - "expectedRequest": { - "uri": "http://example.com/?c=o&m=ortb", - "body": { - "id": "test-request-id", - "imp": [ - { - "id": "test-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 250 - }, - { - "w": 300, - "h": 600 - } - ] - }, - "tagid": "100000000", - "ext": { - "bidder": { - "TagID": "100000000" + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "100000000" + } } - } } - ], - "app": { + ], + "app": { "id": "1", "bundle": "com.wls.testwlsapplication" - }, - "device": { + }, + "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" - } - ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/?c=o&m=ortb&key=__key__", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "TagID": "100000000" + } + } + } + ], + "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" + } + ] } \ No newline at end of file diff --git a/adapters/mobfoxpb/params_test.go b/adapters/mobfoxpb/params_test.go index ddd738ece2b..799fdcfa61b 100644 --- a/adapters/mobfoxpb/params_test.go +++ b/adapters/mobfoxpb/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // TestValidParams makes sure that the mobfoxpb schema accepts all imp.ext fields which we intend to support. @@ -37,6 +37,7 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"TagID": "6"}`, + `{"key": "1"}`, } var invalidParams = []string{ @@ -44,4 +45,7 @@ var invalidParams = []string{ `{"tagid": "123"}`, `{"TagID": 16}`, `{"TagID": ""}`, + `{"Key": "1"}`, + `{"key": 1}`, + `{"key":""}`, } diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go index 4e545ec1adc..47ee1cab743 100644 --- a/adapters/mobilefuse/mobilefuse.go +++ b/adapters/mobilefuse/mobilefuse.go @@ -7,12 +7,12 @@ import ( "strconv" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 MobileFuseAdapter struct { @@ -32,7 +32,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (adapter *MobileFuseAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *MobileFuseAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var adapterRequests []*adapters.RequestData adapterRequest, errs := adapter.makeRequest(request) @@ -44,7 +44,7 @@ func (adapter *MobileFuseAdapter) MakeRequests(request *openrtb.BidRequest, reqI return adapterRequests, errs } -func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb.BidRequest, outgoingRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest, outgoingRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -61,7 +61,7 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb.BidRequest, }} } - var incomingBidResponse openrtb.BidResponse + var incomingBidResponse openrtb2.BidResponse if err := json.Unmarshal(response.Body, &incomingBidResponse); err != nil { return nil, []error{err} @@ -81,7 +81,7 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb.BidRequest, return outgoingBidResponse, nil } -func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error mobileFuseExtension, errs := adapter.getFirstMobileFuseExtension(bidRequest) @@ -124,7 +124,7 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb.BidRequest) (* }, errs } -func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) { +func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) { var mobileFuseImpExtension openrtb_ext.ExtImpMobileFuse var errs []error @@ -167,8 +167,8 @@ func (adapter *MobileFuseAdapter) getEndpoint(ext *openrtb_ext.ExtImpMobileFuse) return url, nil } -func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) []openrtb.Imp { - var validImps []openrtb.Imp +func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb2.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) []openrtb2.Imp { + var validImps []openrtb2.Imp for _, imp := range bidRequest.Imp { if imp.Banner != nil || imp.Video != nil { @@ -187,7 +187,7 @@ func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb.BidRequest, e return validImps } -func (adapter *MobileFuseAdapter) getBidType(imp_id string, imps []openrtb.Imp) openrtb_ext.BidType { +func (adapter *MobileFuseAdapter) getBidType(imp_id string, imps []openrtb2.Imp) openrtb_ext.BidType { if imps[0].Video != nil { return openrtb_ext.BidTypeVideo } diff --git a/adapters/mobilefuse/mobilefuse_test.go b/adapters/mobilefuse/mobilefuse_test.go index 52d2ab20768..3abe627fab0 100644 --- a/adapters/mobilefuse/mobilefuse_test.go +++ b/adapters/mobilefuse/mobilefuse_test.go @@ -3,9 +3,9 @@ package mobilefuse import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/mobilefuse/params_test.go b/adapters/mobilefuse/params_test.go index 6d98f656983..dbfd8894e70 100644 --- a/adapters/mobilefuse/params_test.go +++ b/adapters/mobilefuse/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(test *testing.T) { diff --git a/adapters/nanointeractive/nanointeractive.go b/adapters/nanointeractive/nanointeractive.go index 9dfee06eb42..a2ec89b0d5b 100644 --- a/adapters/nanointeractive/nanointeractive.go +++ b/adapters/nanointeractive/nanointeractive.go @@ -5,21 +5,21 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp var adapterRequests []*adapters.RequestData var referer string = "" @@ -47,7 +47,7 @@ func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb.BidRequest, re // set referer origin if referer != "" { if bidRequest.Site == nil { - bidRequest.Site = &openrtb.Site{} + bidRequest.Site = &openrtb2.Site{} } bidRequest.Site.Ref = referer } @@ -88,21 +88,23 @@ func (a *NanoInteractiveAdapter) MakeRequests(bidRequest *openrtb.BidRequest, re } func (a *NanoInteractiveAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + 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{adapters.BadInput("Invalid request.")} + 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 openrtb.BidResponse + var openRtbBidResponse openrtb2.BidResponse if err := json.Unmarshal(response.Body, &openRtbBidResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ @@ -127,7 +129,7 @@ func (a *NanoInteractiveAdapter) MakeBids( return bidResponse, nil } -func checkImp(imp *openrtb.Imp) (string, error) { +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) diff --git a/adapters/nanointeractive/nanointeractive_test.go b/adapters/nanointeractive/nanointeractive_test.go index 79caf9cf417..d0955511f8b 100644 --- a/adapters/nanointeractive/nanointeractive_test.go +++ b/adapters/nanointeractive/nanointeractive_test.go @@ -3,9 +3,9 @@ package nanointeractive import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/nanointeractive/params_test.go b/adapters/nanointeractive/params_test.go index 309d19b5128..b290f3d94b1 100644 --- a/adapters/nanointeractive/params_test.go +++ b/adapters/nanointeractive/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/nanointeractive.json diff --git a/adapters/nanointeractive/usersync.go b/adapters/nanointeractive/usersync.go index 180e2c53520..6bd9cd1f036 100644 --- a/adapters/nanointeractive/usersync.go +++ b/adapters/nanointeractive/usersync.go @@ -3,10 +3,10 @@ package nanointeractive import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewNanoInteractiveSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("nanointeractive", 72, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("nanointeractive", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/nanointeractive/usersync_test.go b/adapters/nanointeractive/usersync_test.go index 44040756316..4d816ab7384 100644 --- a/adapters/nanointeractive/usersync_test.go +++ b/adapters/nanointeractive/usersync_test.go @@ -4,10 +4,10 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -31,6 +31,5 @@ func TestNewNanoInteractiveSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr=1&consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&redirectUri=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 72, userSync.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/ninthdecimal/ninthdecimal.go b/adapters/ninthdecimal/ninthdecimal.go index 5cb631db6f1..fc29b38cdab 100755 --- a/adapters/ninthdecimal/ninthdecimal.go +++ b/adapters/ninthdecimal/ninthdecimal.go @@ -6,12 +6,12 @@ import ( "net/http" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 { @@ -19,7 +19,7 @@ type NinthDecimalAdapter struct { } //MakeRequests prepares request information for prebid-server core -func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +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"}) @@ -49,10 +49,10 @@ func (adapter *NinthDecimalAdapter) MakeRequests(request *openrtb.BidRequest, re } // getImpressionsInfo checks each impression for validity and returns impressions copy with corresponding exts -func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpNinthDecimal][]openrtb.Imp, []openrtb.Imp, []error) { +func getImpressionsInfo(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpNinthDecimal][]openrtb2.Imp, []openrtb2.Imp, []error) { errors := make([]error, 0, len(imps)) - resImps := make([]openrtb.Imp, 0, len(imps)) - res := make(map[openrtb_ext.ExtImpNinthDecimal][]openrtb.Imp) + resImps := make([]openrtb2.Imp, 0, len(imps)) + res := make(map[openrtb_ext.ExtImpNinthDecimal][]openrtb2.Imp) for _, imp := range imps { impExt, err := getImpressionExt(&imp) @@ -71,7 +71,7 @@ func getImpressionsInfo(imps []openrtb.Imp) (map[openrtb_ext.ExtImpNinthDecimal] continue } if res[*impExt] == nil { - res[*impExt] = make([]openrtb.Imp, 0) + res[*impExt] = make([]openrtb2.Imp, 0) } res[*impExt] = append(res[*impExt], imp) resImps = append(resImps, imp) @@ -87,7 +87,7 @@ func validateImpression(impExt *openrtb_ext.ExtImpNinthDecimal) error { } //Alter impression info to comply with NinthDecimal platform requirements -func compatImpression(imp *openrtb.Imp) error { +func compatImpression(imp *openrtb2.Imp) error { imp.Ext = nil //do not forward ext to NinthDecimal platform if imp.Banner != nil { return compatBannerImpression(imp) @@ -95,7 +95,7 @@ func compatImpression(imp *openrtb.Imp) error { return nil } -func compatBannerImpression(imp *openrtb.Imp) error { +func compatBannerImpression(imp *openrtb2.Imp) error { // Create a copy of the banner, since imp is a shallow copy of the original. bannerCopy := *imp.Banner @@ -114,7 +114,7 @@ func compatBannerImpression(imp *openrtb.Imp) error { return nil } -func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpNinthDecimal, error) { +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{ @@ -130,7 +130,7 @@ func getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtImpNinthDecimal, error) return &NinthDecimalExt, nil } -func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb.Imp) (*adapters.RequestData, error) { +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 { @@ -154,7 +154,7 @@ func (adapter *NinthDecimalAdapter) buildAdapterRequest(prebidBidRequest *openrt Headers: headers}, nil } -func createBidRequest(prebidBidRequest *openrtb.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb.Imp) *openrtb.BidRequest { +func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params *openrtb_ext.ExtImpNinthDecimal, imps []openrtb2.Imp) *openrtb2.BidRequest { bidRequest := *prebidBidRequest bidRequest.Imp = imps for idx := range bidRequest.Imp { @@ -184,7 +184,7 @@ func (adapter *NinthDecimalAdapter) buildEndpointURL(params *openrtb_ext.ExtImpN } //MakeBids translates NinthDecimal bid response to prebid-server specific format -func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +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 @@ -194,7 +194,7 @@ func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb.BidRequest return nil, []error{&errortypes.BadServerResponse{Message: msg}} } - var bidResp openrtb.BidResponse + 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}} @@ -218,7 +218,7 @@ func (adapter *NinthDecimalAdapter) MakeBids(internalRequest *openrtb.BidRequest } // getMediaTypeForImp figures out which media type this bid is for -func getMediaTypeForImpID(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +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 diff --git a/adapters/ninthdecimal/ninthdecimal_test.go b/adapters/ninthdecimal/ninthdecimal_test.go index 9d82f38783b..ccb8114f8a9 100755 --- a/adapters/ninthdecimal/ninthdecimal_test.go +++ b/adapters/ninthdecimal/ninthdecimal_test.go @@ -3,9 +3,9 @@ package ninthdecimal import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/ninthdecimal/params_test.go b/adapters/ninthdecimal/params_test.go index cc06088caff..8d3ef3d706f 100755 --- a/adapters/ninthdecimal/params_test.go +++ b/adapters/ninthdecimal/params_test.go @@ -2,7 +2,7 @@ package ninthdecimal import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/ninthdecimal/usersync.go b/adapters/ninthdecimal/usersync.go index 4ea91d031cb..a01fdb636e3 100755 --- a/adapters/ninthdecimal/usersync.go +++ b/adapters/ninthdecimal/usersync.go @@ -3,10 +3,10 @@ package ninthdecimal import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewNinthDecimalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("ninthdecimal", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("ninthdecimal", temp, adapters.SyncTypeIframe) } diff --git a/adapters/ninthdecimal/usersync_test.go b/adapters/ninthdecimal/usersync_test.go index ded5bf0dbf4..e722a2b6e69 100755 --- a/adapters/ninthdecimal/usersync_test.go +++ b/adapters/ninthdecimal/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -26,6 +26,5 @@ func TestNinthDecimalSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect=localhost/setuid?bidder=ninthdecimal&gdpr=1&gdpr_consent=A&uid=$UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/nobid/nobid.go b/adapters/nobid/nobid.go index 77d1a7f806f..f8db812d9ca 100644 --- a/adapters/nobid/nobid.go +++ b/adapters/nobid/nobid.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) // NoBidAdapter - NoBid Adapter definition @@ -26,7 +26,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests Makes the OpenRTB request payload -func (a *NoBidAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *NoBidAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{&errortypes.BadInput{ @@ -52,7 +52,7 @@ func (a *NoBidAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte } // MakeBids makes the bids -func (a *NoBidAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *NoBidAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -70,7 +70,7 @@ func (a *NoBidAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -98,7 +98,7 @@ func (a *NoBidAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return bidResponse, errs } -func getBidCount(bidResponse openrtb.BidResponse) int { +func getBidCount(bidResponse openrtb2.BidResponse) int { c := 0 for _, sb := range bidResponse.SeatBid { c = c + len(sb.Bid) @@ -106,7 +106,7 @@ func getBidCount(bidResponse openrtb.BidResponse) int { return c } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/nobid/nobid_test.go b/adapters/nobid/nobid_test.go index 420c57f36bc..674d189d661 100644 --- a/adapters/nobid/nobid_test.go +++ b/adapters/nobid/nobid_test.go @@ -3,9 +3,9 @@ package nobid import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/nobid/params_test.go b/adapters/nobid/params_test.go index f8c0533c300..75d69943d35 100644 --- a/adapters/nobid/params_test.go +++ b/adapters/nobid/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/nobid/usersync.go b/adapters/nobid/usersync.go index 3b36e59fa3d..442075648ce 100644 --- a/adapters/nobid/usersync.go +++ b/adapters/nobid/usersync.go @@ -3,10 +3,10 @@ package nobid import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewNoBidSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("nobid", 816, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("nobid", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/nobid/usersync_test.go b/adapters/nobid/usersync_test.go index a59d70a4f91..bc55d130509 100644 --- a/adapters/nobid/usersync_test.go +++ b/adapters/nobid/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) diff --git a/adapters/onetag/onetag.go b/adapters/onetag/onetag.go new file mode 100644 index 00000000000..1721d76df6a --- /dev/null +++ b/adapters/onetag/onetag.go @@ -0,0 +1,152 @@ +package onetag + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb/v15/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 +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (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, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + pubID := "" + for idx, imp := range request.Imp { + onetagExt, err := getImpressionExt(imp) + if err != nil { + return nil, []error{err} + } + if onetagExt.PubId != "" { + if pubID == "" { + pubID = onetagExt.PubId + } else if pubID != onetagExt.PubId { + return nil, []error{&errortypes.BadInput{ + Message: "There must be only one publisher ID", + }} + } + } else { + return nil, []error{&errortypes.BadInput{ + Message: "The publisher ID must not be empty", + }} + } + request.Imp[idx].Ext = onetagExt.Ext + } + + url, err := a.buildEndpointURL(pubID) + 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: url, + Body: requestJSON, + } + + return []*adapters.RequestData{requestData}, nil +} + +func getImpressionExt(imp openrtb2.Imp) (*openrtb_ext.ExtImpOnetag, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Bidder extension not provided or can't be unmarshalled", + } + } + + var onetagExt openrtb_ext.ExtImpOnetag + if err := json.Unmarshal(bidderExt.Bidder, &onetagExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error while unmarshaling bidder extension", + } + } + + return &onetagExt, nil +} + +func (a *adapter) buildEndpointURL(pubID string) (string, error) { + endpointParams := macros.EndpointTemplateParams{PublisherID: pubID} + return macros.ResolveMacros(a.endpointTemplate, endpointParams) +} + +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.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)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bidMediaType, err := getMediaTypeForBid(request.Imp, bid) + if err != nil { + return nil, []error{err} + } + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidMediaType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getMediaTypeForBid(impressions []openrtb2.Imp, bid openrtb2.Bid) (openrtb_ext.BidType, error) { + for _, impression := range impressions { + if impression.ID == bid.ImpID { + if impression.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + if impression.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } + if impression.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + } + } + + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("The impression with ID %s is not present into the request", bid.ImpID), + } +} diff --git a/adapters/onetag/onetag_test.go b/adapters/onetag/onetag_test.go new file mode 100644 index 00000000000..9f7c8e50115 --- /dev/null +++ b/adapters/onetag/onetag_test.go @@ -0,0 +1,26 @@ +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/stretchr/testify/assert" +) + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderOneTag, config.Adapter{ + Endpoint: "{{Malformed}}"}) + + assert.Error(t, buildErr) +} + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderOneTag, config.Adapter{ + Endpoint: "https://example.com/prebid-server/{{.PublisherID}}"}) + + assert.NoError(t, buildErr, "Builder returned unexpected error %v", buildErr) + + adapterstest.RunJSONBidderTest(t, "onetagtest", bidder) +} diff --git a/adapters/onetag/onetagtest/exemplary/no-bid.json b/adapters/onetag/onetagtest/exemplary/no-bid.json new file mode 100644 index 00000000000..012834ca8a4 --- /dev/null +++ b/adapters/onetag/onetagtest/exemplary/no-bid.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/exemplary/simple-banner.json b/adapters/onetag/onetagtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..7489a27dbff --- /dev/null +++ b/adapters/onetag/onetagtest/exemplary/simple-banner.json @@ -0,0 +1,201 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "bidder": { + "pubId": "386276e072", + "ext": { + "key1": "value1", + "key2": "value2" + } + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05 + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "key1": "value1", + "key2": "value2" + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "BID-4-ZIMP-4b309eae-504a-4252-a8a8-4c8ceee9791a", + "seatbid": [ + { + "bid": [ + { + "adid": "52a12b5955314b7194a4c9ff", + "adm": "", + "adomain": ["ads.com"], + "cid": "52a12b5955314b7194a4c9ff", + "crid": "52a12b5955314b7194a4c9ff_1386294105", + "dealid": "DX-1985-010A", + "id": "24195efda36066ee21f967bc1de14c82db841f07", + "impid": "121-dt1", + "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}", + "price": 1.028428, + "attr": [] + } + ], + "seat": "42" + }, + { + "bid": [ + { + "adid": "527c9fdd55314ba06815f25e", + "adm": "", + "adomain": ["ads.com"], + "cid": "527c9fdd55314ba06815f25e", + "crid": "527c9fdd55314ba06815f25e_1383899102", + "id": "24195efda36066ee21f967bc1de14c82db841f08", + "impid": "121-dt2", + "nurl": "http://ads.com/win/527c9fdd55314ba06815f25e?won=${AUCTION_PRICE}", + "price": 0.04958, + "attr": [] + } + ], + "seat": "772" + } + ] + }, + "cur": "USD" + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "type": "banner", + "bid": { + "adid": "52a12b5955314b7194a4c9ff", + "adm": "", + "adomain": ["ads.com"], + "cid": "52a12b5955314b7194a4c9ff", + "crid": "52a12b5955314b7194a4c9ff_1386294105", + "dealid": "DX-1985-010A", + "id": "24195efda36066ee21f967bc1de14c82db841f07", + "impid": "121-dt1", + "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}", + "price": 1.028428 + } + }, + { + "type": "banner", + "bid": { + "adid": "527c9fdd55314ba06815f25e", + "adm": "", + "adomain": ["ads.com"], + "cid": "527c9fdd55314ba06815f25e", + "crid": "527c9fdd55314ba06815f25e_1383899102", + "id": "24195efda36066ee21f967bc1de14c82db841f08", + "impid": "121-dt2", + "nurl": "http://ads.com/win/527c9fdd55314ba06815f25e?won=${AUCTION_PRICE}", + "price": 0.04958 + } + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/exemplary/simple-native.json b/adapters/onetag/onetagtest/exemplary/simple-native.json new file mode 100644 index 00000000000..08fef34f7a7 --- /dev/null +++ b/adapters/onetag/onetagtest/exemplary/simple-native.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "pubId": "386276e072", + "ext": { + "key1": "value1" + } + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "key1": "value1" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + } + ], + "seat": "onetag" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids":[ + { + "type": "native", + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ] + } + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/exemplary/simple-video.json b/adapters/onetag/onetagtest/exemplary/simple-video.json new file mode 100644 index 00000000000..ea656a98fc8 --- /dev/null +++ b/adapters/onetag/onetagtest/exemplary/simple-video.json @@ -0,0 +1,115 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + } + } + ] + } + }, + "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" + } + ], + "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" + }, + "type": "video" + } + ] + } + ] + } \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/banner.json b/adapters/onetag/onetagtest/params/race/banner.json new file mode 100644 index 00000000000..687438d6be6 --- /dev/null +++ b/adapters/onetag/onetagtest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "pubId": "386276e072", + "ext": {} +} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/native.json b/adapters/onetag/onetagtest/params/race/native.json new file mode 100644 index 00000000000..687438d6be6 --- /dev/null +++ b/adapters/onetag/onetagtest/params/race/native.json @@ -0,0 +1,4 @@ +{ + "pubId": "386276e072", + "ext": {} +} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/params/race/video.json b/adapters/onetag/onetagtest/params/race/video.json new file mode 100644 index 00000000000..687438d6be6 --- /dev/null +++ b/adapters/onetag/onetagtest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "pubId": "386276e072", + "ext": {} +} \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json b/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json new file mode 100644 index 00000000000..d5ca3b144bc --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/empty-publisher-id.json @@ -0,0 +1,65 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": { + "pubId" : "1" + } + } + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "bidder": { + "pubId" : "" + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "The publisher ID must not be empty", + "comparison": "literal" + } + ] +} + \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/internal-server-error.json b/adapters/onetag/onetagtest/supplemental/internal-server-error.json new file mode 100644 index 00000000000..4fc069598c7 --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/internal-server-error.json @@ -0,0 +1,81 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + } + } + ] + } + }, + "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/adapters/onetag/onetagtest/supplemental/required-publisher-id.json b/adapters/onetag/onetagtest/supplemental/required-publisher-id.json new file mode 100644 index 00000000000..6e8ccf0d3cc --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/required-publisher-id.json @@ -0,0 +1,66 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": {} + } + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "bidder": { + "ext": { + "key1": "value1", + "key2": "value2" + } + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "The publisher ID must not be empty", + "comparison": "literal" + } + ] +} + \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json b/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json new file mode 100644 index 00000000000..fe2e3914fae --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/unique-publisher-id.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + }, + { + "id": "121-dt2", + "banner": { + "h": 728, + "w": 90, + "pos": 0 + }, + "bidfloor": 0.12, + "ext": { + "bidder": { + "pubId": "386276e072a", + "ext": { + "key1": "value1", + "key2": "value2" + } + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "There must be only one publisher ID", + "comparison": "literal" + } + ] +} + \ No newline at end of file diff --git a/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json b/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json new file mode 100644 index 00000000000..bfdfcc5f3e3 --- /dev/null +++ b/adapters/onetag/onetagtest/supplemental/wrong-impression-id.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05, + "ext": { + "bidder": { + "pubId": "386276e072" + } + } + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://example.com/prebid-server/386276e072", + "headers": {}, + "body": { + "id": "8652a8680db33faabbf3fa76150f35df50a67060", + "imp": [ + { + "id": "121-dt1", + "banner": { + "h": 250, + "w": 300, + "pos": 1 + }, + "bidfloor": 0.05 + } + ], + "site": { + "id": "15047", + "domain": "dailymotion.com", + "cat": ["IAB1"], + "page": "http://www.dailymotion.com/video/xxeauj_www-dramacafe-tv-hd-yyyy-yy-yyyyyyy-2012-yyyy_shortfilms", + "publisher": { + "id": "8796", + "name": "dailymotion", + "cat": ["IAB3-1"], + "domain": "dailymotion.com" + } + }, + "user": { + "id": "518c3da3717203f34019b038" + }, + "device": { + "ua": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; (R1 1.6); SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)", + "ip": "123.145.167.189" + }, + "at": 1, + "cur": [ + "USD" + ] + } + }, + "mockResponse": { + "status": 200, + "headers": {}, + "body": { + "id": "BID-4-ZIMP-4b309eae-504a-4252-a8a8-4c8ceee9791a", + "seatbid": [ + { + "bid": [ + { + "adid": "52a12b5955314b7194a4c9ff", + "adm": "", + "adomain": ["ads.com"], + "cid": "52a12b5955314b7194a4c9ff", + "crid": "52a12b5955314b7194a4c9ff_1386294105", + "dealid": "DX-1985-010A", + "id": "24195efda36066ee21f967bc1de14c82db841f07", + "impid": "1", + "nurl": "http://ads.com/win/52a12b5955314b7194a4c9ff?won=${AUCTION_PRICE}", + "price": 1.028428, + "attr": [] + } + ], + "seat": "42" + } + ] + }, + "cur": "USD" + } + } + ], + "expectedMakeBidsErrors": [{ + "value": "The impression with ID 1 is not present into the request", + "comparison": "literal" + }] +} \ No newline at end of file diff --git a/adapters/onetag/params_test.go b/adapters/onetag/params_test.go new file mode 100644 index 00000000000..4c7326ac9f0 --- /dev/null +++ b/adapters/onetag/params_test.go @@ -0,0 +1,54 @@ +package onetag + +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.BidderOneTag, 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.BidderOneTag, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{ "pubId": "386276e072", + "ext": { + "key1": "value1", + "key2": "value2" + } + }`, + `{"pubId": "386276e072"}`, +} + +var invalidParams = []string{ + `{"ext": { + "key1": "value1", + "key2": "value2" + }`, + `{}`, + `{"pubId": ""}`, + `{"pubId": 123}`, +} diff --git a/adapters/onetag/usersync.go b/adapters/onetag/usersync.go new file mode 100644 index 00000000000..9a2b700dd3d --- /dev/null +++ b/adapters/onetag/usersync.go @@ -0,0 +1,12 @@ +package onetag + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSyncer(template *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("onetag", template, adapters.SyncTypeIframe) +} diff --git a/adapters/onetag/usersync_test.go b/adapters/onetag/usersync_test.go new file mode 100644 index 00000000000..21f4837d5e1 --- /dev/null +++ b/adapters/onetag/usersync_test.go @@ -0,0 +1,23 @@ +package onetag + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestOneTagSyncer(t *testing.T) { + syncURL := "https://onetag-sys.com/usync/?redir=" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{}) + + assert.NoError(t, err) + assert.Equal(t, "https://onetag-sys.com/usync/?redir=", syncInfo.URL) + assert.Equal(t, "iframe", syncInfo.Type) +} diff --git a/adapters/openrtb_util.go b/adapters/openrtb_util.go index 88023920b8d..6aa07c6b764 100644 --- a/adapters/openrtb_util.go +++ b/adapters/openrtb_util.go @@ -3,10 +3,9 @@ package adapters import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/pbs" ) func min(x, y int) int { @@ -37,37 +36,37 @@ func commonMediaTypes(l1 []pbs.MediaType, l2 []pbs.MediaType) []pbs.MediaType { return res[:i] } -func makeBanner(unit pbs.PBSAdUnit) *openrtb.Banner { - return &openrtb.Banner{ - W: openrtb.Uint64Ptr(unit.Sizes[0].W), - H: openrtb.Uint64Ptr(unit.Sizes[0].H), +func makeBanner(unit pbs.PBSAdUnit) *openrtb2.Banner { + return &openrtb2.Banner{ + W: openrtb2.Int64Ptr(unit.Sizes[0].W), + H: openrtb2.Int64Ptr(unit.Sizes[0].H), Format: copyFormats(unit.Sizes), // defensive copy because adapters may mutate Imps, and this is shared data TopFrame: unit.TopFrame, } } -func makeVideo(unit pbs.PBSAdUnit) *openrtb.Video { +func makeVideo(unit pbs.PBSAdUnit) *openrtb2.Video { // empty mimes array is a sign of uninitialized Video object if len(unit.Video.Mimes) < 1 { return nil } mimes := make([]string, len(unit.Video.Mimes)) copy(mimes, unit.Video.Mimes) - pbm := make([]openrtb.PlaybackMethod, 1) + pbm := make([]openrtb2.PlaybackMethod, 1) //this will become int8 soon, so we only care about the first index in the array - pbm[0] = openrtb.PlaybackMethod(unit.Video.PlaybackMethod) + pbm[0] = openrtb2.PlaybackMethod(unit.Video.PlaybackMethod) - protocols := make([]openrtb.Protocol, 0, len(unit.Video.Protocols)) + protocols := make([]openrtb2.Protocol, 0, len(unit.Video.Protocols)) for _, protocol := range unit.Video.Protocols { - protocols = append(protocols, openrtb.Protocol(protocol)) + protocols = append(protocols, openrtb2.Protocol(protocol)) } - return &openrtb.Video{ + return &openrtb2.Video{ MIMEs: mimes, MinDuration: unit.Video.Minduration, MaxDuration: unit.Video.Maxduration, W: unit.Sizes[0].W, H: unit.Sizes[0].H, - StartDelay: openrtb.StartDelay(unit.Video.Startdelay).Ptr(), + StartDelay: openrtb2.StartDelay(unit.Video.Startdelay).Ptr(), PlaybackMethod: pbm, Protocols: protocols, } @@ -77,8 +76,8 @@ func makeVideo(unit pbs.PBSAdUnit) *openrtb.Video { // // Any objects pointed to by the returned BidRequest *must not be mutated*, or we will get race conditions. // The only exception is the Imp property, whose objects will be created new by this method and can be mutated freely. -func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily string, allowedMediatypes []pbs.MediaType) (openrtb.BidRequest, error) { - imps := make([]openrtb.Imp, 0, len(bidder.AdUnits)*len(allowedMediatypes)) +func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily string, allowedMediatypes []pbs.MediaType) (openrtb2.BidRequest, error) { + imps := make([]openrtb2.Imp, 0, len(bidder.AdUnits)*len(allowedMediatypes)) for _, unit := range bidder.AdUnits { if len(unit.Sizes) <= 0 { continue @@ -88,7 +87,7 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily continue } - newImp := openrtb.Imp{ + newImp := openrtb2.Imp{ ID: unit.Code, Secure: &req.Secure, Instl: unit.Instl, @@ -101,7 +100,7 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily newImp.Video = makeVideo(unit) // It's strange to error here... but preserves legacy behavior in legacy code. See #603. if newImp.Video == nil { - return openrtb.BidRequest{}, &errortypes.BadInput{ + return openrtb2.BidRequest{}, &errortypes.BadInput{ Message: "Invalid AdUnit: VIDEO media type with no video data", } } @@ -113,19 +112,19 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily } if len(imps) < 1 { - return openrtb.BidRequest{}, &errortypes.BadInput{ + return openrtb2.BidRequest{}, &errortypes.BadInput{ Message: "openRTB bids need at least one Imp", } } if req.App != nil { - return openrtb.BidRequest{ + return openrtb2.BidRequest{ ID: req.Tid, Imp: imps, App: req.App, Device: req.Device, User: req.User, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: req.Tid, }, AT: 1, @@ -142,20 +141,20 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily userExt = req.User.Ext } - return openrtb.BidRequest{ + return openrtb2.BidRequest{ ID: req.Tid, Imp: imps, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Domain: req.Domain, Page: req.Url, }, Device: req.Device, - User: &openrtb.User{ + User: &openrtb2.User{ BuyerUID: buyerUID, ID: id, Ext: userExt, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ FD: 1, // upstream, aka header TID: req.Tid, }, @@ -165,8 +164,8 @@ func MakeOpenRTBGeneric(req *pbs.PBSRequest, bidder *pbs.PBSBidder, bidderFamily }, nil } -func copyFormats(sizes []openrtb.Format) []openrtb.Format { - sizesCopy := make([]openrtb.Format, len(sizes)) +func copyFormats(sizes []openrtb2.Format) []openrtb2.Format { + sizesCopy := make([]openrtb2.Format, len(sizes)) for i := 0; i < len(sizes); i++ { sizesCopy[i] = sizes[i] sizesCopy[i].Ext = append([]byte(nil), sizes[i].Ext...) diff --git a/adapters/openrtb_util_test.go b/adapters/openrtb_util_test.go index fbb9ab57991..b7d03fbfc6c 100644 --- a/adapters/openrtb_util_test.go +++ b/adapters/openrtb_util_test.go @@ -5,9 +5,9 @@ import ( "encoding/json" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" ) @@ -42,7 +42,7 @@ func TestOpenRTB(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -73,7 +73,7 @@ func TestOpenRTBVideo(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -96,8 +96,8 @@ func TestOpenRTBVideo(t *testing.T) { assert.Equal(t, resp.Imp[0].ID, "unitCode") assert.EqualValues(t, resp.Imp[0].Video.MaxDuration, 30) assert.EqualValues(t, resp.Imp[0].Video.MinDuration, 15) - assert.EqualValues(t, *resp.Imp[0].Video.StartDelay, openrtb.StartDelay(5)) - assert.EqualValues(t, resp.Imp[0].Video.PlaybackMethod, []openrtb.PlaybackMethod{openrtb.PlaybackMethod(1)}) + assert.EqualValues(t, *resp.Imp[0].Video.StartDelay, openrtb2.StartDelay(5)) + assert.EqualValues(t, resp.Imp[0].Video.PlaybackMethod, []openrtb2.PlaybackMethod{openrtb2.PlaybackMethod(1)}) assert.EqualValues(t, resp.Imp[0].Video.MIMEs, []string{"video/mp4"}) } @@ -110,7 +110,7 @@ func TestOpenRTBVideoNoVideoData(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -134,7 +134,7 @@ func TestOpenRTBVideoFilteredOut(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -152,7 +152,7 @@ func TestOpenRTBVideoFilteredOut(t *testing.T) { { Code: "unitCode2", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -179,7 +179,7 @@ func TestOpenRTBMultiMediaImp(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -215,7 +215,7 @@ func TestOpenRTBMultiMediaImpFiltered(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO, pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -237,7 +237,7 @@ func TestOpenRTBMultiMediaImpFiltered(t *testing.T) { assert.Equal(t, len(resp.Imp), 1) assert.Equal(t, resp.Imp[0].ID, "unitCode") assert.EqualValues(t, *resp.Imp[0].Banner.W, 10) - assert.EqualValues(t, resp.Imp[0].Video, (*openrtb.Video)(nil)) + assert.EqualValues(t, resp.Imp[0].Video, (*openrtb2.Video)(nil)) } func TestOpenRTBNoSize(t *testing.T) { @@ -267,20 +267,20 @@ func TestOpenRTBMobile(t *testing.T) { MaxKeyLength: 20, Secure: 1, TimeoutMillis: 1000, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "1995257847363113", }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "test_ua", IP: "test_ip", Make: "test_make", Model: "test_model", IFA: "test_ifa", }, - User: &openrtb.User{ + User: &openrtb2.User{ BuyerUID: "test_buyeruid", }, } @@ -290,7 +290,7 @@ func TestOpenRTBMobile(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 250, @@ -318,7 +318,7 @@ func TestOpenRTBMobile(t *testing.T) { func TestOpenRTBEmptyUser(t *testing.T) { pbReq := pbs.PBSRequest{ - User: &openrtb.User{}, + User: &openrtb2.User{}, } pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", @@ -326,7 +326,7 @@ func TestOpenRTBEmptyUser(t *testing.T) { { Code: "unitCode2", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -337,14 +337,14 @@ func TestOpenRTBEmptyUser(t *testing.T) { } resp, err := MakeOpenRTBGeneric(&pbReq, &pbBidder, "test", []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}) assert.Equal(t, err, nil) - assert.EqualValues(t, resp.User, &openrtb.User{}) + assert.EqualValues(t, resp.User, &openrtb2.User{}) } func TestOpenRTBUserWithCookie(t *testing.T) { pbsCookie := usersync.NewPBSCookie() pbsCookie.TrySync("test", "abcde") pbReq := pbs.PBSRequest{ - User: &openrtb.User{}, + User: &openrtb2.User{}, } pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", @@ -352,7 +352,7 @@ func TestOpenRTBUserWithCookie(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 250, @@ -368,7 +368,7 @@ func TestOpenRTBUserWithCookie(t *testing.T) { } func TestSizesCopy(t *testing.T) { - formats := []openrtb.Format{ + formats := []openrtb2.Format{ { W: 10, }, @@ -402,7 +402,7 @@ func TestMakeVideo(t *testing.T) { adUnit := pbs.PBSAdUnit{ Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -421,7 +421,7 @@ func TestMakeVideo(t *testing.T) { video := makeVideo(adUnit) assert.EqualValues(t, video.MinDuration, 15) assert.EqualValues(t, video.MaxDuration, 30) - assert.EqualValues(t, *video.StartDelay, openrtb.StartDelay(5)) + assert.EqualValues(t, *video.StartDelay, openrtb2.StartDelay(5)) assert.EqualValues(t, len(video.PlaybackMethod), 1) assert.EqualValues(t, len(video.Protocols), 4) } @@ -435,10 +435,10 @@ func TestGDPR(t *testing.T) { regsExt, _ := json.Marshal(rawRegsExt) pbReq := pbs.PBSRequest{ - User: &openrtb.User{ + User: &openrtb2.User{ Ext: userExt, }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: regsExt, }, } @@ -449,7 +449,7 @@ func TestGDPR(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -486,24 +486,24 @@ func TestGDPRMobile(t *testing.T) { MaxKeyLength: 20, Secure: 1, TimeoutMillis: 1000, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "AppNexus.PrebidMobileDemo", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "1995257847363113", }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "test_ua", IP: "test_ip", Make: "test_make", Model: "test_model", IFA: "test_ifa", }, - User: &openrtb.User{ + User: &openrtb2.User{ BuyerUID: "test_buyeruid", Ext: userExt, }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: regsExt, }, } @@ -513,7 +513,7 @@ func TestGDPRMobile(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 300, H: 250, diff --git a/adapters/openx/openx.go b/adapters/openx/openx.go index 21fbd37e99f..208b06f7c86 100644 --- a/adapters/openx/openx.go +++ b/adapters/openx/openx.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const hbconfig = "hb_pbs_1.0.0" @@ -28,10 +28,10 @@ type openxReqExt struct { BidderConfig string `json:"bc"` } -func (a *OpenxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *OpenxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var bannerImps []openrtb.Imp - var videoImps []openrtb.Imp + var bannerImps []openrtb2.Imp + var videoImps []openrtb2.Imp for _, imp := range request.Imp { // OpenX doesn't allow multi-type imp. Banner takes priority over video. @@ -55,7 +55,7 @@ func (a *OpenxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte // OpenX only supports single imp video request for _, videoImp := range videoImps { - reqCopy.Imp = []openrtb.Imp{videoImp} + reqCopy.Imp = []openrtb2.Imp{videoImp} adapterReq, errors := a.makeRequest(&reqCopy) if adapterReq != nil { adapterRequests = append(adapterRequests, adapterReq) @@ -66,9 +66,9 @@ func (a *OpenxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapte return adapterRequests, errs } -func (a *OpenxAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *OpenxAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp reqExt := openxReqExt{BidderConfig: hbconfig} for _, imp := range request.Imp { @@ -111,7 +111,7 @@ func (a *OpenxAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Reque } // Mutate the imp to get it ready to send to openx. -func preprocess(imp *openrtb.Imp, reqExt *openxReqExt) error { +func preprocess(imp *openrtb2.Imp, reqExt *openxReqExt) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -130,7 +130,9 @@ func preprocess(imp *openrtb.Imp, reqExt *openxReqExt) error { reqExt.Platform = openxExt.Platform imp.TagID = openxExt.Unit - imp.BidFloor = openxExt.CustomFloor + if imp.BidFloor == 0 && openxExt.CustomFloor > 0 { + imp.BidFloor = openxExt.CustomFloor + } imp.Ext = nil if openxExt.CustomParams != nil { @@ -158,7 +160,7 @@ func preprocess(imp *openrtb.Imp, reqExt *openxReqExt) error { return nil } -func (a *OpenxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *OpenxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -175,7 +177,7 @@ func (a *OpenxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -202,7 +204,7 @@ func (a *OpenxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq // // OpenX doesn't support multi-type impressions. // If both banner and video exist, take banner as we do not want in-banner video. -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/openx/openx_test.go b/adapters/openx/openx_test.go index 5eec4feb41a..ea90dc875da 100644 --- a/adapters/openx/openx_test.go +++ b/adapters/openx/openx_test.go @@ -4,11 +4,11 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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/stretchr/testify/assert" ) @@ -41,10 +41,10 @@ func assertCurrencyInBidResponse(t *testing.T, expectedCurrency string, currency t.Fatalf("Builder returned unexpected error %v", buildErr) } - prebidRequest := &openrtb.BidRequest{ - Imp: []openrtb.Imp{}, + prebidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{}, } - mockedBidResponse := &openrtb.BidResponse{} + mockedBidResponse := &openrtb2.BidResponse{} if currency != nil { mockedBidResponse.Cur = *currency } diff --git a/adapters/openx/openxtest/exemplary/optional-params.json b/adapters/openx/openxtest/exemplary/optional-params.json index b2fd9c2f4fb..93dbafc5bfb 100644 --- a/adapters/openx/openxtest/exemplary/optional-params.json +++ b/adapters/openx/openxtest/exemplary/optional-params.json @@ -16,6 +16,21 @@ "customParams": {"foo": "bar"} } } + }, + { + "bidfloor": 0.5, + "id": "test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "ext": { + "bidder": { + "unit": "539439964", + "delDomain": "se-demo-d.openx.net", + "platform": "PLATFORM", + "customFloor": 0.1 + } + } } ] }, @@ -37,6 +52,14 @@ "ext": { "customParams": {"foo": "bar"} } + }, + { + "id":"test-imp-id", + "banner": { + "format": [{"w": 728, "h": 90}] + }, + "tagid": "539439964", + "bidfloor": 0.5 } ], "ext": { diff --git a/adapters/openx/params_test.go b/adapters/openx/params_test.go index 87ce08fc733..b7ea970ab1f 100644 --- a/adapters/openx/params_test.go +++ b/adapters/openx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/openx.json diff --git a/adapters/openx/usersync.go b/adapters/openx/usersync.go index f557e5e4095..875b60fbd10 100644 --- a/adapters/openx/usersync.go +++ b/adapters/openx/usersync.go @@ -3,10 +3,10 @@ package openx import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewOpenxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("openx", 69, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("openx", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/openx/usersync_test.go b/adapters/openx/usersync_test.go index 7bb30399069..14ec38be118 100644 --- a/adapters/openx/usersync_test.go +++ b/adapters/openx/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestOpenxSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://rtb.openx.net/sync/prebid?gdpr=&gdpr_consent=&r=localhost%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 69, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/orbidder/orbidder.go b/adapters/orbidder/orbidder.go index 4e13fc6f50e..77985c8dae0 100644 --- a/adapters/orbidder/orbidder.go +++ b/adapters/orbidder/orbidder.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 OrbidderAdapter struct { @@ -17,9 +17,9 @@ type OrbidderAdapter struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids from orbidder. -func (rcv *OrbidderAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (rcv *OrbidderAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp // check if imps exists, if not return error and do send request to orbidder. if len(request.Imp) == 0 { @@ -62,7 +62,7 @@ func (rcv *OrbidderAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a }}, errs } -func preprocess(imp *openrtb.Imp) error { +func preprocess(imp *openrtb2.Imp) error { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return &errortypes.BadInput{ @@ -81,7 +81,7 @@ func preprocess(imp *openrtb.Imp) error { } // MakeBids unpacks server response into Bids. -func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -104,13 +104,12 @@ func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) - for _, seatBid := range bidResp.SeatBid { for _, bid := range seatBid.Bid { bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ @@ -119,6 +118,9 @@ func (rcv OrbidderAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa }) } } + if bidResp.Cur != "" { + bidResponse.Currency = bidResp.Cur + } return bidResponse, nil } diff --git a/adapters/orbidder/orbidder_test.go b/adapters/orbidder/orbidder_test.go index 4e9fc2f8bb7..0eaed23a971 100644 --- a/adapters/orbidder/orbidder_test.go +++ b/adapters/orbidder/orbidder_test.go @@ -4,9 +4,9 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/orbidder/params_test.go b/adapters/orbidder/params_test.go index 98fcf0217db..19c4ed8d9d4 100644 --- a/adapters/orbidder/params_test.go +++ b/adapters/orbidder/params_test.go @@ -2,7 +2,7 @@ package orbidder import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go new file mode 100644 index 00000000000..282a6d53aa0 --- /dev/null +++ b/adapters/outbrain/outbrain.go @@ -0,0 +1,180 @@ +package outbrain + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/native1" + nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" + "github.com/mxmCherry/openrtb/v15/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 Outbrain adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + reqCopy := *request + + var errs []error + var outbrainExt openrtb_ext.ExtImpOutbrain + for i := 0; i < len(reqCopy.Imp); i++ { + imp := reqCopy.Imp[i] + + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, err) + continue + } + if err := json.Unmarshal(bidderExt.Bidder, &outbrainExt); err != nil { + errs = append(errs, err) + continue + } + imp.TagID = outbrainExt.TagId + reqCopy.Imp[i] = imp + } + + publisher := &openrtb2.Publisher{ + ID: outbrainExt.Publisher.Id, + Name: outbrainExt.Publisher.Name, + Domain: outbrainExt.Publisher.Domain, + } + if reqCopy.Site != nil { + siteCopy := *reqCopy.Site + siteCopy.Publisher = publisher + reqCopy.Site = &siteCopy + } else if reqCopy.App != nil { + appCopy := *reqCopy.App + appCopy.Publisher = publisher + reqCopy.App = &appCopy + } + + if outbrainExt.BCat != nil { + reqCopy.BCat = outbrainExt.BCat + } + if outbrainExt.BAdv != nil { + reqCopy.BAdv = outbrainExt.BAdv + } + + requestJSON, err := json.Marshal(reqCopy) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + 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)) + bidResponse.Currency = response.Cur + + var errs []error + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp) + if err != nil { + errs = append(errs, err) + continue + } + if bidType == openrtb_ext.BidTypeNative { + var nativePayload nativeResponse.Response + if err := json.Unmarshal(json.RawMessage(bid.AdM), &nativePayload); err != nil { + errs = append(errs, err) + continue + } + transformEventTrackers(&nativePayload) + nativePayloadJson, err := json.Marshal(nativePayload) + if err != nil { + errs = append(errs, err) + continue + } + bid.AdM = string(nativePayloadJson) + } + + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { + for _, imp := range imps { + if imp.ID == impID { + if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } else if imp.Banner != nil { + return openrtb_ext.BidTypeBanner, nil + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find native/banner impression \"%s\" ", impID), + } +} + +func transformEventTrackers(nativePayload *nativeResponse.Response) { + // the native-trk.js library used to trigger the trackers currently doesn't support the native 1.2 eventtrackers, + // so transform them to the deprecated imptrackers and jstracker + for _, eventTracker := range nativePayload.EventTrackers { + if eventTracker.Event != native1.EventTypeImpression { + continue + } + switch eventTracker.Method { + case native1.EventTrackingMethodImage: + nativePayload.ImpTrackers = append(nativePayload.ImpTrackers, eventTracker.URL) + case native1.EventTrackingMethodJS: + nativePayload.JSTracker = fmt.Sprintf("", eventTracker.URL) + } + } + nativePayload.EventTrackers = nil +} diff --git a/adapters/outbrain/outbrain_test.go b/adapters/outbrain/outbrain_test.go new file mode 100644 index 00000000000..533bad388ce --- /dev/null +++ b/adapters/outbrain/outbrain_test.go @@ -0,0 +1,20 @@ +package outbrain + +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.BidderOutbrain, config.Adapter{ + Endpoint: "http://example.com/bid"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "outbraintest", bidder) +} diff --git a/adapters/outbrain/outbraintest/exemplary/banner.json b/adapters/outbrain/outbraintest/exemplary/banner.json new file mode 100644 index 00000000000..16d52cf1c0f --- /dev/null +++ b/adapters/outbrain/outbraintest/exemplary/banner.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/outbrain/outbraintest/exemplary/native.json b/adapters/outbrain/outbraintest/exemplary/native.json new file mode 100644 index 00000000000..bf1eb7f53ff --- /dev/null +++ b/adapters/outbrain/outbraintest/exemplary/native.json @@ -0,0 +1,121 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":120,\"h\":100}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"id\":0,\"required\":1,\"img\":{\"type\":3,\"w\":120,\"h\":100}},{\"id\":1,\"required\":1,\"title\":{\"len\":140}},{\"id\":2,\"data\":{\"type\":1}}],\"eventtrackers\":[{\"event\":1,\"methods\":[1]}]}", + "ver": "1.2" + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://example.com/impression\"},{\"event\":1,\"method\":2,\"url\":\"http://example.com/impression\"}]}", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ] + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":3,\"required\":1,\"img\":{\"url\":\"http://example.com/img/url\",\"w\":120,\"h\":100}},{\"id\":0,\"required\":1,\"title\":{\"text\":\"Test title\"}},{\"id\":5,\"data\":{\"value\":\"Test sponsor\"}}],\"link\":{\"url\":\"http://example.com/click/url\"},\"imptrackers\":[\"http://example.com/impression\"],\"jstracker\":\"\\u003cscript src=\\\"http://example.com/impression\\\"\\u003e\\u003c/script\\u003e\"}", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ] + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/outbrain/outbraintest/params/race/banner.json b/adapters/outbrain/outbraintest/params/race/banner.json new file mode 100644 index 00000000000..05577a78e8b --- /dev/null +++ b/adapters/outbrain/outbraintest/params/race/banner.json @@ -0,0 +1,10 @@ +{ + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] +} \ No newline at end of file diff --git a/adapters/outbrain/outbraintest/params/race/native.json b/adapters/outbrain/outbraintest/params/race/native.json new file mode 100644 index 00000000000..05577a78e8b --- /dev/null +++ b/adapters/outbrain/outbraintest/params/race/native.json @@ -0,0 +1,10 @@ +{ + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] +} \ No newline at end of file diff --git a/adapters/outbrain/outbraintest/supplemental/app_request.json b/adapters/outbrain/outbraintest/supplemental/app_request.json new file mode 100644 index 00000000000..c8e7c4cf69f --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/app_request.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "app": { + "name": "test-app", + "bundle": "org.test", + "ver": "1.10", + "publisher": { + "id": "pub-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86_arm Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36", + "model": "sdk_gphone_x86_arm", + "os": "android", + "h": 735, + "w": 392 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "app": { + "name": "test-app", + "bundle": "org.test", + "ver": "1.10", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Linux; Android 11; sdk_gphone_x86_arm Build/RSR1.201013.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile Safari/537.36", + "model": "sdk_gphone_x86_arm", + "os": "android", + "h": 735, + "w": 392 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/outbrain/outbraintest/supplemental/optional_params.json b/adapters/outbrain/outbraintest/supplemental/optional_params.json new file mode 100644 index 00000000000..a69ceaa0c85 --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/optional_params.json @@ -0,0 +1,148 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] + } + } + } + ], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + }, + "tagid": "tag-id", + "bcat": ["bad-category"], + "badv": ["bad-advertiser"] + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id", + "name": "publisher-name", + "domain": "publisher-domain.com" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/outbrain/outbraintest/supplemental/status_204.json b/adapters/outbrain/outbraintest/supplemental/status_204.json new file mode 100644 index 00000000000..9f668736953 --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/status_204.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} diff --git a/adapters/outbrain/outbraintest/supplemental/status_400.json b/adapters/outbrain/outbraintest/supplemental/status_400.json new file mode 100644 index 00000000000..441162070d8 --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/status_400.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/outbrain/outbraintest/supplemental/status_418.json b/adapters/outbrain/outbraintest/supplemental/status_418.json new file mode 100644 index 00000000000..08e26804806 --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/status_418.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ] + } + }, + "mockResponse": { + "status": 418, + "body": {} + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 418. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/outbrain/params_test.go b/adapters/outbrain/params_test.go new file mode 100644 index 00000000000..a8d81d6234d --- /dev/null +++ b/adapters/outbrain/params_test.go @@ -0,0 +1,50 @@ +package outbrain + +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.BidderOutbrain, 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.BidderOutbrain, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{"publisher": {"id": "publisher-id"}}`, + `{"publisher": {"id": "publisher-id", "name": "publisher-name", "domain": "publisher-domain.com"}, "tagid": "tag-id", "bcat": ["bad-category"], "badv": ["bad-advertiser"]}`, +} + +var invalidParams = []string{ + `{"publisher": {"id": 1234}}`, + `{"publisher": {"id": "pub-id", "name": 1234}}`, + `{"publisher": {"id": "pub-id", "domain": 1234}}`, + `{"publisher": {"id": "pub-id"}, "tagid": 1234}`, + `{"publisher": {"id": "pub-id"}, "bcat": "not-array"}`, + `{"publisher": {"id": "pub-id"}, "bcat": [1234]}`, + `{"publisher": {"id": "pub-id"}, "badv": "not-array"}`, + `{"publisher": {"id": "pub-id"}, "badv": [1234]}`, +} diff --git a/adapters/outbrain/usersync.go b/adapters/outbrain/usersync.go new file mode 100644 index 00000000000..7dd60306c60 --- /dev/null +++ b/adapters/outbrain/usersync.go @@ -0,0 +1,12 @@ +package outbrain + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewOutbrainSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("outbrain", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/outbrain/usersync_test.go b/adapters/outbrain/usersync_test.go new file mode 100644 index 00000000000..f531834fc48 --- /dev/null +++ b/adapters/outbrain/usersync_test.go @@ -0,0 +1,33 @@ +package outbrain + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestSyncer(t *testing.T) { + syncURL := "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb=host%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewOutbrainSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "A", + Consent: "B", + }, + CCPA: ccpa.Policy{ + Consent: "C", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "http://prebidtest.zemanta.com/usersync/prebidtest?gdpr=A&gdpr_consent=B&us_privacy=C&cb=host%2Fsetuid%3Fbidder%3Dzemanta%26uid%3D__ZUID__", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) +} diff --git a/adapters/pangle/pangle.go b/adapters/pangle/pangle.go new file mode 100644 index 00000000000..a4694c71559 --- /dev/null +++ b/adapters/pangle/pangle.go @@ -0,0 +1,197 @@ +package pangle + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/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 +} + +type wrappedExtImpBidder struct { + *adapters.ExtImpBidder + AdType int `json:"adtype,omitempty"` +} + +type pangleBidExt struct { + Pangle *bidExt `json:"pangle,omitempty"` +} + +type bidExt struct { + AdType *int `json:"adtype,omitempty"` +} + +/* Builder */ + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + Endpoint: config.Endpoint, + } + + return bidder, nil +} + +/* MakeRequests */ + +func getAdType(imp openrtb2.Imp, parsedImpExt *wrappedExtImpBidder) int { + // video + if imp.Video != nil { + if parsedImpExt != nil && parsedImpExt.Prebid != nil && parsedImpExt.Prebid.IsRewardedInventory == 1 { + return 7 + } + if imp.Instl == 1 { + return 8 + } + } + // banner + if imp.Banner != nil { + if imp.Instl == 1 { + return 2 + } else { + return 1 + } + } + // native + if imp.Native != nil && len(imp.Native.Request) > 0 { + return 5 + } + + return -1 +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var requests []*adapters.RequestData + var errs []error + + requestCopy := *request + for _, imp := range request.Imp { + var impExt wrappedExtImpBidder + if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + errs = append(errs, fmt.Errorf("failed unmarshalling imp ext (err)%s", err.Error())) + continue + } + // detect and fill adtype + if adType := getAdType(imp, &impExt); adType == -1 { + errs = append(errs, &errortypes.BadInput{Message: "not a supported adtype"}) + continue + } else { + impExt.AdType = adType + if newImpExt, err := json.Marshal(impExt); err == nil { + imp.Ext = newImpExt + } else { + errs = append(errs, fmt.Errorf("failed re-marshalling imp ext with adtype")) + continue + } + } + // for setting token + var bidderImpExt openrtb_ext.ImpExtPangle + if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil { + errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error())) + continue + } + + requestCopy.Imp = []openrtb2.Imp{imp} + requestJSON, err := json.Marshal(requestCopy) + if err != nil { + errs = append(errs, err) + continue + } + + requestData := &adapters.RequestData{ + Method: "POST", + Uri: a.Endpoint, + Body: requestJSON, + Headers: http.Header{ + "TOKEN": []string{bidderImpExt.Token}, + "Content-Type": []string{"application/json"}, + }, + } + requests = append(requests, requestData) + } + + return requests, errs +} + +/* MakeBids */ + +func getMediaTypeForBid(bid *openrtb2.Bid) (openrtb_ext.BidType, error) { + if bid == nil { + return "", fmt.Errorf("the bid request object is nil") + } + + var bidExt pangleBidExt + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return "", fmt.Errorf("invalid bid ext") + } else if bidExt.Pangle == nil || bidExt.Pangle.AdType == nil { + return "", fmt.Errorf("missing pangleExt/adtype in bid ext") + } + + switch *bidExt.Pangle.AdType { + case 1: + return openrtb_ext.BidTypeBanner, nil + case 2: + return openrtb_ext.BidTypeBanner, nil + case 5: + return openrtb_ext.BidTypeNative, nil + case 7: + return openrtb_ext.BidTypeVideo, nil + case 8: + return openrtb_ext.BidTypeVideo, nil + } + + return "", fmt.Errorf("unrecognized adtype in response") +} + +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} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for _, temp := range seatBid.Bid { + bid := temp // avoid taking address of a for loop variable + mediaType, err := getMediaTypeForBid(&bid) + if err != nil { + errs = append(errs, err) + continue + } + b := &adapters.TypedBid{ + Bid: &bid, + BidType: mediaType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + + return bidResponse, errs +} diff --git a/adapters/pangle/pangle_test.go b/adapters/pangle/pangle_test.go new file mode 100644 index 00000000000..89d5eee56c3 --- /dev/null +++ b/adapters/pangle/pangle_test.go @@ -0,0 +1,21 @@ +package pangle + +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) { + conf := config.Adapter{ + Endpoint: "https://pangle.io/api/get_ads", + } + bidder, buildErr := Builder(openrtb_ext.BidderPangle, conf) + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "pangletest", bidder) +} diff --git a/adapters/pangle/pangletest/exemplary/app_banner.json b/adapters/pangle/pangletest/exemplary/app_banner.json new file mode 100644 index 00000000000..3fa410e5b7f --- /dev/null +++ b/adapters/pangle/pangletest/exemplary/app_banner.json @@ -0,0 +1,127 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "adtype": 1, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300, + "ext": { + "pangle": { + "adtype": 1 + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250, + "ext": { + "pangle": { + "adtype": 1 + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/exemplary/app_banner_instl.json b/adapters/pangle/pangletest/exemplary/app_banner_instl.json new file mode 100644 index 00000000000..585d155a057 --- /dev/null +++ b/adapters/pangle/pangletest/exemplary/app_banner_instl.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "instl": 1, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "instl": 1, + "ext": { + "adtype": 2, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300, + "ext": { + "pangle": { + "adtype": 2 + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250, + "ext": { + "pangle": { + "adtype": 2 + } + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/exemplary/app_native.json b/adapters/pangle/pangletest/exemplary/app_native.json new file mode 100644 index 00000000000..2502baa4f9f --- /dev/null +++ b/adapters/pangle/pangletest/exemplary/app_native.json @@ -0,0 +1,117 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}" + }, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}" + }, + "ext": { + "adtype": 5, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300, + "ext": { + "pangle": { + "adtype": 5 + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250, + "ext": { + "pangle": { + "adtype": 5 + } + } + }, + "type": "native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/exemplary/app_video_instl.json b/adapters/pangle/pangletest/exemplary/app_video_instl.json new file mode 100644 index 00000000000..d5af392fd91 --- /dev/null +++ b/adapters/pangle/pangletest/exemplary/app_video_instl.json @@ -0,0 +1,141 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 1, + "ext": { + "adtype": 8, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300, + "ext": { + "pangle": { + "adtype": 8 + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250, + "ext": { + "pangle": { + "adtype": 8 + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/exemplary/app_video_rewarded.json b/adapters/pangle/pangletest/exemplary/app_video_rewarded.json new file mode 100644 index 00000000000..2dbf08b944e --- /dev/null +++ b/adapters/pangle/pangletest/exemplary/app_video_rewarded.json @@ -0,0 +1,146 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "adtype": 7, + "prebid": { + "bidder": null, + "is_rewarded_inventory": 1, + "storedrequest": null + }, + "bidder": { + "token": "123" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300, + "ext": { + "pangle": { + "adtype": 7 + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250, + "ext": { + "pangle": { + "adtype": 7 + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/params/race/banner.json b/adapters/pangle/pangletest/params/race/banner.json new file mode 100644 index 00000000000..afd1684b04e --- /dev/null +++ b/adapters/pangle/pangletest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "token": "123" +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/params/race/native.json b/adapters/pangle/pangletest/params/race/native.json new file mode 100644 index 00000000000..afd1684b04e --- /dev/null +++ b/adapters/pangle/pangletest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "token": "123" +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/params/race/video.json b/adapters/pangle/pangletest/params/race/video.json new file mode 100644 index 00000000000..afd1684b04e --- /dev/null +++ b/adapters/pangle/pangletest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "token": "123" +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/pangle_ext_check.json b/adapters/pangle/pangletest/supplemental/pangle_ext_check.json new file mode 100644 index 00000000000..c9dc5c28c6a --- /dev/null +++ b/adapters/pangle/pangletest/supplemental/pangle_ext_check.json @@ -0,0 +1,107 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "adtype": 1, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300, + "ext": { + "pangle": {} + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "missing pangleExt/adtype in bid ext", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/response_code_204.json b/adapters/pangle/pangletest/supplemental/response_code_204.json new file mode 100644 index 00000000000..16c13bdf18f --- /dev/null +++ b/adapters/pangle/pangletest/supplemental/response_code_204.json @@ -0,0 +1,79 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "adtype": 1, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/response_code_400.json b/adapters/pangle/pangletest/supplemental/response_code_400.json new file mode 100644 index 00000000000..0a5810325e2 --- /dev/null +++ b/adapters/pangle/pangletest/supplemental/response_code_400.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "adtype": 1, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.*", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/response_code_non_200.json b/adapters/pangle/pangletest/supplemental/response_code_non_200.json new file mode 100644 index 00000000000..0d1447db1fe --- /dev/null +++ b/adapters/pangle/pangletest/supplemental/response_code_non_200.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "adtype": 1, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 403, + "body": {} + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403.*", + "comparison": "regex" + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json b/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json new file mode 100644 index 00000000000..451c0ed1909 --- /dev/null +++ b/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json @@ -0,0 +1,110 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "adtype": 1, + "bidder": { + "token": "123" + }, + "prebid": null + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300, + "ext": { + "pangle": { + "adtype": 100 + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeBidsErrors": [ + { + "value": "unrecognized adtype in response", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/unsupported_adtype.json b/adapters/pangle/pangletest/supplemental/unsupported_adtype.json new file mode 100644 index 00000000000..bcd916ca322 --- /dev/null +++ b/adapters/pangle/pangletest/supplemental/unsupported_adtype.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "instl": 0, + "ext": { + "bidder": { + "token": "123" + } + } + } + ] + }, + "httpCalls": [ + ], + "expectedBidResponses": [ + ], + "expectedMakeRequestsErrors": [ + { + "value": "not a supported adtype", + "comparison": "literal" + } + ], + "expectedMakeBidsErrors": [ + ] + } \ No newline at end of file diff --git a/adapters/pangle/param_test.go b/adapters/pangle/param_test.go new file mode 100644 index 00000000000..7b037bd52d6 --- /dev/null +++ b/adapters/pangle/param_test.go @@ -0,0 +1,45 @@ +package pangle + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{"token": "SomeAccessToken"}`, +} + +var invalidParams = []string{ + `{"token": ""}`, + `{"token": 42}`, + `{"token": null}`, + `{}`, +} + +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.BidderPangle, 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.BidderPangle, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} diff --git a/adapters/pubmatic/params_test.go b/adapters/pubmatic/params_test.go index 9615fb978e6..c8a300b9910 100644 --- a/adapters/pubmatic/params_test.go +++ b/adapters/pubmatic/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/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 b21e31ed930..c9e3660b4c8 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -11,13 +11,13 @@ import ( "strconv" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" "github.com/golang/glog" + "github.com/mxmCherry/openrtb/v15/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/pbs" "golang.org/x/net/context/ctxhttp" ) @@ -171,8 +171,8 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder } pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0]) - pbReq.Imp[i].Banner.H = openrtb.Uint64Ptr(uint64(height)) - pbReq.Imp[i].Banner.W = openrtb.Uint64Ptr(uint64(width)) + pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) + pbReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) if len(params.Keywords) != 0 { kvstr := prepareImpressionExt(params.Keywords) @@ -203,12 +203,12 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder if pbReq.Site != nil { siteCopy := *pbReq.Site - siteCopy.Publisher = &openrtb.Publisher{ID: params.PublisherId, Domain: req.Domain} + siteCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} pbReq.Site = &siteCopy } if pbReq.App != nil { appCopy := *pbReq.App - appCopy.Publisher = &openrtb.Publisher{ID: params.PublisherId, Domain: req.Domain} + appCopy.Publisher = &openrtb2.Publisher{ID: params.PublisherId, Domain: req.Domain} pbReq.App = &appCopy } } @@ -277,7 +277,7 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder debug.ResponseBody = string(body) } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return nil, &errortypes.BadServerResponse{ @@ -326,7 +326,7 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder return bids, nil } -func getBidderParam(request *openrtb.BidRequest, key string) ([]byte, error) { +func getBidderParam(request *openrtb2.BidRequest, key string) ([]byte, error) { var reqExt openrtb_ext.ExtRequest if len(request.Ext) <= 0 { return nil, nil @@ -361,7 +361,7 @@ func getBidderParam(request *openrtb.BidRequest, key string) ([]byte, error) { return bytes, nil } -func getCookiesFromRequest(request *openrtb.BidRequest) ([]string, error) { +func getCookiesFromRequest(request *openrtb2.BidRequest) ([]string, error) { cbytes, err := getBidderParam(request, "Cookie") if err != nil { return nil, err @@ -381,7 +381,7 @@ func getCookiesFromRequest(request *openrtb.BidRequest) ([]string, error) { return cookies, nil } -func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) var err error @@ -422,7 +422,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada publisherCopy.ID = pubID siteCopy.Publisher = &publisherCopy } else { - siteCopy.Publisher = &openrtb.Publisher{ID: pubID} + siteCopy.Publisher = &openrtb2.Publisher{ID: pubID} } request.Site = &siteCopy } else if request.App != nil { @@ -432,7 +432,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada publisherCopy.ID = pubID appCopy.Publisher = &publisherCopy } else { - appCopy.Publisher = &openrtb.Publisher{ID: pubID} + appCopy.Publisher = &openrtb2.Publisher{ID: pubID} } request.App = &appCopy } @@ -442,17 +442,17 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada var userExt *openrtb_ext.ExtUser if err = json.Unmarshal(request.User.Ext, &userExt); err == nil { if userExt != nil && userExt.Eids != nil { - var eidArr []openrtb.Eid + var eidArr []openrtb2.Eid for _, eid := range userExt.Eids { - //var newEid openrtb.Eid - newEid := &openrtb.Eid{ + //var newEid openrtb2.Eid + newEid := &openrtb2.Eid{ ID: eid.ID, Source: eid.Source, Ext: eid.Ext, } - var uidArr []openrtb.Uid + var uidArr []openrtb2.Uid for _, uid := range eid.Uids { - newUID := &openrtb.Uid{ + newUID := &openrtb2.Uid{ ID: uid.ID, AType: uid.Atype, Ext: uid.Ext, @@ -506,7 +506,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada // validateAdslot validate the optional adslot string // valid formats are 'adslot@WxH', 'adslot' and no adslot -func validateAdSlot(adslot string, imp *openrtb.Imp) error { +func validateAdSlot(adslot string, imp *openrtb2.Imp) error { adSlotStr := strings.TrimSpace(adslot) if len(adSlotStr) == 0 { @@ -540,8 +540,8 @@ func validateAdSlot(adslot string, imp *openrtb.Imp) error { //In case of video, size could be derived from the player size if imp.Banner != nil && height != 0 && width != 0 { - imp.Banner.H = openrtb.Uint64Ptr(uint64(height)) - imp.Banner.W = openrtb.Uint64Ptr(uint64(width)) + imp.Banner.H = openrtb2.Int64Ptr(int64(height)) + imp.Banner.W = openrtb2.Int64Ptr(int64(width)) } } else { return errors.New(fmt.Sprintf("Invalid adSlot %v", adSlotStr)) @@ -550,7 +550,7 @@ func validateAdSlot(adslot string, imp *openrtb.Imp) error { return nil } -func assignBannerSize(banner *openrtb.Banner) error { +func assignBannerSize(banner *openrtb2.Banner) error { if banner == nil { return nil } @@ -563,16 +563,16 @@ func assignBannerSize(banner *openrtb.Banner) error { return errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) } - banner.W = new(uint64) + banner.W = new(int64) *banner.W = banner.Format[0].W - banner.H = new(uint64) + banner.H = new(int64) *banner.H = banner.Format[0].H return nil } // parseImpressionObject parse the imp to get it ready to send to pubmatic -func parseImpressionObject(imp *openrtb.Imp, wrapExt *pubmaticWrapperExt, pubID *string, wrapperExtSet *bool) error { +func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID *string, wrapperExtSet *bool) error { // PubMatic supports banner and video impressions. if imp.Banner == nil && imp.Video == nil { return fmt.Errorf("Invalid MediaType. PubMatic only supports Banner and Video. Ignoring ImpID=%s", imp.ID) @@ -668,7 +668,7 @@ func prepareImpressionExt(keywords map[string]string) string { return kvStr } -func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -683,7 +683,7 @@ func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb.BidRequest, external return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index 6709414ac8a..b0df1903a42 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -12,14 +12,14 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" ) func TestJsonSamples(t *testing.T) { @@ -35,7 +35,7 @@ func TestJsonSamples(t *testing.T) { // ---------------------------------------------------------------------------- // Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb. +// clean up the existing code and make everything openrtb2. func CompareStringValue(val1 string, val2 string, t *testing.T) { if val1 != val2 { @@ -51,29 +51,29 @@ func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: breq.ID, BidID: "bidResponse_ID", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "pubmatic", - Bid: make([]openrtb.Bid, 0), + Bid: make([]openrtb2.Bid, 0), }, }, } rand.Seed(int64(time.Now().UnixNano())) - var bids []openrtb.Bid + var bids []openrtb2.Bid for i, imp := range breq.Imp { - bid := openrtb.Bid{ + bid := openrtb2.Bid{ ID: fmt.Sprintf("SeatID_%d", i), ImpID: imp.ID, Price: float64(int(rand.Float64()*1000)) / 100, @@ -132,7 +132,7 @@ func TestPubmaticTimeout(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -167,7 +167,7 @@ func TestPubmaticInvalidJson(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -203,7 +203,7 @@ func TestPubmaticInvalidStatusCode(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -236,7 +236,7 @@ func TestPubmaticInvalidInputParameters(t *testing.T) { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -308,7 +308,7 @@ func TestPubmaticBasicResponse_MandatoryParams(t *testing.T) { Code: "unitCode", BidID: "bidid", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -344,7 +344,7 @@ func TestPubmaticBasicResponse_AllParams(t *testing.T) { Code: "unitCode", BidID: "bidid", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -389,7 +389,7 @@ func TestPubmaticMultiImpressionResponse(t *testing.T) { Code: "unitCode1", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -401,7 +401,7 @@ func TestPubmaticMultiImpressionResponse(t *testing.T) { Code: "unitCode1", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 800, H: 200, @@ -437,7 +437,7 @@ func TestPubmaticMultiAdUnitResponse(t *testing.T) { Code: "unitCode1", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -449,7 +449,7 @@ func TestPubmaticMultiAdUnitResponse(t *testing.T) { Code: "unitCode2", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, BidID: "bidid", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 800, H: 200, @@ -486,7 +486,7 @@ func TestPubmaticMobileResponse(t *testing.T) { Code: "unitCode", BidID: "bidid", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 336, H: 280, @@ -497,7 +497,7 @@ func TestPubmaticMobileResponse(t *testing.T) { }, } - pbReq.App = &openrtb.App{ + pbReq.App = &openrtb2.App{ ID: "com.test", Name: "testApp", } @@ -526,7 +526,7 @@ func TestPubmaticInvalidLookupBidIDParameter(t *testing.T) { { Code: "unitCode", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -558,7 +558,7 @@ func TestPubmaticAdSlotParams(t *testing.T) { Code: "unitCode", BidID: "bidid", MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 120, H: 240, @@ -638,7 +638,7 @@ func TestPubmaticSampleRequest(t *testing.T) { } pbReq.AdUnits[0] = pbs.AdUnit{ Code: "adUnit_1", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 100, H: 120, diff --git a/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json b/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json index 1aa98fcb892..6a344ee091a 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json +++ b/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json @@ -1,152 +1,159 @@ { - "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"] - } - ], - "wrapper": { - "version": 1, - "profile": 5123 - } - } + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 } - }], - "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" - } - }, - "ext":{ - "prebid" :{ - "bidderparams": { - "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": " 999 ", + "keywords": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 } } } - }, - - "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 - } + } + ], + "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": { - "pmZoneID": "Zone1,Zone2", - "preference": "sports,movies" - } + }, + "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" - } - }, - "ext": { - "wrapper": { - "profile": 5123, - "version":1, - "wiid" : "dwzafakjflan-tygannnvlla-mlljvj" - } + } + ], + "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": { + "wrapper": { + "profile": 5123, + "version": 1 } } - }, - "mockResponse": { - "status": 200, - "body": { - "id": "test-request-id", - "seatbid": [ - { - "seat": "958", - "bid": [{ + } + }, + "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"], + "adomain": [ + "pubmatic.com" + ], "crid": "29681110", "h": 250, "w": 300, - "dealid":"test deal", + "dealid": "test deal", "ext": { "dspid": 6, "deal_channel": 1 } - }] - } - ], - "bidid": "5778926625248726496", - "cur": "USD" - } + } + ] + } + ], + "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" - } - ] - } - ] - } + } + ], + "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/pubmatic/usersync.go b/adapters/pubmatic/usersync.go index f35470c0ad9..822f13cea3d 100644 --- a/adapters/pubmatic/usersync.go +++ b/adapters/pubmatic/usersync.go @@ -3,10 +3,10 @@ package pubmatic import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewPubmaticSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("pubmatic", 76, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("pubmatic", temp, adapters.SyncTypeIframe) } diff --git a/adapters/pubmatic/usersync_test.go b/adapters/pubmatic/usersync_test.go index e32650ac543..0f4d2e857d3 100644 --- a/adapters/pubmatic/usersync_test.go +++ b/adapters/pubmatic/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestPubmaticSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ads.pubmatic.com/AdServer/js/user_sync.html?gdpr=A&gdpr_consent=B&us_privacy=C&predirect=localhost%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 76, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/pubnative/pubnative.go b/adapters/pubnative/pubnative.go index 36edceee53f..8093c841fb2 100644 --- a/adapters/pubnative/pubnative.go +++ b/adapters/pubnative/pubnative.go @@ -7,18 +7,18 @@ import ( "net/url" "strconv" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 PubnativeAdapter struct { URI string } -func (a *PubnativeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *PubnativeAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { impCount := len(request.Imp) requestData := make([]*adapters.RequestData, 0, impCount) errs := []error{} @@ -53,7 +53,7 @@ func (a *PubnativeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ad continue } - requestCopy.Imp = []openrtb.Imp{imp} + requestCopy.Imp = []openrtb2.Imp{imp} reqJSON, err := json.Marshal(&requestCopy) if err != nil { errs = append(errs, err) @@ -77,7 +77,7 @@ func (a *PubnativeAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ad return requestData, errs } -func checkRequest(request *openrtb.BidRequest) error { +func checkRequest(request *openrtb2.BidRequest) error { if request.Device == nil || len(request.Device.OS) == 0 { return &errortypes.BadInput{ Message: "Impression is missing device OS information", @@ -87,39 +87,45 @@ func checkRequest(request *openrtb.BidRequest) error { return nil } -func convertImpression(imp *openrtb.Imp) error { +func convertImpression(imp *openrtb2.Imp) error { if imp.Banner == nil && imp.Video == nil && imp.Native == nil { return &errortypes.BadInput{ Message: "Pubnative only supports banner, video or native ads.", } } if imp.Banner != nil { - err := convertBanner(imp.Banner) + bannerCopy, err := convertBanner(imp.Banner) if err != nil { return err } + imp.Banner = bannerCopy } return nil } // make sure that banner has openrtb 2.3-compatible size information -func convertBanner(banner *openrtb.Banner) error { +func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) { if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { if len(banner.Format) > 0 { f := banner.Format[0] - banner.W = &f.W - banner.H = &f.H + + bannerCopy := *banner + + bannerCopy.W = openrtb2.Int64Ptr(f.W) + bannerCopy.H = openrtb2.Int64Ptr(f.H) + + return &bannerCopy, nil } else { - return &errortypes.BadInput{ + return nil, &errortypes.BadInput{ Message: "Size information missing for banner", } } } - return nil + return banner, nil } -func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -136,7 +142,7 @@ func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa }} } - var parsedResponse openrtb.BidResponse + var parsedResponse openrtb2.BidResponse if err := json.Unmarshal(response.Body, &parsedResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), @@ -167,7 +173,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/pubnative/pubnative_test.go b/adapters/pubnative/pubnative_test.go index 484315cac5e..6955b85f5de 100644 --- a/adapters/pubnative/pubnative_test.go +++ b/adapters/pubnative/pubnative_test.go @@ -3,9 +3,9 @@ package pubnative import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/pulsepoint/params_test.go b/adapters/pulsepoint/params_test.go index f6d6baf9f06..ac2b314b96f 100644 --- a/adapters/pulsepoint/params_test.go +++ b/adapters/pulsepoint/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/pulsepoint/pulsepoint.go b/adapters/pulsepoint/pulsepoint.go index 8025e9b29c8..6b6b4305607 100644 --- a/adapters/pulsepoint/pulsepoint.go +++ b/adapters/pulsepoint/pulsepoint.go @@ -1,22 +1,21 @@ package pulsepoint import ( + "bytes" + "context" "encoding/json" "fmt" + "io/ioutil" "net/http" "strconv" - - "bytes" - "context" - "io/ioutil" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" + "github.com/mxmCherry/openrtb/v15/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/pbs" "golang.org/x/net/context/ctxhttp" ) @@ -33,12 +32,12 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *PulsePointAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) var err error pubID := "" - imps := make([]openrtb.Imp, 0, len(request.Imp)) + imps := make([]openrtb2.Imp, 0, len(request.Imp)) for i := 0; i < len(request.Imp); i++ { imp := request.Imp[i] var bidderExt adapters.ExtImpBidder @@ -77,7 +76,7 @@ func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a publisher.ID = pubID site.Publisher = &publisher } else { - site.Publisher = &openrtb.Publisher{ID: pubID} + site.Publisher = &openrtb2.Publisher{ID: pubID} } request.Site = &site } else if request.App != nil { @@ -87,7 +86,7 @@ func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a publisher.ID = pubID app.Publisher = &publisher } else { - app.Publisher = &openrtb.Publisher{ID: pubID} + app.Publisher = &openrtb2.Publisher{ID: pubID} } request.App = &app } @@ -109,7 +108,7 @@ func (a *PulsePointAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *a }}, errs } -func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { // passback if response.StatusCode == http.StatusNoContent { return nil, nil @@ -127,14 +126,14 @@ func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern }} } // parse response - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) // map imps by id - impsByID := make(map[string]openrtb.Imp) + impsByID := make(map[string]openrtb2.Imp) for i := 0; i < len(internalRequest.Imp); i++ { impsByID[internalRequest.Imp[i].ID] = internalRequest.Imp[i] } @@ -156,7 +155,7 @@ func (a *PulsePointAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern return bidResponse, errs } -func getBidType(imp openrtb.Imp) openrtb_ext.BidType { +func getBidType(imp openrtb2.Imp) openrtb_ext.BidType { // derive the bidtype purely from the impression itself if imp.Banner != nil { return openrtb_ext.BidTypeBanner @@ -235,7 +234,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde break } ppReq.Imp[i].TagID = strconv.Itoa(params.TagId) - publisher := &openrtb.Publisher{ID: strconv.Itoa(params.PublisherId)} + publisher := &openrtb2.Publisher{ID: strconv.Itoa(params.PublisherId)} if ppReq.Site != nil { siteCopy := *ppReq.Site siteCopy.Publisher = publisher @@ -250,7 +249,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde if len(size) == 2 { width, err := strconv.Atoi(size[0]) if err == nil { - ppReq.Imp[i].Banner.W = openrtb.Uint64Ptr(uint64(width)) + ppReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) } else { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Invalid Width param %s", size[0]), @@ -258,7 +257,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde } height, err := strconv.Atoi(size[1]) if err == nil { - ppReq.Imp[i].Banner.H = openrtb.Uint64Ptr(uint64(height)) + ppReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) } else { return nil, &errortypes.BadInput{ Message: fmt.Sprintf("Invalid Height param %s", size[1]), @@ -318,7 +317,7 @@ func (a *PulsePointAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidde debug.ResponseBody = string(body) } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return nil, &errortypes.BadServerResponse{ diff --git a/adapters/pulsepoint/pulsepoint_test.go b/adapters/pulsepoint/pulsepoint_test.go index 7e9b51016ea..fac0bfd4de8 100644 --- a/adapters/pulsepoint/pulsepoint_test.go +++ b/adapters/pulsepoint/pulsepoint_test.go @@ -5,9 +5,9 @@ import ( "net/http" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" "bytes" "context" @@ -16,11 +16,11 @@ import ( "net/http/httptest" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" ) func TestJsonSamples(t *testing.T) { @@ -187,7 +187,7 @@ func TestMobileAppRequest(t *testing.T) { server := service.Server ctx := context.TODO() req := SampleRequest(1, t) - req.App = &openrtb.App{ + req.App = &openrtb2.App{ ID: "com.facebook.katana", Name: "facebook", } @@ -215,7 +215,7 @@ func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { for i := 0; i < numberOfImpressions; i++ { req.AdUnits[i] = pbs.AdUnit{ Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 10, H: 12, @@ -265,7 +265,7 @@ func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { */ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb.BidRequest + var lastBidRequest openrtb2.BidRequest server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) @@ -273,14 +273,14 @@ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { http.Error(w, err.Error(), http.StatusInternalServerError) return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } lastBidRequest = breq - var bids []openrtb.Bid + var bids []openrtb2.Bid for i, imp := range breq.Imp { if tagsToBid[imp.TagID] { bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) @@ -290,9 +290,9 @@ func CreateService(tagsToBid map[string]bool) adapterstest.OrtbMockService { if len(bids) == 0 { w.WriteHeader(204) } else { - // serialize the bids to openrtb.BidResponse - js, _ := json.Marshal(openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + // serialize the bids to openrtb2.BidResponse + js, _ := json.Marshal(openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { Bid: bids, }, diff --git a/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json index b5209ed4bbe..8d34bab0578 100644 --- a/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json +++ b/adapters/pulsepoint/pulsepointtest/supplemental/bad-bid-data.json @@ -83,7 +83,7 @@ }], "expectedBidResponses": [], "expectedMakeBidsErrors": [{ - "value": "json: cannot unmarshal string into Go struct field Bid.seatbid.bid.w of type uint64", + "value": "json: cannot unmarshal string into Go struct field Bid.seatbid.bid.w of type int64", "comparison": "literal" } ] diff --git a/adapters/pulsepoint/usersync.go b/adapters/pulsepoint/usersync.go index 4c7d5ca63c8..1b6903f9f02 100644 --- a/adapters/pulsepoint/usersync.go +++ b/adapters/pulsepoint/usersync.go @@ -3,10 +3,10 @@ package pulsepoint import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewPulsepointSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("pulsepoint", 81, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("pulsepoint", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/pulsepoint/usersync_test.go b/adapters/pulsepoint/usersync_test.go index 8dd32564930..7cfea57cb91 100644 --- a/adapters/pulsepoint/usersync_test.go +++ b/adapters/pulsepoint/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestPulsepointSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//bh.contextweb.com/rtset?pid=561205&ev=1&rurl=http%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D%26gdpr_consent%3D%26uid%3D%25%25VGUID%25%25", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 81, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/revcontent/revcontent.go b/adapters/revcontent/revcontent.go index c6a7ff9d827..173917c2314 100644 --- a/adapters/revcontent/revcontent.go +++ b/adapters/revcontent/revcontent.go @@ -3,12 +3,13 @@ package revcontent import ( "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "net/http" + + "github.com/mxmCherry/openrtb/v15/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 { @@ -23,7 +24,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { reqBody, err := json.Marshal(request) if err != nil { @@ -46,7 +47,7 @@ func (a *adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex return []*adapters.RequestData{req}, nil } -func checkRequest(request *openrtb.BidRequest) error { +func checkRequest(request *openrtb2.BidRequest) error { if (request.App == nil || len(request.App.Name) == 0) && (request.Site == nil || len(request.Site.Domain) == 0) { return &errortypes.BadInput{ Message: "Impression is missing app name or site domain, and must contain one.", @@ -57,7 +58,7 @@ func checkRequest(request *openrtb.BidRequest) error { } // MakeBids make the bids for the bid response. -func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -74,7 +75,7 @@ func (a *adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} diff --git a/adapters/revcontent/revcontent_test.go b/adapters/revcontent/revcontent_test.go index d7f83cdb2aa..836ef138eb7 100644 --- a/adapters/revcontent/revcontent_test.go +++ b/adapters/revcontent/revcontent_test.go @@ -3,9 +3,9 @@ package revcontent import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/revcontent/revcontenttest/supplemental/bad_response.json b/adapters/revcontent/revcontenttest/supplemental/bad_response.json index bd562373f1b..751aed92c27 100644 --- a/adapters/revcontent/revcontenttest/supplemental/bad_response.json +++ b/adapters/revcontent/revcontenttest/supplemental/bad_response.json @@ -43,7 +43,7 @@ "expectedMakeBidsErrors": [ { "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse" + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" } ] } diff --git a/adapters/rhythmone/params_test.go b/adapters/rhythmone/params_test.go index 00eacf15082..7d8cad47d53 100644 --- a/adapters/rhythmone/params_test.go +++ b/adapters/rhythmone/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/rhythmone/rhythmone.go b/adapters/rhythmone/rhythmone.go index e2fc9aa8f0d..de43537e55b 100644 --- a/adapters/rhythmone/rhythmone.go +++ b/adapters/rhythmone/rhythmone.go @@ -6,18 +6,18 @@ import ( "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *RhythmoneAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) var uri string @@ -43,7 +43,7 @@ func (a *RhythmoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ad return nil, errs } -func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -59,7 +59,7 @@ func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa Message: fmt.Sprintf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), }} } - var bidResp openrtb.BidResponse + 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), @@ -80,7 +80,7 @@ func (a *RhythmoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externa return bidResponse, errs } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { @@ -103,7 +103,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *RhythmoneAdapter) preProcess(req *openrtb.BidRequest, errors []error) (*openrtb.BidRequest, string, []error) { +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++ { @@ -132,9 +132,9 @@ func (a *RhythmoneAdapter) preProcess(req *openrtb.BidRequest, errors []error) ( errors = append(errors, err) return nil, "", errors } - bidderExtCopy := openrtb_ext.ExtBid{ - Bidder: rhythmoneExtCopy, - } + bidderExtCopy := struct { + Bidder json.RawMessage `json:"bidder,omitempty"` + }{rhythmoneExtCopy} impExtCopy, err := json.Marshal(&bidderExtCopy) if err != nil { errors = append(errors, err) diff --git a/adapters/rhythmone/rhythmone_test.go b/adapters/rhythmone/rhythmone_test.go index e2419f33568..c0c0a891ef4 100644 --- a/adapters/rhythmone/rhythmone_test.go +++ b/adapters/rhythmone/rhythmone_test.go @@ -3,9 +3,9 @@ package rhythmone import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/rhythmone/usersync.go b/adapters/rhythmone/usersync.go index 534c60dd4bc..990fcb065f6 100644 --- a/adapters/rhythmone/usersync.go +++ b/adapters/rhythmone/usersync.go @@ -3,10 +3,10 @@ package rhythmone import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewRhythmoneSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("rhythmone", 36, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("rhythmone", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/rhythmone/usersync_test.go b/adapters/rhythmone/usersync_test.go index 1c725626a46..97920fb4980 100644 --- a/adapters/rhythmone/usersync_test.go +++ b/adapters/rhythmone/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestRhythmoneSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://sync.1rx.io/usersync2/rmphb?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&us_privacy=1NYN&redir=localhost%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D%5BRX_UUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 36, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/rtbhouse/rtbhouse.go b/adapters/rtbhouse/rtbhouse.go index 26f85d4adad..44adddee8fd 100644 --- a/adapters/rtbhouse/rtbhouse.go +++ b/adapters/rtbhouse/rtbhouse.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) // Builder builds a new instance of the RTBHouse adapter for the given bidder with the given config. @@ -27,7 +27,7 @@ type RTBHouseAdapter struct { // MakeRequests prepares the HTTP requests which should be made to fetch bids. func (adapter *RTBHouseAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -57,7 +57,7 @@ const unexpectedStatusCodeFormat = "" + // MakeBids unpacks the server's response into Bids. func (adapter *RTBHouseAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -81,7 +81,7 @@ func (adapter *RTBHouseAdapter) MakeBids( return nil, []error{err} } - var openRTBBidderResponse openrtb.BidResponse + var openRTBBidderResponse openrtb2.BidResponse if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil { return nil, []error{err} } diff --git a/adapters/rtbhouse/rtbhouse_test.go b/adapters/rtbhouse/rtbhouse_test.go index 15dfd1fc9b2..bae9a636038 100644 --- a/adapters/rtbhouse/rtbhouse_test.go +++ b/adapters/rtbhouse/rtbhouse_test.go @@ -3,9 +3,9 @@ package rtbhouse import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) const testsDir = "rtbhousetest" diff --git a/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json b/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json index b6af4209f48..f84f5555259 100644 --- a/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json +++ b/adapters/rtbhouse/rtbhousetest/supplemental/bad_response.json @@ -54,7 +54,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/rtbhouse/usersync.go b/adapters/rtbhouse/usersync.go index 97e673124aa..baa0b994373 100644 --- a/adapters/rtbhouse/usersync.go +++ b/adapters/rtbhouse/usersync.go @@ -3,17 +3,15 @@ package rtbhouse import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) -const rtbHouseGDPRVendorID = uint16(16) const rtbHouseFamilyName = "rtbhouse" func NewRTBHouseSyncer(urlTemplate *template.Template) usersync.Usersyncer { return adapters.NewSyncer( rtbHouseFamilyName, - rtbHouseGDPRVendorID, urlTemplate, adapters.SyncTypeRedirect, ) diff --git a/adapters/rtbhouse/usersync_test.go b/adapters/rtbhouse/usersync_test.go index 7b1d198b74d..a7cb2590f90 100644 --- a/adapters/rtbhouse/usersync_test.go +++ b/adapters/rtbhouse/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,5 @@ func TestRTBHouseSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://creativecdn.com/cm-notify?pi=prebidsrvtst&gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, rtbHouseGDPRVendorID, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 7fe88640cb0..89d69522fe8 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -10,16 +10,14 @@ import ( "net/url" "strings" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" "github.com/golang/glog" - + "github.com/mxmCherry/openrtb/v15/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/pbs" "golang.org/x/net/context/ctxhttp" - - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" ) const badvLimitSize = 50 @@ -93,6 +91,10 @@ type rubiconExtUserTpID struct { UID string `json:"uid"` } +type rubiconUserDataExt struct { + TaxonomyName string `json:"taxonomyname"` +} + type rubiconUserExt struct { Consent string `json:"consent,omitempty"` DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"` @@ -322,7 +324,7 @@ func findPrimary(alt []int) (int, []int) { return primary, alt } -func parseRubiconSizes(sizes []openrtb.Format) (primary int, alt []int, err error) { +func parseRubiconSizes(sizes []openrtb2.Format) (primary int, alt []int, err error) { // Fixes #317 if len(sizes) < 1 { err = &errortypes.BadInput{ @@ -381,7 +383,7 @@ func (a *RubiconAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (res return } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { err = &errortypes.BadServerResponse{ @@ -519,14 +521,14 @@ func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder * if rubiReq.Site != nil { siteCopy := *rubiReq.Site siteCopy.Ext, err = json.Marshal(&siteExt) - siteCopy.Publisher = &openrtb.Publisher{} + siteCopy.Publisher = &openrtb2.Publisher{} siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) - siteCopy.Content = &openrtb.Content{} + siteCopy.Content = &openrtb2.Content{} siteCopy.Content.Language = rubiconUser.Language rubiReq.Site = &siteCopy } else { - site := &openrtb.Site{} - site.Content = &openrtb.Content{} + site := &openrtb2.Site{} + site.Content = &openrtb2.Content{} site.Content.Language = rubiconUser.Language rubiReq.Site = site } @@ -534,12 +536,12 @@ func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder * if rubiReq.App != nil { appCopy := *rubiReq.App appCopy.Ext, err = json.Marshal(&siteExt) - appCopy.Publisher = &openrtb.Publisher{} + appCopy.Publisher = &openrtb2.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiReq.App = &appCopy } - rubiReq.Imp = []openrtb.Imp{thisImp} + rubiReq.Imp = []openrtb2.Imp{thisImp} var reqBuffer bytes.Buffer err = json.NewEncoder(&reqBuffer).Encode(rubiReq) @@ -612,7 +614,7 @@ func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder * return bids, nil } -func resolveVideoSizeId(placement openrtb.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { +func resolveVideoSizeId(placement openrtb2.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { if placement != 0 { if placement == 1 { return 201, nil @@ -670,7 +672,7 @@ func NewRubiconLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, uri string, } } -func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) var err error @@ -752,6 +754,11 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap userCopy := *request.User userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}} + if err := updateUserExtWithIabAttribute(&userExtRP, userCopy.Data); err != nil { + errs = append(errs, err) + continue + } + if request.User.Ext != nil { var userExt *openrtb_ext.ExtUser if err = json.Unmarshal(userCopy.Ext, &userExt); err != nil { @@ -844,14 +851,14 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap if request.Site != nil { siteCopy := *request.Site siteCopy.Ext, err = json.Marshal(&siteExt) - siteCopy.Publisher = &openrtb.Publisher{} + siteCopy.Publisher = &openrtb2.Publisher{} siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.Site = &siteCopy } if request.App != nil { appCopy := *request.App appCopy.Ext, err = json.Marshal(&siteExt) - appCopy.Publisher = &openrtb.Publisher{} + appCopy.Publisher = &openrtb2.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.App = &appCopy } @@ -863,7 +870,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap } } - rubiconRequest.Imp = []openrtb.Imp{thisImp} + rubiconRequest.Imp = []openrtb2.Imp{thisImp} rubiconRequest.Cur = nil rubiconRequest.Ext = nil @@ -886,6 +893,43 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return requestData, errs } +func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { + var segmentIdsToCopy = make([]string, 0) + + for _, dataRecord := range data { + if dataRecord.Ext != nil { + var dataExtObject rubiconUserDataExt + err := json.Unmarshal(dataRecord.Ext, &dataExtObject) + if err != nil { + continue + } + if strings.EqualFold(dataExtObject.TaxonomyName, "iab") { + for _, segment := range dataRecord.Segment { + segmentIdsToCopy = append(segmentIdsToCopy, segment.ID) + } + } + } + } + + 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["iab"] = segmentIdsToCopy + + if target, err := json.Marshal(&userExtRPTarget); err != nil { + return &errortypes.BadInput{Message: err.Error()} + } else { + userExtRP.RP.Target = target + } + + return nil +} + func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) { rubiconUidsParam := mappedRubiconUidsParam{ tpIds: make([]rubiconExtUserTpID, 0), @@ -972,7 +1016,7 @@ func updateUserExtWithTpIdsAndSegments(userExtRP *rubiconUserExt, rubiconUidsPar return nil } -func isVideo(imp openrtb.Imp) bool { +func isVideo(imp openrtb2.Imp) bool { video := imp.Video if video != nil { // Do any other media types exist? Or check required video fields. @@ -981,12 +1025,12 @@ func isVideo(imp openrtb.Imp) bool { return false } -func isFullyPopulatedVideo(video *openrtb.Video) bool { +func isFullyPopulatedVideo(video *openrtb2.Video) bool { // These are just recommended video fields for XAPI return video.MIMEs != nil && video.Protocols != nil && video.MaxDuration != 0 && video.Linearity != 0 && video.API != nil } -func (a *RubiconAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -1003,14 +1047,14 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), }} } - var bidReq openrtb.BidRequest + var bidReq openrtb2.BidRequest if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { return nil, []error{err} } @@ -1057,7 +1101,7 @@ func (a *RubiconAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR return bidResponse, nil } -func cmpOverrideFromBidRequest(bidRequest *openrtb.BidRequest) float64 { +func cmpOverrideFromBidRequest(bidRequest *openrtb2.BidRequest) float64 { var bidRequestExt bidRequestExt if err := json.Unmarshal(bidRequest.Ext, &bidRequestExt); err != nil { return 0 @@ -1066,7 +1110,7 @@ func cmpOverrideFromBidRequest(bidRequest *openrtb.BidRequest) float64 { return bidRequestExt.Prebid.Bidders.Rubicon.Debug.CpmOverride } -func mapImpIdToCpmOverride(imps []openrtb.Imp) map[string]float64 { +func mapImpIdToCpmOverride(imps []openrtb2.Imp) map[string]float64 { impIdToCmpOverride := make(map[string]float64) for _, imp := range imps { var bidderExt adapters.ExtImpBidder diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 54953b213b1..dc5b3a90423 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" "io/ioutil" "net/http" "net/http/httptest" @@ -12,19 +11,21 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/errortypes" + + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" "fmt" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -81,7 +82,7 @@ func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -126,14 +127,14 @@ func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { return } - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "RUBICON", - Bid: make([]openrtb.Bid, 2), + Bid: make([]openrtb2.Bid, 2), }, }, } @@ -185,7 +186,7 @@ func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { targeting := "{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}" rawTargeting := json.RawMessage(targeting) - resp.SeatBid[0].Bid[0] = openrtb.Bid{ + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ ID: "random-id", ImpID: imp.ID, Price: rubidata.tags[ix].bid, @@ -320,8 +321,8 @@ func TestRubiconUserSyncInfo(t *testing.T) { assert.False(t, an.SkipNoCookies(), "SkipNoCookies should be false") } -func getTestSizes() map[int]openrtb.Format { - return map[int]openrtb.Format{ +func getTestSizes() map[int]openrtb2.Format { + return map[int]openrtb2.Format{ 15: {W: 300, H: 250}, 10: {W: 300, H: 600}, 2: {W: 728, H: 91}, @@ -335,7 +336,7 @@ func getTestSizes() map[int]openrtb.Format { func TestParseSizes(t *testing.T) { SIZE_ID := getTestSizes() - sizes := []openrtb.Format{ + sizes := []openrtb2.Format{ SIZE_ID[10], SIZE_ID[15], } @@ -345,7 +346,7 @@ func TestParseSizes(t *testing.T) { assert.Equal(t, 1, len(alt), "Alt not len 1") assert.Equal(t, 10, alt[0], "Alt not 10: %d", alt[0]) - sizes = []openrtb.Format{ + sizes = []openrtb2.Format{ { W: 1111, H: 2222, @@ -357,7 +358,7 @@ func TestParseSizes(t *testing.T) { assert.Equal(t, 15, primary, "Primary %d != 15", primary) assert.Equal(t, 0, len(alt), "Alt len %d != 0", len(alt)) - sizes = []openrtb.Format{ + sizes = []openrtb2.Format{ SIZE_ID[15], } primary, alt, err = parseRubiconSizes(sizes) @@ -365,7 +366,7 @@ func TestParseSizes(t *testing.T) { assert.Equal(t, 15, primary, "Primary %d != 15", primary) assert.Equal(t, 0, len(alt), "Alt len %d != 0", len(alt)) - sizes = []openrtb.Format{ + sizes = []openrtb2.Format{ { W: 1111, H: 1222, @@ -385,20 +386,20 @@ func TestMASAlgorithm(t *testing.T) { ok bool } type testStub struct { - input []openrtb.Format + input []openrtb2.Format output output } testStubs := []testStub{ { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[2], SIZE_ID[9], }, output{2, []int{9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[9], SIZE_ID[15], @@ -406,14 +407,14 @@ func TestMASAlgorithm(t *testing.T) { output{15, []int{9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[2], SIZE_ID[15], }, output{15, []int{2}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[15], SIZE_ID[9], SIZE_ID[2], @@ -421,7 +422,7 @@ func TestMASAlgorithm(t *testing.T) { output{15, []int{2, 9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[10], SIZE_ID[9], SIZE_ID[2], @@ -429,7 +430,7 @@ func TestMASAlgorithm(t *testing.T) { output{2, []int{10, 9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[8], SIZE_ID[15], @@ -437,7 +438,7 @@ func TestMASAlgorithm(t *testing.T) { output{15, []int{33, 8}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[8], SIZE_ID[9], @@ -446,7 +447,7 @@ func TestMASAlgorithm(t *testing.T) { output{2, []int{33, 8, 9}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[8], SIZE_ID[9], @@ -454,7 +455,7 @@ func TestMASAlgorithm(t *testing.T) { output{9, []int{33, 8}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[8], SIZE_ID[2], @@ -462,24 +463,24 @@ func TestMASAlgorithm(t *testing.T) { output{2, []int{33, 8}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[33], SIZE_ID[2], }, output{2, []int{33}, false}, }, { - []openrtb.Format{ + []openrtb2.Format{ SIZE_ID[8], }, output{8, []int{}, false}, }, { - []openrtb.Format{}, + []openrtb2.Format{}, output{0, []int{}, true}, }, { - []openrtb.Format{ + []openrtb2.Format{ {W: 1111, H: 2345, }, @@ -527,7 +528,7 @@ func TestAppendTracker(t *testing.T) { func TestResolveVideoSizeId(t *testing.T) { testScenarios := []struct { - placement openrtb.VideoPlacementType + placement openrtb2.VideoPlacementType instl int8 impId string expected int @@ -626,11 +627,11 @@ func TestWrongFormatResponse(t *testing.T) { func TestZeroSeatBidResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{}, + SeatBid: []openrtb2.SeatBid{}, } js, _ := json.Marshal(resp) w.Write(js) @@ -647,14 +648,14 @@ func TestZeroSeatBidResponse(t *testing.T) { func TestEmptyBidResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "RUBICON", - Bid: make([]openrtb.Bid, 0), + Bid: make([]openrtb2.Bid, 0), }, }, } @@ -673,18 +674,18 @@ func TestEmptyBidResponse(t *testing.T) { func TestWrongBidIdResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "RUBICON", - Bid: make([]openrtb.Bid, 2), + Bid: make([]openrtb2.Bid, 2), }, }, } - resp.SeatBid[0].Bid[0] = openrtb.Bid{ + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ ID: "random-id", ImpID: "zma", Price: 1.67, @@ -710,18 +711,18 @@ func TestWrongBidIdResponse(t *testing.T) { func TestZeroPriceBidResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{ + resp := openrtb2.BidResponse{ ID: "test-response-id", BidID: "test-bid-id", Cur: "USD", - SeatBid: []openrtb.SeatBid{ + SeatBid: []openrtb2.SeatBid{ { Seat: "RUBICON", - Bid: make([]openrtb.Bid, 1), + Bid: make([]openrtb2.Bid, 1), }, }, } - resp.SeatBid[0].Bid[0] = openrtb.Bid{ + resp.SeatBid[0].Bid[0] = openrtb2.Bid{ ID: "test-bid-id", ImpID: "first-tag", Price: 0, @@ -747,7 +748,7 @@ func TestDifferentRequest(t *testing.T) { an, ctx, pbReq := CreatePrebidRequest(server, t) // test app not nil - pbReq.App = &openrtb.App{ + pbReq.App = &openrtb2.App{ ID: "com.test", Name: "testApp", } @@ -775,13 +776,13 @@ func TestDifferentRequest(t *testing.T) { pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", 8394, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) // test invalid size - pbReq.Bidders[0].AdUnits[0].Sizes = []openrtb.Format{ + pbReq.Bidders[0].AdUnits[0].Sizes = []openrtb2.Format{ { W: 2222, H: 333, }, } - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb.Format{ + pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ { W: 222, H: 3333, @@ -795,7 +796,7 @@ func TestDifferentRequest(t *testing.T) { b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) assert.NotNil(t, err, "Should have gotten an error: %v", err) - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb.Format{ + pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ { W: 222, H: 3333, @@ -861,7 +862,7 @@ func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdap pbin := pbs.PBSRequest{ AdUnits: make([]pbs.AdUnit, 3), - Device: &openrtb.Device{PxRatio: rubidata.devicePxRatio}, + Device: &openrtb2.Device{PxRatio: rubidata.devicePxRatio}, SDK: &pbs.SDK{Source: rubidata.sdkSource, Platform: rubidata.sdkPlatform, Version: rubidata.sdkVersion}, } @@ -869,7 +870,7 @@ func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdap pbin.AdUnits[i] = pbs.AdUnit{ Code: tag.code, MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ SIZE_ID[10], SIZE_ID[15], }, @@ -946,12 +947,12 @@ func TestOpenRTBRequest(t *testing.T) { devicePxRatio: 4.0, } - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-banner-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, @@ -965,7 +966,7 @@ func TestOpenRTBRequest(t *testing.T) { }}`), }, { ID: "test-imp-video-id", - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: 640, H: 360, MIMEs: []string{"video/mp4"}, @@ -988,10 +989,10 @@ func TestOpenRTBRequest(t *testing.T) { } }}`), }}, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ PxRatio: rubidata.devicePxRatio, }, - User: &openrtb.User{ + User: &openrtb2.User{ Ext: json.RawMessage(`{"digitrust": { "id": "some-digitrust-id", "keyv": 1, @@ -1015,7 +1016,7 @@ func TestOpenRTBRequest(t *testing.T) { httpReq := reqs[i] assert.Equal(t, "POST", httpReq.Method, "Expected a POST message. Got %s", httpReq.Method) - var rpRequest openrtb.BidRequest + var rpRequest openrtb2.BidRequest if err := json.Unmarshal(httpReq.Body, &rpRequest); err != nil { t.Fatalf("Failed to unmarshal HTTP request: %v", rpRequest) } @@ -1031,16 +1032,16 @@ func TestOpenRTBRequest(t *testing.T) { t.Fatal("Error unmarshalling request from the outgoing request.") } - assert.Equal(t, uint64(300), rpRequest.Imp[0].Banner.Format[0].W, + assert.Equal(t, int64(300), rpRequest.Imp[0].Banner.Format[0].W, "Banner width does not match. Expected %d, Got %d", 300, rpRequest.Imp[0].Banner.Format[0].W) - assert.Equal(t, uint64(250), rpRequest.Imp[0].Banner.Format[0].H, + assert.Equal(t, int64(250), rpRequest.Imp[0].Banner.Format[0].H, "Banner height does not match. Expected %d, Got %d", 250, rpRequest.Imp[0].Banner.Format[0].H) - assert.Equal(t, uint64(300), rpRequest.Imp[0].Banner.Format[1].W, + assert.Equal(t, int64(300), rpRequest.Imp[0].Banner.Format[1].W, "Banner width does not match. Expected %d, Got %d", 300, rpRequest.Imp[0].Banner.Format[1].W) - assert.Equal(t, uint64(600), rpRequest.Imp[0].Banner.Format[1].H, + assert.Equal(t, int64(600), rpRequest.Imp[0].Banner.Format[1].H, "Banner height does not match. Expected %d, Got %d", 600, rpRequest.Imp[0].Banner.Format[1].H) } else if rpRequest.Imp[0].ID == "test-imp-video-id" { var rpExt rubiconVideoExt @@ -1048,10 +1049,10 @@ func TestOpenRTBRequest(t *testing.T) { t.Fatal("Error unmarshalling request from the outgoing request.") } - assert.Equal(t, uint64(640), rpRequest.Imp[0].Video.W, + assert.Equal(t, int64(640), rpRequest.Imp[0].Video.W, "Video width does not match. Expected %d, Got %d", 640, rpRequest.Imp[0].Video.W) - assert.Equal(t, uint64(360), rpRequest.Imp[0].Video.H, + assert.Equal(t, int64(360), rpRequest.Imp[0].Video.H, "Video height does not match. Expected %d, Got %d", 360, rpRequest.Imp[0].Video.H) assert.Equal(t, "video/mp4", rpRequest.Imp[0].Video.MIMEs[0], "Video MIMEs do not match. Expected %s, Got %s", "video/mp4", rpRequest.Imp[0].Video.MIMEs[0]) @@ -1084,17 +1085,17 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: 640, H: 360, MIMEs: []string{"video/mp4"}, @@ -1115,7 +1116,7 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) { assert.Equal(t, 1, len(reqs), "Unexpected number of HTTP requests. Got %d. Expected %d", len(reqs), 1) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1131,12 +1132,12 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, @@ -1160,7 +1161,7 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1191,13 +1192,13 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { badvOverflowed[i] = strconv.Itoa(i) } - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", BAdv: badvOverflowed, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], }, }, @@ -1215,7 +1216,7 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1228,12 +1229,12 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, @@ -1244,7 +1245,7 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { "accountId": 7891 }}`), }}, - User: &openrtb.User{ + User: &openrtb2.User{ Ext: json.RawMessage(`{"eids": [ { "source": "pubcid", @@ -1283,7 +1284,7 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1324,24 +1325,24 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ SIZE_ID[15], SIZE_ID[10], }, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: 640, H: 360, MIMEs: []string{"video/mp4"}, - Protocols: []openrtb.Protocol{openrtb.ProtocolVAST10}, + Protocols: []openrtb2.Protocol{openrtb2.ProtocolVAST10}, MaxDuration: 30, Linearity: 1, - API: []openrtb.APIFramework{}, + API: []openrtb2.APIFramework{}, }, Ext: json.RawMessage(`{"bidder": { "zoneId": 8394, @@ -1360,7 +1361,7 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t assert.Equal(t, 1, len(reqs), "Unexpected number of HTTP requests. Got %d. Expected %d", len(reqs), 1) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1376,18 +1377,18 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) { bidder := new(RubiconAdapter) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Video: &openrtb.Video{ + Video: &openrtb2.Video{ W: 640, H: 360, MIMEs: []string{"video/mp4"}, - Protocols: []openrtb.Protocol{openrtb.ProtocolVAST10}, + Protocols: []openrtb2.Protocol{openrtb2.ProtocolVAST10}, MaxDuration: 30, Linearity: 1, - API: []openrtb.APIFramework{}, + API: []openrtb2.APIFramework{}, }, Ext: json.RawMessage(`{ "prebid":{ @@ -1401,7 +1402,7 @@ func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) - rubiconReq := &openrtb.BidRequest{} + rubiconReq := &openrtb2.BidRequest{} if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { t.Fatalf("Unexpected error while decoding request: %s", err) } @@ -1439,12 +1440,12 @@ func TestOpenRTBSurpriseResponse(t *testing.T) { } func TestOpenRTBStandardResponse(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 320, H: 50, }}, @@ -1486,12 +1487,12 @@ func TestOpenRTBStandardResponse(t *testing.T) { } func TestOpenRTBResponseOverridePriceFromBidRequest(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 320, H: 50, }}, @@ -1533,12 +1534,12 @@ func TestOpenRTBResponseOverridePriceFromBidRequest(t *testing.T) { } func TestOpenRTBResponseOverridePriceFromCorrespondingImp(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "test-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 320, H: 50, }}, @@ -1583,9 +1584,9 @@ func TestOpenRTBResponseOverridePriceFromCorrespondingImp(t *testing.T) { } func TestOpenRTBCopyBidIdFromResponseIfZero(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{{}}, + Imp: []openrtb2.Imp{{}}, } requestJson, _ := json.Marshal(request) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 76c64ff95ec..b85c28def44 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -9,6 +9,52 @@ "id": "1", "bundle": "com.wls.testwlsapplication" }, + "user": { + "data": [ + { + "ext": { + "taxonomyname": "iab" + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "taxonomyname": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "taxonomyname": "IaB" + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "taxonomyname": [ + "wrong iab type" + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + } + ] + }, "imp": [ { "id": "test-imp-id", @@ -30,8 +76,8 @@ "video": { }, "accountId": 1001, - "siteId":113932, - "zoneId":535510 + "siteId": 113932, + "zoneId": 535510 } } } @@ -52,6 +98,63 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" }, + "user": { + "data": [ + { + "ext": { + "taxonomyname": "iab" + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "taxonomyname": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "taxonomyname": "IaB" + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "taxonomyname": [ + "wrong iab type" + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + } + ], + "ext": { + "digitrust": null, + "rp": { + "target": { + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, "app": { "id": "1", "ext": { @@ -91,7 +194,7 @@ }, "ext": { "rp": { - "track":{ + "track": { "mint": "", "mint_version": "" }, diff --git a/adapters/rubicon/usersync.go b/adapters/rubicon/usersync.go index 9c86024771e..a4ab464a73f 100644 --- a/adapters/rubicon/usersync.go +++ b/adapters/rubicon/usersync.go @@ -3,10 +3,10 @@ package rubicon import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewRubiconSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("rubicon", 52, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("rubicon", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/rubicon/usersync_test.go b/adapters/rubicon/usersync_test.go index 2fd53d6b62b..eca4056206e 100644 --- a/adapters/rubicon/usersync_test.go +++ b/adapters/rubicon/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,7 +25,6 @@ func TestRubiconSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://pixel.rubiconproject.com/exchange/sync.php?p=prebid&gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 52, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) assert.Equal(t, "rubicon", syncer.FamilyName()) } diff --git a/adapters/sharethrough/butler.go b/adapters/sharethrough/butler.go index 738d382a938..b34ae0844ab 100644 --- a/adapters/sharethrough/butler.go +++ b/adapters/sharethrough/butler.go @@ -9,11 +9,11 @@ import ( "strconv" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/privacy/ccpa" ) const defaultTmax = 10000 // 10 sec @@ -33,7 +33,7 @@ type StrAdSeverParams struct { } type StrOpenRTBInterface interface { - requestFromOpenRTB(openrtb.Imp, *openrtb.BidRequest, string) (*adapters.RequestData, error) + requestFromOpenRTB(openrtb2.Imp, *openrtb2.BidRequest, string) (*adapters.RequestData, error) responseToOpenRTB([]byte, *adapters.RequestData) (*adapters.BidderResponse, []error) } @@ -70,7 +70,7 @@ type StrOpenRTBTranslator struct { UserAgentParsers UserAgentParsers } -func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb.Imp, request *openrtb.BidRequest, domain string) (*adapters.RequestData, error) { +func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb2.Imp, request *openrtb2.BidRequest, domain string) (*adapters.RequestData, error) { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -111,8 +111,8 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb.Imp, request *openr ConsentString: userInfo.Consent, USPrivacySignal: usPolicySignal, Iframe: strImpParams.Iframe, - Height: height, - Width: width, + Height: uint64(height), + Width: uint64(width), InstantPlayCapable: s.Util.canAutoPlayVideo(request.Device.UA, s.UserAgentParsers), TheTradeDeskUserId: userInfo.TtdUid, SharethroughUserId: userInfo.StxUid, @@ -152,7 +152,7 @@ func (s StrOpenRTBTranslator) responseToOpenRTB(strRawResp []byte, btlrReq *adap return nil, errs } - bid := &openrtb.Bid{ + bid := &openrtb2.Bid{ AdID: strResp.AdServerRequestID, ID: strResp.BidID, ImpID: btlrParams.BidID, @@ -161,8 +161,8 @@ func (s StrOpenRTBTranslator) responseToOpenRTB(strRawResp []byte, btlrReq *adap CrID: creative.Metadata.CreativeKey, DealID: creative.Metadata.DealID, AdM: adm, - H: btlrParams.Height, - W: btlrParams.Width, + H: int64(btlrParams.Height), + W: int64(btlrParams.Width), } typedBid.Bid = bid @@ -171,7 +171,7 @@ func (s StrOpenRTBTranslator) responseToOpenRTB(strRawResp []byte, btlrReq *adap return bidResponse, errs } -func (h StrBodyHelper) buildBody(request *openrtb.BidRequest, strImpParams openrtb_ext.ExtImpSharethrough) (body []byte, err error) { +func (h StrBodyHelper) buildBody(request *openrtb2.BidRequest, strImpParams openrtb_ext.ExtImpSharethrough) (body []byte, err error) { timeout := request.TMax if timeout == 0 { timeout = defaultTmax diff --git a/adapters/sharethrough/butler_test.go b/adapters/sharethrough/butler_test.go index f6d43971f4e..fbef417e530 100644 --- a/adapters/sharethrough/butler_test.go +++ b/adapters/sharethrough/butler_test.go @@ -9,17 +9,17 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) type MockUtil struct { mockCanAutoPlayVideo func() bool mockGdprApplies func() bool - mockGetPlacementSize func() (uint64, uint64) + mockGetPlacementSize func() (int64, int64) mockParseUserInfo func() userInfo UtilityInterface } @@ -28,15 +28,15 @@ func (m MockUtil) canAutoPlayVideo(userAgent string, parsers UserAgentParsers) b return m.mockCanAutoPlayVideo() } -func (m MockUtil) gdprApplies(request *openrtb.BidRequest) bool { +func (m MockUtil) gdprApplies(request *openrtb2.BidRequest) bool { return m.mockGdprApplies() } -func (m MockUtil) getPlacementSize(imp openrtb.Imp, strImpParams openrtb_ext.ExtImpSharethrough) (height uint64, width uint64) { +func (m MockUtil) getPlacementSize(imp openrtb2.Imp, strImpParams openrtb_ext.ExtImpSharethrough) (height, width int64) { return m.mockGetPlacementSize() } -func (m MockUtil) parseUserInfo(user *openrtb.User) (ui userInfo) { +func (m MockUtil) parseUserInfo(user *openrtb2.User) (ui userInfo) { return m.mockParseUserInfo() } @@ -75,26 +75,26 @@ func assertRequestDataEquals(t *testing.T, testName string, expected *adapters.R func TestSuccessRequestFromOpenRTB(t *testing.T) { tests := map[string]struct { - inputImp openrtb.Imp - inputReq *openrtb.BidRequest + inputImp openrtb2.Imp + inputReq *openrtb2.BidRequest inputDom string expected *adapters.RequestData }{ "Generates the correct AdServer request from Imp (no user provided)": { - inputImp: openrtb.Imp{ + inputImp: openrtb2.Imp{ ID: "abc", Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true, "iframeSize": [10, 20], "bidfloor": 1.0} }`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{H: 30, W: 40}}, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{H: 30, W: 40}}, }, }, - inputReq: &openrtb.BidRequest{ - App: &openrtb.App{Ext: []byte(`{}`)}, - Device: &openrtb.Device{ + inputReq: &openrtb2.BidRequest{ + App: &openrtb2.App{Ext: []byte(`{}`)}, + Device: &openrtb2.Device{ UA: "Android Chome/60", IP: "127.0.0.1", }, - Site: &openrtb.Site{Page: "http://a.domain.com/page"}, + Site: &openrtb2.Site{Page: "http://a.domain.com/page"}, BAdv: []string{"domain1.com", "domain2.com"}, TMax: 700, }, @@ -114,20 +114,20 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) { }, }, "Generates width/height if not provided": { - inputImp: openrtb.Imp{ + inputImp: openrtb2.Imp{ ID: "abc", Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true} }`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{H: 30, W: 40}}, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{H: 30, W: 40}}, }, }, - inputReq: &openrtb.BidRequest{ - App: &openrtb.App{Ext: []byte(`{}`)}, - Device: &openrtb.Device{ + inputReq: &openrtb2.BidRequest{ + App: &openrtb2.App{Ext: []byte(`{}`)}, + Device: &openrtb2.Device{ UA: "Android Chome/60", IP: "127.0.0.1", }, - Site: &openrtb.Site{Page: "http://a.domain.com/page"}, + Site: &openrtb2.Site{Page: "http://a.domain.com/page"}, BAdv: []string{"domain1.com", "domain2.com"}, TMax: 700, }, @@ -157,7 +157,7 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) { mockUtil := MockUtil{ mockCanAutoPlayVideo: func() bool { return true }, mockGdprApplies: func() bool { return true }, - mockGetPlacementSize: func() (uint64, uint64) { return 100, 200 }, + mockGetPlacementSize: func() (int64, int64) { return 100, 200 }, mockParseUserInfo: func() userInfo { return userInfo{Consent: "ok", TtdUid: "ttduid", StxUid: "stxuid"} }, } @@ -177,27 +177,27 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) { func TestFailureRequestFromOpenRTB(t *testing.T) { tests := map[string]struct { - inputImp openrtb.Imp - inputReq *openrtb.BidRequest + inputImp openrtb2.Imp + inputReq *openrtb2.BidRequest expectedError string }{ "Fails when unable to parse imp.Ext": { - inputImp: openrtb.Imp{ + inputImp: openrtb2.Imp{ Ext: []byte(`{"abc`), }, - inputReq: &openrtb.BidRequest{ - Device: &openrtb.Device{UA: "A", IP: "ip"}, - Site: &openrtb.Site{Page: "page"}, + inputReq: &openrtb2.BidRequest{ + Device: &openrtb2.Device{UA: "A", IP: "ip"}, + Site: &openrtb2.Site{Page: "page"}, }, expectedError: `unexpected end of JSON input`, }, "Fails when unable to parse imp.Ext.Bidder": { - inputImp: openrtb.Imp{ + inputImp: openrtb2.Imp{ Ext: []byte(`{ "bidder": "{ abc" }`), }, - inputReq: &openrtb.BidRequest{ - Device: &openrtb.Device{UA: "A", IP: "ip"}, - Site: &openrtb.Site{Page: "page"}, + inputReq: &openrtb2.BidRequest{ + Device: &openrtb2.Device{UA: "A", IP: "ip"}, + Site: &openrtb2.Site{Page: "page"}, }, expectedError: `json: cannot unmarshal string into Go value of type openrtb_ext.ExtImpSharethrough`, }, @@ -212,7 +212,7 @@ func TestFailureRequestFromOpenRTB(t *testing.T) { mockUtil := MockUtil{ mockCanAutoPlayVideo: func() bool { return true }, mockGdprApplies: func() bool { return true }, - mockGetPlacementSize: func() (uint64, uint64) { return 100, 200 }, + mockGetPlacementSize: func() (int64, int64) { return 100, 200 }, mockParseUserInfo: func() userInfo { return userInfo{Consent: "ok", TtdUid: "ttduid", StxUid: "stxuid"} }, } @@ -288,7 +288,7 @@ func TestSuccessResponseToOpenRTB(t *testing.T) { expectedSuccess: &adapters.BidderResponse{ Bids: []*adapters.TypedBid{{ BidType: openrtb_ext.BidTypeBanner, - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ AdID: "arid", ID: "bid", ImpID: "bidid", @@ -376,19 +376,19 @@ func TestFailResponseToOpenRTB(t *testing.T) { func TestBuildBody(t *testing.T) { tests := map[string]struct { - inputRequest *openrtb.BidRequest + inputRequest *openrtb2.BidRequest inputImp openrtb_ext.ExtImpSharethrough expectedJson []byte expectedError error }{ "Empty input: skips badomains, tmax default to 10 sec and sets deadline accordingly": { - inputRequest: &openrtb.BidRequest{}, + inputRequest: &openrtb2.BidRequest{}, inputImp: openrtb_ext.ExtImpSharethrough{}, expectedJson: []byte(`{"tmax":10000, "deadline":"2019-09-12T11:29:10.000123456Z"}`), expectedError: nil, }, "Sets badv as list of domains according to Badv (tmax default to 10 sec and sets deadline accordingly)": { - inputRequest: &openrtb.BidRequest{ + inputRequest: &openrtb2.BidRequest{ BAdv: []string{"dom1.com", "dom2.com"}, }, inputImp: openrtb_ext.ExtImpSharethrough{}, @@ -396,7 +396,7 @@ func TestBuildBody(t *testing.T) { expectedError: nil, }, "Sets tmax and deadline according to Tmax": { - inputRequest: &openrtb.BidRequest{ + inputRequest: &openrtb2.BidRequest{ TMax: 500, }, inputImp: openrtb_ext.ExtImpSharethrough{}, @@ -404,7 +404,7 @@ func TestBuildBody(t *testing.T) { expectedError: nil, }, "Sets bidfloor according to the Imp object": { - inputRequest: &openrtb.BidRequest{}, + inputRequest: &openrtb2.BidRequest{}, inputImp: openrtb_ext.ExtImpSharethrough{ BidFloor: 1.23, }, @@ -428,7 +428,7 @@ func TestBuildBody(t *testing.T) { func TestBuildUri(t *testing.T) { tests := map[string]struct { inputParams StrAdSeverParams - inputApp *openrtb.App + inputApp *openrtb2.App expected []string }{ "Generates expected URL, appending all params": { diff --git a/adapters/sharethrough/params_test.go b/adapters/sharethrough/params_test.go index e4e659f4420..416f459341d 100644 --- a/adapters/sharethrough/params_test.go +++ b/adapters/sharethrough/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/sharethrough/sharethrough.go b/adapters/sharethrough/sharethrough.go index 63958d029f1..410ca391bd4 100644 --- a/adapters/sharethrough/sharethrough.go +++ b/adapters/sharethrough/sharethrough.go @@ -5,11 +5,11 @@ import ( "net/http" "regexp" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const supplyId = "FGMrCMMc" @@ -35,7 +35,7 @@ type SharethroughAdapter struct { AdServer StrOpenRTBInterface } -func (a SharethroughAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a SharethroughAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var reqs []*adapters.RequestData if request.Site == nil { @@ -56,7 +56,7 @@ func (a SharethroughAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo * return reqs, []error{} } -func (a SharethroughAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a SharethroughAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } diff --git a/adapters/sharethrough/sharethrough_test.go b/adapters/sharethrough/sharethrough_test.go index 0e31a90bac4..1cf45d1fde2 100644 --- a/adapters/sharethrough/sharethrough_test.go +++ b/adapters/sharethrough/sharethrough_test.go @@ -6,11 +6,11 @@ import ( "regexp" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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/stretchr/testify/assert" ) @@ -21,7 +21,7 @@ type MockStrAdServer struct { StrOpenRTBInterface } -func (m MockStrAdServer) requestFromOpenRTB(imp openrtb.Imp, request *openrtb.BidRequest, domain string) (*adapters.RequestData, error) { +func (m MockStrAdServer) requestFromOpenRTB(imp openrtb2.Imp, request *openrtb2.BidRequest, domain string) (*adapters.RequestData, error) { return m.mockRequestFromOpenRTB() } @@ -88,22 +88,22 @@ func TestSuccessMakeRequests(t *testing.T) { } tests := map[string]struct { - input *openrtb.BidRequest + input *openrtb2.BidRequest expected []*adapters.RequestData }{ "Generates expected Request": { - input: &openrtb.BidRequest{ - Site: &openrtb.Site{ + input: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "test.com", }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "Android Chome/60", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "abc", Ext: []byte(`{"pkey": "pkey", "iframe": true, "iframeSize": [10, 20]}`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{H: 30, W: 40}}, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{H: 30, W: 40}}, }, }}, }, @@ -137,22 +137,22 @@ func TestSuccessMakeRequests(t *testing.T) { func TestFailureMakeRequests(t *testing.T) { tests := map[string]struct { - input *openrtb.BidRequest + input *openrtb2.BidRequest expected string }{ "Returns nil if failed to generate request": { - input: &openrtb.BidRequest{ - Site: &openrtb.Site{ + input: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "test.com", }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ UA: "Android Chome/60", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "abc", Ext: []byte(`{"pkey": "pkey", "iframe": true, "iframeSize": [10, 20]}`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{H: 30, W: 40}}, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{H: 30, W: 40}}, }, }}, }, @@ -216,7 +216,7 @@ func TestSuccessMakeBids(t *testing.T) { for testName, test := range tests { t.Logf("Test case: %s\n", testName) - response, errors := adapter.MakeBids(&openrtb.BidRequest{}, &adapters.RequestData{}, test.inputResponse) + response, errors := adapter.MakeBids(&openrtb2.BidRequest{}, &adapters.RequestData{}, test.inputResponse) if len(errors) > 0 { t.Errorf("Expected no errors, got %d\n", len(errors)) } @@ -264,7 +264,7 @@ func TestFailureMakeBids(t *testing.T) { for testName, test := range tests { t.Logf("Test case: %s\n", testName) - response, errors := adapter.MakeBids(&openrtb.BidRequest{}, &adapters.RequestData{}, test.inputResponse) + response, errors := adapter.MakeBids(&openrtb2.BidRequest{}, &adapters.RequestData{}, test.inputResponse) if response != nil { t.Errorf("Expected response to be nil, got %+v\n", response) } diff --git a/adapters/sharethrough/usersync.go b/adapters/sharethrough/usersync.go index 7d5d6f135a8..f76f41ca83e 100644 --- a/adapters/sharethrough/usersync.go +++ b/adapters/sharethrough/usersync.go @@ -1,11 +1,12 @@ package sharethrough import ( - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewSharethroughSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sharethrough", 80, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("sharethrough", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/sharethrough/usersync_test.go b/adapters/sharethrough/usersync_test.go index c48a6d51f8e..00b3c427fb8 100644 --- a/adapters/sharethrough/usersync_test.go +++ b/adapters/sharethrough/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,7 +25,6 @@ func TestSharethroughSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://match.sharethrough.com?gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 80, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) assert.Equal(t, "sharethrough", syncer.FamilyName()) } diff --git a/adapters/sharethrough/utils.go b/adapters/sharethrough/utils.go index 2dc22615aa7..e10a8f90b7b 100644 --- a/adapters/sharethrough/utils.go +++ b/adapters/sharethrough/utils.go @@ -5,27 +5,28 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/buger/jsonparser" "html/template" "net" "net/url" "regexp" "strconv" "time" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" ) const minChromeVersion = 53 const minSafariVersion = 10 type UtilityInterface interface { - gdprApplies(*openrtb.BidRequest) bool - parseUserInfo(*openrtb.User) userInfo + gdprApplies(*openrtb2.BidRequest) bool + parseUserInfo(*openrtb2.User) userInfo getAdMarkup([]byte, openrtb_ext.ExtImpSharethroughResponse, *StrAdSeverParams) (string, error) - getBestFormat([]openrtb.Format) (uint64, uint64) - getPlacementSize(openrtb.Imp, openrtb_ext.ExtImpSharethrough) (uint64, uint64) + getBestFormat([]openrtb2.Format) (int64, int64) + getPlacementSize(openrtb2.Imp, openrtb_ext.ExtImpSharethrough) (int64, int64) canAutoPlayVideo(string, UserAgentParsers) bool isAndroid(string) bool @@ -123,10 +124,10 @@ func (u Util) getAdMarkup(strRawResp []byte, strResp openrtb_ext.ExtImpSharethro return templatedBuf.String(), nil } -func (u Util) getPlacementSize(imp openrtb.Imp, strImpParams openrtb_ext.ExtImpSharethrough) (height uint64, width uint64) { +func (u Util) getPlacementSize(imp openrtb2.Imp, strImpParams openrtb_ext.ExtImpSharethrough) (height, width int64) { height, width = 1, 1 if len(strImpParams.IframeSize) >= 2 { - height, width = uint64(strImpParams.IframeSize[0]), uint64(strImpParams.IframeSize[1]) + height, width = int64(strImpParams.IframeSize[0]), int64(strImpParams.IframeSize[1]) } else if imp.Banner != nil { height, width = u.getBestFormat(imp.Banner.Format) } @@ -134,7 +135,7 @@ func (u Util) getPlacementSize(imp openrtb.Imp, strImpParams openrtb_ext.ExtImpS return } -func (u Util) getBestFormat(formats []openrtb.Format) (height uint64, width uint64) { +func (u Util) getBestFormat(formats []openrtb2.Format) (height, width int64) { height, width = 1, 1 for i := 0; i < len(formats); i++ { format := formats[i] @@ -195,7 +196,7 @@ func (u Util) isAtMinSafariVersion(userAgent string, parser *regexp.Regexp) bool return u.isAtMinVersion(userAgent, parser, minSafariVersion) } -func (u Util) gdprApplies(request *openrtb.BidRequest) bool { +func (u Util) gdprApplies(request *openrtb2.BidRequest) bool { var gdprApplies int64 if request.Regs != nil { @@ -208,7 +209,7 @@ func (u Util) gdprApplies(request *openrtb.BidRequest) bool { return gdprApplies != 0 } -func (u Util) parseUserInfo(user *openrtb.User) (ui userInfo) { +func (u Util) parseUserInfo(user *openrtb2.User) (ui userInfo) { if user == nil { return } diff --git a/adapters/sharethrough/utils_test.go b/adapters/sharethrough/utils_test.go index aa5ae58cc5c..b842cf0b0c0 100644 --- a/adapters/sharethrough/utils_test.go +++ b/adapters/sharethrough/utils_test.go @@ -4,11 +4,12 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/stretchr/testify/assert" "regexp" "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" ) func TestGetAdMarkup(t *testing.T) { @@ -71,31 +72,31 @@ func TestGetAdMarkup(t *testing.T) { func TestGetPlacementSize(t *testing.T) { tests := map[string]struct { - imp openrtb.Imp + imp openrtb2.Imp strImpParams openrtb_ext.ExtImpSharethrough - expectedHeight uint64 - expectedWidth uint64 + expectedHeight int64 + expectedWidth int64 }{ "Returns size from STR params if provided": { - imp: openrtb.Imp{}, + imp: openrtb2.Imp{}, strImpParams: openrtb_ext.ExtImpSharethrough{IframeSize: []int{100, 200}}, expectedHeight: 100, expectedWidth: 200, }, "Skips size from STR params if malformed": { - imp: openrtb.Imp{}, + imp: openrtb2.Imp{}, strImpParams: openrtb_ext.ExtImpSharethrough{IframeSize: []int{100}}, expectedHeight: 1, expectedWidth: 1, }, "Returns size from banner format if provided": { - imp: openrtb.Imp{Banner: &openrtb.Banner{Format: []openrtb.Format{{H: 100, W: 200}}}}, + imp: openrtb2.Imp{Banner: &openrtb2.Banner{Format: []openrtb2.Format{{H: 100, W: 200}}}}, strImpParams: openrtb_ext.ExtImpSharethrough{}, expectedHeight: 100, expectedWidth: 200, }, "Defaults to 1x1": { - imp: openrtb.Imp{}, + imp: openrtb2.Imp{}, strImpParams: openrtb_ext.ExtImpSharethrough{}, expectedHeight: 1, expectedWidth: 1, @@ -114,22 +115,22 @@ func TestGetPlacementSize(t *testing.T) { func TestGetBestFormat(t *testing.T) { tests := map[string]struct { - input []openrtb.Format - expectedHeight uint64 - expectedWidth uint64 + input []openrtb2.Format + expectedHeight int64 + expectedWidth int64 }{ "Returns default size if empty input": { - input: []openrtb.Format{}, + input: []openrtb2.Format{}, expectedHeight: 1, expectedWidth: 1, }, "Returns size if only one is passed": { - input: []openrtb.Format{{H: 100, W: 100}}, + input: []openrtb2.Format{{H: 100, W: 100}}, expectedHeight: 100, expectedWidth: 100, }, "Returns biggest size if multiple are passed": { - input: []openrtb.Format{{H: 100, W: 100}, {H: 200, W: 200}, {H: 50, W: 50}}, + input: []openrtb2.Format{{H: 100, W: 100}, {H: 200, W: 200}, {H: 50, W: 50}}, expectedHeight: 200, expectedWidth: 200, }, @@ -344,27 +345,27 @@ func TestIsAtMinSafariVersion(t *testing.T) { } func TestGdprApplies(t *testing.T) { - bidRequestGdpr := openrtb.BidRequest{ - Regs: &openrtb.Regs{ + bidRequestGdpr := openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ Ext: []byte(`{"gdpr": 1}`), }, } - bidRequestNonGdpr := openrtb.BidRequest{ - Regs: &openrtb.Regs{ + bidRequestNonGdpr := openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ Ext: []byte(`{"gdpr": 0}`), }, } - bidRequestEmptyGdpr := openrtb.BidRequest{ - Regs: &openrtb.Regs{ + bidRequestEmptyGdpr := openrtb2.BidRequest{ + Regs: &openrtb2.Regs{ Ext: []byte(``), }, } - bidRequestEmptyRegs := openrtb.BidRequest{ - Regs: &openrtb.Regs{}, + bidRequestEmptyRegs := openrtb2.BidRequest{ + Regs: &openrtb2.Regs{}, } tests := map[string]struct { - input *openrtb.BidRequest + input *openrtb2.BidRequest expected bool }{ "Return true if gdpr set to 1": { @@ -396,7 +397,7 @@ func TestGdprApplies(t *testing.T) { func TestParseUserInfo(t *testing.T) { tests := map[string]struct { - input *openrtb.User + input *openrtb2.User expected userInfo }{ "Return empty strings if no User": { @@ -404,31 +405,31 @@ func TestParseUserInfo(t *testing.T) { expected: userInfo{Consent: "", TtdUid: "", StxUid: ""}, }, "Return empty strings if no uids": { - input: &openrtb.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": []}] }`)}, + input: &openrtb2.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": []}] }`)}, expected: userInfo{Consent: "", TtdUid: "", StxUid: ""}, }, "Return empty strings if ID is not defined or empty string": { - input: &openrtb.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": [{"id": null}]}, {"source": "adserver.org", "uids": [{"id": ""}]}] }`)}, + input: &openrtb2.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": [{"id": null}]}, {"source": "adserver.org", "uids": [{"id": ""}]}] }`)}, expected: userInfo{Consent: "", TtdUid: "", StxUid: ""}, }, "Return consent correctly": { - input: &openrtb.User{Ext: []byte(`{ "consent": "abc" }`)}, + input: &openrtb2.User{Ext: []byte(`{ "consent": "abc" }`)}, expected: userInfo{Consent: "abc", TtdUid: "", StxUid: ""}, }, "Return ttd uid correctly": { - input: &openrtb.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": [{"id": "abc123"}]}] }`)}, + input: &openrtb2.User{Ext: []byte(`{ "eids": [{"source": "adserver.org", "uids": [{"id": "abc123"}]}] }`)}, expected: userInfo{Consent: "", TtdUid: "abc123", StxUid: ""}, }, "Ignore non-trade-desk uid": { - input: &openrtb.User{Ext: []byte(`{ "eids": [{"source": "something", "uids": [{"id": "xyz"}]}] }`)}, + input: &openrtb2.User{Ext: []byte(`{ "eids": [{"source": "something", "uids": [{"id": "xyz"}]}] }`)}, expected: userInfo{Consent: "", TtdUid: "", StxUid: ""}, }, "Returns STX user id from buyer id": { - input: &openrtb.User{BuyerUID: "myid"}, + input: &openrtb2.User{BuyerUID: "myid"}, expected: userInfo{Consent: "", TtdUid: "", StxUid: "myid"}, }, "Full test": { - input: &openrtb.User{BuyerUID: "myid", Ext: []byte(`{ "consent": "abc", "eids": [{"source": "something", "uids": [{"id": "xyz"}]}, {"source": "adserver.org", "uids": [{"id": "abc123"}]}] }`)}, + input: &openrtb2.User{BuyerUID: "myid", Ext: []byte(`{ "consent": "abc", "eids": [{"source": "something", "uids": [{"id": "xyz"}]}, {"source": "adserver.org", "uids": [{"id": "abc123"}]}] }`)}, expected: userInfo{Consent: "abc", TtdUid: "abc123", StxUid: "myid"}, }, } diff --git a/adapters/silvermob/params_test.go b/adapters/silvermob/params_test.go index fbfb5f3d097..13009f6a08b 100644 --- a/adapters/silvermob/params_test.go +++ b/adapters/silvermob/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/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 0e79f44369f..8a5c6b259b6 100644 --- a/adapters/silvermob/silvermob.go +++ b/adapters/silvermob/silvermob.go @@ -6,12 +6,12 @@ import ( "net/http" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 SilverMobAdapter struct { @@ -31,7 +31,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func GetHeaders(request *openrtb.BidRequest) *http.Header { +func GetHeaders(request *openrtb2.BidRequest) *http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -55,7 +55,7 @@ func GetHeaders(request *openrtb.BidRequest) *http.Header { } func (a *SilverMobAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( []*adapters.RequestData, @@ -84,7 +84,7 @@ func (a *SilverMobAdapter) MakeRequests( continue } - requestCopy.Imp = []openrtb.Imp{imp} + requestCopy.Imp = []openrtb2.Imp{imp} reqJSON, err := json.Marshal(requestCopy) if err != nil { errs = append(errs, err) @@ -104,7 +104,7 @@ func (a *SilverMobAdapter) MakeRequests( return requestData, errs } -func (a *SilverMobAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtSilverMob, error) { +func (a *SilverMobAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtSilverMob, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -126,7 +126,7 @@ func (a *SilverMobAdapter) buildEndpointURL(params *openrtb_ext.ExtSilverMob) (s } func (a *SilverMobAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -149,7 +149,7 @@ func (a *SilverMobAdapter) MakeBids( } responseBody := bidderRawResponse.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Error unmarshaling server Response: %s", err), @@ -176,7 +176,7 @@ func (a *SilverMobAdapter) MakeBids( return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/silvermob/silvermob_test.go b/adapters/silvermob/silvermob_test.go index 8045542c7c5..795d58fd834 100644 --- a/adapters/silvermob/silvermob_test.go +++ b/adapters/silvermob/silvermob_test.go @@ -3,9 +3,9 @@ package silvermob import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/silvermob/silvermobtest/exemplary/native-app.json b/adapters/silvermob/silvermobtest/exemplary/native-app.json index b5af81f07e4..c1cb8cf3d49 100644 --- a/adapters/silvermob/silvermobtest/exemplary/native-app.json +++ b/adapters/silvermob/silvermobtest/exemplary/native-app.json @@ -156,4 +156,4 @@ } ] } - + \ No newline at end of file diff --git a/adapters/silvermob/silvermobtest/supplemental/invalid-response.json b/adapters/silvermob/silvermobtest/supplemental/invalid-response.json index d2a1e890df0..4970678bef9 100644 --- a/adapters/silvermob/silvermobtest/supplemental/invalid-response.json +++ b/adapters/silvermob/silvermobtest/supplemental/invalid-response.json @@ -111,7 +111,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "Error unmarshaling server Response: json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "Error unmarshaling server Response: json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/smaato/params_test.go b/adapters/smaato/params_test.go index 80993dc5739..6c71cbe75c6 100644 --- a/adapters/smaato/params_test.go +++ b/adapters/smaato/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file intends to test static/bidder-params/smaato.json diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index 2873f1311a4..9aea2e1e614 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -6,15 +6,15 @@ import ( "net/http" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/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" ) -const clientVersion = "prebid_server_0.1" +const clientVersion = "prebid_server_0.2" type adMarkupType string @@ -58,7 +58,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SmaatoAdapter) 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 impressions in bid request"}) @@ -81,9 +81,10 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt i-- } } + if request.Site != nil { siteCopy := *request.Site - siteCopy.Publisher = &openrtb.Publisher{ID: publisherID} + siteCopy.Publisher = &openrtb2.Publisher{ID: publisherID} if request.Site.Ext != nil { var siteExt siteExt @@ -98,6 +99,13 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt request.Site = &siteCopy } + if request.App != nil { + appCopy := *request.App + appCopy.Publisher = &openrtb2.Publisher{ID: publisherID} + + request.App = &appCopy + } + if request.User != nil && request.User.Ext != nil { var userExt userExt var userExtRaw map[string]json.RawMessage @@ -155,7 +163,7 @@ func (a *SmaatoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } // MakeBids unpacks the server's response into Bids. -func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -170,7 +178,7 @@ func (a *SmaatoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return nil, []error{fmt.Errorf("unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -251,7 +259,7 @@ func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkup return "", fmt.Errorf("Invalid ad markup %s", adMarkup) } -func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) { +func assignBannerSize(banner *openrtb2.Banner) (*openrtb2.Banner, error) { if banner.W != nil && banner.H != nil { return banner, nil } @@ -259,16 +267,14 @@ func assignBannerSize(banner *openrtb.Banner) (*openrtb.Banner, error) { return banner, fmt.Errorf("No sizes provided for Banner %v", banner.Format) } bannerCopy := *banner - bannerCopy.W = new(uint64) - *bannerCopy.W = banner.Format[0].W - bannerCopy.H = new(uint64) - *bannerCopy.H = banner.Format[0].H + bannerCopy.W = openrtb2.Int64Ptr(banner.Format[0].W) + bannerCopy.H = openrtb2.Int64Ptr(banner.Format[0].H) return &bannerCopy, nil } // parseImpressionObject parse the imp to get it ready to send to smaato -func parseImpressionObject(imp *openrtb.Imp) error { +func parseImpressionObject(imp *openrtb2.Imp) error { adSpaceID, err := jsonparser.GetString(imp.Ext, "bidder", "adspaceId") if err != nil { return err @@ -295,7 +301,7 @@ func parseImpressionObject(imp *openrtb.Imp) error { return fmt.Errorf("invalid MediaType. SMAATO only supports Banner and Video. Ignoring ImpID=%s", imp.ID) } -func extractUserExtAttributes(userExt userExt, userCopy *openrtb.User) { +func extractUserExtAttributes(userExt userExt, userCopy *openrtb2.User) { gender := userExt.Data.Gender if gender != "" { userCopy.Gender = gender diff --git a/adapters/smaato/smaato_test.go b/adapters/smaato/smaato_test.go index 5fdea31693f..c7c4a65017f 100644 --- a/adapters/smaato/smaato_test.go +++ b/adapters/smaato/smaato_test.go @@ -3,9 +3,9 @@ package smaato import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json new file mode 100644 index 00000000000..8194f568c28 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json @@ -0,0 +1,225 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "app": { + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "app": { + "publisher": { + "id": "1100042525" + }, + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json new file mode 100644 index 00000000000..46722c4ff71 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json @@ -0,0 +1,229 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "app": { + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "gender": "M", + "keywords": "a,b", + "yob": 1984, + "ext": { + "consent": "gdprConsentString" + } + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "app": { + "publisher": { + "id": "1100042525" + }, + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"richmedia\":{\"mediadata\":{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
hello
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json index 7b662e8813a..1018dbc39ac 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json @@ -129,7 +129,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json index a50fd9289e3..0ba4050a143 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/exemplary/video-app.json b/adapters/smaato/smaatotest/exemplary/video-app.json new file mode 100644 index 00000000000..bf939eb078a --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/video-app.json @@ -0,0 +1,230 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "postbid_iframe", + "video": { + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ], + "ext": { + "rewarded": 0 + } + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "app": { + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + }, + "ext": { + "prebid": { + "auctiontimestamp": 1598262728811, + "targeting": { + "includewinners": true, + "includebidderkeys": false + } + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "postbid_iframe", + "tagid": "130563103", + "video": { + "w": 1024, + "h": 768, + "ext": { + "rewarded": 0 + }, + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ] + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "app": { + "publisher": { + "id": "1100042525" + }, + "id": "app-id", + "name": "app-name", + "bundle": "app-bundle", + "storeurl": "app-storeurl", + "cat": [ + "IAB3-1" + ], + "ver": "app-version", + "paid": 1, + "content": { + "id": "content-id", + "title": "content-title", + "series": "content-series", + "genre": "content-genre", + "producer": { + "id": "producer-id", + "name": "producer-name" + }, + "cat": [ + "IAB8-6" + ], + "livestream": 1, + "language": "en" + }, + "keywords": "keywords" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json index f2896d3d9d8..bad3825bb62 100644 --- a/adapters/smaato/smaatotest/exemplary/video.json +++ b/adapters/smaato/smaatotest/exemplary/video.json @@ -122,7 +122,7 @@ } }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json index 1fce58f0dfe..db724565d52 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json index b9560f0f9ca..768b4ef9d2c 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json +++ b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json @@ -40,7 +40,7 @@ } }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } } diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info.json b/adapters/smaato/smaatotest/supplemental/no-consent-info.json index 9e0ccfdcdde..b9a4294b00b 100644 --- a/adapters/smaato/smaatotest/supplemental/no-consent-info.json +++ b/adapters/smaato/smaatotest/supplemental/no-consent-info.json @@ -72,7 +72,7 @@ } }, "ext": { - "client": "prebid_server_0.1" + "client": "prebid_server_0.2" } } }, diff --git a/adapters/smaato/smaatotest/supplemental/status-code-204.json b/adapters/smaato/smaatotest/supplemental/status-code-204.json new file mode 100644 index 00000000000..b409597f986 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/status-code-204.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/status-code-400.json b/adapters/smaato/smaatotest/supplemental/status-code-400.json new file mode 100644 index 00000000000..fc84c93e269 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/status-code-400.json @@ -0,0 +1,144 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.2" + } + } + }, + "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/smartadserver/params_test.go b/adapters/smartadserver/params_test.go index 363886d96b1..b424cbe0a1d 100644 --- a/adapters/smartadserver/params_test.go +++ b/adapters/smartadserver/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/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 1231ab677ec..3954a00b4d5 100644 --- a/adapters/smartadserver/smartadserver.go +++ b/adapters/smartadserver/smartadserver.go @@ -8,11 +8,11 @@ import ( "path" "strconv" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 SmartAdserverAdapter struct { @@ -28,7 +28,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SmartAdserverAdapter) 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", @@ -43,7 +43,7 @@ func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // We create or copy the Site object. if smartRequest.Site == nil { - smartRequest.Site = &openrtb.Site{} + smartRequest.Site = &openrtb2.Site{} } else { site := *smartRequest.Site smartRequest.Site = &site @@ -51,7 +51,7 @@ func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // We create or copy the Publisher object. if smartRequest.Site.Publisher == nil { - smartRequest.Site.Publisher = &openrtb.Publisher{} + smartRequest.Site.Publisher = &openrtb2.Publisher{} } else { publisher := *smartRequest.Site.Publisher smartRequest.Site.Publisher = &publisher @@ -79,7 +79,7 @@ func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo smartRequest.Site.Publisher.ID = strconv.Itoa(smartadserverExt.NetworkID) // We send one request for each impression. - smartRequest.Imp = []openrtb.Imp{imp} + smartRequest.Imp = []openrtb2.Imp{imp} var errMarshal error if imp.Ext, errMarshal = json.Marshal(smartadserverExt); errMarshal != nil { @@ -117,7 +117,7 @@ func (a *SmartAdserverAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo } // MakeBids unpacks the server's response into Bids. -func (a *SmartAdserverAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SmartAdserverAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -134,7 +134,7 @@ func (a *SmartAdserverAdapter) MakeBids(internalRequest *openrtb.BidRequest, ext }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -169,7 +169,7 @@ func (a *SmartAdserverAdapter) BuildEndpointURL(params *openrtb_ext.ExtImpSmarta return uri.String(), nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { for _, imp := range imps { if imp.ID == impID { if imp.Video != nil { diff --git a/adapters/smartadserver/smartadserver_test.go b/adapters/smartadserver/smartadserver_test.go index e6cf5e12b55..978be336471 100644 --- a/adapters/smartadserver/smartadserver_test.go +++ b/adapters/smartadserver/smartadserver_test.go @@ -3,9 +3,9 @@ package smartadserver import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/smartadserver/usersync.go b/adapters/smartadserver/usersync.go index cc155966c12..f7199965441 100644 --- a/adapters/smartadserver/usersync.go +++ b/adapters/smartadserver/usersync.go @@ -3,10 +3,10 @@ package smartadserver import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewSmartadserverSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartadserver", 45, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("smartadserver", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/smartadserver/usersync_test.go b/adapters/smartadserver/usersync_test.go index aeb34dd69f9..319ce5b58a6 100644 --- a/adapters/smartadserver/usersync_test.go +++ b/adapters/smartadserver/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestSmartadserverSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ssbsync.smartadserver.com/getuid?gdpr=1&gdpr_consent=COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA&us_privacy=1YNN&url=localhost%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D1%26gdpr_consent%3DCOyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA%26uid%3D%5Bsas_uid%5D%22", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 45, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/smartrtb/smartrtb.go b/adapters/smartrtb/smartrtb.go index 0613bb6815f..e123d4eb6d2 100644 --- a/adapters/smartrtb/smartrtb.go +++ b/adapters/smartrtb/smartrtb.go @@ -6,12 +6,12 @@ import ( "net/http" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) // Base adapter structure. @@ -59,15 +59,19 @@ func (adapter *SmartRTBAdapter) buildEndpointURL(pubID string) (string, error) { return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams) } -func parseExtImp(dst *bidRequestExt, imp *openrtb.Imp) error { +func parseExtImp(dst *bidRequestExt, imp *openrtb2.Imp) error { var ext adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &ext); err != nil { - return adapters.BadInput(err.Error()) + return &errortypes.BadInput{ + Message: err.Error(), + } } var src openrtb_ext.ExtImpSmartRTB if err := json.Unmarshal(ext.Bidder, &src); err != nil { - return adapters.BadInput(err.Error()) + return &errortypes.BadInput{ + Message: err.Error(), + } } if dst.PubID == "" { @@ -80,8 +84,8 @@ func parseExtImp(dst *bidRequestExt, imp *openrtb.Imp) error { return nil } -func (s *SmartRTBAdapter) MakeRequests(brq *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - var imps []openrtb.Imp +func (s *SmartRTBAdapter) MakeRequests(brq *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var imps []openrtb2.Imp var err error ext := bidRequestExt{} nrImps := len(brq.Imp) @@ -107,7 +111,7 @@ func (s *SmartRTBAdapter) MakeRequests(brq *openrtb.BidRequest, reqInfo *adapter } if ext.PubID == "" { - return nil, append(errs, adapters.BadInput("Cannot infer publisher ID from bid ext")) + return nil, append(errs, &errortypes.BadInput{Message: "Cannot infer publisher ID from bid ext"}) } brq.Ext, err = json.Marshal(ext) @@ -140,20 +144,20 @@ func (s *SmartRTBAdapter) MakeRequests(brq *openrtb.BidRequest, reqInfo *adapter } func (s *SmartRTBAdapter) MakeBids( - brq *openrtb.BidRequest, drq *adapters.RequestData, + brq *openrtb2.BidRequest, drq *adapters.RequestData, rs *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { if rs.StatusCode == http.StatusNoContent { return nil, nil } else if rs.StatusCode == http.StatusBadRequest { - return nil, []error{adapters.BadInput("Invalid request.")} + return nil, []error{&errortypes.BadInput{Message: "Invalid request."}} } else if rs.StatusCode != http.StatusOK { return nil, []error{&errortypes.BadServerResponse{ Message: fmt.Sprintf("Unexpected HTTP status %d.", rs.StatusCode), }} } - var brs openrtb.BidResponse + var brs openrtb2.BidResponse if err := json.Unmarshal(rs.Body, &brs); err != nil { return nil, []error{err} } diff --git a/adapters/smartrtb/smartrtb_test.go b/adapters/smartrtb/smartrtb_test.go index 1d592dfc6d8..ef5fce668f4 100644 --- a/adapters/smartrtb/smartrtb_test.go +++ b/adapters/smartrtb/smartrtb_test.go @@ -3,9 +3,9 @@ package smartrtb import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/smartrtb/smartrtbtest/supplemental/empty-imps.json b/adapters/smartrtb/smartrtbtest/supplemental/empty-imps.json index 1c951dd9f8e..2ba12019f41 100644 --- a/adapters/smartrtb/smartrtbtest/supplemental/empty-imps.json +++ b/adapters/smartrtb/smartrtbtest/supplemental/empty-imps.json @@ -13,4 +13,4 @@ }, "httpCalls": [], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json index c56e7f21515..3d9f92df4a7 100644 --- a/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json +++ b/adapters/smartrtb/smartrtbtest/supplemental/invalid-bid-json.json @@ -69,7 +69,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/smartrtb/usersync.go b/adapters/smartrtb/usersync.go index 1148dcc1584..74ef0e9960b 100644 --- a/adapters/smartrtb/usersync.go +++ b/adapters/smartrtb/usersync.go @@ -3,10 +3,10 @@ package smartrtb import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewSmartRTBSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartrtb", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("smartrtb", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/smartrtb/usersync_test.go b/adapters/smartrtb/usersync_test.go index 6fa5c837296..68a8452a316 100644 --- a/adapters/smartrtb/usersync_test.go +++ b/adapters/smartrtb/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -15,6 +15,5 @@ func TestSmartRTBSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://market-east.smrtb.com/sync/all?nid=smartrtb&gdpr=&gdpr_consent=&url=localhost%2Fsetuid%3Fbidder%smartrtb%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/smartyads/params_test.go b/adapters/smartyads/params_test.go index 048989a2c40..3aa5c0e837d 100644 --- a/adapters/smartyads/params_test.go +++ b/adapters/smartyads/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) var validParams = []string{ diff --git a/adapters/smartyads/smartyads.go b/adapters/smartyads/smartyads.go index 93a94a74568..b5a09223eb0 100644 --- a/adapters/smartyads/smartyads.go +++ b/adapters/smartyads/smartyads.go @@ -7,12 +7,12 @@ import ( "strconv" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 SmartyAdsAdapter struct { @@ -32,7 +32,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func GetHeaders(request *openrtb.BidRequest) *http.Header { +func GetHeaders(request *openrtb2.BidRequest) *http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -64,7 +64,7 @@ func GetHeaders(request *openrtb.BidRequest) *http.Header { } func (a *SmartyAdsAdapter) MakeRequests( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo, ) ( requestsToBidder []*adapters.RequestData, @@ -106,7 +106,7 @@ func (a *SmartyAdsAdapter) MakeRequests( }}, nil } -func (a *SmartyAdsAdapter) getImpressionExt(imp *openrtb.Imp) (*openrtb_ext.ExtSmartyAds, error) { +func (a *SmartyAdsAdapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtSmartyAds, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -154,7 +154,7 @@ func (a *SmartyAdsAdapter) CheckResponseStatusCodes(response *adapters.ResponseD } func (a *SmartyAdsAdapter) MakeBids( - openRTBRequest *openrtb.BidRequest, + openRTBRequest *openrtb2.BidRequest, requestToBidder *adapters.RequestData, bidderRawResponse *adapters.ResponseData, ) ( @@ -167,7 +167,7 @@ func (a *SmartyAdsAdapter) MakeBids( } responseBody := bidderRawResponse.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Bad Server Response", @@ -192,7 +192,7 @@ func (a *SmartyAdsAdapter) MakeBids( return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/smartyads/smartyads_test.go b/adapters/smartyads/smartyads_test.go index dae8e2a0726..4ea20e98c74 100644 --- a/adapters/smartyads/smartyads_test.go +++ b/adapters/smartyads/smartyads_test.go @@ -3,9 +3,9 @@ package smartyads import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/smartyads/smartyadstest/exemplary/banner-app.json b/adapters/smartyads/smartyadstest/exemplary/banner-app.json index 4d913fd91f0..56052236310 100644 --- a/adapters/smartyads/smartyadstest/exemplary/banner-app.json +++ b/adapters/smartyads/smartyadstest/exemplary/banner-app.json @@ -159,4 +159,4 @@ } ] } - + \ No newline at end of file diff --git a/adapters/smartyads/smartyadstest/exemplary/native-app.json b/adapters/smartyads/smartyadstest/exemplary/native-app.json index 73bdb8d7f3a..c14beb550ee 100644 --- a/adapters/smartyads/smartyadstest/exemplary/native-app.json +++ b/adapters/smartyads/smartyadstest/exemplary/native-app.json @@ -157,4 +157,4 @@ } ] } - + \ No newline at end of file diff --git a/adapters/smartyads/usersync.go b/adapters/smartyads/usersync.go index d394fb730b0..9075aa9bcd7 100644 --- a/adapters/smartyads/usersync.go +++ b/adapters/smartyads/usersync.go @@ -3,10 +3,10 @@ package smartyads import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewSmartyAdsSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("smartyads", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("smartyads", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/smartyads/usersync_test.go b/adapters/smartyads/usersync_test.go index 6ea8aa81846..4f94591c634 100644 --- a/adapters/smartyads/usersync_test.go +++ b/adapters/smartyads/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -29,6 +29,5 @@ func TestSmartyAdsSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://as.ck-ie.com/prebid.gif?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redir=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/somoaudience/params_test.go b/adapters/somoaudience/params_test.go index b74725aac21..2cbb2b1f51a 100644 --- a/adapters/somoaudience/params_test.go +++ b/adapters/somoaudience/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/somoaudience.json diff --git a/adapters/somoaudience/somoaudience.go b/adapters/somoaudience/somoaudience.go index 2e096dfe1b3..1e807bb2370 100644 --- a/adapters/somoaudience/somoaudience.go +++ b/adapters/somoaudience/somoaudience.go @@ -6,12 +6,11 @@ import ( "net/http" "strconv" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/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" ) const hbconfig = "hb_pbs_1.0.0" @@ -24,12 +23,12 @@ type somoaudienceReqExt struct { BidderConfig string `json:"prebid"` } -func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SomoaudienceAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error - var bannerImps []openrtb.Imp - var videoImps []openrtb.Imp - var nativeImps []openrtb.Imp + var bannerImps []openrtb2.Imp + var videoImps []openrtb2.Imp + var nativeImps []openrtb2.Imp for _, imp := range request.Imp { if imp.Banner != nil { @@ -53,7 +52,7 @@ func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // Somoaudience only supports single imp video request for _, videoImp := range videoImps { - reqCopy.Imp = []openrtb.Imp{videoImp} + reqCopy.Imp = []openrtb2.Imp{videoImp} adapterReq, errors := a.makeRequest(&reqCopy) if adapterReq != nil { adapterRequests = append(adapterRequests, adapterReq) @@ -63,7 +62,7 @@ func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // Somoaudience only supports single imp video request for _, nativeImp := range nativeImps { - reqCopy.Imp = []openrtb.Imp{nativeImp} + reqCopy.Imp = []openrtb2.Imp{nativeImp} adapterReq, errors := a.makeRequest(&reqCopy) if adapterReq != nil { adapterRequests = append(adapterRequests, adapterReq) @@ -74,10 +73,10 @@ func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo } -func (a *SomoaudienceAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *SomoaudienceAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error var err error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp reqExt := somoaudienceReqExt{BidderConfig: hbconfig} var placementHash string @@ -132,7 +131,7 @@ func (a *SomoaudienceAdapter) makeRequest(request *openrtb.BidRequest) (*adapter }, errs } -func preprocess(imp *openrtb.Imp, reqExt *somoaudienceReqExt) (string, error) { +func preprocess(imp *openrtb2.Imp, reqExt *somoaudienceReqExt) (string, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return "", &errortypes.BadInput{ @@ -153,7 +152,7 @@ func preprocess(imp *openrtb.Imp, reqExt *somoaudienceReqExt) (string, error) { return somoExt.PlacementHash, nil } -func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb2.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -171,7 +170,7 @@ func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapt }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -190,7 +189,7 @@ func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapt return bidResponse, nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/somoaudience/somoaudience_test.go b/adapters/somoaudience/somoaudience_test.go index 3f3ce8c6ca9..07a1d2b3b65 100644 --- a/adapters/somoaudience/somoaudience_test.go +++ b/adapters/somoaudience/somoaudience_test.go @@ -3,9 +3,9 @@ package somoaudience import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/somoaudience/usersync.go b/adapters/somoaudience/usersync.go index 2d0b715d669..5d1ddd71bc6 100644 --- a/adapters/somoaudience/usersync.go +++ b/adapters/somoaudience/usersync.go @@ -3,10 +3,10 @@ package somoaudience import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewSomoaudienceSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("somoaudience", 341, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("somoaudience", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/somoaudience/usersync_test.go b/adapters/somoaudience/usersync_test.go index 9abcc65e748..2367a5674dd 100644 --- a/adapters/somoaudience/usersync_test.go +++ b/adapters/somoaudience/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestSomoaudienceSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//publisher-east.mobileadtrading.com/usersync?ru=localhost%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24%7BUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 341, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/sonobi/params_test.go b/adapters/sonobi/params_test.go index 00fe63c6b6e..3e9d63e8101 100644 --- a/adapters/sonobi/params_test.go +++ b/adapters/sonobi/params_test.go @@ -2,7 +2,7 @@ package sonobi import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/sonobi/sonobi.go b/adapters/sonobi/sonobi.go index 3deebbbd9d4..690d5f59f67 100644 --- a/adapters/sonobi/sonobi.go +++ b/adapters/sonobi/sonobi.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) // SonobiAdapter - Sonobi SonobiAdapter definition @@ -30,7 +30,7 @@ type sonobiParams struct { } // MakeRequests Makes the OpenRTB request payload -func (a *SonobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SonobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var sonobiExt openrtb_ext.ExtImpSonobi var err error @@ -42,7 +42,7 @@ func (a *SonobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt for _, imp := range request.Imp { // Make a copy as we don't want to change the original request reqCopy := *request - reqCopy.Imp = append(make([]openrtb.Imp, 0, 1), imp) + reqCopy.Imp = append(make([]openrtb2.Imp, 0, 1), imp) var bidderExt adapters.ExtImpBidder if err = json.Unmarshal(reqCopy.Imp[0].Ext, &bidderExt); err != nil { @@ -69,7 +69,7 @@ func (a *SonobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } // makeRequest helper method to crete the http request data -func (a *SonobiAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *SonobiAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error @@ -92,7 +92,7 @@ func (a *SonobiAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Requ } // MakeBids makes the bids -func (a *SonobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SonobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { var errs []error if response.StatusCode == http.StatusNoContent { @@ -111,7 +111,7 @@ func (a *SonobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -136,7 +136,7 @@ func (a *SonobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe return bidResponse, errs } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impID { diff --git a/adapters/sonobi/sonobi_test.go b/adapters/sonobi/sonobi_test.go index 7a5d94bd5ac..7e8f2dc2916 100644 --- a/adapters/sonobi/sonobi_test.go +++ b/adapters/sonobi/sonobi_test.go @@ -3,9 +3,9 @@ package sonobi import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/sonobi/usersync.go b/adapters/sonobi/usersync.go index 6ac950563bc..6fedd8bfa05 100644 --- a/adapters/sonobi/usersync.go +++ b/adapters/sonobi/usersync.go @@ -1,11 +1,12 @@ package sonobi import ( - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewSonobiSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sonobi", 104, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("sonobi", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/sonobi/usersync_test.go b/adapters/sonobi/usersync_test.go index 8eadaef8f9c..995c3757ba4 100644 --- a/adapters/sonobi/usersync_test.go +++ b/adapters/sonobi/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,5 @@ func TestSonobiSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.go.sonobi.com/us.gif?loc=external.com%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D0%26gdpr%3D%26uid%3D%5BUID%5D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 104, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index 24920fa7998..be1c2221ae5 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -12,12 +12,12 @@ import ( "strconv" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" + "github.com/mxmCherry/openrtb/v15/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/pbs" "golang.org/x/net/context/ctxhttp" ) @@ -49,7 +49,7 @@ func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pb return nil, err } - sovrnReq := openrtb.BidRequest{ + sovrnReq := openrtb2.BidRequest{ ID: sReq.ID, Imp: sReq.Imp, Site: sReq.Site, @@ -133,7 +133,7 @@ func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pb debug.ResponseBody = responseBody } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { return nil, &errortypes.BadServerResponse{ @@ -173,7 +173,7 @@ func (s *SovrnAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pb return bids, nil } -func (s *SovrnAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) for i := 0; i < len(request.Imp); i++ { @@ -228,7 +228,7 @@ func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue str } } -func (s *SovrnAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (s *SovrnAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -245,7 +245,7 @@ func (s *SovrnAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: err.Error(), @@ -271,7 +271,7 @@ func (s *SovrnAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return bidResponse, nil } -func preprocess(imp *openrtb.Imp) (string, error) { +func preprocess(imp *openrtb2.Imp) (string, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return "", &errortypes.BadInput{ diff --git a/adapters/sovrn/sovrn_test.go b/adapters/sovrn/sovrn_test.go index 5f43c1a569f..c3290c30a2b 100644 --- a/adapters/sovrn/sovrn_test.go +++ b/adapters/sovrn/sovrn_test.go @@ -8,10 +8,10 @@ import ( "net/http/httptest" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/usersync" "context" "net/http" @@ -19,10 +19,10 @@ import ( "strconv" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/config" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" ) func TestJsonSamples(t *testing.T) { @@ -38,7 +38,7 @@ func TestJsonSamples(t *testing.T) { // ---------------------------------------------------------------------------- // Code below this line tests the legacy, non-openrtb code flow. It can be deleted after we -// clean up the existing code and make everything openrtb. +// clean up the existing code and make everything openrtb2. var testSovrnUserId = "SovrnUser123" var testUserAgent = "user-agent-test" @@ -144,12 +144,12 @@ func checkHttpRequest(req http.Request, t *testing.T) { func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { dnt := int8(0) - device := openrtb.Device{ + device := openrtb2.Device{ Language: "murican", DNT: &dnt, } - user := openrtb.User{ + user := openrtb2.User{ ID: testSovrnUserId, } @@ -165,7 +165,7 @@ func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest { for i := 0; i < numberOfImpressions; i++ { req.AdUnits[i] = pbs.AdUnit{ Code: fmt.Sprintf("div-adunit-%d", i+1), - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 728, H: 90, @@ -251,7 +251,7 @@ func TestNotFoundResponse(t *testing.T) { func CreateSovrnService(tagsToBid map[string]bool) adapterstest.OrtbMockService { service := adapterstest.OrtbMockService{} - var lastBidRequest openrtb.BidRequest + var lastBidRequest openrtb2.BidRequest var lastHttpReq http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -262,23 +262,23 @@ func CreateSovrnService(tagsToBid map[string]bool) adapterstest.OrtbMockService http.Error(w, err.Error(), http.StatusInternalServerError) return } - var breq openrtb.BidRequest + var breq openrtb2.BidRequest err = json.Unmarshal(body, &breq) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } lastBidRequest = breq - var bids []openrtb.Bid + var bids []openrtb2.Bid for i, imp := range breq.Imp { if tagsToBid[imp.TagID] { bids = append(bids, adapterstest.SampleBid(imp.Banner.W, imp.Banner.H, imp.ID, i+1)) } } - // serialize the bids to openrtb.BidResponse - js, _ := json.Marshal(openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + // serialize the bids to openrtb2.BidResponse + js, _ := json.Marshal(openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { Bid: bids, }, diff --git a/adapters/sovrn/usersync.go b/adapters/sovrn/usersync.go index 3f4e81439c6..225f2888196 100644 --- a/adapters/sovrn/usersync.go +++ b/adapters/sovrn/usersync.go @@ -3,10 +3,10 @@ package sovrn import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewSovrnSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("sovrn", 13, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("sovrn", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/sovrn/usersync_test.go b/adapters/sovrn/usersync_test.go index e91c73245bb..6c35ecdb05d 100644 --- a/adapters/sovrn/usersync_test.go +++ b/adapters/sovrn/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,5 @@ func TestSovrnSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ap.lijit.com/pixel?redir=external.com%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 13, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/spotx/params_test.go b/adapters/spotx/params_test.go index 6212fef3bec..de96ebe7953 100644 --- a/adapters/spotx/params_test.go +++ b/adapters/spotx/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestSpotxParams(t *testing.T) { diff --git a/adapters/spotx/spotx.go b/adapters/spotx/spotx.go index 6b9c4ff2ea1..92d12c82d90 100644 --- a/adapters/spotx/spotx.go +++ b/adapters/spotx/spotx.go @@ -6,18 +6,18 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 { url string } -func (a *Adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *Adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -42,7 +42,7 @@ func (a *Adapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.Ex return adapterRequests, errs } -func makeRequest(a *Adapter, originalReq *openrtb.BidRequest, imp openrtb.Imp) (*adapters.RequestData, []error) { +func makeRequest(a *Adapter, originalReq *openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, []error) { var errs []error var bidderExt adapters.ExtImpBidder @@ -113,12 +113,12 @@ func makeRequest(a *Adapter, originalReq *openrtb.BidRequest, imp openrtb.Imp) ( 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 openrtb.BidRequest + Body: reqJSON, //TODO: This is a custom request struct, other adapters are sending this openrtb2.BidRequest Headers: headers, }, errs } -func (a *Adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *Adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -135,7 +135,7 @@ func (a *Adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -155,7 +155,7 @@ func (a *Adapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest return bidResponse, nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +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 diff --git a/adapters/spotx/spotx_test.go b/adapters/spotx/spotx_test.go index a6641b27c0f..d516f39b8a2 100644 --- a/adapters/spotx/spotx_test.go +++ b/adapters/spotx/spotx_test.go @@ -2,9 +2,9 @@ package spotx import ( "encoding/json" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" "github.com/magiconair/properties/assert" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" "testing" ) @@ -23,12 +23,12 @@ func TestSpotxMakeBid(t *testing.T) { } }`) - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "1559039248176", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "28635736ddc2bb", - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: []string{"video/3gpp"}, }, Secure: &secure, diff --git a/adapters/synacormedia/params_test.go b/adapters/synacormedia/params_test.go index d7585c9d58c..a216818e382 100644 --- a/adapters/synacormedia/params_test.go +++ b/adapters/synacormedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/synacormedia.json diff --git a/adapters/synacormedia/synacormedia.go b/adapters/synacormedia/synacormedia.go index e87c665da59..aec3169fe54 100644 --- a/adapters/synacormedia/synacormedia.go +++ b/adapters/synacormedia/synacormedia.go @@ -6,12 +6,12 @@ import ( "net/http" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 SynacorMediaAdapter struct { @@ -40,7 +40,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *SynacorMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *SynacorMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var bidRequests []*adapters.RequestData @@ -53,9 +53,9 @@ func (a *SynacorMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo return bidRequests, errs } -func (a *SynacorMediaAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *SynacorMediaAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error - var validImps []openrtb.Imp + var validImps []openrtb2.Imp var re *ReqExt var firstExtImp *openrtb_ext.ExtImpSynacormedia = nil @@ -130,7 +130,7 @@ func (adapter *SynacorMediaAdapter) buildEndpointURL(params *openrtb_ext.ExtImpS return macros.ResolveMacros(adapter.EndpointTemplate, macros.EndpointTemplateParams{Host: params.SeatId}) } -func getExtImpObj(imp *openrtb.Imp) (*openrtb_ext.ExtImpSynacormedia, error) { +func getExtImpObj(imp *openrtb2.Imp) (*openrtb_ext.ExtImpSynacormedia, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -149,7 +149,7 @@ func getExtImpObj(imp *openrtb.Imp) (*openrtb_ext.ExtImpSynacormedia, error) { } // MakeBids make the bids for the bid response. -func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { const errorMessage string = "Unexpected status code: %d. Run with request.debug = 1 for more info" switch { case response.StatusCode == http.StatusNoContent: @@ -164,7 +164,7 @@ func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, exte }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -187,7 +187,7 @@ func (a *SynacorMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, exte return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/synacormedia/synacormedia_test.go b/adapters/synacormedia/synacormedia_test.go index ce696a67ee1..6a018dc3287 100644 --- a/adapters/synacormedia/synacormedia_test.go +++ b/adapters/synacormedia/synacormedia_test.go @@ -3,9 +3,9 @@ package synacormedia import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/synacormedia/synacormediatest/supplemental/bad_response.json b/adapters/synacormedia/synacormediatest/supplemental/bad_response.json index 8e8b9a4d944..520f415bcf4 100644 --- a/adapters/synacormedia/synacormediatest/supplemental/bad_response.json +++ b/adapters/synacormedia/synacormediatest/supplemental/bad_response.json @@ -64,7 +64,7 @@ "expectedMakeBidsErrors": [ { "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse" + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" } ] } diff --git a/adapters/synacormedia/usersync.go b/adapters/synacormedia/usersync.go index d7de9150744..c7fa5c42aea 100644 --- a/adapters/synacormedia/usersync.go +++ b/adapters/synacormedia/usersync.go @@ -3,10 +3,10 @@ package synacormedia import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewSynacorMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("synacormedia", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("synacormedia", temp, adapters.SyncTypeIframe) } diff --git a/adapters/synacormedia/usersync_test.go b/adapters/synacormedia/usersync_test.go index 7ee7b1aa19b..ce45353c7e5 100644 --- a/adapters/synacormedia/usersync_test.go +++ b/adapters/synacormedia/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestSynacormediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.technoratimedia.com/services?srv=cs&pid=70&cb=localhost%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/syncer.go b/adapters/syncer.go index 13985db6275..b9752b50ce1 100644 --- a/adapters/syncer.go +++ b/adapters/syncer.go @@ -3,35 +3,22 @@ package adapters import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/macros" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/usersync" ) -func GDPRAwareSyncerIDs(syncers map[openrtb_ext.BidderName]usersync.Usersyncer) map[openrtb_ext.BidderName]uint16 { - gdprAwareSyncers := make(map[openrtb_ext.BidderName]uint16, len(syncers)) - for bidderName, syncer := range syncers { - if syncer.GDPRVendorID() != 0 { - gdprAwareSyncers[bidderName] = syncer.GDPRVendorID() - } - } - return gdprAwareSyncers -} - type Syncer struct { - familyName string - gdprVendorID uint16 - urlTemplate *template.Template - syncType SyncType + familyName string + syncType SyncType + urlTemplate *template.Template } -func NewSyncer(familyName string, vendorID uint16, urlTemplate *template.Template, syncType SyncType) *Syncer { +func NewSyncer(familyName string, urlTemplate *template.Template, syncType SyncType) *Syncer { return &Syncer{ - familyName: familyName, - gdprVendorID: vendorID, - urlTemplate: urlTemplate, - syncType: syncType, + familyName: familyName, + urlTemplate: urlTemplate, + syncType: syncType, } } @@ -62,7 +49,3 @@ func (s *Syncer) GetUsersyncInfo(privacyPolicies privacy.Policies) (*usersync.Us func (s *Syncer) FamilyName() string { return s.familyName } - -func (s *Syncer) GDPRVendorID() uint16 { - return s.gdprVendorID -} diff --git a/adapters/syncer_test.go b/adapters/syncer_test.go index 7961608c29d..ca33a9a130d 100644 --- a/adapters/syncer_test.go +++ b/adapters/syncer_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) diff --git a/adapters/tappx/params_test.go b/adapters/tappx/params_test.go index d6fcbb9cff6..3a73d4dab53 100644 --- a/adapters/tappx/params_test.go +++ b/adapters/tappx/params_test.go @@ -2,7 +2,7 @@ package tappx import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index 2e044e82bb9..5970ccb6cfe 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -6,18 +6,19 @@ import ( "net/http" "net/url" "strconv" + "strings" "text/template" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) -const TAPPX_BIDDER_VERSION = "1.1" +const TAPPX_BIDDER_VERSION = "1.2" const TYPE_CNN = "prebid" type TappxAdapter struct { @@ -37,7 +38,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *TappxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TappxAdapter) 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", @@ -126,7 +127,9 @@ func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test in } } - thisURI.Path += params.Endpoint + if !(strings.Contains(strings.ToLower(thisURI.Host), strings.ToLower(params.Endpoint))) { + thisURI.Path += params.Endpoint //Now version is backward compatible. In future, this condition and content will be delete + } queryParams := url.Values{} @@ -146,7 +149,7 @@ func (a *TappxAdapter) buildEndpointURL(params *openrtb_ext.ExtImpTappx, test in return thisURI.String(), nil } -func (a *TappxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TappxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -161,7 +164,7 @@ func (a *TappxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -181,7 +184,7 @@ func (a *TappxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalReq return bidResponse, []error{} } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index 465828b0bdf..10e57d12132 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -4,15 +4,15 @@ import ( "regexp" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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.BidderTappx, config.Adapter{ - Endpoint: "https://{{.Host}}"}) + Endpoint: "http://{{.Host}}"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -30,7 +30,7 @@ func TestEndpointTemplateMalformed(t *testing.T) { func TestTsValue(t *testing.T) { bidder, buildErr := Builder(openrtb_ext.BidderTappx, config.Adapter{ - Endpoint: "https://{{.Host}}"}) + Endpoint: "http://{{.Host}}"}) if buildErr != nil { t.Fatalf("Builder returned unexpected error %v", buildErr) @@ -47,7 +47,7 @@ func TestTsValue(t *testing.T) { url, err := bidderTappx.buildEndpointURL(&tappxExt, test) - match, err := regexp.MatchString(`https://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.1`, url) + match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.2`, url) if err != nil { t.Errorf("Error while running regex validation: %s", err.Error()) return diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json new file mode 100644 index 00000000000..3c3037afefb --- /dev/null +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json @@ -0,0 +1,116 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "endpoint": "ZZ123456PS", + "host": "ZZ123456PS.ssp.tappx.com/rtb/" + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.rovio.angrybirds", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "body": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "endpoint": "ZZ123456PS", + "host": "ZZ123456PS.ssp.tappx.com/rtb/" + } + } + } + ], + "app": { + "bundle": "com.rovio.angrybirds", + "id": "app_001", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [{ + "bid": [{ + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "cid": "3706", + "crid": "19005", + "adid": "19005", + "adm": "", + "cat": ["IAB2"], + "adomain": ["test.com"], + "h": 250, + "w": 300 + }] + }], + "bidid": "wehM-93KGr0" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "adm": "", + "adid": "19005", + "adomain": ["test.com"], + "cid": "3706", + "crid": "19005", + "w": 300, + "h": 250, + "cat": ["IAB2"] + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json index 3a365db645e..54f472d9fff 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, @@ -47,8 +47,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json new file mode 100644 index 00000000000..58490233ede --- /dev/null +++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-site-9876", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" + } + } + } + ], + "site": { + "id": "102855", + "cat": [ "IAB3-1" ], + "domain": "www.tappx.com", + "page": "https://www.tappx.com/example.html ", + "publisher": { + "id": "8953", "name": "publisher.com", + "cat": [ "IAB3-1" ], + "domain": "publisher.com" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2", + "body": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-site-9876", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" + } + } + } + ], + "site": { + "id": "102855", + "cat": [ "IAB3-1" ], + "domain": "www.tappx.com", + "page": "https://www.tappx.com/example.html ", + "publisher": { + "id": "8953", "name": "publisher.com", + "cat": [ "IAB3-1" ], + "domain": "publisher.com" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [{ + "bid": [{ + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "cid": "3706", + "crid": "19005", + "adid": "19005", + "adm": "", + "cat": ["IAB2"], + "adomain": ["test.com"], + "h": 250, + "w": 300 + }] + }], + "bidid": "wehM-93KGr0" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "adm": "", + "adid": "19005", + "adomain": ["test.com"], + "cid": "3706", + "crid": "19005", + "w": 300, + "h": 250, + "cat": ["IAB2"] + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json index 49cb3c7e568..d6ce0554c5f 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json @@ -15,8 +15,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, @@ -51,8 +51,8 @@ }, "ext": { "bidder": { - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com", + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", "tappxkey": "pub-12345-android-9876" } } diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json new file mode 100644 index 00000000000..f151151e776 --- /dev/null +++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "video-adunit-2", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-site-9876", + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" + } + } + } + ], + "site": { + "id": "102855", + "cat": [ "IAB3-1" ], + "domain": "www.tappx.com", + "page": "https://www.tappx.com/example.html ", + "publisher": { + "id": "8953", "name": "publisher.com", + "cat": [ "IAB3-1" ], + "domain": "publisher.com" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2", + "body": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "video-adunit-2", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", + "tappxkey": "pub-12345-site-9876" + } + } + } + ], + "site": { + "id": "102855", + "cat": [ "IAB3-1" ], + "domain": "www.tappx.com", + "page": "https://www.tappx.com/example.html ", + "publisher": { + "id": "8953", "name": "publisher.com", + "cat": [ "IAB3-1" ], + "domain": "publisher.com" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [ + { + "bid": [ + { + "id": "bid02", + "impid": "video-adunit-2", + "price": 2.25, + "cid": "1001", + "crid": "2002", + "adid": "2002", + "adm": "", + "cat": [ + "IAB2" + ], + "adomain": [ + "video-example.com" + ] + } + ] + } + ], + "bidid": "wehM-93KGr0" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "bid02", + "impid": "video-adunit-2", + "price": 2.25, + "adm": "", + "adomain": [ + "video-example.com" + ], + "cid": "1001", + "adid": "2002", + "crid": "2002", + "cat": [ + "IAB2" + ] + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/params/race/banner.json b/adapters/tappx/tappxtest/params/race/banner.json index 4c2ec640ff5..9264443a5ca 100644 --- a/adapters/tappx/tappxtest/params/race/banner.json +++ b/adapters/tappx/tappxtest/params/race/banner.json @@ -1,5 +1,5 @@ { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } \ No newline at end of file diff --git a/adapters/tappx/tappxtest/params/race/video.json b/adapters/tappx/tappxtest/params/race/video.json index 4c2ec640ff5..438543f2362 100644 --- a/adapters/tappx/tappxtest/params/race/video.json +++ b/adapters/tappx/tappxtest/params/race/video.json @@ -1,5 +1,5 @@ { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "VZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } \ No newline at end of file diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json index 288837b7b91..1c72cc90f24 100644 --- a/adapters/tappx/tappxtest/supplemental/204status.json +++ b/adapters/tappx/tappxtest/supplemental/204status.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, @@ -44,8 +44,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json b/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json index c82d0e852f3..12f9c54ae02 100644 --- a/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json +++ b/adapters/tappx/tappxtest/supplemental/banner-impression-badhost.json @@ -11,7 +11,7 @@ "ext": { "bidder": { "host": "example.ho%st.tappx.com", - "endpoint": "PREBIDTEMPLATE", + "endpoint": "ZZ123456PS", "tappxkey": "pub-12345-android-9876" } } @@ -31,7 +31,7 @@ "expectedMakeRequestsErrors": [ { - "value": "Malformed URL: parse (\\\")?https://example\\.ho%st\\.tappx.com(\\\")?: invalid URL escape \\\"%st\\\"", + "value": "Malformed URL: parse (\\\")?http://example\\.ho%st\\.tappx.com(\\\")?: invalid URL escape \\\"%st\\\"", "comparison": "regex" } ] diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json b/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json index 9a49c8b05a0..85370b4b07c 100644 --- a/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json +++ b/adapters/tappx/tappxtest/supplemental/banner-impression-noendpoint.json @@ -11,7 +11,7 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "host": "test.tappx.com" + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json b/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json index ffe0f14f949..df650dde39a 100644 --- a/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json +++ b/adapters/tappx/tappxtest/supplemental/banner-impression-nohost.json @@ -10,7 +10,7 @@ }, "ext": { "bidder": { - "endpoint": "PREBIDTEMPLATE", + "endpoint": "ZZ123456PS", "tappxkey": "pub-12345-android-9876" } } diff --git a/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json b/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json index 2bc147ec07f..4dba6d3b366 100644 --- a/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json +++ b/adapters/tappx/tappxtest/supplemental/banner-impression-nokey.json @@ -10,8 +10,8 @@ }, "ext": { "bidder": { - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json index 61e96a442a0..093f77adfc6 100644 --- a/adapters/tappx/tappxtest/supplemental/bidfloor.json +++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", "bidfloor": 1.5 } } @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, @@ -49,8 +49,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", "bidfloor": 1.5 } } diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json index c2db2e1eea8..a80a5eaa675 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, @@ -44,8 +44,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json index b09ee26b68e..41dcc26d653 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json @@ -12,8 +12,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "https://test.tappx.com/PREBIDTEMPLATE?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.1", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", "body": { "id": "0000000000001", "test": 1, @@ -44,8 +44,8 @@ "ext": { "bidder": { "tappxkey": "pub-12345-android-9876", - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json b/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json index 1cbd3eefda1..1404204eaf1 100644 --- a/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json +++ b/adapters/tappx/tappxtest/supplemental/wrong-imp-ext-1.json @@ -11,8 +11,8 @@ "ext": { "bidder": { "tappxkey": 1, - "endpoint": "PREBIDTEMPLATE", - "host": "test.tappx.com" + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/" } } } diff --git a/adapters/tappx/usersync.go b/adapters/tappx/usersync.go new file mode 100644 index 00000000000..01477c35363 --- /dev/null +++ b/adapters/tappx/usersync.go @@ -0,0 +1,12 @@ +package tappx + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewTappxSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("tappx", temp, adapters.SyncTypeIframe) +} diff --git a/adapters/tappx/usersync_test.go b/adapters/tappx/usersync_test.go new file mode 100644 index 00000000000..992a35de9a8 --- /dev/null +++ b/adapters/tappx/usersync_test.go @@ -0,0 +1,34 @@ +package tappx + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestTappxSyncer(t *testing.T) { + syncURL := "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BTPPXUID%7D%7D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewTappxSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "A", + }, + CCPA: ccpa.Policy{ + Consent: "1YNN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//testing.ssp.tappx.com/cs/usersync.php?gdpr_optin=1&gdpr_consent=A&us_privacy=1YNN&type=iframe&ruid=localhost%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D1%26gdpr_consent%3DA%26uid%3D%7B%7BTPPXUID%7D%7D", syncInfo.URL) + assert.Equal(t, "iframe", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/telaria/params_test.go b/adapters/telaria/params_test.go index 76f936cecfc..efa3fba1be9 100644 --- a/adapters/telaria/params_test.go +++ b/adapters/telaria/params_test.go @@ -2,7 +2,7 @@ package telaria import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/telaria/telaria.go b/adapters/telaria/telaria.go index 57cb53929b8..e0a451a9e6c 100644 --- a/adapters/telaria/telaria.go +++ b/adapters/telaria/telaria.go @@ -6,11 +6,11 @@ import ( "net/http" "strconv" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid" @@ -35,7 +35,7 @@ func (a *TelariaAdapter) FetchEndpoint() string { } // Checker method to ensure len(request.Imp) > 0 -func (a *TelariaAdapter) CheckHasImps(request *openrtb.BidRequest) error { +func (a *TelariaAdapter) CheckHasImps(request *openrtb2.BidRequest) error { if len(request.Imp) == 0 { err := &errortypes.BadInput{ Message: "Telaria: Missing Imp Object", @@ -46,7 +46,7 @@ func (a *TelariaAdapter) CheckHasImps(request *openrtb.BidRequest) error { } // Checking if Imp[i].Video exists and Imp[i].Banner doesn't exist -func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb.BidRequest) error { +func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb2.BidRequest) error { hasVideoObject := false for _, imp := range request.Imp { @@ -69,7 +69,7 @@ func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb.BidRequest) error } // Fetches the populated header object -func GetHeaders(request *openrtb.BidRequest) *http.Header { +func GetHeaders(request *openrtb2.BidRequest) *http.Header { headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") @@ -97,7 +97,7 @@ func GetHeaders(request *openrtb.BidRequest) *http.Header { } // Checks the imp[i].ext object and returns a imp.ext object as per ExtImpTelaria format -func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpTelaria, error) { +func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTelaria, error) { var bidderExt adapters.ExtImpBidder err := json.Unmarshal(imp.Ext, &bidderExt) @@ -125,7 +125,7 @@ func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb.Imp) (*openrtb_ex // Method to fetch the original publisher ID. Note that this method must be called // before we replace publisher.ID with seatCode -func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb.BidRequest) string { +func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb2.BidRequest) string { if request.Site != nil && request.Site.Publisher != nil { return request.Site.Publisher.ID @@ -137,8 +137,8 @@ func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb.BidRequest) s } // Method to do a deep copy of the publisher object. It also adds the seatCode as publisher.ID -func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb.Publisher) *openrtb.Publisher { - var pub = &openrtb.Publisher{ID: seatCode} +func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb2.Publisher) *openrtb2.Publisher { + var pub = &openrtb2.Publisher{ID: seatCode} if publisher != nil { pub.Domain = publisher.Domain @@ -151,7 +151,7 @@ func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb } // This method changes .publisher.id to the seatCode -func (a *TelariaAdapter) PopulatePublisherId(request *openrtb.BidRequest, seatCode string) (*openrtb.Site, *openrtb.App) { +func (a *TelariaAdapter) PopulatePublisherId(request *openrtb2.BidRequest, seatCode string) (*openrtb2.Site, *openrtb2.App) { if request.Site != nil { siteCopy := *request.Site siteCopy.Publisher = a.MakePublisherObject(seatCode, request.Site.Publisher) @@ -164,7 +164,7 @@ func (a *TelariaAdapter) PopulatePublisherId(request *openrtb.BidRequest, seatCo return nil, nil } -func (a *TelariaAdapter) MakeRequests(requestIn *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { // make a copy of the incoming request request := *requestIn @@ -263,7 +263,7 @@ func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseDat return nil } -func (a *TelariaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TelariaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { httpStatusError := a.CheckResponseStatusCodes(response) if httpStatusError != nil { @@ -272,7 +272,7 @@ func (a *TelariaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR responseBody := response.Body - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(responseBody, &bidResp); err != nil { return nil, []error{&errortypes.BadServerResponse{ Message: "Telaria: Bad Server Response", diff --git a/adapters/telaria/telaria_test.go b/adapters/telaria/telaria_test.go index b7eddbad523..be29ed40f03 100644 --- a/adapters/telaria/telaria_test.go +++ b/adapters/telaria/telaria_test.go @@ -3,9 +3,9 @@ package telaria import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/telaria/usersync.go b/adapters/telaria/usersync.go index 71cf85b6110..76be62026f8 100644 --- a/adapters/telaria/usersync.go +++ b/adapters/telaria/usersync.go @@ -3,10 +3,10 @@ package telaria import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewTelariaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("telaria", 202, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("telaria", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/telaria/usersync_test.go b/adapters/telaria/usersync_test.go index dc3accbbafa..f019ed5ceaf 100644 --- a/adapters/telaria/usersync_test.go +++ b/adapters/telaria/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -26,7 +26,6 @@ func TestTelariaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://pbs.publishers.tremorhub.com/pubsync?gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 202, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) assert.Equal(t, "telaria", syncer.FamilyName()) diff --git a/adapters/triplelift/triplelift.go b/adapters/triplelift/triplelift.go index 6b9f5ecfd41..3cd651cee5c 100644 --- a/adapters/triplelift/triplelift.go +++ b/adapters/triplelift/triplelift.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 TripleliftAdapter struct { @@ -32,7 +32,7 @@ func getBidType(ext TripleliftRespExt) openrtb_ext.BidType { return openrtb_ext.BidTypeBanner } -func processImp(imp *openrtb.Imp) error { +func processImp(imp *openrtb2.Imp) error { // get the triplelift extension var ext adapters.ExtImpBidder var tlext openrtb_ext.ExtImpTriplelift @@ -56,13 +56,13 @@ func processImp(imp *openrtb.Imp) error { return nil } -func (a *TripleliftAdapter) MakeRequests(request *openrtb.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TripleliftAdapter) MakeRequests(request *openrtb2.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)+1) reqs := make([]*adapters.RequestData, 0, 1) // copy the request, because we are going to mutate it tlRequest := *request // this will contain all the valid impressions - var validImps []openrtb.Imp + var validImps []openrtb2.Imp // pre-process the imps for _, imp := range tlRequest.Imp { if err := processImp(&imp); err == nil { @@ -94,7 +94,7 @@ func (a *TripleliftAdapter) MakeRequests(request *openrtb.BidRequest, extra *ada return reqs, errs } -func getBidCount(bidResponse openrtb.BidResponse) int { +func getBidCount(bidResponse openrtb2.BidResponse) int { c := 0 for _, sb := range bidResponse.SeatBid { c = c + len(sb.Bid) @@ -102,7 +102,7 @@ func getBidCount(bidResponse openrtb.BidResponse) int { return c } -func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -116,7 +116,7 @@ func (a *TripleliftAdapter) MakeBids(internalRequest *openrtb.BidRequest, extern if response.StatusCode != http.StatusOK { return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/triplelift/triplelift_test.go b/adapters/triplelift/triplelift_test.go index 3f23e0dcdc6..5107d7cc997 100644 --- a/adapters/triplelift/triplelift_test.go +++ b/adapters/triplelift/triplelift_test.go @@ -3,9 +3,9 @@ package triplelift import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/triplelift/usersync.go b/adapters/triplelift/usersync.go index 0bd678b0e14..4a47615bd29 100644 --- a/adapters/triplelift/usersync.go +++ b/adapters/triplelift/usersync.go @@ -3,10 +3,10 @@ package triplelift import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewTripleliftSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("triplelift", 28, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("triplelift", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/triplelift/usersync_test.go b/adapters/triplelift/usersync_test.go index 30b1a33b3e9..012e33a4d0a 100644 --- a/adapters/triplelift/usersync_test.go +++ b/adapters/triplelift/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestTripleliftSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//eb2.3lift.com/getuid?gdpr=&cmp_cs=&us_privacy=&redir=https%3A%2F%2Feb2.3lift.com%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 28, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/triplelift_native/triplelift_native.go b/adapters/triplelift_native/triplelift_native.go index 65c6994c0b3..d412def437f 100644 --- a/adapters/triplelift_native/triplelift_native.go +++ b/adapters/triplelift_native/triplelift_native.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 TripleliftNativeAdapter struct { @@ -37,7 +37,7 @@ func getBidType(ext TripleliftRespExt) openrtb_ext.BidType { return openrtb_ext.BidTypeNative } -func processImp(imp *openrtb.Imp) error { +func processImp(imp *openrtb2.Imp) error { // get the triplelift extension var ext adapters.ExtImpBidder var tlext openrtb_ext.ExtImpTriplelift @@ -64,7 +64,7 @@ func processImp(imp *openrtb.Imp) error { } // Returns the effective publisher ID -func effectivePubID(pub *openrtb.Publisher) string { +func effectivePubID(pub *openrtb2.Publisher) string { if pub != nil { if pub.Ext != nil { var pubExt openrtb_ext.ExtPublisher @@ -80,13 +80,13 @@ func effectivePubID(pub *openrtb.Publisher) string { return "unknown" } -func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb2.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)+1) reqs := make([]*adapters.RequestData, 0, 1) // copy the request, because we are going to mutate it tlRequest := *request // this will contain all the valid impressions - var validImps []openrtb.Imp + var validImps []openrtb2.Imp // pre-process the imps for _, imp := range tlRequest.Imp { if err := processImp(&imp); err == nil { @@ -124,14 +124,14 @@ func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb.BidRequest, extr return reqs, errs } -func getPublisher(request *openrtb.BidRequest) *openrtb.Publisher { +func getPublisher(request *openrtb2.BidRequest) *openrtb2.Publisher { if request.App != nil { return request.App.Publisher } return request.Site.Publisher } -func getBidCount(bidResponse openrtb.BidResponse) int { +func getBidCount(bidResponse openrtb2.BidResponse) int { c := 0 for _, sb := range bidResponse.SeatBid { c = c + len(sb.Bid) @@ -139,7 +139,7 @@ func getBidCount(bidResponse openrtb.BidResponse) int { return c } -func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -153,7 +153,7 @@ func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb.BidRequest, 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 openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } diff --git a/adapters/triplelift_native/triplelift_native_test.go b/adapters/triplelift_native/triplelift_native_test.go index 19b40232155..fef24949e79 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/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/triplelift_native/usersync.go b/adapters/triplelift_native/usersync.go index a8d246f07cd..2a07740a761 100644 --- a/adapters/triplelift_native/usersync.go +++ b/adapters/triplelift_native/usersync.go @@ -3,10 +3,10 @@ package triplelift_native import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewTripleliftSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("triplelift", 28, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("triplelift", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/triplelift_native/usersync_test.go b/adapters/triplelift_native/usersync_test.go index ec229e2e68c..7fd878b7f92 100644 --- a/adapters/triplelift_native/usersync_test.go +++ b/adapters/triplelift_native/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -20,6 +20,5 @@ func TestTripleliftSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//eb2.3lift.com/getuid?gdpr=&cmp_cs=&us_privacy=&redir=https%3A%2F%2Feb2.3lift.com%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 28, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/trustx/usersync.go b/adapters/trustx/usersync.go new file mode 100644 index 00000000000..a738e8e9b79 --- /dev/null +++ b/adapters/trustx/usersync.go @@ -0,0 +1,12 @@ +package trustx + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewTrustXSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("trustx", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/trustx/usersync_test.go b/adapters/trustx/usersync_test.go new file mode 100644 index 00000000000..ba371bc2061 --- /dev/null +++ b/adapters/trustx/usersync_test.go @@ -0,0 +1,29 @@ +package trustx + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestTrustXSyncer(t *testing.T) { + syncURL := "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewTrustXSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/ucfunnel/params_test.go b/adapters/ucfunnel/params_test.go index c33bc89a6b6..4faec8739da 100644 --- a/adapters/ucfunnel/params_test.go +++ b/adapters/ucfunnel/params_test.go @@ -2,7 +2,7 @@ package ucfunnel import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/ucfunnel/ucfunnel.go b/adapters/ucfunnel/ucfunnel.go index 7a6060f2930..1d3efc04451 100644 --- a/adapters/ucfunnel/ucfunnel.go +++ b/adapters/ucfunnel/ucfunnel.go @@ -6,11 +6,11 @@ import ( "net/http" "net/url" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 UcfunnelAdapter struct { @@ -25,7 +25,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -43,12 +43,12 @@ func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb.BidRequest, external } var errs []error - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } - var bidReq openrtb.BidRequest + var bidReq openrtb2.BidRequest if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { return nil, []error{err} } @@ -69,7 +69,7 @@ func (a *UcfunnelAdapter) MakeBids(internalRequest *openrtb.BidRequest, external return bidResponse, errs } -func (a *UcfunnelAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *UcfunnelAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) // If all the requests were malformed, don't bother making a server call with no impressions. @@ -101,7 +101,7 @@ func (a *UcfunnelAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada }}, errs } -func getPartnerId(request *openrtb.BidRequest) (string, []error) { +func getPartnerId(request *openrtb2.BidRequest) (string, []error) { var ext ExtBidderUcfunnel var errs = []error{} err := json.Unmarshal(request.Imp[0].Ext, &ext) @@ -125,7 +125,7 @@ func checkBidderParameter(ext ExtBidderUcfunnel) []error { return nil } -func getBidType(bidReq openrtb.BidRequest, impid string) openrtb_ext.BidType { +func getBidType(bidReq openrtb2.BidRequest, impid string) openrtb_ext.BidType { for i := range bidReq.Imp { if bidReq.Imp[i].ID == impid { if bidReq.Imp[i].Banner != nil { diff --git a/adapters/ucfunnel/ucfunnel_test.go b/adapters/ucfunnel/ucfunnel_test.go index 3860c988b32..95ac5985f56 100644 --- a/adapters/ucfunnel/ucfunnel_test.go +++ b/adapters/ucfunnel/ucfunnel_test.go @@ -5,40 +5,40 @@ import ( "fmt" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestMakeRequests(t *testing.T) { - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: "1234", - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, } - imp2 := openrtb.Imp{ + imp2 := openrtb2.Imp{ ID: "1235", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imp3 := openrtb.Imp{ + imp3 := openrtb2.Imp{ ID: "1236", - Audio: &openrtb.Audio{}, + Audio: &openrtb2.Audio{}, } - imp4 := openrtb.Imp{ + imp4 := openrtb2.Imp{ ID: "1237", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, } - imp5 := openrtb.Imp{ + imp5 := openrtb2.Imp{ ID: "1237", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, } - internalRequest01 := openrtb.BidRequest{Imp: []openrtb.Imp{}} - internalRequest02 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} - internalRequest03 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} + internalRequest01 := openrtb2.BidRequest{Imp: []openrtb2.Imp{}} + internalRequest02 := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2, imp3, imp4, imp5}} + internalRequest03 := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2, imp3, imp4, imp5}} internalRequest03.Imp[0].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) internalRequest03.Imp[1].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) @@ -54,12 +54,12 @@ func TestMakeRequests(t *testing.T) { } var testCases = []struct { - in []openrtb.BidRequest + in []openrtb2.BidRequest out1 [](int) out2 [](bool) }{ { - in: []openrtb.BidRequest{internalRequest01, internalRequest02, internalRequest03}, + in: []openrtb2.BidRequest{internalRequest01, internalRequest02, internalRequest03}, out1: [](int){1, 1, 0}, out2: [](bool){false, false, true}, }, @@ -76,31 +76,31 @@ func TestMakeRequests(t *testing.T) { } func TestMakeBids(t *testing.T) { - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: "1234", - Banner: &openrtb.Banner{}, + Banner: &openrtb2.Banner{}, } - imp2 := openrtb.Imp{ + imp2 := openrtb2.Imp{ ID: "1235", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imp3 := openrtb.Imp{ + imp3 := openrtb2.Imp{ ID: "1236", - Audio: &openrtb.Audio{}, + Audio: &openrtb2.Audio{}, } - imp4 := openrtb.Imp{ + imp4 := openrtb2.Imp{ ID: "1237", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, } - imp5 := openrtb.Imp{ + imp5 := openrtb2.Imp{ ID: "1237", - Native: &openrtb.Native{}, + Native: &openrtb2.Native{}, } - internalRequest03 := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2, imp3, imp4, imp5}} - internalRequest04 := openrtb.BidRequest{Imp: []openrtb.Imp{imp}} + internalRequest03 := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2, imp3, imp4, imp5}} + internalRequest04 := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp}} internalRequest03.Imp[0].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) internalRequest03.Imp[1].Ext = []byte(`{"bidder": {"adunitid": "ad-488663D474E44841E8A293379892348","partnerid": "par-7E6D2DB9A8922AB07B44A444D2BA67"}}`) @@ -126,14 +126,14 @@ func TestMakeBids(t *testing.T) { } var testCases = []struct { - in1 []openrtb.BidRequest + in1 []openrtb2.BidRequest in2 []adapters.RequestData in3 []adapters.ResponseData out1 [](bool) out2 [](bool) }{ { - in1: []openrtb.BidRequest{internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest04}, + in1: []openrtb2.BidRequest{internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest03, internalRequest04}, in2: []adapters.RequestData{RequestData01, RequestData01, RequestData01, RequestData01, RequestData01, RequestData02}, in3: []adapters.ResponseData{mockResponse200, mockResponse203, mockResponse204, mockResponse400, mockResponseError, mockResponse200}, out1: [](bool){true, false, false, false, false, false}, diff --git a/adapters/ucfunnel/ucfunneltest/exemplary/ucfunnel.json b/adapters/ucfunnel/ucfunneltest/exemplary/ucfunnel.json index e1e361875e2..9a4dad9b0f9 100644 --- a/adapters/ucfunnel/ucfunneltest/exemplary/ucfunnel.json +++ b/adapters/ucfunnel/ucfunneltest/exemplary/ucfunnel.json @@ -101,4 +101,4 @@ } } } -} +} \ No newline at end of file diff --git a/adapters/ucfunnel/usersync.go b/adapters/ucfunnel/usersync.go index 8220fe6e1fa..0ae9948dd77 100644 --- a/adapters/ucfunnel/usersync.go +++ b/adapters/ucfunnel/usersync.go @@ -3,10 +3,10 @@ package ucfunnel import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewUcfunnelSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("ucfunnel", 607, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("ucfunnel", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/ucfunnel/usersync_test.go b/adapters/ucfunnel/usersync_test.go index 25d879d808a..204de978150 100644 --- a/adapters/ucfunnel/usersync_test.go +++ b/adapters/ucfunnel/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,5 @@ func TestUcfunnelSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//sync.aralego.com/idsync?gdpr=0&redirect=externalURL.com%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 607, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/unicorn/params_test.go b/adapters/unicorn/params_test.go new file mode 100644 index 00000000000..4a534208e84 --- /dev/null +++ b/adapters/unicorn/params_test.go @@ -0,0 +1,61 @@ +package unicorn + +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.BidderUnicorn, 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.BidderUnicorn, json.RawMessage(p)); err == nil { + t.Errorf("Schema allowed invalid params: %s", p) + } + } +} + +var validParams = []string{ + `{ + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }`, + `{ + "accountId": 199578, + "mediaId": "test_media" + }`, +} + +var invalidParams = []string{ + `{}`, + `{ + "accountId": "199578", + "publisherId": "123456", + "mediaId": 12345, + "placementId": 12345 + }`, + `{ + "publisherId": 123456, + "placementId": "test_placement" + }`, +} diff --git a/adapters/unicorn/unicorn.go b/adapters/unicorn/unicorn.go new file mode 100644 index 00000000000..8d1413f43dd --- /dev/null +++ b/adapters/unicorn/unicorn.go @@ -0,0 +1,244 @@ +package unicorn + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/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 +} + +// unicornImpExt is imp ext for UNICORN +type unicornImpExt struct { + Context *unicornImpExtContext `json:"context,omitempty"` + Bidder openrtb_ext.ExtImpUnicorn `json:"bidder"` +} + +type unicornImpExtContext struct { + Data interface{} `json:"data,omitempty"` +} + +// unicornExt is ext for UNICORN +type unicornExt struct { + Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` + AccountID int64 `json:"accountId,omitempty"` +} + +// Builder builds a new instance of the UNICORN adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (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, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var extRegs openrtb_ext.ExtRegs + if request.Regs != nil { + if request.Regs.COPPA == 1 { + return nil, []error{&errortypes.BadInput{ + Message: "COPPA is not supported", + }} + } + if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil { + if extRegs.GDPR != nil && (*extRegs.GDPR == 1) { + return nil, []error{&errortypes.BadInput{ + Message: "GDPR is not supported", + }} + } + if extRegs.USPrivacy != "" { + return nil, []error{&errortypes.BadInput{ + Message: "CCPA is not supported", + }} + } + } + } + + err := modifyImps(request) + if err != nil { + return nil, []error{err} + } + + var modifiableSource openrtb2.Source + if request.Source != nil { + modifiableSource = *request.Source + } else { + modifiableSource = openrtb2.Source{} + } + modifiableSource.Ext = setSourceExt() + request.Source = &modifiableSource + + request.Ext, err = setExt(request) + 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: a.endpoint, + Body: requestJSON, + Headers: getHeaders(request), + } + + return []*adapters.RequestData{requestData}, 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.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 +} + +func modifyImps(request *openrtb2.BidRequest) error { + for i := 0; i < len(request.Imp); i++ { + imp := &request.Imp[i] + + var ext unicornImpExt + err := json.Unmarshal(imp.Ext, &ext) + + if err != nil { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Error while decoding imp[%d].ext: %s", i, err), + } + } + + if ext.Bidder.PlacementID == "" { + ext.Bidder.PlacementID, err = getStoredRequestImpID(imp) + if err != nil { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Error get StoredRequestImpID from imp[%d]: %s", i, err), + } + } + } + + imp.Ext, err = json.Marshal(ext) + if err != nil { + return &errortypes.BadInput{ + Message: fmt.Sprintf("Error while encoding imp[%d].ext: %s", i, err), + } + } + + secure := int8(1) + imp.Secure = &secure + imp.TagID = ext.Bidder.PlacementID + } + return nil +} + +func getStoredRequestImpID(imp *openrtb2.Imp) (string, error) { + v, err := jsonparser.GetString(imp.Ext, "prebid", "storedrequest", "id") + + if err != nil { + return "", fmt.Errorf("stored request id not found: %s", err) + } + + return v, nil +} + +func setSourceExt() json.RawMessage { + return json.RawMessage(`{"stype": "prebid_server_uncn", "bidder": "unicorn"}`) +} + +func setExt(request *openrtb2.BidRequest) (json.RawMessage, error) { + accountID, err := jsonparser.GetInt(request.Imp[0].Ext, "bidder", "accountId") + if err != nil { + accountID = 0 + } + var decodedExt *unicornExt + err = json.Unmarshal(request.Ext, &decodedExt) + if err != nil { + decodedExt = &unicornExt{ + Prebid: nil, + } + } + decodedExt.AccountID = accountID + + ext, err := json.Marshal(decodedExt) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("Error while encoding ext, err: %s", err), + } + } + return ext, nil +} + +// MakeBids unpacks the server's response into Bids. +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 http status code: 400", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected http status code: %d", 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)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid + var bidType openrtb_ext.BidType + for _, imp := range request.Imp { + if imp.ID == bid.ImpID { + if imp.Banner != nil { + bidType = openrtb_ext.BidTypeBanner + } + } + } + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} diff --git a/adapters/unicorn/unicorn_test.go b/adapters/unicorn/unicorn_test.go new file mode 100644 index 00000000000..1a0d67d29f7 --- /dev/null +++ b/adapters/unicorn/unicorn_test.go @@ -0,0 +1,20 @@ +package unicorn + +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.BidderUnicorn, config.Adapter{ + Endpoint: "https://ds.uncn.jp"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "unicorntest", bidder) +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json b/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json new file mode 100644 index 00000000000..c37c2095d48 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-no-source.json @@ -0,0 +1,228 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + } + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json new file mode 100644 index 00000000000..2d38a990db3 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ip.json @@ -0,0 +1,236 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "ip": "12.1.2.3", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "ip": "12.1.2.3", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json new file mode 100644 index 00000000000..595741a0aa1 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-with-ipv6.json @@ -0,0 +1,236 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "ipv6": "2400:2410:9120:3400:15f3:8c50:3a0:6820", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "ipv6": "2400:2410:9120:3400:15f3:8c50:3a0:6820", + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148", + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json new file mode 100644 index 00000000000..8b5423a5556 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-ext.json @@ -0,0 +1,212 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 78.3, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 78.3, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json new file mode 100644 index 00000000000..c05d3b6f536 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app-without-placementid.json @@ -0,0 +1,231 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media" + } + } + } + ], + "cur": ["USD"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["USD"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_unicorn" + } + }, + "tagid": "test_unicorn", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "USD", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 0.783, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "USD", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 0.783, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app.json b/adapters/unicorn/unicorntest/exemplary/banner-app.json new file mode 100644 index 00000000000..b29c1e1d625 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app.json @@ -0,0 +1,232 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json b/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json new file mode 100644 index 00000000000..0a6fb420adc --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app_with_fpd.json @@ -0,0 +1,244 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }, + "context": { + "data": { + "firstPartyData1": ["firstPartyData1"], + "uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] + } + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }, + "context": { + "data": { + "firstPartyData1": ["firstPartyData1"], + "uuid": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"] + } + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json b/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json new file mode 100644 index 00000000000..022246382f8 --- /dev/null +++ b/adapters/unicorn/unicorntest/exemplary/banner-app_with_no_fpd.json @@ -0,0 +1,238 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }, + "context": { + "data": {} + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + }, + "context": { + "data": {} + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "url": "https://ds.uncn.jp", + "status": 200, + "headers": { + "Content-Type": ["application/json"], + "Date": ["Thu, 04 Feb 2021 06:36:31 GMT"], + "Vary": ["Accept-Encoding"] + }, + "body": { + "bidid": "f1248280-24ec-4fbc-bdf5-6a866e7dcea4", + "cur": "JPY", + "ext": {}, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "seatbid": [ + { + "bid": [ + { + "adid": "uoNYbq1L_-123456", + "adm": "awesome domain", + "adomain": ["example.com"], + "attr": [], + "bundle": "example.com", + "cid": "7314", + "crid": "-123456", + "ext": {}, + "h": 250, + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "iurl": "https://example.com/banner.png", + "price": 73.283, + "w": 300 + } + ], + "group": 0, + "seat": "274" + } + ], + "units": 0 + } + } + } + ], + "expectedBidResponses": [ + { + "Currency": "JPY", + "bids": [ + { + "Bid": { + "id": "1", + "impid": "29D2F33E-F865-40DA-9320-16EF77935254", + "price": 73.283, + "adm": "awesome domain", + "adid": "uoNYbq1L_-123456", + "adomain": ["example.com"], + "bundle": "example.com", + "iurl": "https://example.com/banner.png", + "cid": "7314", + "crid": "-123456", + "w": 300, + "h": 250, + "ext": {} + }, + "type": "banner", + "BidType": "banner", + "BidVideo": null, + "DealPriority": 0 + } + ] + } + ] +} diff --git a/adapters/unicorn/unicorntest/params/race/banner.json b/adapters/unicorn/unicorntest/params/race/banner.json new file mode 100644 index 00000000000..668983e3d19 --- /dev/null +++ b/adapters/unicorn/unicorntest/params/race/banner.json @@ -0,0 +1,6 @@ +{ + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" +} diff --git a/adapters/unicorn/unicorntest/supplemental/204.json b/adapters/unicorn/unicorntest/supplemental/204.json new file mode 100644 index 00000000000..a05864090ea --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/204.json @@ -0,0 +1,170 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} + diff --git a/adapters/unicorn/unicorntest/supplemental/400.json b/adapters/unicorn/unicorntest/supplemental/400.json new file mode 100644 index 00000000000..6578082ca19 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/400.json @@ -0,0 +1,175 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "status": 400, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 400", + "comparison": "literal" + } + ] +} + diff --git a/adapters/unicorn/unicorntest/supplemental/500.json b/adapters/unicorn/unicorntest/supplemental/500.json new file mode 100644 index 00000000000..c0811be4b24 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/500.json @@ -0,0 +1,175 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + }, + "ext": { + "prebid": { + "targeting": {}, + "cache": { + "bids": {} + }, + "storedrequest": { + "id": "test" + } + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://ds.uncn.jp", + "body": { + "app": { + "bundle": "net.ada.test", + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "publisher": { + "id": "test" + }, + "ver": "1.9.0" + }, + "at": 1, + "cur": ["JPY"], + "device": { + "connectiontype": 1, + "ext": { + "atts": 0 + }, + "h": 568, + "ifa": "00000000-0000-0000-0000-000000000000", + "make": "Apple", + "model": "Simulator", + "os": "iOS", + "osv": "14.4", + "pxratio": 2, + "w": 320 + }, + "ext": { + "prebid": { + "bidder": null, + "is_rewarded_inventory": 0, + "storedrequest": { + "id": "test" + } + }, + "accountId": 199578 + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "imp": [ + { + "banner": { + "api": [5], + "format": [ + { + "h": 250, + "w": 300 + } + ] + }, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + }, + "tagid": "test_placement", + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "source": { + "ext": { + "bidder": "unicorn", + "stype": "prebid_server_uncn" + }, + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "user": { + "gender": "O" + } + } + }, + "mockResponse": { + "status": 500, + "body": {} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected http status code: 500", + "comparison": "literal" + } + ] +} + diff --git a/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json new file mode 100644 index 00000000000..6bc396c67f1 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/ccpa-is-enabled.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "at": 1, + "regs": { + "ext": { + "us_privacy": "1YNN" + } + }, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "CCPA is not supported", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json new file mode 100644 index 00000000000..1c33ce2e805 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/coppa-is-enabled.json @@ -0,0 +1,78 @@ +{ + "mockBidRequest": { + "at": 1, + "regs": { + "coppa": 1 + }, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "COPPA is not supported", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json b/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json new file mode 100644 index 00000000000..3c9222d8cc2 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/gdpr-is-enabled.json @@ -0,0 +1,80 @@ +{ + "mockBidRequest": { + "at": 1, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + "storedrequest": { + "id": "test_unicorn" + } + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media", + "placementId": "test_placement" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "GDPR is not supported", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json b/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json new file mode 100644 index 00000000000..2e6ce79a176 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/no-imp-ext-prebid.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors" : [{ + "value": "Error get StoredRequestImpID from imp[0]: stored request id not found: Key path not found", + "comparison": "literal" + }] +} diff --git a/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json b/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json new file mode 100644 index 00000000000..bab6e8d9603 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/no-imp-ext.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1 + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Error while decoding imp[0].ext: unexpected end of JSON input", + "comparison": "literal" + } + ] +} diff --git a/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json b/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json new file mode 100644 index 00000000000..d903effd466 --- /dev/null +++ b/adapters/unicorn/unicorntest/supplemental/no-storedrequest-imp.json @@ -0,0 +1,69 @@ +{ + "mockBidRequest": { + "at": 1, + "imp": [ + { + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ], + "api": [5] + }, + "id": "29D2F33E-F865-40DA-9320-16EF77935254", + "secure": 1, + "ext": { + "prebid": { + }, + "bidder": { + "accountId": 199578, + "publisherId": 123456, + "mediaId": "test_media" + } + } + } + ], + "cur": ["JPY"], + "app": { + "publisher": { + "id": "test" + }, + "ext": { + "prebid": { + "source": "prebid-mobile", + "version": "1.9.0" + } + }, + "bundle": "net.ada.test", + "ver": "1.9.0" + }, + "id": "CFD24FB1-916F-467D-8825-34892B315DB7", + "source": { + "tid": "A4B3EA9F-FF57-4716-AC85-6CBF6A46CFBD" + }, + "device": { + "make": "Apple", + "osv": "14.4", + "connectiontype": 1, + "os": "iOS", + "w": 320, + "model": "Simulator", + "ifa": "00000000-0000-0000-0000-000000000000", + "devtime": 1612413327, + "h": 568, + "pxratio": 2, + "ext": { + "atts": 0 + } + }, + "user": { + "gender": "O" + } + }, + "expectedMakeRequestsErrors" : [{ + "value": "Error get StoredRequestImpID from imp[0]: stored request id not found: Key path not found", + "comparison": "literal" + }] +} diff --git a/adapters/unruly/params_test.go b/adapters/unruly/params_test.go index af1f2f2bce8..9b8f3110912 100644 --- a/adapters/unruly/params_test.go +++ b/adapters/unruly/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestValidParams(t *testing.T) { diff --git a/adapters/unruly/unruly.go b/adapters/unruly/unruly.go index 131e53af393..0077fae4df5 100644 --- a/adapters/unruly/unruly.go +++ b/adapters/unruly/unruly.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 UnrulyAdapter struct { @@ -24,13 +24,13 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (a *UnrulyAdapter) ReplaceImp(imp openrtb.Imp, request *openrtb.BidRequest) *openrtb.BidRequest { +func (a *UnrulyAdapter) ReplaceImp(imp openrtb2.Imp, request *openrtb2.BidRequest) *openrtb2.BidRequest { reqCopy := *request - reqCopy.Imp = append(make([]openrtb.Imp, 0, 1), imp) + reqCopy.Imp = append(make([]openrtb2.Imp, 0, 1), imp) return &reqCopy } -func (a *UnrulyAdapter) BuildRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *UnrulyAdapter) BuildRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { reqJSON, err := json.Marshal(request) if err != nil { return nil, []error{err} @@ -52,7 +52,7 @@ func AddHeadersToRequest() http.Header { return headers } -func (a *UnrulyAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *UnrulyAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData for _, imp := range request.Imp { @@ -71,7 +71,7 @@ func (a *UnrulyAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt return adapterRequests, errs } -func getMediaTypeForImpWithId(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImpWithId(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { return openrtb_ext.BidTypeVideo, nil @@ -91,9 +91,9 @@ func CheckResponse(response *adapters.ResponseData) error { return nil } -func convertToAdapterBidResponse(response *adapters.ResponseData, internalRequest *openrtb.BidRequest) (*adapters.BidderResponse, []error) { +func convertToAdapterBidResponse(response *adapters.ResponseData, internalRequest *openrtb2.BidRequest) (*adapters.BidderResponse, []error) { var errs []error - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -115,7 +115,7 @@ func convertToAdapterBidResponse(response *adapters.ResponseData, internalReques return bidResponse, errs } -func convertBidderNameInExt(imp *openrtb.Imp) (*openrtb.Imp, error) { +func convertBidderNameInExt(imp *openrtb2.Imp) (*openrtb2.Imp, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, err @@ -136,7 +136,7 @@ func convertBidderNameInExt(imp *openrtb.Imp) (*openrtb.Imp, error) { return imp, nil } -func (a *UnrulyAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *UnrulyAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if err := CheckResponse(response); err != nil { return nil, []error{err} } diff --git a/adapters/unruly/unruly_test.go b/adapters/unruly/unruly_test.go index e0f3ccc75e0..445745c102d 100644 --- a/adapters/unruly/unruly_test.go +++ b/adapters/unruly/unruly_test.go @@ -7,12 +7,12 @@ import ( "reflect" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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/stretchr/testify/assert" ) @@ -41,7 +41,7 @@ func TestReturnsNewUnrulyBidderWithParams(t *testing.T) { } func TestBuildRequest(t *testing.T) { - request := openrtb.BidRequest{} + request := openrtb2.BidRequest{} expectedJson, _ := json.Marshal(request) mockHeaders := http.Header{} mockHeaders.Add("Content-Type", "application/json;charset=utf-8") @@ -65,19 +65,19 @@ func TestBuildRequest(t *testing.T) { } func TestReplaceImp(t *testing.T) { - imp1 := openrtb.Imp{ID: "imp1"} - imp2 := openrtb.Imp{ID: "imp2"} - imp3 := openrtb.Imp{ID: "imp3"} - newImp := openrtb.Imp{ID: "imp4"} - request := openrtb.BidRequest{Imp: []openrtb.Imp{imp1, imp2, imp3}} + imp1 := openrtb2.Imp{ID: "imp1"} + imp2 := openrtb2.Imp{ID: "imp2"} + imp3 := openrtb2.Imp{ID: "imp3"} + newImp := openrtb2.Imp{ID: "imp4"} + request := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}} adapter := UnrulyAdapter{URI: "http://mockEndpoint.com"} newRequest := adapter.ReplaceImp(newImp, &request) if len(newRequest.Imp) != 1 { t.Errorf("Size of Imp Array should be 1") } - if !reflect.DeepEqual(request, openrtb.BidRequest{Imp: []openrtb.Imp{imp1, imp2, imp3}}) { - t.Errorf("actual = %v expected = %v", request, openrtb.BidRequest{Imp: []openrtb.Imp{imp1, imp2, imp3}}) + if !reflect.DeepEqual(request, openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}}) { + t.Errorf("actual = %v expected = %v", request, openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}}) } if !reflect.DeepEqual(newImp, newRequest.Imp[0]) { t.Errorf("actual = %v expected = %v", newRequest.Imp[0], newImp) @@ -85,7 +85,7 @@ func TestReplaceImp(t *testing.T) { } func TestConvertBidderNameInExt(t *testing.T) { - imp := openrtb.Imp{Ext: json.RawMessage(`{"bidder": {"uuid": "1234", "siteid": "aSiteID"}}`)} + imp := openrtb2.Imp{Ext: json.RawMessage(`{"bidder": {"uuid": "1234", "siteid": "aSiteID"}}`)} actualImp, err := convertBidderNameInExt(&imp) @@ -112,17 +112,17 @@ func TestConvertBidderNameInExt(t *testing.T) { func TestMakeRequests(t *testing.T) { adapter := UnrulyAdapter{URI: "http://mockEndpoint.com"} - imp1 := openrtb.Imp{ID: "imp1", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid1", "siteid": "siteID1"}}`)} - imp2 := openrtb.Imp{ID: "imp2", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid2", "siteid": "siteID2"}}`)} - imp3 := openrtb.Imp{ID: "imp3", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid3", "siteid": "siteID3"}}`)} + imp1 := openrtb2.Imp{ID: "imp1", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid1", "siteid": "siteID1"}}`)} + imp2 := openrtb2.Imp{ID: "imp2", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid2", "siteid": "siteID2"}}`)} + imp3 := openrtb2.Imp{ID: "imp3", Ext: json.RawMessage(`{"bidder": {"uuid": "uuid3", "siteid": "siteID3"}}`)} - expectImp1 := openrtb.Imp{ID: "imp1", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid1", "siteid": "siteID1"}}`)} - expectImp2 := openrtb.Imp{ID: "imp2", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid2", "siteid": "siteID2"}}`)} - expectImp3 := openrtb.Imp{ID: "imp3", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid3", "siteid": "siteID3"}}`)} + expectImp1 := openrtb2.Imp{ID: "imp1", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid1", "siteid": "siteID1"}}`)} + expectImp2 := openrtb2.Imp{ID: "imp2", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid2", "siteid": "siteID2"}}`)} + expectImp3 := openrtb2.Imp{ID: "imp3", Ext: json.RawMessage(`{"unruly": {"uuid": "uuid3", "siteid": "siteID3"}}`)} - expectImps := []openrtb.Imp{expectImp1, expectImp2, expectImp3} + expectImps := []openrtb2.Imp{expectImp1, expectImp2, expectImp3} - inputRequest := openrtb.BidRequest{Imp: []openrtb.Imp{imp1, imp2, imp3}} + inputRequest := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp1, imp2, imp3}} actualAdapterRequests, _ := adapter.MakeRequests(&inputRequest, &adapters.ExtraRequestInfo{}) mockHeaders := http.Header{} mockHeaders.Add("Content-Type", "application/json;charset=utf-8") @@ -132,7 +132,7 @@ func TestMakeRequests(t *testing.T) { t.Errorf("should have 3 imps") } for n, imp := range expectImps { - request := openrtb.BidRequest{Imp: []openrtb.Imp{imp}} + request := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp}} expectedJson, _ := json.Marshal(request) data := adapters.RequestData{ Method: "POST", @@ -149,11 +149,11 @@ func TestMakeRequests(t *testing.T) { func TestGetMediaTypeForImpIsVideo(t *testing.T) { testID := string("4321") testBidMediaType := openrtb_ext.BidTypeVideo - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: testID, - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imps := []openrtb.Imp{imp} + imps := []openrtb2.Imp{imp} actual, _ := getMediaTypeForImpWithId(testID, imps) if actual != "video" { @@ -162,11 +162,11 @@ func TestGetMediaTypeForImpIsVideo(t *testing.T) { } func TestGetMediaTypeForImpWithNoIDPresent(t *testing.T) { - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: "4321", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imps := []openrtb.Imp{imp} + imps := []openrtb2.Imp{imp} _, err := getMediaTypeForImpWithId("1234", imps) expected := &errortypes.BadInput{ Message: fmt.Sprintf("Failed to find impression \"%s\" ", "1234"), @@ -177,26 +177,26 @@ func TestGetMediaTypeForImpWithNoIDPresent(t *testing.T) { } func TestConvertToAdapterBidResponseHasCorrectNumberOfBids(t *testing.T) { - imp := openrtb.Imp{ + imp := openrtb2.Imp{ ID: "1234", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } - imp2 := openrtb.Imp{ + imp2 := openrtb2.Imp{ ID: "1235", - Video: &openrtb.Video{}, + Video: &openrtb2.Video{}, } mockResponse := adapters.ResponseData{StatusCode: 200, Body: json.RawMessage(`{"seatbid":[{"bid":[{"impid":"1234"}]},{"bid":[{"impid":"1235"}]}]}`)} - internalRequest := openrtb.BidRequest{Imp: []openrtb.Imp{imp, imp2}} + internalRequest := openrtb2.BidRequest{Imp: []openrtb2.Imp{imp, imp2}} mockBidResponse := adapters.NewBidderResponseWithBidsCapacity(5) typedBid := &adapters.TypedBid{ - Bid: &openrtb.Bid{ImpID: "1234"}, + Bid: &openrtb2.Bid{ImpID: "1234"}, BidType: "Video", } typedBid2 := &adapters.TypedBid{ - Bid: &openrtb.Bid{ImpID: "1235"}, + Bid: &openrtb2.Bid{ImpID: "1235"}, BidType: "Video", } diff --git a/adapters/unruly/usersync.go b/adapters/unruly/usersync.go index 248b4923875..5dc60c859bb 100644 --- a/adapters/unruly/usersync.go +++ b/adapters/unruly/usersync.go @@ -3,10 +3,10 @@ package unruly import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewUnrulySyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("unruly", 162, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("unruly", temp, adapters.SyncTypeIframe) } diff --git a/adapters/unruly/usersync_test.go b/adapters/unruly/usersync_test.go index e85a066dddc..f0702cebd34 100644 --- a/adapters/unruly/usersync_test.go +++ b/adapters/unruly/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestUnrulySyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr=A&consent=B&us_privacy=C&rurl=%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "iframe", syncInfo.Type) - assert.EqualValues(t, 162, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/valueimpression/params_test.go b/adapters/valueimpression/params_test.go index b80962ff4dd..46471de24bb 100644 --- a/adapters/valueimpression/params_test.go +++ b/adapters/valueimpression/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/valueimpression.json diff --git a/adapters/valueimpression/usersync.go b/adapters/valueimpression/usersync.go index 4cc1539103b..08490a5ed3e 100644 --- a/adapters/valueimpression/usersync.go +++ b/adapters/valueimpression/usersync.go @@ -3,10 +3,10 @@ package valueimpression import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewValueImpressionSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("valueimpression", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("valueimpression", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/valueimpression/usersync_test.go b/adapters/valueimpression/usersync_test.go index ed75969ac0d..7b3a13c5dd6 100644 --- a/adapters/valueimpression/usersync_test.go +++ b/adapters/valueimpression/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestValueImpressionSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://rtb.valueimpression.com/usersync?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&redirectUri=http%3A%2F%2Flocalhost:8000%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/valueimpression/valueimpression.go b/adapters/valueimpression/valueimpression.go index 87b66674f85..aac8faab52c 100644 --- a/adapters/valueimpression/valueimpression.go +++ b/adapters/valueimpression/valueimpression.go @@ -5,18 +5,18 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 ValueImpressionAdapter struct { endpoint string } -func (a *ValueImpressionAdapter) MakeRequests(request *openrtb.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ValueImpressionAdapter) MakeRequests(request *openrtb2.BidRequest, unused *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -36,7 +36,7 @@ func (a *ValueImpressionAdapter) MakeRequests(request *openrtb.BidRequest, unuse return adapterRequests, errs } -func (a *ValueImpressionAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, error) { +func (a *ValueImpressionAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { var err error jsonBody, err := json.Marshal(request) @@ -55,7 +55,7 @@ func (a *ValueImpressionAdapter) makeRequest(request *openrtb.BidRequest) (*adap }, nil } -func preprocess(request *openrtb.BidRequest) error { +func preprocess(request *openrtb2.BidRequest) error { if len(request.Imp) == 0 { return &errortypes.BadInput{ Message: "No Imps in Bid Request", @@ -85,7 +85,7 @@ func preprocess(request *openrtb.BidRequest) error { } // MakeBids based on valueimpression server response -func (a *ValueImpressionAdapter) MakeBids(bidRequest *openrtb.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *ValueImpressionAdapter) MakeBids(bidRequest *openrtb2.BidRequest, unused *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) { if responseData.StatusCode == http.StatusNoContent { return nil, nil } @@ -102,7 +102,7 @@ func (a *ValueImpressionAdapter) MakeBids(bidRequest *openrtb.BidRequest, unused }} } - var bidResponse openrtb.BidResponse + var bidResponse openrtb2.BidResponse if err := json.Unmarshal(responseData.Body, &bidResponse); err != nil { return nil, []error{&errortypes.BadServerResponse{ diff --git a/adapters/valueimpression/valueimpression_test.go b/adapters/valueimpression/valueimpression_test.go index b2c6500389f..f4d33864978 100644 --- a/adapters/valueimpression/valueimpression_test.go +++ b/adapters/valueimpression/valueimpression_test.go @@ -3,9 +3,9 @@ package valueimpression import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json b/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json index a92080aa96f..fd9d401cd44 100644 --- a/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json +++ b/adapters/valueimpression/valueimpressiontest/exemplary/banner-and-video.json @@ -127,4 +127,4 @@ } ]} ] -} +} \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json b/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json index d9d8e9db251..cf2fd7dd01e 100644 --- a/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json +++ b/adapters/valueimpression/valueimpressiontest/supplemental/explicit-dimensions.json @@ -54,4 +54,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json index 6ef3b3e5838..48122f5926a 100644 --- a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json +++ b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-no-bids.json @@ -48,4 +48,4 @@ } ], "expectedBidResponses": [] -} +} \ No newline at end of file diff --git a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json index c854548b78b..2de5213d686 100644 --- a/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json +++ b/adapters/valueimpression/valueimpressiontest/supplemental/invalid-response-unmarshall-error.json @@ -59,7 +59,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type uint64", + "value": "json: cannot unmarshal string into Go struct field Bid(\\.seatbid\\.bid)?\\.w of type int64", "comparison": "regex" } ] diff --git a/adapters/verizonmedia/params_test.go b/adapters/verizonmedia/params_test.go index 9250c265526..febda6058e6 100644 --- a/adapters/verizonmedia/params_test.go +++ b/adapters/verizonmedia/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/verizonmedia.json diff --git a/adapters/verizonmedia/usersync.go b/adapters/verizonmedia/usersync.go index 612aab3b1f0..2c7354d1146 100644 --- a/adapters/verizonmedia/usersync.go +++ b/adapters/verizonmedia/usersync.go @@ -1,11 +1,12 @@ package verizonmedia import ( - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewVerizonMediaSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("verizonmedia", 25, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("verizonmedia", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/verizonmedia/usersync_test.go b/adapters/verizonmedia/usersync_test.go index 6455078a6f5..9dd1ae70c25 100644 --- a/adapters/verizonmedia/usersync_test.go +++ b/adapters/verizonmedia/usersync_test.go @@ -4,7 +4,7 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy" "github.com/stretchr/testify/assert" ) @@ -19,5 +19,4 @@ func TestVerizonMediaSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 25, syncer.GDPRVendorID()) } diff --git a/adapters/verizonmedia/verizonmedia.go b/adapters/verizonmedia/verizonmedia.go index d90deea324b..aa215c01691 100644 --- a/adapters/verizonmedia/verizonmedia.go +++ b/adapters/verizonmedia/verizonmedia.go @@ -6,18 +6,18 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 VerizonMediaAdapter struct { URI string } -func (a *VerizonMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *VerizonMediaAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errors := make([]error, 0, 1) if len(request.Imp) == 0 { @@ -79,7 +79,7 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo // Split up multi-impression requests into multiple requests so that // each split request is only associated to a single impression reqCopy := *request - reqCopy.Imp = []openrtb.Imp{imp} + reqCopy.Imp = []openrtb2.Imp{imp} if request.Site != nil { siteCopy := *request.Site @@ -111,7 +111,7 @@ func (a *VerizonMediaAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo return reqs, errors } -func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -123,7 +123,7 @@ func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, exte }} } - var bidResp openrtb.BidResponse + 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), @@ -156,7 +156,7 @@ func (a *VerizonMediaAdapter) MakeBids(internalRequest *openrtb.BidRequest, exte return bidResponse, nil } -func getImpInfo(impId string, imps []openrtb.Imp) (bool, openrtb_ext.BidType) { +func getImpInfo(impId string, imps []openrtb2.Imp) (bool, openrtb_ext.BidType) { var mediaType openrtb_ext.BidType var exists bool for _, imp := range imps { @@ -171,7 +171,7 @@ func getImpInfo(impId string, imps []openrtb.Imp) (bool, openrtb_ext.BidType) { return exists, mediaType } -func changeRequestForBidService(request *openrtb.BidRequest, extension *openrtb_ext.ExtImpVerizonMedia) error { +func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpVerizonMedia) error { /* Always override the tag ID and (site ID or app ID) of the request */ request.Imp[0].TagID = extension.Pos if request.Site != nil { @@ -198,10 +198,8 @@ func changeRequestForBidService(request *openrtb.BidRequest, extension *openrtb_ return errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) } - banner.W = new(uint64) - *banner.W = banner.Format[0].W - banner.H = new(uint64) - *banner.H = banner.Format[0].H + banner.W = openrtb2.Int64Ptr(banner.Format[0].W) + banner.H = openrtb2.Int64Ptr(banner.Format[0].H) return nil } diff --git a/adapters/verizonmedia/verizonmedia_test.go b/adapters/verizonmedia/verizonmedia_test.go index e0e46c462c1..869ae9e9faa 100644 --- a/adapters/verizonmedia/verizonmedia_test.go +++ b/adapters/verizonmedia/verizonmedia_test.go @@ -3,10 +3,10 @@ package verizonmedia import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/influxdata/influxdb/pkg/testing/assert" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestVerizonMediaBidderEndpointConfig(t *testing.T) { diff --git a/adapters/visx/params_test.go b/adapters/visx/params_test.go index f59ce49a46d..e857e8d2639 100644 --- a/adapters/visx/params_test.go +++ b/adapters/visx/params_test.go @@ -2,7 +2,7 @@ package visx import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/visx/usersync.go b/adapters/visx/usersync.go index e8223083033..dc143570ab9 100644 --- a/adapters/visx/usersync.go +++ b/adapters/visx/usersync.go @@ -3,10 +3,10 @@ package visx import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewVisxSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("visx", 154, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("visx", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/visx/usersync_test.go b/adapters/visx/usersync_test.go index e71ee35f5d8..ec4cf82b04b 100644 --- a/adapters/visx/usersync_test.go +++ b/adapters/visx/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -30,6 +30,5 @@ func TestVisxSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://t.visx.net/s2s_sync?gdpr=A&gdpr_consent=B&us_privacy=C&redir=%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUUID%7D", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 154, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/visx/visx.go b/adapters/visx/visx.go index 2a00b2def69..8e2b10f7c32 100644 --- a/adapters/visx/visx.go +++ b/adapters/visx/visx.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 VisxAdapter struct { @@ -38,7 +38,7 @@ type visxResponse struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *VisxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *VisxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) // copy the request, because we are going to mutate it @@ -65,7 +65,7 @@ func (a *VisxAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapter } // MakeBids unpacks the server's response into Bids. -func (a *VisxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *VisxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -91,14 +91,14 @@ func (a *VisxAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequ for _, sb := range bidResp.SeatBid { for i := range sb.Bid { - bid := openrtb.Bid{} + bid := openrtb2.Bid{} bid.ID = internalRequest.ID bid.CrID = sb.Bid[i].CrID bid.ImpID = sb.Bid[i].ImpID bid.Price = sb.Bid[i].Price bid.AdM = sb.Bid[i].AdM - bid.W = sb.Bid[i].W - bid.H = sb.Bid[i].H + bid.W = int64(sb.Bid[i].W) + bid.H = int64(sb.Bid[i].H) bid.ADomain = sb.Bid[i].ADomain bid.DealID = sb.Bid[i].DealID diff --git a/adapters/visx/visx_test.go b/adapters/visx/visx_test.go index d637e908913..2f51af76597 100644 --- a/adapters/visx/visx_test.go +++ b/adapters/visx/visx_test.go @@ -3,9 +3,9 @@ package visx import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/vrtcal/params_test.go b/adapters/vrtcal/params_test.go index 5f80812f26f..ba57b6d82f8 100644 --- a/adapters/vrtcal/params_test.go +++ b/adapters/vrtcal/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) //Vrtcal doesn't currently require any custom fields. This file is included for conformity only diff --git a/adapters/vrtcal/usersync.go b/adapters/vrtcal/usersync.go index 0fd97875a0e..8a6ddc0ca26 100644 --- a/adapters/vrtcal/usersync.go +++ b/adapters/vrtcal/usersync.go @@ -3,10 +3,10 @@ package vrtcal import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewVrtcalSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("vrtcal", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("vrtcal", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/vrtcal/usersync_test.go b/adapters/vrtcal/usersync_test.go index faea653f795..7f8ca220a96 100644 --- a/adapters/vrtcal/usersync_test.go +++ b/adapters/vrtcal/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,7 +25,6 @@ func TestVrtcalSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "http://usync-prebid.vrtcal.com/s?gdpr=0&gdpr_consent=", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) assert.Equal(t, "vrtcal", syncer.FamilyName()) } diff --git a/adapters/vrtcal/vrtcal.go b/adapters/vrtcal/vrtcal.go index 5d4bd705d2f..e4986b2f6fb 100644 --- a/adapters/vrtcal/vrtcal.go +++ b/adapters/vrtcal/vrtcal.go @@ -5,18 +5,18 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 VrtcalAdapter struct { endpoint string } -func (a *VrtcalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *VrtcalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -42,7 +42,7 @@ func (a *VrtcalAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapt } // MakeBids make the bids for the bid response. -func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil @@ -60,7 +60,7 @@ func (a *VrtcalAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRe }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} diff --git a/adapters/vrtcal/vrtcal_test.go b/adapters/vrtcal/vrtcal_test.go index c1f3cfb0796..332217650f6 100644 --- a/adapters/vrtcal/vrtcal_test.go +++ b/adapters/vrtcal/vrtcal_test.go @@ -3,9 +3,9 @@ package vrtcal import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/yeahmobi/params_test.go b/adapters/yeahmobi/params_test.go index 79b8273a362..997bf93a53f 100644 --- a/adapters/yeahmobi/params_test.go +++ b/adapters/yeahmobi/params_test.go @@ -2,7 +2,7 @@ package yeahmobi import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/yeahmobi/yeahmobi.go b/adapters/yeahmobi/yeahmobi.go index 0e342052008..8a692d4ff2e 100644 --- a/adapters/yeahmobi/yeahmobi.go +++ b/adapters/yeahmobi/yeahmobi.go @@ -7,13 +7,13 @@ import ( "net/url" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/golang/glog" + "github.com/mxmCherry/openrtb/v15/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 { @@ -33,7 +33,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var adapterRequests []*adapters.RequestData adapterRequest, errs := adapter.makeRequest(request) @@ -44,7 +44,7 @@ func (adapter *YeahmobiAdapter) MakeRequests(request *openrtb.BidRequest, reqInf return adapterRequests, errs } -func (adapter *YeahmobiAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (adapter *YeahmobiAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error yeahmobiExt, errs := getYeahmobiExt(request) @@ -75,7 +75,7 @@ func (adapter *YeahmobiAdapter) makeRequest(request *openrtb.BidRequest) (*adapt }, errs } -func transform(request *openrtb.BidRequest) { +func transform(request *openrtb2.BidRequest) { for i, imp := range request.Imp { if imp.Native != nil { var nativeRequest map[string]interface{} @@ -95,13 +95,15 @@ func transform(request *openrtb.BidRequest) { continue } - request.Imp[i].Native.Request = string(nativeReqByte) + nativeCopy := *request.Imp[i].Native + nativeCopy.Request = string(nativeReqByte) + request.Imp[i].Native = &nativeCopy } } } } -func getYeahmobiExt(request *openrtb.BidRequest) (*openrtb_ext.ExtImpYeahmobi, []error) { +func getYeahmobiExt(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpYeahmobi, []error) { var extImpYeahmobi openrtb_ext.ExtImpYeahmobi var errs []error @@ -129,7 +131,7 @@ func (adapter *YeahmobiAdapter) getEndpoint(ext *openrtb_ext.ExtImpYeahmobi) (st } // MakeBids make the bids for the bid response. -func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -146,7 +148,7 @@ func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, external }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -167,7 +169,7 @@ func (a *YeahmobiAdapter) MakeBids(internalRequest *openrtb.BidRequest, external } -func getBidType(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { bidType := openrtb_ext.BidTypeBanner for _, imp := range imps { if imp.ID == impId { diff --git a/adapters/yeahmobi/yeahmobi_test.go b/adapters/yeahmobi/yeahmobi_test.go index 9f983633452..a38480b0486 100644 --- a/adapters/yeahmobi/yeahmobi_test.go +++ b/adapters/yeahmobi/yeahmobi_test.go @@ -3,9 +3,9 @@ package yeahmobi import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json index 22939466309..0d77e5af93a 100644 --- a/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json +++ b/adapters/yeahmobi/yeahmobitest/supplemental/bad_response.json @@ -49,7 +49,7 @@ "expectedMakeBidsErrors": [ { "comparison": "literal", - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse" + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse" } ] } diff --git a/adapters/yieldlab/params_test.go b/adapters/yieldlab/params_test.go index f66121e35e8..8c230c15b15 100644 --- a/adapters/yieldlab/params_test.go +++ b/adapters/yieldlab/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/yieldlab.json diff --git a/adapters/yieldlab/usersync.go b/adapters/yieldlab/usersync.go index a0462e19e6e..90507e31161 100644 --- a/adapters/yieldlab/usersync.go +++ b/adapters/yieldlab/usersync.go @@ -3,10 +3,10 @@ package yieldlab import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewYieldlabSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldlab", 70, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("yieldlab", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/yieldlab/usersync_test.go b/adapters/yieldlab/usersync_test.go index cdca7f9f417..eabd46f6dce 100644 --- a/adapters/yieldlab/usersync_test.go +++ b/adapters/yieldlab/usersync_test.go @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/assert" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" ) func TestYieldlabSyncer(t *testing.T) { @@ -21,6 +21,5 @@ func TestYieldlabSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "https://ad.yieldlab.net/mr?t=2&pid=9140838&gdpr=0&gdpr_consent=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldlab%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%25%25YL_UID%25%25", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 70, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go index adce598d60d..ee9170c25cf 100644 --- a/adapters/yieldlab/yieldlab.go +++ b/adapters/yieldlab/yieldlab.go @@ -9,13 +9,13 @@ import ( "strconv" "strings" - "github.com/PubMatic-OpenWrap/openrtb" "golang.org/x/text/currency" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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" ) // YieldlabAdapter connects the Yieldlab API to prebid server @@ -36,7 +36,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } // Builds endpoint url based on adapter-specific pub settings from imp.ext -func (a *YieldlabAdapter) makeEndpointURL(req *openrtb.BidRequest, params *openrtb_ext.ExtImpYieldlab) (string, error) { +func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *openrtb_ext.ExtImpYieldlab) (string, error) { uri, err := url.Parse(a.endpoint) if err != nil { return "", fmt.Errorf("failed to parse yieldlab endpoint: %v", err) @@ -86,7 +86,7 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb.BidRequest, params *openr return uri.String(), nil } -func (a *YieldlabAdapter) getGDPR(request *openrtb.BidRequest) (string, string, error) { +func (a *YieldlabAdapter) getGDPR(request *openrtb2.BidRequest) (string, string, error) { gdpr := "" var extRegs openrtb_ext.ExtRegs if request.Regs != nil { @@ -118,7 +118,7 @@ func (a *YieldlabAdapter) makeTargetingValues(params *openrtb_ext.ExtImpYieldlab return values.Encode() } -func (a *YieldlabAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *YieldlabAdapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { return nil, []error{fmt.Errorf("invalid request %+v, no Impressions given", request)} } @@ -149,7 +149,7 @@ func (a *YieldlabAdapter) MakeRequests(request *openrtb.BidRequest, _ *adapters. } // parseRequest extracts the Yieldlab request information from the request -func (a *YieldlabAdapter) parseRequest(request *openrtb.BidRequest) []*openrtb_ext.ExtImpYieldlab { +func (a *YieldlabAdapter) parseRequest(request *openrtb2.BidRequest) []*openrtb_ext.ExtImpYieldlab { params := make([]*openrtb_ext.ExtImpYieldlab, 0) for i := 0; i < len(request.Imp); i++ { @@ -187,7 +187,7 @@ func (a *YieldlabAdapter) mergeParams(params []*openrtb_ext.ExtImpYieldlab) *ope } // MakeBids make the bids for the bid response. -func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode != 200 { return nil, []error{ &errortypes.BadServerResponse{ @@ -226,14 +226,14 @@ func (a *YieldlabAdapter) MakeBids(internalRequest *openrtb.BidRequest, external } var bidType openrtb_ext.BidType - responseBid := &openrtb.Bid{ + responseBid := &openrtb2.Bid{ ID: strconv.FormatUint(bid.ID, 10), Price: float64(bid.Price) / 100, ImpID: internalRequest.Imp[i].ID, CrID: a.makeCreativeID(req, bid), DealID: strconv.FormatUint(bid.Pid, 10), - W: width, - H: height, + W: int64(width), + H: int64(height), } if internalRequest.Imp[i].Video != nil { @@ -268,11 +268,11 @@ func (a *YieldlabAdapter) findBidReq(adslotID uint64, params []*openrtb_ext.ExtI return nil } -func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { +func (a *YieldlabAdapter) makeBannerAdSource(req *openrtb2.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { return fmt.Sprintf(adSourceBanner, a.makeAdSourceURL(req, ext, res)) } -func (a *YieldlabAdapter) makeAdSourceURL(req *openrtb.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { +func (a *YieldlabAdapter) makeAdSourceURL(req *openrtb2.BidRequest, ext *openrtb_ext.ExtImpYieldlab, res *bidResponse) string { val := url.Values{} val.Set("ts", a.cacheBuster()) val.Set("id", ext.ExtId) diff --git a/adapters/yieldlab/yieldlab_test.go b/adapters/yieldlab/yieldlab_test.go index 83f7d1e813f..273117e3bdf 100644 --- a/adapters/yieldlab/yieldlab_test.go +++ b/adapters/yieldlab/yieldlab_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters/adapterstest" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) const testURL = "https://ad.yieldlab.net/testing/" diff --git a/adapters/yieldmo/params_test.go b/adapters/yieldmo/params_test.go index 87044a2dd57..0a8fe2d10f1 100644 --- a/adapters/yieldmo/params_test.go +++ b/adapters/yieldmo/params_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file actually intends to test static/bidder-params/yieldmo.json diff --git a/adapters/yieldmo/usersync.go b/adapters/yieldmo/usersync.go index 25d65f229a2..d06caa90c0b 100644 --- a/adapters/yieldmo/usersync.go +++ b/adapters/yieldmo/usersync.go @@ -3,10 +3,10 @@ package yieldmo import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewYieldmoSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldmo", 173, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("yieldmo", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/yieldmo/usersync_test.go b/adapters/yieldmo/usersync_test.go index 1212efdb878..5d12c63e4aa 100644 --- a/adapters/yieldmo/usersync_test.go +++ b/adapters/yieldmo/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,5 @@ func TestYieldmoSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//ads.yieldmo.com/pbsync?gdpr=0&gdpr_consent=&us_privacy=&redirectUri=http%3A%2F%2Flocalhost%2F%2Fsetuid%3Fbidder%3Dyieldmo%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 173, syncer.GDPRVendorID()) assert.False(t, syncInfo.SupportCORS) } diff --git a/adapters/yieldmo/yieldmo.go b/adapters/yieldmo/yieldmo.go index 95fbfa33f48..7d7a8f22b01 100644 --- a/adapters/yieldmo/yieldmo.go +++ b/adapters/yieldmo/yieldmo.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 YieldmoAdapter struct { @@ -20,7 +20,7 @@ type Ext struct { PlacementId string `json:"placement_id"` } -func (a *YieldmoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *YieldmoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errs []error var adapterRequests []*adapters.RequestData @@ -33,7 +33,7 @@ func (a *YieldmoAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adap return adapterRequests, errors } -func (a *YieldmoAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { +func (a *YieldmoAdapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, []error) { var errs []error if err := preprocess(request); err != nil { @@ -59,7 +59,7 @@ func (a *YieldmoAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.Req } // Mutate the request to get it ready to send to yieldmo. -func preprocess(request *openrtb.BidRequest) error { +func preprocess(request *openrtb2.BidRequest) error { for i := 0; i < len(request.Imp); i++ { var imp = request.Imp[i] var bidderExt adapters.ExtImpBidder @@ -95,7 +95,7 @@ func preprocess(request *openrtb.BidRequest) error { } // MakeBids make the bids for the bid response. -func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -112,7 +112,7 @@ func (a *YieldmoAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalR }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -140,7 +140,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { //default to video unless banner exists in impression for _, imp := range imps { if imp.ID == impId && imp.Banner != nil { diff --git a/adapters/yieldmo/yieldmo_test.go b/adapters/yieldmo/yieldmo_test.go index faee55c3890..cb0a8d60aa5 100644 --- a/adapters/yieldmo/yieldmo_test.go +++ b/adapters/yieldmo/yieldmo_test.go @@ -3,9 +3,9 @@ package yieldmo import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/yieldone/params_test.go b/adapters/yieldone/params_test.go index e0142334d6e..6048ea5d7dc 100644 --- a/adapters/yieldone/params_test.go +++ b/adapters/yieldone/params_test.go @@ -2,7 +2,7 @@ package yieldone import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "testing" ) diff --git a/adapters/yieldone/usersync.go b/adapters/yieldone/usersync.go index 333550aa775..4d5d8283a68 100644 --- a/adapters/yieldone/usersync.go +++ b/adapters/yieldone/usersync.go @@ -3,10 +3,10 @@ package yieldone import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewYieldoneSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("yieldone", 0, temp, adapters.SyncTypeRedirect) + return adapters.NewSyncer("yieldone", temp, adapters.SyncTypeRedirect) } diff --git a/adapters/yieldone/usersync_test.go b/adapters/yieldone/usersync_test.go index 730f9103017..c4d0fee92dd 100644 --- a/adapters/yieldone/usersync_test.go +++ b/adapters/yieldone/usersync_test.go @@ -4,8 +4,8 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) @@ -25,6 +25,5 @@ func TestYieldoneSyncer(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "//not_localhost/synclocalhost%2Fsetuid%3Fbidder%3Dyieldone%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24UID", syncInfo.URL) assert.Equal(t, "redirect", syncInfo.Type) - assert.EqualValues(t, 0, syncer.GDPRVendorID()) assert.Equal(t, false, syncInfo.SupportCORS) } diff --git a/adapters/yieldone/yieldone.go b/adapters/yieldone/yieldone.go index 33debb26bd9..4e22b1446a7 100644 --- a/adapters/yieldone/yieldone.go +++ b/adapters/yieldone/yieldone.go @@ -5,11 +5,11 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 YieldoneAdapter struct { @@ -17,10 +17,10 @@ type YieldoneAdapter struct { } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *YieldoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *YieldoneAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { var errors = make([]error, 0) - var validImps []openrtb.Imp + var validImps []openrtb2.Imp for i := 0; i < len(request.Imp); i++ { if err := preprocess(&request.Imp[i]); err == nil { validImps = append(validImps, request.Imp[i]) @@ -49,7 +49,7 @@ func (a *YieldoneAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *ada } // MakeBids unpacks the server's response into Bids. -func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { if response.StatusCode == http.StatusNoContent { return nil, nil } @@ -66,7 +66,7 @@ func (a *YieldoneAdapter) MakeBids(internalRequest *openrtb.BidRequest, external }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} } @@ -98,7 +98,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func preprocess(imp *openrtb.Imp) error { +func preprocess(imp *openrtb2.Imp) error { var ext adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &ext); err != nil { @@ -122,7 +122,7 @@ func preprocess(imp *openrtb.Imp) error { return nil } -func getMediaTypeForImp(impID string, imps []openrtb.Imp) (openrtb_ext.BidType, error) { +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) { for _, imp := range imps { if imp.ID == impID { if imp.Banner != nil { diff --git a/adapters/yieldone/yieldone_test.go b/adapters/yieldone/yieldone_test.go index ca2c3851ad4..8544c21f4e7 100644 --- a/adapters/yieldone/yieldone_test.go +++ b/adapters/yieldone/yieldone_test.go @@ -3,9 +3,9 @@ package yieldone import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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) { diff --git a/adapters/yieldone/yieldonetest/supplemental/bad_response.json b/adapters/yieldone/yieldonetest/supplemental/bad_response.json index fa993a2fff5..3112d1f7ba0 100644 --- a/adapters/yieldone/yieldonetest/supplemental/bad_response.json +++ b/adapters/yieldone/yieldonetest/supplemental/bad_response.json @@ -58,7 +58,7 @@ "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/adapters/zeroclickfraud/usersync.go b/adapters/zeroclickfraud/usersync.go index a5435335ab8..41c589818ca 100644 --- a/adapters/zeroclickfraud/usersync.go +++ b/adapters/zeroclickfraud/usersync.go @@ -3,10 +3,10 @@ package zeroclickfraud import ( "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" ) func NewZeroClickFraudSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("zeroclickfraud", 0, temp, adapters.SyncTypeIframe) + return adapters.NewSyncer("zeroclickfraud", temp, adapters.SyncTypeIframe) } diff --git a/adapters/zeroclickfraud/usersync_test.go b/adapters/zeroclickfraud/usersync_test.go index 445b07ab8eb..5e8f8fdf111 100644 --- a/adapters/zeroclickfraud/usersync_test.go +++ b/adapters/zeroclickfraud/usersync_test.go @@ -4,9 +4,9 @@ import ( "testing" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" "github.com/stretchr/testify/assert" ) diff --git a/adapters/zeroclickfraud/zeroclickfraud.go b/adapters/zeroclickfraud/zeroclickfraud.go index eb98aed1ece..cf27c83d4a7 100644 --- a/adapters/zeroclickfraud/zeroclickfraud.go +++ b/adapters/zeroclickfraud/zeroclickfraud.go @@ -7,19 +7,19 @@ import ( "strconv" "text/template" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/macros" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/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 ZeroClickFraudAdapter struct { EndpointTemplate template.Template } -func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { errs := make([]error, 0, len(request.Imp)) headers := http.Header{ @@ -69,7 +69,7 @@ func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb.BidRequest, reqInf internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests) */ func (a *ZeroClickFraudAdapter) MakeBids( - internalRequest *openrtb.BidRequest, + internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData, ) (*adapters.BidderResponse, []error) { @@ -88,7 +88,7 @@ func (a *ZeroClickFraudAdapter) MakeBids( }} } - var bidResp openrtb.BidResponse + var bidResp openrtb2.BidResponse if err := json.Unmarshal(response.Body, &bidResp); err != nil { return nil, []error{err} @@ -110,9 +110,9 @@ func (a *ZeroClickFraudAdapter) MakeBids( return bidResponse, nil } -func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp, error) { +func splitImpressions(imps []openrtb2.Imp) (map[openrtb_ext.ExtImpZeroClickFraud][]openrtb2.Imp, error) { - var m = make(map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp) + var m = make(map[openrtb_ext.ExtImpZeroClickFraud][]openrtb2.Imp) for _, imp := range imps { bidderParams, err := getBidderParams(&imp) @@ -126,7 +126,7 @@ func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpZeroClickFraud] return m, nil } -func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error) { +func getBidderParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error) { var bidderExt adapters.ExtImpBidder if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return nil, &errortypes.BadInput{ @@ -155,7 +155,7 @@ func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error return &zeroclickfraudExt, nil } -func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaType(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { bidType := openrtb_ext.BidTypeBanner diff --git a/adapters/zeroclickfraud/zeroclickfraud_test.go b/adapters/zeroclickfraud/zeroclickfraud_test.go index 2942c25a4f3..654d9450da0 100644 --- a/adapters/zeroclickfraud/zeroclickfraud_test.go +++ b/adapters/zeroclickfraud/zeroclickfraud_test.go @@ -3,9 +3,9 @@ package zeroclickfraud import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adapterstest" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" ) diff --git a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json index 84d6bd9d889..87ad168467d 100644 --- a/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json +++ b/adapters/zeroclickfraud/zeroclickfraudtest/supplemental/bad-response-body.json @@ -81,7 +81,7 @@ }], "expectedMakeBidsErrors": [ { - "value": "json: cannot unmarshal string into Go value of type openrtb.BidResponse", + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", "comparison": "literal" } ] diff --git a/amp/parse.go b/amp/parse.go index c3606e83563..05e43aca1e2 100644 --- a/amp/parse.go +++ b/amp/parse.go @@ -6,7 +6,7 @@ import ( "strconv" "strings" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) // Params defines the paramters of an AMP request. @@ -24,11 +24,11 @@ type Params struct { // Size defines size information of an AMP request. type Size struct { - Height uint64 - Multisize []openrtb.Format - OverrideHeight uint64 - OverrideWidth uint64 - Width uint64 + Height int64 + Multisize []openrtb2.Format + OverrideHeight int64 + OverrideWidth int64 + Width int64 } // ParseParams parses the AMP paramters from a HTTP request. @@ -67,26 +67,26 @@ func parseIntPtr(value string) *uint64 { return nil } -func parseInt(value string) uint64 { - if parsed, err := strconv.ParseUint(value, 10, 64); err == nil { +func parseInt(value string) int64 { + if parsed, err := strconv.ParseInt(value, 10, 64); err == nil { return parsed } return 0 } -func parseMultisize(multisize string) []openrtb.Format { +func parseMultisize(multisize string) []openrtb2.Format { if multisize == "" { return nil } sizeStrings := strings.Split(multisize, ",") - sizes := make([]openrtb.Format, 0, len(sizeStrings)) + sizes := make([]openrtb2.Format, 0, len(sizeStrings)) for _, sizeString := range sizeStrings { wh := strings.Split(sizeString, "x") if len(wh) != 2 { return nil } - f := openrtb.Format{ + f := openrtb2.Format{ W: parseInt(wh[0]), H: parseInt(wh[1]), } diff --git a/amp/parse_test.go b/amp/parse_test.go index 1b9d30804da..91027f8e67c 100644 --- a/amp/parse_test.go +++ b/amp/parse_test.go @@ -4,7 +4,7 @@ import ( "net/http" "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -40,7 +40,7 @@ func TestParseParams(t *testing.T) { OverrideHeight: 3, OverrideWidth: 4, Width: 2, - Multisize: []openrtb.Format{ + Multisize: []openrtb2.Format{ {W: 10, H: 11}, {W: 12, H: 13}, }, }, @@ -96,7 +96,7 @@ func TestParseMultisize(t *testing.T) { testCases := []struct { description string multisize string - expectedFormats []openrtb.Format + expectedFormats []openrtb2.Format }{ { description: "Empty", @@ -106,18 +106,18 @@ func TestParseMultisize(t *testing.T) { { description: "One", multisize: "1x2", - expectedFormats: []openrtb.Format{{W: 1, H: 2}}, + expectedFormats: []openrtb2.Format{{W: 1, H: 2}}, }, { description: "Many", multisize: "1x2,3x4", - expectedFormats: []openrtb.Format{{W: 1, H: 2}, {W: 3, H: 4}}, + expectedFormats: []openrtb2.Format{{W: 1, H: 2}, {W: 3, H: 4}}, }, { // Existing Behavior: The " 3" token in the second size is parsed as 0. description: "Many With Space - Quirky Result", multisize: "1x2, 3x4", - expectedFormats: []openrtb.Format{{W: 1, H: 2}, {W: 0, H: 4}}, + expectedFormats: []openrtb2.Format{{W: 1, H: 2}, {W: 0, H: 4}}, }, { description: "One - Zero Size - Ignored", diff --git a/analytics/clients/http.go b/analytics/clients/http.go index 68af3073a78..bc7f863ebdd 100644 --- a/analytics/clients/http.go +++ b/analytics/clients/http.go @@ -7,6 +7,6 @@ import ( var defaultHttpInstance = http.DefaultClient func GetDefaultHttpInstance() *http.Client { - // TODO 2020-06-22 @see https://github.com/PubMatic-OpenWrap/prebid-server/pull/1331#discussion_r436110097 + // TODO 2020-06-22 @see https://github.com/prebid/prebid-server/pull/1331#discussion_r436110097 return defaultHttpInstance } diff --git a/analytics/config/config.go b/analytics/config/config.go index ac145ee363e..fa63cb5a1e4 100644 --- a/analytics/config/config.go +++ b/analytics/config/config.go @@ -1,12 +1,12 @@ package config import ( - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/analytics/clients" - "github.com/PubMatic-OpenWrap/prebid-server/analytics/filesystem" - "github.com/PubMatic-OpenWrap/prebid-server/analytics/pubstack" - "github.com/PubMatic-OpenWrap/prebid-server/config" "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" ) //Modules that need to be logged to need to be initialized here diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 02ea6a54261..310dbe1a481 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -1,14 +1,15 @@ package config import ( - "github.com/stretchr/testify/assert" "net/http" "os" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/config" ) const TEST_DIR string = "testFiles" @@ -19,8 +20,8 @@ func TestSampleModule(t *testing.T) { am.LogAuctionObject(&analytics.AuctionObject{ Status: http.StatusOK, Errors: nil, - Request: &openrtb.BidRequest{}, - Response: &openrtb.BidResponse{}, + Request: &openrtb2.BidRequest{}, + Response: &openrtb2.BidResponse{}, }) if count != 1 { t.Errorf("PBSAnalyticsModule failed at LogAuctionObject") diff --git a/analytics/core.go b/analytics/core.go index 78ca1df2c9a..38c4f779327 100644 --- a/analytics/core.go +++ b/analytics/core.go @@ -3,10 +3,10 @@ package analytics import ( "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/usersync" ) /* @@ -30,8 +30,8 @@ type PBSAnalyticsModule interface { type AuctionObject struct { Status int Errors []error - Request *openrtb.BidRequest - Response *openrtb.BidResponse + Request *openrtb2.BidRequest + Response *openrtb2.BidResponse Account *config.Account StartTime time.Time } @@ -40,8 +40,8 @@ type AuctionObject struct { type AmpObject struct { Status int Errors []error - Request *openrtb.BidRequest - AuctionResponse *openrtb.BidResponse + Request *openrtb2.BidRequest + AuctionResponse *openrtb2.BidResponse AmpTargetingValues map[string]string Origin string StartTime time.Time @@ -51,8 +51,8 @@ type AmpObject struct { type VideoObject struct { Status int Errors []error - Request *openrtb.BidRequest - Response *openrtb.BidResponse + Request *openrtb2.BidRequest + Response *openrtb2.BidResponse VideoRequest *openrtb_ext.BidRequestVideo VideoResponse *openrtb_ext.BidResponseVideo StartTime time.Time diff --git a/analytics/filesystem/file_module.go b/analytics/filesystem/file_module.go index bb94fa783a5..a0721d98a2a 100644 --- a/analytics/filesystem/file_module.go +++ b/analytics/filesystem/file_module.go @@ -5,8 +5,8 @@ import ( "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" "github.com/chasex/glog" + "github.com/prebid/prebid-server/analytics" ) type RequestType string diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index e46e9259319..0c3d3c9e6ac 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -1,15 +1,16 @@ package filesystem import ( - "github.com/PubMatic-OpenWrap/prebid-server/config" "net/http" "os" "strings" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/usersync" ) const TEST_DIR string = "testFiles" @@ -18,7 +19,7 @@ func TestAmpObject_ToJson(t *testing.T) { ao := &analytics.AmpObject{ Status: http.StatusOK, Errors: make([]error, 0), - AuctionResponse: &openrtb.BidResponse{}, + AuctionResponse: &openrtb2.BidResponse{}, AmpTargetingValues: map[string]string{}, } if aoJson := jsonifyAmpObject(ao); strings.Contains(aoJson, "Transactional Logs Error") { diff --git a/analytics/pubstack/helpers/json.go b/analytics/pubstack/helpers/json.go index 11c5bf8ec05..f02f1120626 100644 --- a/analytics/pubstack/helpers/json.go +++ b/analytics/pubstack/helpers/json.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" + "github.com/prebid/prebid-server/analytics" ) 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 4f453ccdf58..7673b067f8a 100644 --- a/analytics/pubstack/helpers/json_test.go +++ b/analytics/pubstack/helpers/json_test.go @@ -1,11 +1,12 @@ package helpers import ( - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "net/http" "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/usersync" ) func TestJsonifyAuctionObject(t *testing.T) { @@ -52,7 +53,7 @@ func TestJsonifyAmpObject(t *testing.T) { ao := &analytics.AmpObject{ Status: http.StatusOK, Errors: make([]error, 0), - AuctionResponse: &openrtb.BidResponse{}, + AuctionResponse: &openrtb2.BidResponse{}, AmpTargetingValues: map[string]string{}, } if _, err := JsonifyAmpObject(ao, "scopeId"); err != nil { diff --git a/analytics/pubstack/pubstack_module.go b/analytics/pubstack/pubstack_module.go index ae32dc52638..60ccde02a8c 100644 --- a/analytics/pubstack/pubstack_module.go +++ b/analytics/pubstack/pubstack_module.go @@ -2,7 +2,7 @@ package pubstack import ( "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/analytics/pubstack/eventchannel" + "github.com/prebid/prebid-server/analytics/pubstack/eventchannel" "net/http" "net/url" "os" @@ -11,10 +11,10 @@ import ( "syscall" "time" - "github.com/PubMatic-OpenWrap/prebid-server/analytics/pubstack/helpers" "github.com/golang/glog" + "github.com/prebid/prebid-server/analytics/pubstack/helpers" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" + "github.com/prebid/prebid-server/analytics" ) type Configuration struct { diff --git a/analytics/pubstack/pubstack_module_test.go b/analytics/pubstack/pubstack_module_test.go index 143fb217913..cb8f088d0bf 100644 --- a/analytics/pubstack/pubstack_module_test.go +++ b/analytics/pubstack/pubstack_module_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/analytics" "github.com/stretchr/testify/assert" ) @@ -21,7 +21,7 @@ func loadJSONFromFile() (*analytics.AuctionObject, error) { } defer req.Close() - reqCtn := openrtb.BidRequest{} + reqCtn := openrtb2.BidRequest{} reqPayload, err := ioutil.ReadAll(req) if err != nil { return nil, err @@ -38,7 +38,7 @@ func loadJSONFromFile() (*analytics.AuctionObject, error) { } defer res.Close() - resCtn := openrtb.BidResponse{} + resCtn := openrtb2.BidResponse{} resPayload, err := ioutil.ReadAll(res) if err != nil { return nil, err diff --git a/cache/dummycache/dummycache.go b/cache/dummycache/dummycache.go index 245b36325aa..8cf9f2c4dff 100644 --- a/cache/dummycache/dummycache.go +++ b/cache/dummycache/dummycache.go @@ -3,7 +3,7 @@ package dummycache import ( "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/cache" + "github.com/prebid/prebid-server/cache" ) // Cache dummy config that will echo back results diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go index d49f755edb4..0c1ba435388 100644 --- a/cache/filecache/filecache.go +++ b/cache/filecache/filecache.go @@ -4,8 +4,8 @@ import ( "fmt" "io/ioutil" - "github.com/PubMatic-OpenWrap/prebid-server/cache" "github.com/golang/glog" + "github.com/prebid/prebid-server/cache" "gopkg.in/yaml.v2" ) diff --git a/cache/postgrescache/postgrescache.go b/cache/postgrescache/postgrescache.go index aa0d3c3ea8b..df8b8fe49b2 100644 --- a/cache/postgrescache/postgrescache.go +++ b/cache/postgrescache/postgrescache.go @@ -7,11 +7,11 @@ import ( "encoding/gob" "time" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/cache" "github.com/coocood/freecache" "github.com/lib/pq" + "github.com/prebid/prebid-server/cache" ) type CacheConfig struct { diff --git a/config/accounts.go b/config/accounts.go index 548092451c3..f7b380fca48 100644 --- a/config/accounts.go +++ b/config/accounts.go @@ -39,8 +39,9 @@ func (a *AccountCCPA) EnabledForIntegrationType(integrationType IntegrationType) // AccountGDPR represents account-specific GDPR configuration type AccountGDPR struct { - Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` - IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"` + Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"` + IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"` + BasicEnforcementVendors []string `mapstructure:"basic_enforcement_vendors" json:"basic_enforcement_vendors"` } // EnabledForIntegrationType indicates whether GDPR is turned on at the account level for the specified integration type diff --git a/config/adapter.go b/config/adapter.go index 5f188ac4a62..ff262b186fd 100644 --- a/config/adapter.go +++ b/config/adapter.go @@ -4,8 +4,8 @@ import ( "fmt" "text/template" - "github.com/PubMatic-OpenWrap/prebid-server/macros" validator "github.com/asaskevich/govalidator" + "github.com/prebid/prebid-server/macros" ) type Adapter struct { diff --git a/config/bidderinfo.go b/config/bidderinfo.go new file mode 100644 index 00000000000..9eff288f64f --- /dev/null +++ b/config/bidderinfo.go @@ -0,0 +1,101 @@ +package config + +import ( + "fmt" + "io/ioutil" + "strings" + + "github.com/prebid/prebid-server/openrtb_ext" + "gopkg.in/yaml.v2" +) + +// BidderInfos contains a mapping of bidder name to bidder info. +type BidderInfos map[string]BidderInfo + +// BidderInfo is the maintainer information, supported auction types, and feature opts-in for a bidder. +type BidderInfo struct { + Enabled bool // copied from adapter config for convenience. to be refactored. + Maintainer *MaintainerInfo `yaml:"maintainer"` + Capabilities *CapabilitiesInfo `yaml:"capabilities"` + ModifyingVastXmlAllowed bool `yaml:"modifyingVastXmlAllowed"` + Debug *DebugInfo `yaml:"debug,omitempty"` + GVLVendorID uint16 `yaml:"gvlVendorID,omitempty"` +} + +// MaintainerInfo is the support email address for a bidder. +type MaintainerInfo struct { + Email string `yaml:"email"` +} + +// CapabilitiesInfo is the supported platforms for a bidder. +type CapabilitiesInfo struct { + App *PlatformInfo `yaml:"app"` + Site *PlatformInfo `yaml:"site"` +} + +// PlatformInfo is the supported media types for a bidder. +type PlatformInfo struct { + MediaTypes []openrtb_ext.BidType `yaml:"mediaTypes"` +} + +// DebugInfo is the supported debug options for a bidder. +type DebugInfo struct { + Allow bool `yaml:"allow"` +} + +// LoadBidderInfoFromDisk parses all static/bidder-info/{bidder}.yaml files from the file system. +func LoadBidderInfoFromDisk(path string, adapterConfigs map[string]Adapter, bidders []string) (BidderInfos, error) { + reader := infoReaderFromDisk{path} + return loadBidderInfo(reader, adapterConfigs, bidders) +} + +func loadBidderInfo(r infoReader, adapterConfigs map[string]Adapter, bidders []string) (BidderInfos, error) { + infos := BidderInfos{} + + for _, bidder := range bidders { + data, err := r.Read(bidder) + if err != nil { + return nil, err + } + + info := BidderInfo{} + if err := yaml.Unmarshal(data, &info); err != nil { + return nil, fmt.Errorf("error parsing yaml for bidder %s: %v", bidder, err) + } + + info.Enabled = isEnabledByConfig(adapterConfigs, bidder) + infos[bidder] = info + } + + return infos, nil +} + +func isEnabledByConfig(adapterConfigs map[string]Adapter, bidderName string) bool { + a, ok := adapterConfigs[strings.ToLower(bidderName)] + return ok && !a.Disabled +} + +type infoReader interface { + Read(bidder string) ([]byte, error) +} + +type infoReaderFromDisk struct { + path string +} + +func (r infoReaderFromDisk) Read(bidder string) ([]byte, error) { + path := fmt.Sprintf("%v/%v.yaml", r.path, bidder) + return ioutil.ReadFile(path) +} + +// ToGVLVendorIDMap transforms a BidderInfos object to a map of bidder names to GVL id. Disabled +// bidders are omitted from the result. +func (infos BidderInfos) ToGVLVendorIDMap() map[openrtb_ext.BidderName]uint16 { + m := make(map[openrtb_ext.BidderName]uint16, len(infos)) + for name, info := range infos { + if info.Enabled && info.GVLVendorID != 0 { + m[openrtb_ext.BidderName(name)] = info.GVLVendorID + } + } + return m +} diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go new file mode 100644 index 00000000000..62a0b853abd --- /dev/null +++ b/config/bidderinfo_test.go @@ -0,0 +1,210 @@ +package config + +import ( + "errors" + "strings" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +const testInfoFilesPath = "./test/bidder-info" +const testYAML = ` +maintainer: + email: "some-email@domain.com" +gvlVendorID: 42 +capabilities: + app: + mediaTypes: + - banner + - native + site: + mediaTypes: + - banner + - video + - native +` + +func TestLoadBidderInfoFromDisk(t *testing.T) { + bidder := "someBidder" + + adapterConfigs := make(map[string]Adapter) + adapterConfigs[strings.ToLower(bidder)] = Adapter{} + + infos, err := LoadBidderInfoFromDisk(testInfoFilesPath, adapterConfigs, []string{bidder}) + if err != nil { + t.Fatal(err) + } + + expected := BidderInfos{ + bidder: { + Enabled: true, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + GVLVendorID: 42, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + }, + }, + } + assert.Equal(t, expected, infos) +} + +func TestLoadBidderInfo(t *testing.T) { + bidder := "someBidder" // important to be mixed case for tests + + testCases := []struct { + description string + givenConfigs map[string]Adapter + givenContent string + givenError error + expectedInfo BidderInfos + expectedError string + }{ + { + description: "Enabled", + givenConfigs: map[string]Adapter{strings.ToLower(bidder): {}}, + givenContent: testYAML, + expectedInfo: map[string]BidderInfo{ + bidder: { + Enabled: true, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + GVLVendorID: 42, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + }, + }, + }, + }, + { + description: "Disabled - Bidder Not Configured", + givenConfigs: map[string]Adapter{}, + givenContent: testYAML, + expectedInfo: map[string]BidderInfo{ + bidder: { + Enabled: false, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + GVLVendorID: 42, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + }, + }, + }, + }, + { + description: "Disabled - Bidder Wrong Case", + givenConfigs: map[string]Adapter{bidder: {}}, + givenContent: testYAML, + expectedInfo: map[string]BidderInfo{ + bidder: { + Enabled: false, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + GVLVendorID: 42, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + }, + }, + }, + }, + { + description: "Disabled - Explicitly Configured", + givenConfigs: map[string]Adapter{strings.ToLower(bidder): {Disabled: false}}, + givenContent: testYAML, + expectedInfo: map[string]BidderInfo{ + bidder: { + Enabled: true, + Maintainer: &MaintainerInfo{ + Email: "some-email@domain.com", + }, + GVLVendorID: 42, + Capabilities: &CapabilitiesInfo{ + App: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeNative}, + }, + Site: &PlatformInfo{ + MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo, openrtb_ext.BidTypeNative}, + }, + }, + }, + }, + }, + { + description: "Read Error", + givenConfigs: map[string]Adapter{strings.ToLower(bidder): {}}, + givenError: errors.New("any read error"), + expectedError: "any read error", + }, + { + description: "Unmarshal Error", + givenConfigs: map[string]Adapter{strings.ToLower(bidder): {}}, + givenContent: "invalid yaml", + expectedError: "error parsing yaml for bidder someBidder: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `invalid...` into config.BidderInfo", + }, + } + + for _, test := range testCases { + r := fakeInfoReader{test.givenContent, test.givenError} + info, err := loadBidderInfo(r, test.givenConfigs, []string{bidder}) + + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + + assert.Equal(t, test.expectedInfo, info, test.description) + } +} + +type fakeInfoReader struct { + content string + err error +} + +func (r fakeInfoReader) Read(bidder string) ([]byte, error) { + return []byte(r.content), r.err +} + +func TestToGVLVendorIDMap(t *testing.T) { + givenBidderInfos := BidderInfos{ + "bidderA": BidderInfo{Enabled: true, GVLVendorID: 0}, + "bidderB": BidderInfo{Enabled: true, GVLVendorID: 100}, + "bidderC": BidderInfo{Enabled: false, GVLVendorID: 0}, + "bidderD": BidderInfo{Enabled: false, GVLVendorID: 200}, + } + + expectedGVLVendorIDMap := map[openrtb_ext.BidderName]uint16{ + "bidderB": 100, + } + + result := givenBidderInfos.ToGVLVendorIDMap() + assert.Equal(t, expectedGVLVendorIDMap, result) +} diff --git a/config/bidderinfo_validate_test.go b/config/bidderinfo_validate_test.go new file mode 100644 index 00000000000..23d6e3df034 --- /dev/null +++ b/config/bidderinfo_validate_test.go @@ -0,0 +1,115 @@ +package config + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v2" +) + +const bidderInfoRelativePath = "../static/bidder-info" + +// TestBidderInfoFiles ensures each bidder has a valid static/bidder-info/bidder.yaml file. Validation is performed directly +// against the file system with separate yaml unmarshalling from the LoadBidderInfoFromDisk func. +func TestBidderInfoFiles(t *testing.T) { + fileInfos, err := ioutil.ReadDir(bidderInfoRelativePath) + if err != nil { + assert.FailNow(t, "Error reading the static/bidder-info directory: %v", err) + } + + // Ensure YAML Files Are For A Known Core Bidder + for _, fileInfo := range fileInfos { + bidder := strings.TrimSuffix(fileInfo.Name(), ".yaml") + + _, isKnown := openrtb_ext.NormalizeBidderName(bidder) + assert.True(t, isKnown, "unexpected bidder info yaml file %s", fileInfo.Name()) + } + + // Ensure YAML Files Are Defined For Each Core Bidder + expectedFileInfosLength := len(openrtb_ext.CoreBidderNames()) + assert.Len(t, fileInfos, expectedFileInfosLength, "static/bidder-info contains %d files, but there are %d known bidders. Did you forget to add a YAML file for your bidder?", len(fileInfos), expectedFileInfosLength) + + // Validate Contents + for _, fileInfo := range fileInfos { + path := fmt.Sprintf(bidderInfoRelativePath + "/" + fileInfo.Name()) + + infoFileData, err := os.Open(path) + assert.NoError(t, err, "Unexpected error: %v", err) + + content, err := ioutil.ReadAll(infoFileData) + assert.NoError(t, err, "Failed to read static/bidder-info/%s: %v", fileInfo.Name(), err) + + var fileInfoContent BidderInfo + err = yaml.Unmarshal(content, &fileInfoContent) + assert.NoError(t, err, "Error interpreting content from static/bidder-info/%s: %v", fileInfo.Name(), err) + + err = validateInfo(&fileInfoContent) + assert.NoError(t, err, "Invalid content in static/bidder-info/%s: %v", fileInfo.Name(), err) + } +} + +func validateInfo(info *BidderInfo) error { + if err := validateMaintainer(info.Maintainer); err != nil { + return err + } + + if err := validateCapabilities(info.Capabilities); err != nil { + return err + } + + return nil +} + +func validateMaintainer(info *MaintainerInfo) error { + if info == nil || info.Email == "" { + return errors.New("missing required field: maintainer.email") + } + return nil +} + +func validateCapabilities(info *CapabilitiesInfo) error { + if info == nil { + return errors.New("missing required field: capabilities") + } + + if info.App == nil && info.Site == nil { + return errors.New("at least one of capabilities.site or capabilities.app must exist") + } + + if info.App != nil { + if err := validatePlatformInfo(info.App); err != nil { + return fmt.Errorf("capabilities.app failed validation: %v", err) + } + } + + if info.Site != nil { + if err := validatePlatformInfo(info.Site); err != nil { + return fmt.Errorf("capabilities.site failed validation: %v", err) + } + } + return nil +} + +func validatePlatformInfo(info *PlatformInfo) error { + if info == nil { + return errors.New("object cannot be empty") + } + + if len(info.MediaTypes) == 0 { + return errors.New("mediaTypes should be an array with at least one string element") + } + + for index, mediaType := range info.MediaTypes { + if mediaType != "banner" && mediaType != "video" && mediaType != "native" && mediaType != "audio" { + return fmt.Errorf("unrecognized media type at index %d: %s", index, mediaType) + } + } + + return nil +} diff --git a/config/config.go b/config/config.go index d9d2b331bb9..7a7deaa43ad 100755 --- a/config/config.go +++ b/config/config.go @@ -9,9 +9,9 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/golang/glog" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/spf13/viper" ) @@ -79,8 +79,9 @@ type Configuration struct { // RequestValidation specifies the request validation options. RequestValidation RequestValidation `mapstructure:"request_validation"` // When true, PBS will assign a randomly generated UUID to req.Source.TID if it is empty - AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"` - TrackerURL string `mapstructure:"tracker_url"` + AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"` + //When true, new bid id will be generated in seatbid[].bid[].ext.prebid.bidid and used in event urls instead + GenerateBidID bool `mapstructure:"generate_bid_id"` } const MIN_COOKIE_SIZE_BYTES = 500 @@ -523,7 +524,7 @@ func New(v *viper.Viper) (*Configuration, error) { glog.Info("Logging the resolved configuration:") logGeneral(reflect.ValueOf(c), " \t") if errs := c.validate(); len(errs) > 0 { - return &c, errortypes.NewAggregateErrors("validation errors", errs) + return &c, errortypes.NewAggregateError("validation errors", errs) } return &c, nil @@ -572,12 +573,14 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernel, "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernelAdn, "https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3DadkernelAdn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdpone, "https://usersync.adpone.com/csync?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadpone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtarget, "https://sync.console.adtarget.com.tr/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtarget%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdtelligent, "https://sync.adtelligent.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadtelligent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") + // openrtb_ext.BidderAdtarget doesn't have a good default. + // openrtb_ext.BidderAdtelligent doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdmixer, "https://inv-nets.admixer.net/adxcm.aspx?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadmixer%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdman, "https://sync.admanmedia.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadman%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") // openrtb_ext.BidderAdOcean doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdvangelists, "https://nep.advangelists.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadvangelists%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + // openrtb_ext.BidderAdxcg doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdyoulike, "http://visitor.omnitagjs.com/visitor/bsync?uid=19340f4f097d16f41f34fc0274981ca4&name=PrebidServer&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadyoulike%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BBUYER_USERID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAJA, "https://ad.as.amanad.adtdp.com/v1/sync/ssp?ssp=4&gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Daja%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25s") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAMX, "https://prebid.a-mo.net/cchain/0?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Damx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAppnexus, "https://ib.adnxs.com/getuid?"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadnxs%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -593,9 +596,11 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDatablocks, "https://sync.v5prebid.datablocks.net/s2ssync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D") // openrtb_ext.BidderDecenterAds doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDmx, "https://dmx.districtm.io/s/v1/img/s/10007?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDeepintent, "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + // openrtb_ext.BidderEpom doesn't have a good default. // openrtb_ext.BidderFacebook doesn't have a good default. // openrtb_ext.BidderGamma doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGamoshi, "https://rtb.gamoshi.io/user_sync_prebid?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgamoshi%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bgusr%5D") @@ -604,18 +609,21 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderImprovedigital, "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=186523&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") // openrtb_ext.BidderInvibes doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderJixie, "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderKrushmedia, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMediafuse, "https://sync.hbmp.mediafuse.com/csync?t=p&ep=0&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D") + // openrtb_ext.BidderMediafuse doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNanoInteractive, "https://ad.audiencemanager.de/hbs/cookie_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnanointeractive%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNoBid, "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnobid%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOneTag, "https://onetag-sys.com/usync/?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Donetag%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_TOKEN%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOutbrain, "https://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Doutbrain%26uid%3D__ZUID__") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPulsepoint, "https://bh.contextweb.com/rtset?pid=561205&ev=1&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpulsepoint%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25VGUID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderRhythmone, "https://sync.1rx.io/usersync2/rmphb?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Drhythmone%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BRX_UUID%5D") @@ -629,11 +637,13 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSynacormedia, "https://sync.technoratimedia.com/services?srv=cs&pid=70&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsynacormedia%26uid%3D%5BUSER_ID%5D") - // openrtb_ext.BidderTappx doesn't have a good default. + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTappx, "https://ssp.api.tappx.com/cs/usersync.php?gdpr_optin={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&type=iframe&ruid="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtappx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BTPPXUID%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTelaria, "https://pbs.publishers.tremorhub.com/pubsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtelaria%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Btvid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTriplelift, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTripleliftNative, "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtriplelift_native%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTrustX, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtrustx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") + // openrtb_ext.BidderUnicorn doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") @@ -790,9 +800,9 @@ func SetupViper(v *viper.Viper, filename string) { } // Disabling adapters by default that require some specific config params. - // If you're using one of these, make sure you check out the documentation (https://github.com/PubMatic-OpenWrap/prebid-server/tree/master/docs/bidders) + // If you're using one of these, make sure you check out the documentation (https://github.com/prebid/prebid-server/tree/master/docs/bidders) // for them and specify all the parameters they need for them to work correctly. - v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb") + v.SetDefault("adapters.33across.endpoint", "https://ssc.33across.com/api/v1/s2s") v.SetDefault("adapters.33across.partner_id", "") v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}") v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx") @@ -810,6 +820,8 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adtarget.endpoint", "http://ghb.console.adtarget.com.tr/pbs/ortb") v.SetDefault("adapters.adtelligent.endpoint", "http://ghb.adtelligent.com/pbs/ortb") v.SetDefault("adapters.advangelists.endpoint", "http://nep.advangelists.com/xp/get?pubid={{.PublisherID}}") + v.SetDefault("adapters.adxcg.disabled", true) + v.SetDefault("adapters.adyoulike.endpoint", "https://broker.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4") v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4") v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb") v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") @@ -822,12 +834,14 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") v.SetDefault("adapters.between.endpoint", "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}") + v.SetDefault("adapters.bidmachine.endpoint", "https://{{.Host}}.bidmachine.io") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb") v.SetDefault("adapters.connectad.endpoint", "http://bidder.connectad.io/API?src=pbs") v.SetDefault("adapters.consumable.endpoint", "https://e.serverbid.com/api/v2") v.SetDefault("adapters.conversant.endpoint", "http://api.hb.ad.cpe.dotomi.com/cvx/server/hb/ortb/25") v.SetDefault("adapters.cpmstar.endpoint", "https://server.cpmstar.com/openrtbbidrq.aspx") + v.SetDefault("adapters.criteo.endpoint", "https://bidder.criteo.com/cdb?profileId=230") v.SetDefault("adapters.datablocks.endpoint", "http://{{.Host}}/openrtb2?sid={{.SourceId}}") v.SetDefault("adapters.decenterads.endpoint", "http://supply.decenterads.com/?c=o&m=rtb") v.SetDefault("adapters.deepintent.endpoint", "https://prebid.deepintent.com/prebid") @@ -835,13 +849,17 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.emx_digital.endpoint", "https://hb.emxdgt.com") v.SetDefault("adapters.engagebdr.endpoint", "http://dsp.bnmla.com/hb") v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1") + v.SetDefault("adapters.epom.endpoint", "https://an.epom.com/ortb") + v.SetDefault("adapters.epom.disabled", true) v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid") + v.SetDefault("adapters.ix.disabled", false) v.SetDefault("adapters.ix.endpoint", "http://exchange.indexww.com/pbs?p=192919") + v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost") v.SetDefault("adapters.krushmedia.endpoint", "http://ads4.krushmedia.com/?c=rtb&m=req&key={{.AccountID}}") v.SetDefault("adapters.invibes.endpoint", "https://{{.Host}}/bid/ServerBidAdContent") v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server") @@ -854,12 +872,15 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb") v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") v.SetDefault("adapters.mobilefuse.endpoint", "http://mfx.mobilefuse.com/openrtb?pub_id={{.PublisherID}}") - v.SetDefault("adapters.mobfoxpb.endpoint", "http://bes.mobfox.com/?c=o&m=ortb") + v.SetDefault("adapters.mobfoxpb.endpoint", "http://bes.mobfox.com/?c=__route__&m=__method__&key=__key__") v.SetDefault("adapters.nanointeractive.endpoint", "https://ad.audiencemanager.de/hbs") v.SetDefault("adapters.ninthdecimal.endpoint", "http://rtb.ninthdecimal.com/xp/get?pubid={{.PublisherID}}") v.SetDefault("adapters.nobid.endpoint", "https://ads.servenobid.com/ortb_adreq?tek=pbs&ver=1") + v.SetDefault("adapters.onetag.endpoint", "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2") + v.SetDefault("adapters.outbrain.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/") + v.SetDefault("adapters.pangle.disabled", true) v.SetDefault("adapters.pubmatic.endpoint", "https://hbopenbid.pubmatic.com/translator?source=prebid-server") v.SetDefault("adapters.pubnative.endpoint", "http://dsp.pubnative.net/bid/v1/request") v.SetDefault("adapters.pulsepoint.endpoint", "http://bid.contextweb.com/header/s/ortb/prebid-s2s") @@ -878,14 +899,16 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid") v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af") v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") - v.SetDefault("adapters.synacormedia.endpoint", "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}") v.SetDefault("adapters.spotx.endpoint", "https://search.spotxchange.com/openrtb/2.3/dados") - v.SetDefault("adapters.tappx.endpoint", "https://{{.Host}}") + v.SetDefault("adapters.synacormedia.endpoint", "http://{{.Host}}.technoratimedia.com/openrtb/bids/{{.Host}}") + v.SetDefault("adapters.tappx.endpoint", "http://{{.Host}}") v.SetDefault("adapters.telaria.endpoint", "https://ads.tremorhub.com/ad/rtb/prebid") v.SetDefault("adapters.triplelift_native.disabled", true) v.SetDefault("adapters.triplelift_native.extra_info", "{\"publisher_whitelist\":[]}") v.SetDefault("adapters.triplelift.endpoint", "https://tlx.3lift.com/s2s/auction?sra=1&supplier_id=20") + v.SetDefault("adapters.trustx.endpoint", "https://grid.bidswitch.net/sp_bid?sp=trustx") v.SetDefault("adapters.ucfunnel.endpoint", "https://pbs.aralego.com/prebid") + v.SetDefault("adapters.unicorn.endpoint", "https://ds.uncn.jp/pb/0/bid.json") v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2") v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") v.SetDefault("adapters.verizonmedia.disabled", true) @@ -943,6 +966,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("account_defaults.debug_allow", true) v.SetDefault("certificates_file", "") v.SetDefault("auto_gen_source_tid", true) + v.SetDefault("generate_bid_id", false) v.SetDefault("request_timeout_headers.request_time_in_queue", "") v.SetDefault("request_timeout_headers.request_timeout_in_queue", "") diff --git a/config/config_test.go b/config/config_test.go index f6792cb7648..43ee8fa21df 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -11,7 +11,7 @@ import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) @@ -139,6 +139,7 @@ func TestDefaults(t *testing.T) { cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path) cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, true) + cmpBools(t, "generate_bid_id", cfg.GenerateBidID, false) } var fullConfig = []byte(` @@ -230,6 +231,7 @@ certificates_file: /etc/ssl/cert.pem request_validation: ipv4_private_networks: ["1.1.1.0/24"] ipv6_private_networks: ["1111::/16", "2222::/16"] +generate_bid_id: true `) var adapterExtraInfoConfig = []byte(` @@ -415,6 +417,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24") cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16") cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[1], "2222::/16") + cmpBools(t, "generate_bid_id", cfg.GenerateBidID, true) } func TestUnmarshalAdapterExtraInfo(t *testing.T) { diff --git a/adapters/adapterstest/bidder-info/someBidder.yaml b/config/test/bidder-info/someBidder.yaml similarity index 91% rename from adapters/adapterstest/bidder-info/someBidder.yaml rename to config/test/bidder-info/someBidder.yaml index b5216f0fe46..232b73d0aac 100644 --- a/adapters/adapterstest/bidder-info/someBidder.yaml +++ b/config/test/bidder-info/someBidder.yaml @@ -1,5 +1,6 @@ maintainer: email: "some-email@domain.com" +gvlVendorID: 42 capabilities: app: mediaTypes: diff --git a/currency/rate_converter.go b/currency/rate_converter.go index 269cf7551ea..39b9cd59ca2 100644 --- a/currency/rate_converter.go +++ b/currency/rate_converter.go @@ -8,9 +8,9 @@ import ( "sync/atomic" "time" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/util/timeutil" "github.com/golang/glog" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/util/timeutil" ) // RateConverter holds the currencies conversion rates dictionary diff --git a/currency/rate_converter_test.go b/currency/rate_converter_test.go index b3e26c86a47..e1d6741dff2 100644 --- a/currency/rate_converter_test.go +++ b/currency/rate_converter_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/util/task" + "github.com/prebid/prebid-server/util/task" "github.com/stretchr/testify/assert" ) diff --git a/docs/developers/automated-tests.md b/docs/developers/automated-tests.md index 93bd28f6187..9e435aaf57e 100644 --- a/docs/developers/automated-tests.md +++ b/docs/developers/automated-tests.md @@ -1,6 +1,6 @@ # Automated Tests -This project uses [TravisCI](https://travis-ci.org/) to make sure that every PR passes automated tests. +This project uses GitHub Actions to make sure that every PR passes automated tests. To reproduce these tests locally, use: ``` diff --git a/docs/developers/contributing.md b/docs/developers/contributing.md index b418efa2877..cc2daaecd11 100644 --- a/docs/developers/contributing.md +++ b/docs/developers/contributing.md @@ -40,7 +40,7 @@ those updates must be submitted in the same Pull Request as the code changes. 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/PubMatic-OpenWrap/prebid-server/compare). -Pull Requests will be vetted through [Travis CI](https://travis-ci.com/). +Pull Requests will be vetted through GitHub Actions. To reproduce these same tests locally, do: ```bash diff --git a/endpoints/auction.go b/endpoints/auction.go index 4416b602f1a..069abfec4e5 100644 --- a/endpoints/auction.go +++ b/endpoints/auction.go @@ -10,21 +10,21 @@ import ( "strconv" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/cache" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - pbc "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - gdprPrivacy "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/golang/glog" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/cache" + "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/metrics" + "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/privacy" + gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/usersync" ) type bidResult struct { @@ -307,8 +307,8 @@ func sortBidsAddKeywordsMobile(bids pbs.PBSBidSlice, pbs_req *pbs.PBSRequest, pr hbSize := "" if bid.Width != 0 && bid.Height != 0 { - width := strconv.FormatUint(bid.Width, 10) - height := strconv.FormatUint(bid.Height, 10) + width := strconv.FormatInt(bid.Width, 10) + height := strconv.FormatInt(bid.Height, 10) hbSize = width + "x" + height } diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 331ddc2500c..17ed7f74f45 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -10,17 +10,17 @@ import ( "net/http/httptest" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - metricsConf "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - gdprPolicy "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/usersync/usersyncers" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" + metricsConf "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" + "github.com/prebid/prebid-server/prebid_cache_client" + gdprPolicy "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/usersync/usersyncers" "github.com/spf13/viper" "github.com/stretchr/testify/assert" @@ -454,7 +454,7 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o return m.allowBidderSync, nil } -func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string) (bool, bool, bool, error) { +func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return m.allowPI, m.allowGeo, m.allowID, nil } @@ -518,7 +518,7 @@ func TestBidSizeValidate(t *testing.T) { AdUnits: []pbs.PBSAdUnit{ { BidID: "test_bidid1", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 350, H: 250, @@ -535,7 +535,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid2", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 100, H: 100, @@ -548,7 +548,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid3", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 200, H: 200, @@ -561,7 +561,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid_video", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 400, H: 400, @@ -574,7 +574,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid3", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 150, H: 150, @@ -587,7 +587,7 @@ func TestBidSizeValidate(t *testing.T) { }, { BidID: "test_bidid_y", - Sizes: []openrtb.Format{ + Sizes: []openrtb2.Format{ { W: 150, H: 150, diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index aaa7a2425a3..0396ee9f107 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -10,18 +10,18 @@ import ( "net/http" "strconv" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - gdprPrivacy "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/config" + "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" + gdprPrivacy "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/usersync" ) func NewCookieSyncEndpoint( @@ -156,7 +156,6 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h Status: cookieSyncStatus(userSyncCookie.LiveSyncCount()), BidderStatus: make([]*usersync.CookieSyncBidders, 0, len(parsedReq.Bidders)), } - for i := 0; i < len(parsedReq.Bidders); i++ { bidder := parsedReq.Bidders[i] diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 14a699177bc..d4b89a15118 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -9,18 +9,18 @@ import ( "text/template" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/appnexus" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/audienceNetwork" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lifestreet" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/pubmatic" - analyticsConf "github.com/PubMatic-OpenWrap/prebid-server/analytics/config" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - metricsConf "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/buger/jsonparser" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/adapters/appnexus" + "github.com/prebid/prebid-server/adapters/audienceNetwork" + "github.com/prebid/prebid-server/adapters/lifestreet" + "github.com/prebid/prebid-server/adapters/pubmatic" + analyticsConf "github.com/prebid/prebid-server/analytics/config" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/gdpr" + metricsConf "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" ) @@ -28,9 +28,7 @@ func TestCookieSyncNoCookies(t *testing.T) { rr := doPost(`{"bidders":["appnexus", "audienceNetwork", "random"]}`, nil, true, syncersForTest()) assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, http.StatusOK, rr.Code) - syncs := parseSyncs(t, rr.Body.Bytes()) - assert.Contains(t, syncs, "appnexus") - assert.Contains(t, syncs, "audienceNetwork") + assert.ElementsMatch(t, []string{"appnexus", "audienceNetwork"}, parseSyncs(t, rr.Body.Bytes())) assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) } @@ -48,8 +46,7 @@ func TestGDPRPreventsBidders(t *testing.T) { }) assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, http.StatusOK, rr.Code) - syncs := parseSyncs(t, rr.Body.Bytes()) - assert.Contains(t, syncs, "lifestreet") + assert.ElementsMatch(t, []string{"lifestreet"}, parseSyncs(t, rr.Body.Bytes())) assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) } @@ -57,9 +54,7 @@ func TestGDPRIgnoredIfZero(t *testing.T) { rr := doPost(`{"gdpr":0,"bidders":["appnexus", "pubmatic"]}`, nil, false, nil) assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, http.StatusOK, rr.Code) - syncs := parseSyncs(t, rr.Body.Bytes()) - assert.Contains(t, syncs, "appnexus") - assert.Contains(t, syncs, "pubmatic") + assert.ElementsMatch(t, []string{"appnexus", "pubmatic"}, parseSyncs(t, rr.Body.Bytes())) assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) } @@ -150,11 +145,7 @@ func TestCookieSyncNoBidders(t *testing.T) { rr := doPost("{}", nil, true, syncersForTest()) assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, http.StatusOK, rr.Code) - syncs := parseSyncs(t, rr.Body.Bytes()) - assert.Contains(t, syncs, "appnexus") - assert.Contains(t, syncs, "audienceNetwork") - assert.Contains(t, syncs, "lifestreet") - assert.Contains(t, syncs, "pubmatic") + assert.ElementsMatch(t, []string{"appnexus", "audienceNetwork", "lifestreet", "pubmatic"}, parseSyncs(t, rr.Body.Bytes())) assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) } @@ -162,9 +153,7 @@ func TestCookieSyncNoCookiesBrokenGDPR(t *testing.T) { rr := doConfigurablePost(`{"bidders":["appnexus", "audienceNetwork", "random"],"gdpr_consent":"GLKHGKGKKGK"}`, nil, true, map[openrtb_ext.BidderName]usersync.Usersyncer{}, config.GDPR{UsersyncIfAmbiguous: true}, config.CCPA{}) assert.Equal(t, rr.Header().Get("Content-Type"), "application/json; charset=utf-8") assert.Equal(t, http.StatusOK, rr.Code) - syncs := parseSyncs(t, rr.Body.Bytes()) - assert.Contains(t, syncs, "appnexus") - assert.Contains(t, syncs, "audienceNetwork") + assert.ElementsMatch(t, []string{"appnexus", "audienceNetwork"}, parseSyncs(t, rr.Body.Bytes())) assert.Equal(t, "no_cookie", parseStatus(t, rr.Body.Bytes())) } @@ -265,6 +254,6 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi return ok, nil } -func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string) (bool, bool, bool, error) { +func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return true, true, true, nil } diff --git a/endpoints/currency_rates.go b/endpoints/currency_rates.go index de5a47e19b9..d35cb74cea4 100644 --- a/endpoints/currency_rates.go +++ b/endpoints/currency_rates.go @@ -5,8 +5,8 @@ import ( "net/http" "time" - "github.com/PubMatic-OpenWrap/prebid-server/currency" "github.com/golang/glog" + "github.com/prebid/prebid-server/currency" ) // currencyRatesInfo holds currency rates information. diff --git a/endpoints/currency_rates_test.go b/endpoints/currency_rates_test.go index e73893aa837..7fc513e7dbe 100644 --- a/endpoints/currency_rates_test.go +++ b/endpoints/currency_rates_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/currency" + "github.com/prebid/prebid-server/currency" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/events/account_test.go b/endpoints/events/account_test.go index 559b39d096c..b61f29dc5c9 100644 --- a/endpoints/events/account_test.go +++ b/endpoints/events/account_test.go @@ -3,16 +3,16 @@ package events import ( "errors" "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/julienschmidt/httprouter" - "github.com/stretchr/testify/assert" "io/ioutil" "net/http" "net/http/httptest" "strings" "testing" + + "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/stored_requests" + "github.com/stretchr/testify/assert" ) func TestHandleAccountServiceErrors(t *testing.T) { @@ -155,7 +155,7 @@ func vast(t *testing.T, cfg *config.Configuration, fetcher stored_requests.Accou r *http.Request }{ name: "vast", - h: NewVTrackEndpoint(cfg, fetcher, &vtrackMockCacheClient{}, adapters.BidderInfos{}), + h: NewVTrackEndpoint(cfg, fetcher, &vtrackMockCacheClient{}, config.BidderInfos{}), r: httptest.NewRequest("POST", "/vtrack?a=testacc", strings.NewReader(vtrackBody)), } } diff --git a/endpoints/events/event.go b/endpoints/events/event.go index da18b16bd53..fe178d8f271 100644 --- a/endpoints/events/event.go +++ b/endpoints/events/event.go @@ -4,12 +4,12 @@ import ( "context" "errors" "fmt" - accountService "github.com/PubMatic-OpenWrap/prebid-server/account" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" "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/stored_requests" "net/http" "net/url" "strconv" diff --git a/endpoints/events/event_test.go b/endpoints/events/event_test.go index d32d01ad562..ba8071843f4 100644 --- a/endpoints/events/event_test.go +++ b/endpoints/events/event_test.go @@ -4,9 +4,9 @@ import ( "context" "encoding/base64" "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/stored_requests" "github.com/stretchr/testify/assert" "io/ioutil" "net/http" diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index 8ef09f50b78..da845162de2 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -3,29 +3,21 @@ package events import ( "context" "encoding/json" - "errors" "fmt" "io" "io/ioutil" "net/http" - "net/url" "strings" "time" - "github.com/PubMatic-OpenWrap/openrtb" - accountService "github.com/PubMatic-OpenWrap/prebid-server/account" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - - // "github.com/beevik/etree" - "github.com/PubMatic-OpenWrap/etree" "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/prebid_cache_client" + "github.com/prebid/prebid-server/stored_requests" ) const ( @@ -37,7 +29,7 @@ const ( type vtrackEndpoint struct { Cfg *config.Configuration Accounts stored_requests.AccountFetcher - BidderInfos adapters.BidderInfos + BidderInfos config.BidderInfos Cache prebid_cache_client.Client } @@ -53,41 +45,7 @@ type CacheObject struct { UUID string `json:"uuid"` } -// 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-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]" -) - -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", -} - -func NewVTrackEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, cache prebid_cache_client.Client, bidderInfos adapters.BidderInfos) httprouter.Handle { +func NewVTrackEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, cache prebid_cache_client.Client, bidderInfos config.BidderInfos) httprouter.Handle { vte := &vtrackEndpoint{ Cfg: cfg, Accounts: accounts, @@ -281,7 +239,7 @@ 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 *adapters.BidderInfos, allowUnknownBidder bool) map[string]struct{} { +func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *config.BidderInfos, allowUnknownBidder bool) map[string]struct{} { bl := map[string]struct{}{} for _, bcr := range req.Puts { @@ -294,12 +252,12 @@ func getBiddersAllowingVastUpdate(req *BidCacheRequest, bidderInfos *adapters.Bi } // isAllowVastForBidder checks if a bidder is active and allowed to modify vast xml data -func isAllowVastForBidder(bidder string, bidderInfos *adapters.BidderInfos, allowUnknownBidder bool) bool { +func isAllowVastForBidder(bidder string, bidderInfos *config.BidderInfos, allowUnknownBidder bool) 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.Status == adapters.StatusActive && b.ModifyingVastXmlAllowed + return b.Enabled && b.ModifyingVastXmlAllowed } return allowUnknownBidder @@ -343,197 +301,3 @@ func ModifyVastXmlJSON(externalUrl string, data json.RawMessage, bidid, bidder, } return json.RawMessage(vast) } - -//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 *openrtb.Bid, bidder, accountID string, timestamp int64, bidRequest *openrtb.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]*openrtb.Imp) - for i := range bidRequest.Imp { - impMap[bidRequest.Imp[i].ID] = &bidRequest.Imp[i] - } - - eventURLMap := GetVideoEventTracking(trackerURL, bid, bidder, 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 openrtb.VideoLinearityLinearInStream: - creative.AddChild(doc.CreateElement("Linear")) - case openrtb.VideoLinearityNonLinearOverlay: - 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 *openrtb.Bid, bidder string, accountId string, timestamp int64, req *openrtb.BidRequest, doc *etree.Document, impMap map[string]*openrtb.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, req.Site.Domain) - eventURL = replaceMacro(eventURL, VASTPageURLMacro, req.Site.Page) - if nil != req.Site.Publisher { - eventURL = replaceMacro(eventURL, PBSAccountMacro, req.Site.Publisher.ID) - } - } - - if len(bid.ADomain) > 0 { - //eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, strings.Join(bid.ADomain, ",")) - domain, err := extractDomain(bid.ADomain[0]) - if nil == err { - eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, domain) - } else { - glog.Warningf("Unable to extract domain from '%s'. [%s]", bid.ADomain[0], err.Error()) - } - } - - eventURL = replaceMacro(eventURL, PBSBidderMacro, bidder) - eventURL = replaceMacro(eventURL, PBSBidIDMacro, 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) - } - eventURLMap[event] = eventURL - } - return eventURLMap -} - -func replaceMacro(trackerURL, macro, value string) string { - macro = strings.TrimSpace(macro) - if strings.HasPrefix(macro, "[") && strings.HasSuffix(macro, "]") && len(strings.TrimSpace(value)) > 0 { - trackerURL = strings.ReplaceAll(trackerURL, macro, url.QueryEscape(value)) - } 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 -} diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 3d2bca3bb4d..1766f2e2e0d 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -7,17 +7,12 @@ import ( "fmt" "io/ioutil" "net/http/httptest" - "net/url" "strings" "testing" - "github.com/PubMatic-OpenWrap/etree" - // "github.com/beevik/etree" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/stored_requests" "github.com/stretchr/testify/assert" ) @@ -379,13 +374,13 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastNotAllowe cfg.MarshalAccountDefaults() // bidder info - bidderInfos := make(adapters.BidderInfos) - bidderInfos["bidder"] = adapters.BidderInfo{ - Status: adapters.StatusActive, + bidderInfos := make(config.BidderInfos) + bidderInfos["bidder"] = config.BidderInfo{ + Enabled: true, ModifyingVastXmlAllowed: false, } - bidderInfos["updatable_bidder"] = adapters.BidderInfo{ - Status: adapters.StatusActive, + bidderInfos["updatable_bidder"] = config.BidderInfo{ + Enabled: true, ModifyingVastXmlAllowed: true, } @@ -442,13 +437,13 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableBiddersWhenBidderVastAllowed(t cfg.MarshalAccountDefaults() // bidder info - bidderInfos := make(adapters.BidderInfos) - bidderInfos["bidder"] = adapters.BidderInfo{ - Status: adapters.StatusActive, + bidderInfos := make(config.BidderInfos) + bidderInfos["bidder"] = config.BidderInfo{ + Enabled: true, ModifyingVastXmlAllowed: true, } - bidderInfos["updatable_bidder"] = adapters.BidderInfo{ - Status: adapters.StatusActive, + bidderInfos["updatable_bidder"] = config.BidderInfo{ + Enabled: true, ModifyingVastXmlAllowed: true, } @@ -505,7 +500,7 @@ func TestShouldSendToCacheExpectedPutsAndUpdatableUnknownBiddersWhenUnknownBidde cfg.MarshalAccountDefaults() // bidder info - bidderInfos := make(adapters.BidderInfos) + bidderInfos := make(config.BidderInfos) // prepare data, err := getValidVTrackRequestBody(true, false) @@ -561,7 +556,7 @@ func TestShouldReturnBadRequestWhenRequestExceedsMaxRequestSize(t *testing.T) { cfg.MarshalAccountDefaults() // bidder info - bidderInfos := make(adapters.BidderInfos) + bidderInfos := make(config.BidderInfos) // prepare data, err := getValidVTrackRequestBody(true, false) @@ -695,560 +690,3 @@ func getVTrackRequestData(wi bool, wic bool) (db []byte, e error) { return data.Bytes(), e } - -func TestInjectVideoEventTrackers(t *testing.T) { - type args struct { - externalURL string - bid *openrtb.Bid - req *openrtb.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: &openrtb.Bid{ - AdM: ` - - - - http://example.com/tracking/midpoint - http://example.com/tracking/thirdQuartile - http://example.com/tracking/complete - http://partner.tracking.url - - - `, - }, - req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag - AdM: ` - - - http://something.com - - - `, - }, - req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag - AdM: ` - - - `, - }, - req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag - AdM: ` - - - iabtechlab - http://somevasturl - - - - - - `, - }, - req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag - // AdM: ``, - // }, - // req: &openrtb.BidRequest{App: &openrtb.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: &openrtb.Bid{ // Adm contains to TrackingEvents tag - // AdM: "", - // NURL: "nurl_contents", - // }, - // req: &openrtb.BidRequest{App: &openrtb.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 = []openrtb.Imp{{ID: "123", Video: &openrtb.Video{}}} - tc.args.bid.ImpID = tc.args.req.Imp[0].ID - accountID := "" - timestamp := int64(0) - biddername := "test_bidder" - injectedVast, injected, ierr := InjectVideoEventTrackers(tc.args.externalURL, vast, tc.args.bid, biddername, 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 *openrtb.Bid - bidder string - accountId string - timestamp int64 - req *openrtb.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: &openrtb.Bid{ - // AdM: vastXMLWith2Creatives, - }, - req: &openrtb.BidRequest{ - App: &openrtb.App{ - Bundle: "someappbundle", - }, - Imp: []openrtb.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: &openrtb.Bid{}, - req: &openrtb.BidRequest{ - App: &openrtb.App{}, // no app bundle value - Imp: []openrtb.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=[DOMAIN]", - "midpoint": "http://company.tracker.com?eventId=3&appbundle=[DOMAIN]", - "thirdQuartile": "http://company.tracker.com?eventId=5&appbundle=[DOMAIN]", - "start": "http://company.tracker.com?eventId=2&appbundle=[DOMAIN]", - "complete": "http://company.tracker.com?eventId=6&appbundle=[DOMAIN]"}, - }, - }, - { - name: "prefer_company_value_for_standard_macro", - args: args{ - trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", - req: &openrtb.BidRequest{ - App: &openrtb.App{ - Bundle: "myapp", // do not expect this value - }, - Imp: []openrtb.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: &openrtb.BidRequest{ - App: &openrtb.App{ - Bundle: "myapp123", - }, - Imp: []openrtb.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: &openrtb.BidRequest{ - Ext: []byte(`{"prebid":{ - "macros": { - "CUSTOM_MACRO": "my_custom_value" - } - }}`), - Imp: []openrtb.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: &openrtb.BidRequest{ - Ext: []byte(`{"prebid":{ - "macros": { - "": "my_custom_value" - } - }}`), - Imp: []openrtb.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: &openrtb.BidRequest{ - Ext: []byte(`{"prebid":{ - "macros": { - "": "my_custom_value" - } - }}`), - Imp: []openrtb.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: &openrtb.BidRequest{Imp: []openrtb.Imp{}}}, - want: want{trackerURLMap: make(map[string]string)}, - }, - { - name: "all_macros", // 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]", - req: &openrtb.BidRequest{ - App: &openrtb.App{Bundle: "com.someapp.com", Publisher: &openrtb.Publisher{ID: "5890"}}, - Ext: []byte(`{ - "prebid": { - "macros": { - "[PROFILE_ID]": "100", - "[PROFILE_VERSION]": "2", - "[UNIX_TIMESTAMP]": "1234567890", - "[PLATFORM]": "7", - "[WRAPPER_IMPRESSION_ID]": "abc~!@#$%^&&*()_+{}|:\"<>?[]\\;',./" - } - } - }`), - Imp: []openrtb.Imp{ - {TagID: "/testadunit/1", ID: "imp_1"}, - }, - }, - bid: &openrtb.Bid{ADomain: []string{"http://a.com/32?k=v", "b.com"}, ImpID: "imp_1", ID: "test_bid_id"}, - bidder: "test_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_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", - "midpoint": "https://company.tracker.com?operId=8&e=3&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", - "thirdQuartile": "https://company.tracker.com?operId=8&e=5&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", - "complete": "https://company.tracker.com?operId=8&e=6&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", - "start": "https://company.tracker.com?operId=8&e=2&p=5890&pid=100&v=2&ts=1234567890&pn=test_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"}, - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - - if nil == tc.args.bid { - tc.args.bid = &openrtb.Bid{} - } - - impMap := map[string]*openrtb.Imp{} - - for _, imp := range tc.args.req.Imp { - impMap[imp.ID] = &imp - } - - eventURLMap := GetVideoEventTracking(tc.args.trackerURL, tc.args.bid, tc.args.bidder, 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=[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/getuids.go b/endpoints/getuids.go index e02efe15b3a..859c0e7288c 100644 --- a/endpoints/getuids.go +++ b/endpoints/getuids.go @@ -3,9 +3,9 @@ package endpoints import ( "net/http" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/usersync" "encoding/json" ) diff --git a/endpoints/getuids_test.go b/endpoints/getuids_test.go index fb984e15c35..7988acbaffe 100644 --- a/endpoints/getuids_test.go +++ b/endpoints/getuids_test.go @@ -5,7 +5,7 @@ import ( "net/http/httptest" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" + "github.com/prebid/prebid-server/config" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/info/bidders.go b/endpoints/info/bidders.go index 7c44c7ec6fa..2bd925ce62d 100644 --- a/endpoints/info/bidders.go +++ b/endpoints/info/bidders.go @@ -3,128 +3,40 @@ package info import ( "encoding/json" "net/http" + "sort" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" ) -// NewBiddersEndpoint implements /info/bidders -func NewBiddersEndpoint(aliases map[string]string) httprouter.Handle { - bidderNames := make([]string, 0) - - for _, bidderName := range openrtb_ext.CoreBidderNames() { - bidderNames = append(bidderNames, string(bidderName)) - } - - for aliasName := range aliases { - bidderNames = append(bidderNames, aliasName) - } - - biddersJson, err := json.Marshal(bidderNames) +// NewBiddersEndpoint builds a handler for the /info/bidders endpoint. +func NewBiddersEndpoint(bidders config.BidderInfos, aliases map[string]string) httprouter.Handle { + response, err := prepareBiddersResponse(bidders, aliases) if err != nil { glog.Fatalf("error creating /info/bidders endpoint response: %v", err) } return func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) { w.Header().Set("Content-Type", "application/json") - if _, err := w.Write(biddersJson); err != nil { + if _, err := w.Write(response); err != nil { glog.Errorf("error writing response to /info/bidders: %v", err) } } } -// NewBidderDetailsEndpoint implements /info/bidders/* -func NewBidderDetailsEndpoint(infos adapters.BidderInfos, aliases map[string]string) httprouter.Handle { - // Validate if there exist and alias with name "all". If it does error out because - // that will break the /info/bidders/all endpoint. - if _, ok := aliases["all"]; ok { - glog.Fatal("Default aliases shouldn't contain an alias with name \"all\". This will break the /info/bidders/all endpoint") - } - - // Build a new map that's basically a copy of "infos" but will also contain - // alias bidder infos - var allBidderInfo = make(map[string]adapters.BidderInfo, len(infos)+len(aliases)) - - // Build all the responses up front, since there are a finite number and it won't use much memory. - responses := make(map[string]json.RawMessage, len(infos)+len(aliases)) - for bidderName, bidderInfo := range infos { - // Copy bidderInfo into "allBidderInfo" map - allBidderInfo[bidderName] = bidderInfo +func prepareBiddersResponse(bidders config.BidderInfos, aliases map[string]string) ([]byte, error) { + bidderNames := make([]string, 0, len(bidders)+len(aliases)) - // JSON encode bidder info and add it to the "responses" map - jsonData, err := json.Marshal(bidderInfo) - if err != nil { - glog.Fatalf("Failed to JSON-marshal bidder-info/%s.yaml data.", bidderName) - } - responses[bidderName] = jsonData + for name := range bidders { + bidderNames = append(bidderNames, name) } - // Add in any default aliases - for aliasName, bidderName := range aliases { - // Add the alias bidder info into "allBidderInfo" map - aliasInfo := infos[bidderName] - aliasInfo.AliasOf = bidderName - allBidderInfo[aliasName] = aliasInfo - - // JSON encode core bidder info for the alias and add it to the "responses" map - responses[aliasName] = createAliasInfo(responses, aliasName, bidderName) + for name := range aliases { + bidderNames = append(bidderNames, name) } - allBidderResponse, err := json.Marshal(allBidderInfo) - if err != nil { - glog.Fatal("Failed to JSON-marshal all bidder info data.") - } - // Add the json response containing all bidders info for the /info/bidders/all endpoint - responses["all"] = allBidderResponse - - // Return an endpoint which writes the responses from memory. - return func(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) { - forBidder := ps.ByName("bidderName") - - // If the requested path was /info/bidders/{bidderName} then return the info about that bidder - if response, ok := responses[forBidder]; 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", forBidder, err) - } - } else { - w.WriteHeader(http.StatusNotFound) - } - } -} - -func createAliasInfo(responses map[string]json.RawMessage, alias string, core string) json.RawMessage { - coreJSON, ok := responses[core] - if !ok { - glog.Fatalf("Unknown core bidder %s for default alias %s", core, alias) - } - jsonData := make(json.RawMessage, len(coreJSON)) - copy(jsonData, coreJSON) - - jsonInfo, err := jsonparser.Set(jsonData, []byte(`"`+core+`"`), "aliasOf") - if err != nil { - glog.Fatalf("Failed to generate bidder info for %s, an alias of %s", alias, core) - } - return jsonInfo -} - -type infoFile struct { - Maintainer *maintainerInfo `yaml:"maintainer" json:"maintainer"` - Capabilities *capabilitiesInfo `yaml:"capabilities" json:"capabilities"` -} - -type maintainerInfo struct { - Email string `yaml:"email" json:"email"` -} - -type capabilitiesInfo struct { - App *platformInfo `yaml:"app" json:"app"` - Site *platformInfo `yaml:"site" json:"site"` -} + sort.Strings(bidderNames) -type platformInfo struct { - MediaTypes []string `yaml:"mediaTypes" json:"mediaTypes"` + return json.Marshal(bidderNames) } diff --git a/endpoints/info/bidders_detail.go b/endpoints/info/bidders_detail.go new file mode 100644 index 00000000000..04bc9b04fca --- /dev/null +++ b/endpoints/info/bidders_detail.go @@ -0,0 +1,185 @@ +package info + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/golang/glog" + "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +const ( + statusActive string = "ACTIVE" + statusDisabled string = "DISABLED" +) + +// NewBiddersDetailEndpoint builds a handler for the /info/bidders/ endpoint. +func NewBiddersDetailEndpoint(bidders config.BidderInfos, biddersConfig map[string]config.Adapter, aliases map[string]string) httprouter.Handle { + responses, err := prepareBiddersDetailResponse(bidders, biddersConfig, aliases) + if err != nil { + glog.Fatalf("error creating /info/bidders/ endpoint response: %v", err) + } + + return func(w http.ResponseWriter, _ *http.Request, ps httprouter.Params) { + bidder := ps.ByName("bidderName") + + if response, ok := responses[bidder]; 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) + } + } else { + w.WriteHeader(http.StatusNotFound) + } + } +} + +func prepareBiddersDetailResponse(bidders config.BidderInfos, biddersConfig map[string]config.Adapter, aliases map[string]string) (map[string][]byte, error) { + details, err := mapDetails(bidders, biddersConfig, aliases) + if err != nil { + return nil, err + } + + responses, err := marshalDetailsResponse(details) + if err != nil { + return nil, err + } + + all, err := marshalAllResponse(responses) + if err != nil { + return nil, err + } + responses["all"] = all + + return responses, nil +} + +func mapDetails(bidders config.BidderInfos, biddersConfig map[string]config.Adapter, aliases map[string]string) (map[string]bidderDetail, error) { + details := map[string]bidderDetail{} + + for bidderName, bidderInfo := range bidders { + endpoint := resolveEndpoint(bidderName, biddersConfig) + details[bidderName] = mapDetailFromConfig(bidderInfo, endpoint) + } + + for aliasName, bidderName := range aliases { + aliasBaseInfo, aliasBaseInfoFound := details[bidderName] + if !aliasBaseInfoFound { + return nil, fmt.Errorf("base adapter %s for alias %s not found", bidderName, aliasName) + } + + aliasInfo := aliasBaseInfo + aliasInfo.AliasOf = bidderName + details[aliasName] = aliasInfo + } + + return details, nil +} + +func resolveEndpoint(bidder string, biddersConfig map[string]config.Adapter) string { + if c, found := biddersConfig[bidder]; found { + return c.Endpoint + } + + return "" +} + +func marshalDetailsResponse(details map[string]bidderDetail) (map[string][]byte, error) { + responses := map[string][]byte{} + + for bidder, detail := range details { + json, err := json.Marshal(detail) + if err != nil { + return nil, fmt.Errorf("unable to marshal info for bidder %s: %v", bidder, err) + } + responses[bidder] = json + } + + return responses, nil +} + +func marshalAllResponse(responses map[string][]byte) ([]byte, error) { + responsesJSON := make(map[string]json.RawMessage, len(responses)) + + for k, v := range responses { + responsesJSON[k] = json.RawMessage(v) + } + + json, err := json.Marshal(responsesJSON) + if err != nil { + return nil, fmt.Errorf("unable to marshal info for bidder all: %v", err) + } + return json, nil +} + +type bidderDetail struct { + Status string `json:"status"` + UsesHTTPS *bool `json:"usesHttps,omitempty"` + Maintainer *maintainer `json:"maintainer,omitempty"` + Capabilities *capabilities `json:"capabilities,omitempty"` + AliasOf string `json:"aliasOf,omitempty"` +} + +type maintainer struct { + Email string `json:"email"` +} + +type capabilities struct { + App *platform `json:"app,omitempty"` + Site *platform `json:"site,omitempty"` +} + +type platform struct { + MediaTypes []string `json:"mediaTypes"` +} + +func mapDetailFromConfig(c config.BidderInfo, endpoint string) bidderDetail { + var bidderDetail bidderDetail + + if c.Maintainer != nil { + bidderDetail.Maintainer = &maintainer{ + Email: c.Maintainer.Email, + } + } + + if c.Enabled { + bidderDetail.Status = statusActive + + usesHTTPS := strings.HasPrefix(strings.ToLower(endpoint), "https://") + bidderDetail.UsesHTTPS = &usesHTTPS + + if c.Capabilities != nil { + bidderDetail.Capabilities = &capabilities{} + + if c.Capabilities.App != nil { + bidderDetail.Capabilities.App = &platform{ + MediaTypes: mapMediaTypes(c.Capabilities.App.MediaTypes), + } + } + + if c.Capabilities.Site != nil { + bidderDetail.Capabilities.Site = &platform{ + MediaTypes: mapMediaTypes(c.Capabilities.Site.MediaTypes), + } + } + } + } else { + bidderDetail.Status = statusDisabled + } + + return bidderDetail +} + +func mapMediaTypes(m []openrtb_ext.BidType) []string { + mediaTypes := make([]string, len(m)) + + for i, v := range m { + mediaTypes[i] = string(v) + } + + return mediaTypes +} diff --git a/endpoints/info/bidders_detail_test.go b/endpoints/info/bidders_detail_test.go new file mode 100644 index 00000000000..d8b6f2bf4ad --- /dev/null +++ b/endpoints/info/bidders_detail_test.go @@ -0,0 +1,497 @@ +package info + +import ( + "bytes" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" +) + +func TestPrepareBiddersDetailResponse(t *testing.T) { + bidderAInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} + bidderAConfig := config.Adapter{Endpoint: "https://secureEndpoint.com"} + bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`) + + bidderBInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} + bidderBConfig := config.Adapter{Endpoint: "http://unsecureEndpoint.com"} + bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`) + + allResponseBidderA := bytes.Buffer{} + allResponseBidderA.WriteString(`{"a":`) + allResponseBidderA.Write(bidderAResponse) + allResponseBidderA.WriteString(`}`) + + allResponseBidderAB := bytes.Buffer{} + allResponseBidderAB.WriteString(`{"a":`) + allResponseBidderAB.Write(bidderAResponse) + allResponseBidderAB.WriteString(`,"b":`) + allResponseBidderAB.Write(bidderBResponse) + allResponseBidderAB.WriteString(`}`) + + var testCases = []struct { + description string + givenBidders config.BidderInfos + givenBiddersConfig map[string]config.Adapter + givenAliases map[string]string + expectedResponses map[string][]byte + expectedError string + }{ + { + description: "None", + givenBidders: config.BidderInfos{}, + givenBiddersConfig: map[string]config.Adapter{}, + givenAliases: map[string]string{}, + expectedResponses: map[string][]byte{"all": []byte(`{}`)}, + }, + { + description: "One", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, + givenAliases: map[string]string{}, + expectedResponses: map[string][]byte{"a": bidderAResponse, "all": allResponseBidderA.Bytes()}, + }, + { + description: "Many", + givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig, "b": bidderBConfig}, + givenAliases: map[string]string{}, + expectedResponses: map[string][]byte{"a": bidderAResponse, "b": bidderBResponse, "all": allResponseBidderAB.Bytes()}, + }, + { + description: "Error - Map Details", // Returns error due to invalid alias. + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, + givenAliases: map[string]string{"zAlias": "z"}, + expectedError: "base adapter z for alias zAlias not found", + }, + } + + for _, test := range testCases { + responses, err := prepareBiddersDetailResponse(test.givenBidders, test.givenBiddersConfig, test.givenAliases) + + if test.expectedError == "" { + assert.Equal(t, test.expectedResponses, responses, test.description+":responses") + assert.NoError(t, err, test.expectedError, test.description+":err") + } else { + assert.Empty(t, responses, test.description+":responses") + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + } +} + +func TestMapDetails(t *testing.T) { + trueValue := true + falseValue := false + + bidderAInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} + bidderAConfig := config.Adapter{Endpoint: "https://secureEndpoint.com"} + bidderADetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &trueValue, Maintainer: &maintainer{Email: "bidderA"}} + aliasADetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &trueValue, Maintainer: &maintainer{Email: "bidderA"}, AliasOf: "a"} + + bidderBInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} + bidderBConfig := config.Adapter{Endpoint: "http://unsecureEndpoint.com"} + bidderBDetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &falseValue, Maintainer: &maintainer{Email: "bidderB"}} + aliasBDetail := bidderDetail{Status: "ACTIVE", UsesHTTPS: &falseValue, Maintainer: &maintainer{Email: "bidderB"}, AliasOf: "b"} + + var testCases = []struct { + description string + givenBidders config.BidderInfos + givenBiddersConfig map[string]config.Adapter + givenAliases map[string]string + expectedDetails map[string]bidderDetail + expectedError string + }{ + { + description: "None", + givenBidders: config.BidderInfos{}, + givenBiddersConfig: map[string]config.Adapter{}, + givenAliases: map[string]string{}, + expectedDetails: map[string]bidderDetail{}, + }, + { + description: "One Core Bidder", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, + givenAliases: map[string]string{}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail}, + }, + { + description: "Many Core Bidders", + givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig, "b": bidderBConfig}, + givenAliases: map[string]string{}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail}, + }, + { + description: "One Alias", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, + givenAliases: map[string]string{"aAlias": "a"}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias": aliasADetail}, + }, + { + description: "Many Aliases - Same Core Bidder", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, + givenAliases: map[string]string{"aAlias1": "a", "aAlias2": "a"}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail, "aAlias1": aliasADetail, "aAlias2": aliasADetail}, + }, + { + description: "Many Aliases - Different Core Bidders", + givenBidders: config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig, "b": bidderBConfig}, + givenAliases: map[string]string{"aAlias": "a", "bAlias": "b"}, + expectedDetails: map[string]bidderDetail{"a": bidderADetail, "b": bidderBDetail, "aAlias": aliasADetail, "bAlias": aliasBDetail}, + }, + { + description: "Error - Alias Without Core Bidder", + givenBidders: config.BidderInfos{"a": bidderAInfo}, + givenBiddersConfig: map[string]config.Adapter{"a": bidderAConfig}, + givenAliases: map[string]string{"zAlias": "z"}, + expectedError: "base adapter z for alias zAlias not found", + }, + } + + for _, test := range testCases { + details, err := mapDetails(test.givenBidders, test.givenBiddersConfig, test.givenAliases) + + if test.expectedError == "" { + assert.Equal(t, test.expectedDetails, details, test.description+":details") + assert.NoError(t, err, test.expectedError, test.description+":err") + } else { + assert.Empty(t, details, test.description+":details") + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + } +} + +func TestResolveEndpoint(t *testing.T) { + var testCases = []struct { + description string + givenBidder string + givenBiddersConfig map[string]config.Adapter + expectedEndpoint string + }{ + { + description: "Bidder Found - Uses Config Value", + givenBidder: "a", + givenBiddersConfig: map[string]config.Adapter{"a": {Endpoint: "anyEndpoint"}}, + expectedEndpoint: "anyEndpoint", + }, + { + description: "Bidder Not Found - Returns Empty", + givenBidder: "hasNoConfig", + givenBiddersConfig: map[string]config.Adapter{"a": {Endpoint: "anyEndpoint"}}, + expectedEndpoint: "", + }, + } + + for _, test := range testCases { + result := resolveEndpoint(test.givenBidder, test.givenBiddersConfig) + assert.Equal(t, test.expectedEndpoint, result, test.description) + } +} + +func TestMarshalDetailsResponse(t *testing.T) { + // Verifies omitempty is working correctly for bidderDetail, maintainer, capabilities, and aliasOf. + bidderDetailA := bidderDetail{Status: "ACTIVE", Maintainer: &maintainer{Email: "bidderA"}} + bidderDetailAResponse := []byte(`{"status":"ACTIVE","maintainer":{"email":"bidderA"}}`) + + // Verifies omitempty is working correctly for capabilities.app / capabilities.site. + bidderDetailB := bidderDetail{Status: "ACTIVE", Maintainer: &maintainer{Email: "bidderB"}, Capabilities: &capabilities{App: &platform{MediaTypes: []string{"banner"}}}} + bidderDetailBResponse := []byte(`{"status":"ACTIVE","maintainer":{"email":"bidderB"},"capabilities":{"app":{"mediaTypes":["banner"]}}}`) + + var testCases = []struct { + description string + givenDetails map[string]bidderDetail + expectedResponse map[string][]byte + }{ + { + description: "None", + givenDetails: map[string]bidderDetail{}, + expectedResponse: map[string][]byte{}, + }, + { + description: "One", + givenDetails: map[string]bidderDetail{"a": bidderDetailA}, + expectedResponse: map[string][]byte{"a": bidderDetailAResponse}, + }, + { + description: "Many", + givenDetails: map[string]bidderDetail{"a": bidderDetailA, "b": bidderDetailB}, + expectedResponse: map[string][]byte{"a": bidderDetailAResponse, "b": bidderDetailBResponse}, + }, + } + + for _, test := range testCases { + response, err := marshalDetailsResponse(test.givenDetails) + + assert.NoError(t, err, test.description+":err") + assert.Equal(t, test.expectedResponse, response, test.description+":response") + } +} + +func TestMarshalAllResponse(t *testing.T) { + responses := map[string][]byte{ + "a": []byte(`{"Status":"ACTIVE"}`), + "b": []byte(`{"Status":"DISABLED"}`), + } + + result, err := marshalAllResponse(responses) + + assert.NoError(t, err) + assert.Equal(t, []byte(`{"a":{"Status":"ACTIVE"},"b":{"Status":"DISABLED"}}`), result) +} + +func TestMapDetailFromConfig(t *testing.T) { + trueValue := true + falseValue := false + + var testCases = []struct { + description string + givenBidderInfo config.BidderInfo + givenEndpoint string + expected bidderDetail + }{ + { + description: "Enabled - All Values Present", + givenBidderInfo: config.BidderInfo{ + Enabled: true, + Maintainer: &config.MaintainerInfo{ + Email: "foo@bar.com", + }, + Capabilities: &config.CapabilitiesInfo{ + App: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}, + Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}, + }, + }, + givenEndpoint: "http://amyEndpoint", + expected: bidderDetail{ + Status: "ACTIVE", + UsesHTTPS: &falseValue, + Maintainer: &maintainer{ + Email: "foo@bar.com", + }, + Capabilities: &capabilities{ + App: &platform{MediaTypes: []string{"banner"}}, + Site: &platform{MediaTypes: []string{"video"}}, + }, + AliasOf: "", + }, + }, + { + description: "Disabled - All Values Present", + givenBidderInfo: config.BidderInfo{ + Enabled: false, + Maintainer: &config.MaintainerInfo{ + Email: "foo@bar.com", + }, + Capabilities: &config.CapabilitiesInfo{ + App: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}, + Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeVideo}}, + }, + }, + givenEndpoint: "http://amyEndpoint", + expected: bidderDetail{ + Status: "DISABLED", + UsesHTTPS: nil, + Maintainer: &maintainer{ + Email: "foo@bar.com", + }, + Capabilities: nil, + AliasOf: "", + }, + }, + { + description: "Enabled - No Values Present", + givenBidderInfo: config.BidderInfo{ + Enabled: true, + }, + givenEndpoint: "http://amyEndpoint", + expected: bidderDetail{ + Status: "ACTIVE", + UsesHTTPS: &falseValue, + }, + }, + { + description: "Enabled - Protocol - HTTP", + givenBidderInfo: config.BidderInfo{ + Enabled: true, + }, + givenEndpoint: "http://amyEndpoint", + expected: bidderDetail{ + Status: "ACTIVE", + UsesHTTPS: &falseValue, + }, + }, + { + description: "Enabled - Protocol - HTTPS", + givenBidderInfo: config.BidderInfo{ + Enabled: true, + }, + givenEndpoint: "https://amyEndpoint", + expected: bidderDetail{ + Status: "ACTIVE", + UsesHTTPS: &trueValue, + }, + }, + { + description: "Enabled - Protocol - HTTPS - Case Insensitive", + givenBidderInfo: config.BidderInfo{ + Enabled: true, + }, + givenEndpoint: "https://amyEndpoint", + expected: bidderDetail{ + Status: "ACTIVE", + UsesHTTPS: &trueValue, + }, + }, + { + description: "Enabled - Protocol - Unknown", + givenBidderInfo: config.BidderInfo{ + Enabled: true, + }, + givenEndpoint: "endpointWithoutProtocol", + expected: bidderDetail{ + Status: "ACTIVE", + UsesHTTPS: &falseValue, + }, + }, + } + + for _, test := range testCases { + result := mapDetailFromConfig(test.givenBidderInfo, test.givenEndpoint) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestMapMediaTypes(t *testing.T) { + var testCases = []struct { + description string + mediaTypes []openrtb_ext.BidType + expected []string + }{ + { + description: "Nil", + mediaTypes: nil, + expected: nil, + }, + { + description: "None", + mediaTypes: []openrtb_ext.BidType{}, + expected: []string{}, + }, + { + description: "One", + mediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, + expected: []string{"banner"}, + }, + { + description: "Many", + mediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo}, + expected: []string{"banner", "video"}, + }, + } + + for _, test := range testCases { + result := mapMediaTypes(test.mediaTypes) + assert.ElementsMatch(t, test.expected, result, test.description) + } +} + +func TestBiddersDetailHandler(t *testing.T) { + bidderAInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderA"}} + bidderAConfig := config.Adapter{Endpoint: "https://secureEndpoint.com"} + bidderAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"}}`) + aliasAResponse := []byte(`{"status":"ACTIVE","usesHttps":true,"maintainer":{"email":"bidderA"},"aliasOf":"a"}`) + + bidderBInfo := config.BidderInfo{Enabled: true, Maintainer: &config.MaintainerInfo{Email: "bidderB"}} + bidderBConfig := config.Adapter{Endpoint: "http://unsecureEndpoint.com"} + bidderBResponse := []byte(`{"status":"ACTIVE","usesHttps":false,"maintainer":{"email":"bidderB"}}`) + + allResponse := bytes.Buffer{} + allResponse.WriteString(`{"a":`) + allResponse.Write(bidderAResponse) + allResponse.WriteString(`,"aAlias":`) + allResponse.Write(aliasAResponse) + allResponse.WriteString(`,"b":`) + allResponse.Write(bidderBResponse) + allResponse.WriteString(`}`) + + bidders := config.BidderInfos{"a": bidderAInfo, "b": bidderBInfo} + biddersConfig := map[string]config.Adapter{"a": bidderAConfig, "b": bidderBConfig} + aliases := map[string]string{"aAlias": "a"} + + handler := NewBiddersDetailEndpoint(bidders, biddersConfig, aliases) + + var testCases = []struct { + description string + givenBidder string + expectedStatus int + expectedHeaders http.Header + expectedResponse []byte + }{ + { + description: "Bidder A", + givenBidder: "a", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: bidderAResponse, + }, + { + description: "Bidder B", + givenBidder: "b", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: bidderBResponse, + }, + { + description: "Bidder A Alias", + givenBidder: "aAlias", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: aliasAResponse, + }, + { + description: "All Bidders", + givenBidder: "all", + expectedStatus: http.StatusOK, + expectedHeaders: http.Header{"Content-Type": []string{"application/json"}}, + expectedResponse: allResponse.Bytes(), + }, + { + description: "All Bidders - Wrong Case", + givenBidder: "ALL", + expectedStatus: http.StatusNotFound, + expectedHeaders: http.Header{}, + expectedResponse: []byte{}, + }, + { + description: "Invalid Bidder", + givenBidder: "doesntExist", + expectedStatus: http.StatusNotFound, + expectedHeaders: http.Header{}, + expectedResponse: []byte{}, + }, + } + + for _, test := range testCases { + responseRecorder := httptest.NewRecorder() + handler(responseRecorder, nil, httprouter.Params{{"bidderName", test.givenBidder}}) + + result := responseRecorder.Result() + assert.Equal(t, result.StatusCode, test.expectedStatus, test.description+":statuscode") + + resultBody, _ := ioutil.ReadAll(result.Body) + assert.Equal(t, test.expectedResponse, resultBody, test.description+":body") + + resultHeaders := result.Header + assert.Equal(t, test.expectedHeaders, resultHeaders, test.description+":headers") + } +} diff --git a/endpoints/info/bidders_test.go b/endpoints/info/bidders_test.go index 15e5f77de23..e48dd3d0e8e 100644 --- a/endpoints/info/bidders_test.go +++ b/endpoints/info/bidders_test.go @@ -1,321 +1,89 @@ -package info_test +package info import ( - "encoding/json" - "errors" - "fmt" "io/ioutil" "net/http" "net/http/httptest" - "os" - "strings" "testing" - "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" "github.com/stretchr/testify/assert" - - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/info" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - yaml "gopkg.in/yaml.v2" ) -func TestGetBiddersNoAliases(t *testing.T) { - testGetBidders(t, map[string]string{}) -} - -func TestGetBiddersWithAliases(t *testing.T) { - aliases := map[string]string{ - "test1": "appnexus", - "test2": "rubicon", - "test3": "openx", - } - testGetBidders(t, aliases) -} - -func testGetBidders(t *testing.T, aliases map[string]string) { - endpoint := info.NewBiddersEndpoint(aliases) - - req, err := http.NewRequest("GET", "http://prebid-server.com/info/bidders", strings.NewReader("")) - if err != nil { - assert.FailNow(t, "Failed to create a GET /info/bidders request: %v", err) - } - - r := httptest.NewRecorder() - endpoint(r, req, nil) - - assert.Equal(t, http.StatusOK, r.Code, "GET /info/bidders returned bad status: %d", r.Code) - assert.Equal(t, "application/json", r.Header().Get("Content-Type"), "Bad /info/bidders content type. Expected application/json. Got %s", r.Header().Get("Content-Type")) - - bidderMap := openrtb_ext.BuildBidderNameHashSet() - - bodyBytes := r.Body.Bytes() - bidderSlice := make([]string, 0) - err = json.Unmarshal(bodyBytes, &bidderSlice) - assert.NoError(t, err, "Failed to unmarshal /info/bidders response: %v", err) - - for _, bidderName := range bidderSlice { - if _, ok := bidderMap[bidderName]; !ok { - assert.Contains(t, aliases, bidderName, "Response from /info/bidders contained unexpected BidderName: %s", bidderName) - } - } - - expectedBidderSliceLength := len(bidderMap) + len(aliases) - assert.Len(t, bidderSlice, expectedBidderSliceLength, "Response from /info/bidders did not match BidderMap. Expected %d elements. Got %d", expectedBidderSliceLength) -} - -// TestGetSpecificBidders validates all the GET /info/bidders/{bidderName} endpoints -func TestGetSpecificBidders(t *testing.T) { - // Setup: +func TestPrepareBiddersResponse(t *testing.T) { testCases := []struct { - status adapters.BidderStatus - description string + description string + givenBidders config.BidderInfos + givenAliases map[string]string + expected string }{ { - status: adapters.StatusActive, - description: "case 1 - bidder status is active", + description: "None", + givenBidders: config.BidderInfos{}, + givenAliases: nil, + expected: `[]`, }, { - status: adapters.StatusDisabled, - description: "case 2 - bidder status is disabled", + description: "Core Bidders Only - One", + givenBidders: config.BidderInfos{"a": {}}, + givenAliases: nil, + expected: `["a"]`, + }, + { + description: "Core Bidders Only - Many", + givenBidders: config.BidderInfos{"a": {}, "b": {}}, + givenAliases: nil, + expected: `["a","b"]`, + }, + { + description: "Core Bidders Only - Many Sorted", + givenBidders: config.BidderInfos{"z": {}, "a": {}}, + givenAliases: nil, + expected: `["a","z"]`, + }, + { + description: "With Aliases - One", + givenBidders: config.BidderInfos{"a": {}}, + givenAliases: map[string]string{"b": "b"}, + expected: `["a","b"]`, + }, + { + description: "With Aliases - Many", + givenBidders: config.BidderInfos{"a": {}}, + givenAliases: map[string]string{"b": "b", "c": "c"}, + expected: `["a","b","c"]`, + }, + { + description: "With Aliases - Sorted", + givenBidders: config.BidderInfos{"z": {}}, + givenAliases: map[string]string{"a": "a"}, + expected: `["a","z"]`, }, } - for _, tc := range testCases { - bidderDisabled := false - if tc.status == adapters.StatusDisabled { - bidderDisabled = true - } - cfg := blankAdapterConfigWithStatus(openrtb_ext.CoreBidderNames(), bidderDisabled) - bidderInfos := adapters.ParseBidderInfos(cfg, "../../static/bidder-info", openrtb_ext.CoreBidderNames()) - endpoint := info.NewBidderDetailsEndpoint(bidderInfos, map[string]string{}) - - for _, bidderName := range openrtb_ext.CoreBidderNames() { - req, err := http.NewRequest("GET", "http://prebid-server.com/info/bidders/"+string(bidderName), strings.NewReader("")) - if err != nil { - t.Errorf("Failed to create a GET /info/bidders request: %v", err) - continue - } - params := []httprouter.Param{{ - Key: "bidderName", - Value: string(bidderName), - }} - r := httptest.NewRecorder() - - // Execute: - endpoint(r, req, params) - - // Verify: - assert.Equal(t, http.StatusOK, r.Code, "GET /info/bidders/"+string(bidderName)+" returned a %d. Expected 200", r.Code, tc.description) - assert.Equal(t, "application/json", r.HeaderMap.Get("Content-Type"), "GET /info/bidders/"+string(bidderName)+" returned Content-Type %s. Expected application/json", r.HeaderMap.Get("Content-Type"), tc.description) - - var resBidderInfo adapters.BidderInfo - if err := json.Unmarshal(r.Body.Bytes(), &resBidderInfo); err != nil { - assert.FailNow(t, "Failed to unmarshal JSON from endpoints/info/bidders/%s: %v", bidderName, err, tc.description) - } - - assert.Equal(t, tc.status, resBidderInfo.Status, tc.description) - } - } -} - -func TestGetBidderAccuracyNoAliases(t *testing.T) { - testGetBidderAccuracy(t, "") -} - -func TestGetBidderAccuracyAliases(t *testing.T) { - testGetBidderAccuracy(t, "aliasedBidder") -} - -// TestGetBidderAccuracyAlias validates the output for an alias of a known file. -func testGetBidderAccuracy(t *testing.T, alias string) { - cfg := blankAdapterConfig(openrtb_ext.CoreBidderNames()) - bidderInfos := adapters.ParseBidderInfos(cfg, "../../adapters/adapterstest/bidder-info", []openrtb_ext.BidderName{openrtb_ext.BidderName("someBidder")}) - - aliases := map[string]string{} - bidder := "someBidder" - if len(alias) > 0 { - aliases[alias] = bidder - bidder = alias - } - - endpoint := info.NewBidderDetailsEndpoint(bidderInfos, aliases) - req, err := http.NewRequest("GET", "http://prebid-server.com/info/bidders/"+bidder, strings.NewReader("")) - assert.NoError(t, err, "Failed to create a GET /info/bidders request: %v", err) - params := []httprouter.Param{{ - Key: "bidderName", - Value: bidder, - }} - - r := httptest.NewRecorder() - endpoint(r, req, params) - - var fileData adapters.BidderInfo - if err := json.Unmarshal(r.Body.Bytes(), &fileData); err != nil { - assert.FailNow(t, "Failed to unmarshal JSON from endpoints/info/sample/someBidder.yaml: %v", err) - } - - assert.Equal(t, "some-email@domain.com", fileData.Maintainer.Email, "maintainer.email should be some-email@domain.com. Got %s", fileData.Maintainer.Email) - assert.Len(t, fileData.Capabilities.App.MediaTypes, 2, "Expected 2 supported mediaTypes on app. Got %d", len(fileData.Capabilities.App.MediaTypes)) - assert.Equal(t, openrtb_ext.BidType("banner"), fileData.Capabilities.App.MediaTypes[0], "capabilities.app.mediaTypes[0] should be banner. Got %s", fileData.Capabilities.App.MediaTypes[0]) - assert.Equal(t, openrtb_ext.BidType("native"), fileData.Capabilities.App.MediaTypes[1], "capabilities.app.mediaTypes[1] should be native. Got %s", fileData.Capabilities.App.MediaTypes[1]) - assert.Len(t, fileData.Capabilities.Site.MediaTypes, 3, "Expected 3 supported mediaTypes on app. Got %d", len(fileData.Capabilities.Site.MediaTypes)) - assert.Equal(t, openrtb_ext.BidType("banner"), fileData.Capabilities.Site.MediaTypes[0], "capabilities.app.mediaTypes[0] should be banner. Got %s", fileData.Capabilities.Site.MediaTypes[0]) - assert.Equal(t, openrtb_ext.BidType("video"), fileData.Capabilities.Site.MediaTypes[1], "capabilities.app.mediaTypes[1] should be video. Got %s", fileData.Capabilities.Site.MediaTypes[1]) - assert.Equal(t, openrtb_ext.BidType("native"), fileData.Capabilities.Site.MediaTypes[2], "capabilities.app.mediaTypes[2] should be native. Got %s", fileData.Capabilities.Site.MediaTypes[2]) - if len(alias) > 0 { - assert.Equal(t, "someBidder", fileData.AliasOf, "aliasOf should be \"someBidder\". Got \"%s\"", fileData.AliasOf) - } else { - assert.Zero(t, len(fileData.AliasOf), "aliasOf should be empty. Got \"%s\"", fileData.AliasOf) - } -} - -func TestGetUnknownBidder(t *testing.T) { - bidderInfos := adapters.BidderInfos(make(map[string]adapters.BidderInfo)) - endpoint := info.NewBidderDetailsEndpoint(bidderInfos, map[string]string{}) - req, err := http.NewRequest("GET", "http://prebid-server.com/info/bidders/someUnknownBidder", strings.NewReader("")) - if err != nil { - assert.FailNow(t, "Failed to create a GET /info/bidders/someUnknownBidder request: %v", err) - } - - params := []httprouter.Param{{ - Key: "bidderName", - Value: "someUnknownBidder", - }} - r := httptest.NewRecorder() - - endpoint(r, req, params) - assert.Equal(t, http.StatusNotFound, r.Code, "GET /info/bidders/* should return a 404 on unknown bidders. Got %d", r.Code) -} -func TestGetAllBidders(t *testing.T) { - cfg := blankAdapterConfig(openrtb_ext.CoreBidderNames()) - bidderInfos := adapters.ParseBidderInfos(cfg, "../../static/bidder-info", openrtb_ext.CoreBidderNames()) - endpoint := info.NewBidderDetailsEndpoint(bidderInfos, map[string]string{}) - req, err := http.NewRequest("GET", "http://prebid-server.com/info/bidders/all", strings.NewReader("")) - if err != nil { - assert.FailNow(t, "Failed to create a GET /info/bidders/someUnknownBidder request: %v", err) - } - params := []httprouter.Param{{ - Key: "bidderName", - Value: "all", - }} - r := httptest.NewRecorder() - - endpoint(r, req, params) - assert.Equal(t, http.StatusOK, r.Code, "GET /info/bidders/all returned a %d. Expected 200", r.Code) - assert.Equal(t, "application/json", r.HeaderMap.Get("Content-Type"), "GET /info/bidders/all returned Content-Type %s. Expected application/json", r.HeaderMap.Get("Content-Type")) - - var resBidderInfos map[string]adapters.BidderInfo - - if err := json.Unmarshal(r.Body.Bytes(), &resBidderInfos); err != nil { - assert.FailNow(t, "Failed to unmarshal JSON from endpoints/info/sample/someBidder.yaml: %v", err) - } - - assert.Len(t, resBidderInfos, len(bidderInfos), "GET /info/bidders/all should respond with all bidders info") -} - -// TestInfoFiles makes sure that static/bidder-info contains a .yaml file for every BidderName. -func TestInfoFiles(t *testing.T) { - fileInfos, err := ioutil.ReadDir("../../static/bidder-info") - if err != nil { - assert.FailNow(t, "Error reading the static/bidder-info directory: %v", err) - } - - // Make sure that files exist for each BidderName - for _, bidderName := range openrtb_ext.CoreBidderNames() { - _, err := os.Stat(fmt.Sprintf("../../static/bidder-info/%s.yaml", string(bidderName))) - assert.False(t, os.IsNotExist(err), "static/bidder-info/%s.yaml not found. Did you forget to create it?", bidderName) - } - - expectedFileInfosLength := len(openrtb_ext.CoreBidderNames()) - assert.Len(t, fileInfos, expectedFileInfosLength, "static/bidder-info contains %d files, but the BidderMap has %d entries. These two should be in sync.", len(fileInfos), expectedFileInfosLength) - - // Make sure that all the files have valid content - for _, fileInfo := range fileInfos { - infoFileData, err := os.Open(fmt.Sprintf("../../static/bidder-info/%s", fileInfo.Name())) - assert.NoError(t, err, "Unexpected error: %v", err) - - content, err := ioutil.ReadAll(infoFileData) - assert.NoError(t, err, "Failed to read static/bidder-info/%s: %v", fileInfo.Name(), err) - - var fileInfoContent adapters.BidderInfo - err = yaml.Unmarshal(content, &fileInfoContent) - assert.NoError(t, err, "Error interpreting content from static/bidder-info/%s: %v", fileInfo.Name(), err) - - err = validateInfo(&fileInfoContent) - assert.NoError(t, err, "Invalid content in static/bidder-info/%s: %v", fileInfo.Name(), err) - - } -} - -func validateInfo(info *adapters.BidderInfo) error { - if err := validateMaintainer(info.Maintainer); err != nil { - return err - } - - if err := validateCapabilities(info.Capabilities); err != nil { - return err - } - - return nil -} - -func validateCapabilities(info *adapters.CapabilitiesInfo) error { - if info == nil { - return errors.New("missing required field: capabilities") + for _, test := range testCases { + result, err := prepareBiddersResponse(test.givenBidders, test.givenAliases) + assert.NoError(t, err, test.description) + assert.Equal(t, []byte(test.expected), result, test.description) } - if info.App == nil && info.Site == nil { - return errors.New("at least one of capabilities.site or capabilities.app should exist") - } - if info.App != nil { - if err := validatePlatformInfo(info.App); err != nil { - return fmt.Errorf("capabilities.app failed validation: %v", err) - } - } - if info.Site != nil { - if err := validatePlatformInfo(info.Site); err != nil { - return fmt.Errorf("capabilities.site failed validation: %v", err) - } - } - return nil } -func validatePlatformInfo(info *adapters.PlatformInfo) error { - if info == nil { - return errors.New("we can't validate a nil platformInfo") - } - if len(info.MediaTypes) == 0 { - return errors.New("mediaTypes should be an array with at least one string element") - } +func TestBiddersHandler(t *testing.T) { + bidders := config.BidderInfos{"a": {}} + aliases := map[string]string{"b": "b"} - for index, mediaType := range info.MediaTypes { - if mediaType != "banner" && mediaType != "video" && mediaType != "native" && mediaType != "audio" { - return fmt.Errorf("unrecognized media type at index %d: %s", index, mediaType) - } - } + handler := NewBiddersEndpoint(bidders, aliases) - return nil -} + responseRecorder := httptest.NewRecorder() + handler(responseRecorder, nil, nil) -func validateMaintainer(info *adapters.MaintainerInfo) error { - if info == nil || info.Email == "" { - return errors.New("missing required field: maintainer.email") - } - return nil -} + result := responseRecorder.Result() + assert.Equal(t, result.StatusCode, http.StatusOK) -func blankAdapterConfig(bidderList []openrtb_ext.BidderName) map[string]config.Adapter { - return blankAdapterConfigWithStatus(bidderList, false) -} + resultBody, _ := ioutil.ReadAll(result.Body) + assert.Equal(t, []byte(`["a","b"]`), resultBody) -func blankAdapterConfigWithStatus(bidderList []openrtb_ext.BidderName, biddersAreDisabled bool) map[string]config.Adapter { - adapters := make(map[string]config.Adapter) - for _, b := range bidderList { - adapters[strings.ToLower(string(b))] = config.Adapter{ - Disabled: biddersAreDisabled, - } - } - return adapters + resultHeaders := result.Header + assert.Equal(t, http.Header{"Content-Type": []string{"application/json"}}, resultHeaders) } diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index f6cef770694..6767151f15f 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -11,34 +11,34 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/openrtb" - accountService "github.com/PubMatic-OpenWrap/prebid-server/account" - "github.com/PubMatic-OpenWrap/prebid-server/amp" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" - "github.com/PubMatic-OpenWrap/prebid-server/util/iputil" "github.com/buger/jsonparser" "github.com/golang/glog" "github.com/julienschmidt/httprouter" + "github.com/mxmCherry/openrtb/v15/openrtb2" + 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/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/gdpr" + "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" ) const defaultAmpRequestTimeoutMillis = 900 type AmpResponse struct { - Targeting map[string]string `json:"targeting"` - Debug *openrtb_ext.ExtResponseDebug `json:"debug,omitempty"` - Errors map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"errors,omitempty"` - Warnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError `json:"warnings,omitempty"` + Targeting map[string]string `json:"targeting"` + Debug *openrtb_ext.ExtResponseDebug `json:"debug,omitempty"` + Errors map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage `json:"errors,omitempty"` + Warnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage `json:"warnings,omitempty"` } // NewAmpEndpoint modifies the OpenRTB endpoint to handle AMP requests. This will basically modify the parsing @@ -239,13 +239,16 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h ao.Errors = append(ao.Errors, fmt.Errorf("AMP response: failed to unpack OpenRTB response.ext, debug info cannot be forwarded: %v", eRErr)) } - warnings := make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError) + warnings := extResponse.Warnings + if warnings == nil { + warnings = make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage) + } for _, v := range errortypes.WarningOnly(errL) { - bidderErr := openrtb_ext.ExtBidderError{ + bidderErr := openrtb_ext.ExtBidderMessage{ Code: errortypes.ReadCode(v), Message: v.Error(), } - warnings[openrtb_ext.BidderNameGeneral] = append(warnings[openrtb_ext.BidderNameGeneral], bidderErr) + warnings[openrtb_ext.BidderReservedGeneral] = append(warnings[openrtb_ext.BidderReservedGeneral], bidderErr) } // Now JSONify the targets for the AMP response. @@ -286,7 +289,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h // possible, it will return errors with messages that suggest improvements. // // If the errors list has at least one element, then no guarantees are made about the returned request. -func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb.BidRequest, errs []error) { +func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, errs []error) { // Load the stored request for the AMP ID. req, e := deps.loadRequestJSONForAmp(httpRequest) if errs = append(errs, e...); errortypes.ContainsFatalError(errs) { @@ -310,8 +313,8 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr } // Load the stored OpenRTB request for an incoming AMP request, or return the errors found. -func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb.BidRequest, errs []error) { - req = &openrtb.BidRequest{} +func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req *openrtb2.BidRequest, errs []error) { + req = &openrtb2.BidRequest{} errs = nil ampParams, err := amp.ParseParams(httpRequest) @@ -369,9 +372,9 @@ func (deps *endpointDeps) loadRequestJSONForAmp(httpRequest *http.Request) (req return } -func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb.BidRequest) []error { +func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb2.BidRequest) []error { if req.Site == nil { - req.Site = &openrtb.Site{} + req.Site = &openrtb2.Site{} } // Override the stored request sizes with AMP ones, if they exist. @@ -420,25 +423,25 @@ func (deps *endpointDeps) overrideWithParams(ampParams amp.Params, req *openrtb. return nil } -func makeFormatReplacement(size amp.Size) []openrtb.Format { - var formats []openrtb.Format +func makeFormatReplacement(size amp.Size) []openrtb2.Format { + var formats []openrtb2.Format if size.OverrideWidth != 0 && size.OverrideHeight != 0 { - formats = []openrtb.Format{{ + formats = []openrtb2.Format{{ W: size.OverrideWidth, H: size.OverrideHeight, }} } else if size.OverrideWidth != 0 && size.Height != 0 { - formats = []openrtb.Format{{ + formats = []openrtb2.Format{{ W: size.OverrideWidth, H: size.Height, }} } else if size.Width != 0 && size.OverrideHeight != 0 { - formats = []openrtb.Format{{ + formats = []openrtb2.Format{{ W: size.Width, H: size.OverrideHeight, }} } else if size.Width != 0 && size.Height != 0 { - formats = []openrtb.Format{{ + formats = []openrtb2.Format{{ W: size.Width, H: size.Height, }} @@ -447,13 +450,13 @@ func makeFormatReplacement(size amp.Size) []openrtb.Format { return append(formats, size.Multisize...) } -func setWidths(formats []openrtb.Format, width uint64) { +func setWidths(formats []openrtb2.Format, width int64) { for i := 0; i < len(formats); i++ { formats[i].W = width } } -func setHeights(formats []openrtb.Format, height uint64) { +func setHeights(formats []openrtb2.Format, height int64) { for i := 0; i < len(formats); i++ { formats[i].H = height } @@ -461,7 +464,7 @@ func setHeights(formats []openrtb.Format, height uint64) { // AMP won't function unless ext.prebid.targeting and ext.prebid.cache.bids are defined. // If the user didn't include them, default those here. -func defaultRequestExt(req *openrtb.BidRequest) (errs []error) { +func defaultRequestExt(req *openrtb2.BidRequest) (errs []error) { errs = nil extRequest := &openrtb_ext.ExtRequest{} if req.Ext != nil && len(req.Ext) > 0 { @@ -503,7 +506,7 @@ func defaultRequestExt(req *openrtb.BidRequest) (errs []error) { return } -func setAmpExt(site *openrtb.Site, value string) { +func setAmpExt(site *openrtb2.Site, value string) { if len(site.Ext) > 0 { if _, dataType, _, _ := jsonparser.Get(site.Ext, "amp"); dataType == jsonparser.NotExist { if val, err := jsonparser.Set(site.Ext, []byte(value), "amp"); err == nil { @@ -528,22 +531,23 @@ func readPolicy(consent string) (privacy.PolicyWriter, error) { return ccpa.ConsentWriter{consent}, nil } - return privacy.NilPolicyWriter{}, &errortypes.InvalidPrivacyConsent{ - Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), + return privacy.NilPolicyWriter{}, &errortypes.Warning{ + Message: fmt.Sprintf("Consent '%s' is not recognized as either CCPA or GDPR TCF.", consent), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, } } // Sets the effective publisher ID for amp request -func setEffectiveAmpPubID(req *openrtb.BidRequest, account string) { - var pub *openrtb.Publisher +func setEffectiveAmpPubID(req *openrtb2.BidRequest, account string) { + var pub *openrtb2.Publisher if req.App != nil { if req.App.Publisher == nil { - req.App.Publisher = new(openrtb.Publisher) + req.App.Publisher = new(openrtb2.Publisher) } pub = req.App.Publisher } else if req.Site != nil { if req.Site.Publisher == nil { - req.Site.Publisher = new(openrtb.Publisher) + req.Site.Publisher = new(openrtb2.Publisher) } pub = req.Site.Publisher } diff --git a/endpoints/openrtb2/amp_auction_test.go b/endpoints/openrtb2/amp_auction_test.go index cbff32413ba..079b9adb6d4 100644 --- a/endpoints/openrtb2/amp_auction_test.go +++ b/endpoints/openrtb2/amp_auction_test.go @@ -11,15 +11,15 @@ import ( "strconv" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/empty_fetcher" - - "github.com/PubMatic-OpenWrap/openrtb" - analyticsConf "github.com/PubMatic-OpenWrap/prebid-server/analytics/config" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" + + analyticsConf "github.com/prebid/prebid-server/analytics/config" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" gometrics "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" ) @@ -189,7 +189,7 @@ func TestGDPRConsent(t *testing.T) { // Build Request bid, err := getTestBidRequest(test.nilUser, test.userExt, true, nil) if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } // Simulated Stored Request Backend @@ -341,7 +341,7 @@ func TestCCPAConsent(t *testing.T) { // Build Request bid, err := getTestBidRequest(true, nil, test.nilRegs, test.regsExt) if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } // Simulated Stored Request Backend @@ -395,103 +395,119 @@ func TestCCPAConsent(t *testing.T) { } } -func TestNoConsent(t *testing.T) { - // Build Request - bid, err := getTestBidRequest(true, nil, true, nil) - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) +func TestConsentWarnings(t *testing.T) { + type inputTest struct { + regs *openrtb_ext.ExtRegs + invalidConsentURL bool + expectedWarnings map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage } + invalidConsent := "invalid" - // Simulated Stored Request Backend - stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + bidderWarning := openrtb_ext.ExtBidderMessage{ + Code: 10003, + Message: "debug turned off for bidder", + } + invalidCCPAWarning := openrtb_ext.ExtBidderMessage{ + Code: 10001, + Message: "Consent '" + invalidConsent + "' is not recognized as either CCPA or GDPR TCF.", + } + invalidConsentWarning := openrtb_ext.ExtBidderMessage{ + Code: 10001, + Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", + } - // Build Exchange Endpoint - mockExchange := &mockAmpExchange{} - endpoint, _ := NewAmpEndpoint( - mockExchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{stored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BuildBidderMap(), - ) + testData := []inputTest{ + { + regs: nil, + invalidConsentURL: false, + expectedWarnings: nil, + }, + { + regs: nil, + invalidConsentURL: true, + expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning}}, + }, + { + regs: &openrtb_ext.ExtRegs{USPrivacy: "invalid"}, + invalidConsentURL: true, + expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ + openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning, invalidConsentWarning}, + openrtb_ext.BidderName("appnexus"): {bidderWarning}, + }, + }, + { + regs: &openrtb_ext.ExtRegs{USPrivacy: "1NYN"}, + invalidConsentURL: false, + expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderName("appnexus"): {bidderWarning}}, + }, + } - // Invoke Endpoint - request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) - responseRecorder := httptest.NewRecorder() - endpoint(responseRecorder, request, nil) + for _, testCase := range testData { - // Parse Response - var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { - t.Fatalf("Error unmarshalling response: %s", err.Error()) - } + bid, err := getTestBidRequest(true, nil, testCase.regs == nil, testCase.regs) + if err != nil { + t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) + } - // Assert Result - result := mockExchange.lastRequest - assert.NotNil(t, result, "lastRequest") - assert.Nil(t, result.User, "lastRequest.User") - assert.Nil(t, result.Regs, "lastRequest.Regs") - assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) - assert.Empty(t, response.Warnings) -} + // Simulated Stored Request Backend + stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} -func TestInvalidConsent(t *testing.T) { - // Build Request - bid, err := getTestBidRequest(true, nil, true, nil) - if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) - } + // Build Exchange Endpoint + var mockExchange exchange.Exchange + if testCase.regs != nil { + mockExchange = &mockAmpExchangeWarnings{} + } else { + mockExchange = &mockAmpExchange{} + } + endpoint, _ := NewAmpEndpoint( + mockExchange, + newParamsValidator(t), + &mockAmpStoredReqFetcher{stored}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + newTestMetrics(), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + []byte{}, + openrtb_ext.BuildBidderMap(), + ) - // Simulated Stored Request Backend - stored := map[string]json.RawMessage{"1": json.RawMessage(bid)} + // Invoke Endpoint + var request *http.Request - // Build Exchange Endpoint - mockExchange := &mockAmpExchange{} - endpoint, _ := NewAmpEndpoint( - mockExchange, - newParamsValidator(t), - &mockAmpStoredReqFetcher{stored}, - empty_fetcher.EmptyFetcher{}, - &config.Configuration{MaxRequestSize: maxSize}, - newTestMetrics(), - analyticsConf.NewPBSAnalytics(&config.Analytics{}), - map[string]string{}, - []byte{}, - openrtb_ext.BuildBidderMap(), - ) + if testCase.invalidConsentURL { + request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_string="+invalidConsent, nil) - // Invoke Endpoint - invalidConsent := "invalid" - request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_string="+invalidConsent, nil) - responseRecorder := httptest.NewRecorder() - endpoint(responseRecorder, request, nil) + } else { + request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil) + } - // Parse Response - var response AmpResponse - if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { - t.Fatalf("Error unmarshalling response: %s", err.Error()) - } + responseRecorder := httptest.NewRecorder() + endpoint(responseRecorder, request, nil) - // Assert Result - expectedWarnings := map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{ - openrtb_ext.BidderNameGeneral: { - { - Code: 10001, - Message: "Consent '" + invalidConsent + "' is not recognized as either CCPA or GDPR TCF.", - }, - }, + // Parse Response + var response AmpResponse + if err := json.Unmarshal(responseRecorder.Body.Bytes(), &response); err != nil { + t.Fatalf("Error unmarshalling response: %s", err.Error()) + } + + // Assert Result + if testCase.regs == nil { + result := mockExchange.(*mockAmpExchange).lastRequest + assert.NotNil(t, result, "lastRequest") + assert.Nil(t, result.User, "lastRequest.User") + assert.Nil(t, result.Regs, "lastRequest.Regs") + assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) + if testCase.invalidConsentURL { + assert.Equal(t, testCase.expectedWarnings, response.Warnings) + } else { + assert.Empty(t, response.Warnings) + } + + } else { + assert.Equal(t, testCase.expectedWarnings, response.Warnings) + } } - result := mockExchange.lastRequest - assert.NotNil(t, result, "lastRequest") - assert.Nil(t, result.User, "lastRequest.User") - assert.Nil(t, result.Regs, "lastRequest.Regs") - assert.Equal(t, expectedErrorsFromHoldAuction, response.Errors) - assert.Equal(t, expectedWarnings, response.Warnings) } func TestNewAndLegacyConsentBothProvided(t *testing.T) { @@ -527,7 +543,7 @@ func TestNewAndLegacyConsentBothProvided(t *testing.T) { // Build Request bid, err := getTestBidRequest(false, nil, true, nil) if err != nil { - t.Fatalf("Failed to marshal the complete openrtb.BidRequest object %v", err) + t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err) } // Simulated Stored Request Backend @@ -696,7 +712,7 @@ func TestAmpDebug(t *testing.T) { // Prevents #452 func TestAmpTargetingDefaults(t *testing.T) { - req := &openrtb.BidRequest{} + req := &openrtb2.BidRequest{} if errs := defaultRequestExt(req); len(errs) != 0 { t.Fatalf("Unexpected error defaulting request.ext for AMP: %v", errs) } @@ -781,7 +797,7 @@ func TestOverrideDimensions(t *testing.T) { formatOverrideSpec{ overrideWidth: 20, overrideHeight: 40, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 20, H: 40, }}, @@ -792,7 +808,7 @@ func TestOverrideHeightNormalWidth(t *testing.T) { formatOverrideSpec{ width: 20, overrideHeight: 40, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 20, H: 40, }}, @@ -803,7 +819,7 @@ func TestOverrideWidthNormalHeight(t *testing.T) { formatOverrideSpec{ overrideWidth: 20, height: 40, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 20, H: 40, }}, @@ -813,7 +829,7 @@ func TestOverrideWidthNormalHeight(t *testing.T) { func TestMultisize(t *testing.T) { formatOverrideSpec{ multisize: "200x50,100x60", - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 200, H: 50, }, { @@ -828,7 +844,7 @@ func TestSizeWithMultisize(t *testing.T) { width: 20, height: 40, multisize: "200x50,100x60", - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 20, H: 40, }, { @@ -844,7 +860,7 @@ func TestSizeWithMultisize(t *testing.T) { func TestHeightOnly(t *testing.T) { formatOverrideSpec{ height: 200, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 300, H: 200, }}, @@ -854,7 +870,7 @@ func TestHeightOnly(t *testing.T) { func TestWidthOnly(t *testing.T) { formatOverrideSpec{ width: 150, - expect: []openrtb.Format{{ + expect: []openrtb2.Format{{ W: 150, H: 600, }}, @@ -868,7 +884,7 @@ type formatOverrideSpec struct { overrideHeight uint64 multisize string account string - expect []openrtb.Format + expect []openrtb2.Format } func (s formatOverrideSpec) execute(t *testing.T) { @@ -926,10 +942,10 @@ func (cf *mockAmpStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs } type mockAmpExchange struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest } -var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError{ +var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{ openrtb_ext.BidderName("openx"): { { Code: 1, @@ -938,12 +954,12 @@ var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBi }, } -func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest - response := &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ + response := &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ AdM: "", Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`), }}, @@ -962,17 +978,32 @@ func (m *mockAmpExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq return response, nil } +type mockAmpExchangeWarnings struct{} + +func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { + response := &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ + AdM: "", + Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`), + }}, + }}, + Ext: json.RawMessage(`{ "warnings": {"appnexus": [{"code": 10003, "message": "debug turned off for bidder"}] }}`), + } + return response, nil +} + func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) { - var width uint64 = 300 - var height uint64 = 300 - bidRequest := &openrtb.BidRequest{ + var width int64 = 300 + var height int64 = 300 + bidRequest := &openrtb2.BidRequest{ ID: "test-request-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "/19968336/header-bid-tag-0", Ext: json.RawMessage(`{"appnexus": { "placementId":12883451 }}`), - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ { W: width, H: 250, @@ -987,7 +1018,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, }, }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "site-id", Page: "some-page", }, @@ -1003,7 +1034,7 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, } if !nilUser { - bidRequest.User = &openrtb.User{ + bidRequest.User = &openrtb2.User{ ID: "aUserId", BuyerUID: "aBuyerID", Ext: userExtData, @@ -1020,12 +1051,11 @@ func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, } if !nilRegs { - bidRequest.Regs = &openrtb.Regs{ + bidRequest.Regs = &openrtb2.Regs{ COPPA: 1, Ext: regsExtData, } } - return json.Marshal(bidRequest) } @@ -1034,14 +1064,14 @@ func TestSetEffectiveAmpPubID(t *testing.T) { testCases := []struct { description string - req *openrtb.BidRequest + req *openrtb2.BidRequest account string expectedPubID string }{ { description: "No publisher ID provided", - req: &openrtb.BidRequest{ - App: &openrtb.App{ + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ Publisher: nil, }, }, @@ -1049,9 +1079,9 @@ func TestSetEffectiveAmpPubID(t *testing.T) { }, { description: "Publisher ID present in req.App.Publisher.ID", - req: &openrtb.BidRequest{ - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: testPubID, }, }, @@ -1060,9 +1090,9 @@ func TestSetEffectiveAmpPubID(t *testing.T) { }, { description: "Publisher ID present in req.Site.Publisher.ID", - req: &openrtb.BidRequest{ - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + req: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: testPubID, }, }, @@ -1071,9 +1101,9 @@ func TestSetEffectiveAmpPubID(t *testing.T) { }, { description: "Publisher ID present in account parameter", - req: &openrtb.BidRequest{ - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + req: &openrtb2.BidRequest{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: "", }, }, @@ -1083,9 +1113,9 @@ func TestSetEffectiveAmpPubID(t *testing.T) { }, { description: "req.Site.Publisher present but ID set to empty string", - req: &openrtb.BidRequest{ - Site: &openrtb.Site{ - Publisher: &openrtb.Publisher{ + req: &openrtb2.BidRequest{ + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ ID: "", }, }, @@ -1176,21 +1206,21 @@ func TestBuildAmpObject(t *testing.T) { expectedAmpObject: &analytics.AmpObject{ Status: http.StatusOK, Errors: nil, - Request: &openrtb.BidRequest{ + Request: &openrtb2.BidRequest{ ID: "some-request-id", - Device: &openrtb.Device{ + Device: &openrtb2.Device{ IP: "192.0.2.1", }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Page: "prebid.org", - Publisher: &openrtb.Publisher{}, + Publisher: &openrtb2.Publisher{}, Ext: json.RawMessage(`{"amp":1}`), }, - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "some-impression-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ { W: 300, H: 250, @@ -1205,9 +1235,9 @@ func TestBuildAmpObject(t *testing.T) { TMax: 500, Ext: json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":null},"vastxml":null},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"includewinners":true,"includebidderkeys":true,"includebrandcategory":null,"includeformat":false,"durationrangesec":null,"preferdeals":false}}}`), }, - AuctionResponse: &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ + AuctionResponse: &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ AdM: "", Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`), }}, diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index fa48daf30ad..bc8946fa90c 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -13,28 +13,29 @@ import ( "strconv" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/openrtb/native" - nativeRequests "github.com/PubMatic-OpenWrap/openrtb/native/request" - accountService "github.com/PubMatic-OpenWrap/prebid-server/account" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" - "github.com/PubMatic-OpenWrap/prebid-server/util/httputil" - "github.com/PubMatic-OpenWrap/prebid-server/util/iputil" "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" + "github.com/mxmCherry/openrtb/v15/native1" + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + "github.com/mxmCherry/openrtb/v15/openrtb2" + 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/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/lmt" + "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/httputil" + "github.com/prebid/prebid-server/util/iputil" "golang.org/x/net/publicsuffix" ) @@ -138,6 +139,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) { return } + warnings := errortypes.WarningOnly(errL) ctx := context.Background() @@ -178,6 +180,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http RequestType: labels.RType, StartTime: start, LegacyLabels: labels, + Warnings: warnings, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) @@ -220,8 +223,8 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http // possible, it will return errors with messages that suggest improvements. // // If the errors list has at least one element, then no guarantees are made about the returned request. -func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb.BidRequest, errs []error) { - req = &openrtb.BidRequest{} +func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb2.BidRequest, errs []error) { + req = &openrtb2.BidRequest{} errs = nil // Pull the request body into a buffer, so we have it for later usage. @@ -264,6 +267,8 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb. return } + lmt.ModifyForIOS(req) + errL := deps.validateRequest(req) if len(errL) > 0 { errs = append(errs, errL...) @@ -287,7 +292,7 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio return defaultTimeout } -func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { +func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { errL := []error{} if req.ID == "" { return []error{errors.New("request missing required field: \"id\"")} @@ -335,10 +340,6 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { if err := deps.validateEidPermissions(bidExt, aliases); err != nil { return []error{err} } - - if err := validateSChains(bidExt); err != nil { - return []error{err} - } } if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { @@ -361,11 +362,17 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { return append(errL, err) } + if err := validateDevice(req.Device); err != nil { + return append(errL, err) + } + if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil { return append(errL, err) } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); err != nil { - if _, invalidConsent := err.(*errortypes.InvalidPrivacyConsent); invalidConsent { - errL = append(errL, &errortypes.InvalidPrivacyConsent{Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err)}) + if _, invalidConsent := err.(*errortypes.Warning); invalidConsent { + errL = append(errL, &errortypes.Warning{ + Message: fmt.Sprintf("CCPA consent is invalid and will be ignored. (%v)", err), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) consentWriter := ccpa.ConsentWriter{Consent: ""} if err := consentWriter.Write(req); err != nil { return append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) @@ -394,9 +401,9 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { return errL } -func validateAndFillSourceTID(req *openrtb.BidRequest) error { +func validateAndFillSourceTID(req *openrtb2.BidRequest) error { if req.Source == nil { - req.Source = &openrtb.Source{} + req.Source = &openrtb2.Source{} } if req.Source.TID == "" { if rawUUID, err := uuid.NewV4(); err == nil { @@ -472,7 +479,7 @@ func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.Bidde return nil } -func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]string, index int) []error { +func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]string, index int) []error { if imp.ID == "" { return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)} } @@ -489,12 +496,12 @@ func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]strin return []error{err} } - if imp.Video != nil && len(imp.Video.MIMEs) < 1 { - return []error{fmt.Errorf("request.imp[%d].video.mimes must contain at least one supported MIME type", index)} + if err := validateVideo(imp.Video, index); err != nil { + return []error{err} } - if imp.Audio != nil && len(imp.Audio.MIMEs) < 1 { - return []error{fmt.Errorf("request.imp[%d].audio.mimes must contain at least one supported MIME type", index)} + if err := validateAudio(imp.Audio, index); err != nil { + return []error{err} } if err := fillAndValidateNative(imp.Native, index); err != nil { @@ -513,13 +520,22 @@ func (deps *endpointDeps) validateImp(imp *openrtb.Imp, aliases map[string]strin return nil } -func validateBanner(banner *openrtb.Banner, impIndex int) error { +func validateBanner(banner *openrtb2.Banner, impIndex int) error { if banner == nil { return nil } - // Although these are only deprecated in the spec... since this is a new endpoint, we know nobody uses them yet. - // Let's start things off by pointing callers in the right direction. + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if banner.W != nil && *banner.W < 0 { + return fmt.Errorf("request.imp[%d].banner.w must be a positive number", impIndex) + } + if banner.H != nil && *banner.H < 0 { + return fmt.Errorf("request.imp[%d].banner.h must be a positive number", impIndex) + } + + // The following fields are deprecated in the OpenRTB 2.5 spec but are still present + // in the OpenRTB library we use. Enforce they are not specified. if banner.WMin != 0 { return fmt.Errorf("request.imp[%d].banner uses unsupported property: \"wmin\". Use the \"format\" array instead.", impIndex) } @@ -538,16 +554,71 @@ func validateBanner(banner *openrtb.Banner, impIndex int) error { return fmt.Errorf("request.imp[%d].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements.", impIndex) } - for fmtIndex, format := range banner.Format { - if err := validateFormat(&format, impIndex, fmtIndex); err != nil { + for i, format := range banner.Format { + if err := validateFormat(&format, impIndex, i); err != nil { return err } } + + return nil +} + +func validateVideo(video *openrtb2.Video, impIndex int) error { + if video == nil { + return nil + } + + if len(video.MIMEs) < 1 { + return fmt.Errorf("request.imp[%d].video.mimes must contain at least one supported MIME type", impIndex) + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if video.W < 0 { + return fmt.Errorf("request.imp[%d].video.w must be a positive number", impIndex) + } + if video.H < 0 { + return fmt.Errorf("request.imp[%d].video.h must be a positive number", impIndex) + } + if video.MinBitRate < 0 { + return fmt.Errorf("request.imp[%d].video.minbitrate must be a positive number", impIndex) + } + if video.MaxBitRate < 0 { + return fmt.Errorf("request.imp[%d].video.maxbitrate must be a positive number", impIndex) + } + + return nil +} + +func validateAudio(audio *openrtb2.Audio, impIndex int) error { + if audio == nil { + return nil + } + + if len(audio.MIMEs) < 1 { + return fmt.Errorf("request.imp[%d].audio.mimes must contain at least one supported MIME type", impIndex) + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if audio.Sequence < 0 { + return fmt.Errorf("request.imp[%d].audio.sequence must be a positive number", impIndex) + } + if audio.MaxSeq < 0 { + return fmt.Errorf("request.imp[%d].audio.maxseq must be a positive number", impIndex) + } + if audio.MinBitrate < 0 { + return fmt.Errorf("request.imp[%d].audio.minbitrate must be a positive number", impIndex) + } + if audio.MaxBitrate < 0 { + return fmt.Errorf("request.imp[%d].audio.maxbitrate must be a positive number", impIndex) + } + return nil } // fillAndValidateNative validates the request, and assigns the Asset IDs as recommended by the Native v1.2 spec. -func fillAndValidateNative(n *openrtb.Native, impIndex int) error { +func fillAndValidateNative(n *openrtb2.Native, impIndex int) error { if n == nil { return nil } @@ -581,12 +652,12 @@ func fillAndValidateNative(n *openrtb.Native, impIndex int) error { return nil } -func validateNativeContextTypes(cType native.ContextType, cSubtype native.ContextSubType, impIndex int) error { +func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.ContextSubType, impIndex int) error { if cType == 0 { // Context is only recommended, so none is a valid type. return nil } - if cType < native.ContextTypeContent || cType > native.ContextTypeProduct { + if cType < native1.ContextTypeContent || cType > native1.ContextTypeProduct { return fmt.Errorf("request.imp[%d].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) } if cSubtype < 0 { @@ -599,20 +670,20 @@ func validateNativeContextTypes(cType native.ContextType, cSubtype native.Contex if cSubtype >= 500 { return fmt.Errorf("request.imp[%d].native.request.contextsubtype can't be greater than or equal to 500. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) } - if cSubtype >= native.ContextSubTypeGeneral && cSubtype <= native.ContextSubTypeUserGenerated { - if cType != native.ContextTypeContent { + if cSubtype >= native1.ContextSubTypeGeneral && cSubtype <= native1.ContextSubTypeUserGenerated { + if cType != native1.ContextTypeContent { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil } - if cSubtype >= native.ContextSubTypeSocial && cSubtype <= native.ContextSubTypeChat { - if cType != native.ContextTypeSocial { + if cSubtype >= native1.ContextSubTypeSocial && cSubtype <= native1.ContextSubTypeChat { + if cType != native1.ContextTypeSocial { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil } - if cSubtype >= native.ContextSubTypeSelling && cSubtype <= native.ContextSubTypeProductReview { - if cType != native.ContextTypeProduct { + if cSubtype >= native1.ContextSubTypeSelling && cSubtype <= native1.ContextSubTypeProductReview { + if cType != native1.ContextTypeProduct { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil @@ -621,12 +692,12 @@ func validateNativeContextTypes(cType native.ContextType, cSubtype native.Contex return fmt.Errorf("request.imp[%d].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) } -func validateNativePlacementType(pt native.PlacementType, impIndex int) error { +func validateNativePlacementType(pt native1.PlacementType, impIndex int) error { if pt == 0 { // Placement Type is only reccomended, not required. return nil } - if pt < native.PlacementTypeFeed || pt > native.PlacementTypeRecommendationWidget { + if pt < native1.PlacementTypeFeed || pt > native1.PlacementTypeRecommendationWidget { return fmt.Errorf("request.imp[%d].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex) } return nil @@ -683,7 +754,9 @@ func validateNativeAsset(asset nativeRequests.Asset, impIndex int, assetIndex in return fmt.Errorf(assetErr, impIndex, assetIndex) } foundType = true - // It is technically valid to have neither w/h nor wmin/hmin, so no check + if err := validateNativeAssetImage(asset.Img, impIndex, assetIndex); err != nil { + return err + } } if asset.Video != nil { @@ -724,20 +797,20 @@ func validateNativeEventTrackers(trackers []nativeRequests.EventTracker, impInde func validateNativeAssetTitle(title *nativeRequests.Title, impIndex int, assetIndex int) error { if title.Len < 1 { - return fmt.Errorf("request.imp[%d].native.request.assets[%d].title.len must be a positive integer", impIndex, assetIndex) + return fmt.Errorf("request.imp[%d].native.request.assets[%d].title.len must be a positive number", impIndex, assetIndex) } return nil } func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex int, eventIndex int) error { - if tracker.Event < native.EventTypeImpression || tracker.Event > native.EventTypeViewableVideo50 { + if tracker.Event < native1.EventTypeImpression || tracker.Event > native1.EventTypeViewableVideo50 { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) } if len(tracker.Methods) < 1 { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) } for methodIndex, method := range tracker.Methods { - if method < native.EventTrackingMethodImage || method > native.EventTrackingMethodJS { + if method < native1.EventTrackingMethodImage || method > native1.EventTrackingMethodJS { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].methods[%d] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex, methodIndex) } } @@ -745,6 +818,22 @@ func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex in return nil } +func validateNativeAssetImage(img *nativeRequests.Image, impIndex int, assetIndex int) error { + if img.W < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.w must be a positive integer", impIndex, assetIndex) + } + if img.H < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.h must be a positive integer", impIndex, assetIndex) + } + if img.WMin < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.wmin must be a positive integer", impIndex, assetIndex) + } + if img.HMin < 0 { + return fmt.Errorf("request.imp[%d].native.request.assets[%d].img.hmin must be a positive integer", impIndex, assetIndex) + } + return nil +} + func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIndex int) error { if len(video.MIMEs) < 1 { return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.mimes must be an array with at least one MIME type", impIndex, assetIndex) @@ -763,14 +852,14 @@ func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIn } func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex int) error { - if data.Type < native.DataAssetTypeSponsored || data.Type > native.DataAssetTypeCTAText { + if data.Type < native1.DataAssetTypeSponsored || data.Type > native1.DataAssetTypeCTAText { return fmt.Errorf("request.imp[%d].native.request.assets[%d].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex, assetIndex) } return nil } -func validateNativeVideoProtocols(protocols []native.Protocol, impIndex int, assetIndex int) error { +func validateNativeVideoProtocols(protocols []native1.Protocol, impIndex int, assetIndex int) error { if len(protocols) < 1 { return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols must be an array with at least one element", impIndex, assetIndex) } @@ -782,16 +871,35 @@ func validateNativeVideoProtocols(protocols []native.Protocol, impIndex int, ass return nil } -func validateNativeVideoProtocol(protocol native.Protocol, impIndex int, assetIndex int, protocolIndex int) error { - if protocol < native.ProtocolVAST10 || protocol > native.ProtocolDAAST10Wrapper { +func validateNativeVideoProtocol(protocol native1.Protocol, impIndex int, assetIndex int, protocolIndex int) error { + if protocol < native1.ProtocolVAST10 || protocol > native1.ProtocolDAAST10Wrapper { return fmt.Errorf("request.imp[%d].native.request.assets[%d].video.protocols[%d] is invalid. See Section 5.8: https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=52", impIndex, assetIndex, protocolIndex) } return nil } -func validateFormat(format *openrtb.Format, impIndex int, formatIndex int) error { +func validateFormat(format *openrtb2.Format, impIndex, formatIndex int) error { usesHW := format.W != 0 || format.H != 0 usesRatios := format.WMin != 0 || format.WRatio != 0 || format.HRatio != 0 + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if format.W < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].w must be a positive number", impIndex, formatIndex) + } + if format.H < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].h must be a positive number", impIndex, formatIndex) + } + if format.WRatio < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].wratio must be a positive number", impIndex, formatIndex) + } + if format.HRatio < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].hratio must be a positive number", impIndex, formatIndex) + } + if format.WMin < 0 { + return fmt.Errorf("request.imp[%d].banner.format[%d].wmin must be a positive number", impIndex, formatIndex) + } + if usesHW && usesRatios { return fmt.Errorf("Request imp[%d].banner.format[%d] should define *either* {w, h} *or* {wmin, wratio, hratio}, but not both. If both are valid, send two \"format\" objects in the request.", impIndex, formatIndex) } @@ -807,7 +915,7 @@ func validateFormat(format *openrtb.Format, impIndex int, formatIndex int) error return nil } -func validatePmp(pmp *openrtb.PMP, impIndex int) error { +func validatePmp(pmp *openrtb2.PMP, impIndex int) error { if pmp == nil { return nil } @@ -820,7 +928,7 @@ func validatePmp(pmp *openrtb.PMP, impIndex int) error { return nil } -func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]string, impIndex int) []error { +func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]string, impIndex int) []error { errL := []error{} if len(imp.Ext) == 0 { return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)} @@ -831,19 +939,15 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st return []error{err} } - // Also accept bidder exts within imp[...].ext.prebid.bidder - // NOTE: This is not part of the official API yet, so we are not expecting clients - // to migrate from imp[...].ext.${BIDDER} to imp[...].ext.prebid.bidder.${BIDDER} - // at this time - // https://github.com/PubMatic-OpenWrap/prebid-server/pull/846#issuecomment-476352224 - if rawPrebidExt, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok { - var prebidExt openrtb_ext.ExtImpPrebid - if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil && prebidExt.Bidder != nil { - for bidder, ext := range prebidExt.Bidder { + // Prefer bidder params from request.imp.ext.prebid.bidder.BIDDER over request.imp.ext.BIDDER + // to avoid confusion beteween prebid specific adapter config and other ext protocols. + if extPrebidJSON, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok { + var extPrebid openrtb_ext.ExtImpPrebid + if err := json.Unmarshal(extPrebidJSON, &extPrebid); err == nil && extPrebid.Bidder != nil { + for bidder, ext := range extPrebid.Bidder { if ext == nil { continue } - bidderExts[bidder] = ext } } @@ -851,7 +955,6 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st /* Process all the bidder exts in the request */ disabledBidders := []string{} - validationFailedBidders := []string{} otherExtElements := 0 for bidder, ext := range bidderExts { if isBidderToValidate(bidder) { @@ -861,10 +964,7 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st } if bidderName, isValid := deps.bidderMap[coreBidder]; isValid { if err := deps.paramsValidator.Validate(bidderName, ext); err != nil { - validationFailedBidders = append(validationFailedBidders, bidder) - msg := fmt.Sprintf("request.imp[%d].ext.%s failed validation.\n%v", impIndex, coreBidder, err) - glog.Errorf("BidderSchemaValidationError: %s", msg) - errL = append(errL, &errortypes.BidderFailedSchemaValidation{Message: msg}) + return []error{fmt.Errorf("request.imp[%d].ext.%s failed validation.\n%v", impIndex, coreBidder, err)} } } else { if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled { @@ -884,15 +984,6 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st for _, bidder := range disabledBidders { delete(bidderExts, bidder) } - } - - if len(validationFailedBidders) > 0 { - for _, bidder := range validationFailedBidders { - delete(bidderExts, bidder) - } - } - - if len(disabledBidders) > 0 || len(validationFailedBidders) > 0 { extJSON, err := json.Marshal(bidderExts) if err != nil { return []error{err} @@ -907,13 +998,20 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb.Imp, aliases map[string]st return errL } +// isBidderToValidate determines if the bidder name in request.imp[].prebid should be validated. func isBidderToValidate(bidder string) bool { - // PrebidExtKey is a special case for the prebid config section and is not considered a bidder. - - // FirstPartyDataContextExtKey is a special case for the first party data context section - // and is not considered a bidder. - - return bidder != openrtb_ext.PrebidExtKey && bidder != openrtb_ext.FirstPartyDataContextExtKey + switch openrtb_ext.BidderName(bidder) { + case openrtb_ext.BidderReservedContext: + return false + case openrtb_ext.BidderReservedData: + return false + case openrtb_ext.BidderReservedPrebid: + return false + case openrtb_ext.BidderReservedSKAdN: + return false + default: + return true + } } func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequest, error) { @@ -928,18 +1026,23 @@ func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequ } func (deps *endpointDeps) validateAliases(aliases map[string]string) error { - for thisAlias, coreBidder := range aliases { + 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) + } + if _, isCoreBidder := deps.bidderMap[coreBidder]; !isCoreBidder { - return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", thisAlias, coreBidder) + return fmt.Errorf("request.ext.prebid.aliases.%s refers to unknown bidder: %s", alias, coreBidder) } - if thisAlias == coreBidder { - return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", thisAlias) + + if alias == coreBidder { + return fmt.Errorf("request.ext.prebid.aliases.%s defines a no-op alias. Choose a different alias, or remove this entry.", alias) } } return nil } -func (deps *endpointDeps) validateSite(site *openrtb.Site) error { +func (deps *endpointDeps) validateSite(site *openrtb2.Site) error { if site == nil { return nil } @@ -957,7 +1060,7 @@ func (deps *endpointDeps) validateSite(site *openrtb.Site) error { return nil } -func (deps *endpointDeps) validateApp(app *openrtb.App) error { +func (deps *endpointDeps) validateApp(app *openrtb2.App) error { if app == nil { return nil } @@ -978,9 +1081,18 @@ func (deps *endpointDeps) validateApp(app *openrtb.App) error { return nil } -func (deps *endpointDeps) validateUser(user *openrtb.User, aliases map[string]string) error { - // DigiTrust support - if user != nil && user.Ext != nil { +func (deps *endpointDeps) validateUser(user *openrtb2.User, aliases map[string]string) error { + if user == nil { + return nil + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if user.Geo != nil && user.Geo.Accuracy < 0 { + return errors.New("request.user.geo.accuracy must be a positive number") + } + + if user.Ext != nil { // Creating ExtUser object to check if DigiTrust is valid var userExt openrtb_ext.ExtUser if err := json.Unmarshal(user.Ext, &userExt); err == nil { @@ -1032,7 +1144,6 @@ func (deps *endpointDeps) validateUser(user *openrtb.User, aliases map[string]st } } } else { - // Return error. return fmt.Errorf("request.user.ext object is not valid: %v", err) } } @@ -1040,7 +1151,7 @@ func (deps *endpointDeps) validateUser(user *openrtb.User, aliases map[string]st return nil } -func validateRegs(regs *openrtb.Regs) error { +func validateRegs(regs *openrtb2.Regs) error { if regs != nil && len(regs.Ext) > 0 { var regsExt openrtb_ext.ExtRegs if err := json.Unmarshal(regs.Ext, ®sExt); err != nil { @@ -1053,7 +1164,30 @@ func validateRegs(regs *openrtb.Regs) error { return nil } -func sanitizeRequest(r *openrtb.BidRequest, ipValidator iputil.IPValidator) { +func validateDevice(device *openrtb2.Device) error { + if device == nil { + return nil + } + + // The following fields were previously uints in the OpenRTB library we use, but have + // since been changed to ints. We decided to maintain the non-negative check. + if device.W < 0 { + return errors.New("request.device.w must be a positive number") + } + if device.H < 0 { + return errors.New("request.device.h must be a positive number") + } + if device.PPI < 0 { + return errors.New("request.device.ppi must be a positive number") + } + if device.Geo != nil && device.Geo.Accuracy < 0 { + return errors.New("request.device.geo.accuracy must be a positive number") + } + + return nil +} + +func sanitizeRequest(r *openrtb2.BidRequest, ipValidator iputil.IPValidator) { if r.Device != nil { if ip, ver := iputil.ParseIP(r.Device.IP); ip == nil || ver != iputil.IPv4 || !ipValidator.IsValid(ip, ver) { r.Device.IP = "" @@ -1070,7 +1204,7 @@ func sanitizeRequest(r *openrtb.BidRequest, ipValidator iputil.IPValidator) { // OpenRTB properties from the headers and other implicit info. // // This function _should not_ override any fields which were defined explicitly by the caller in the request. -func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { +func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { sanitizeRequest(bidReq, deps.privateNetworkIPValidator) setDeviceImplicitly(httpReq, bidReq, deps.privateNetworkIPValidator) @@ -1085,7 +1219,7 @@ func (deps *endpointDeps) setFieldsImplicitly(httpReq *http.Request, bidReq *ope } // setDeviceImplicitly uses implicit info from httpReq to populate bidReq.Device -func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidtor iputil.IPValidator) { +func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest, ipValidtor iputil.IPValidator) { setIPImplicitly(httpReq, bidReq, ipValidtor) setUAImplicitly(httpReq, bidReq) setDoNotTrackImplicitly(httpReq, bidReq) @@ -1094,7 +1228,7 @@ func setDeviceImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipVa // setAuctionTypeImplicitly sets the auction type to 1 if it wasn't on the request, // since header bidding is generally a first-price auction. -func setAuctionTypeImplicitly(bidReq *openrtb.BidRequest) { +func setAuctionTypeImplicitly(bidReq *openrtb2.BidRequest) { if bidReq.AT == 0 { bidReq.AT = 1 } @@ -1102,13 +1236,13 @@ func setAuctionTypeImplicitly(bidReq *openrtb.BidRequest) { } // setSiteImplicitly uses implicit info from httpReq to populate bidReq.Site -func setSiteImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { +func setSiteImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { if bidReq.Site == nil || bidReq.Site.Page == "" || bidReq.Site.Domain == "" { referrerCandidate := httpReq.Referer() if parsedUrl, err := url.Parse(referrerCandidate); err == nil { if domain, err := publicsuffix.EffectiveTLDPlusOne(parsedUrl.Host); err == nil { if bidReq.Site == nil { - bidReq.Site = &openrtb.Site{} + bidReq.Site = &openrtb2.Site{} } if bidReq.Site.Domain == "" { bidReq.Site.Domain = domain @@ -1127,7 +1261,7 @@ func setSiteImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { } } -func setImpsImplicitly(httpReq *http.Request, imps []openrtb.Imp) { +func setImpsImplicitly(httpReq *http.Request, imps []openrtb2.Imp) { secure := int8(1) for i := 0; i < len(imps); i++ { if imps[i].Secure == nil && httputil.IsSecure(httpReq) { @@ -1287,18 +1421,18 @@ func getStoredRequestId(data []byte) (string, bool, error) { } // setIPImplicitly sets the IP address on bidReq, if it's not explicitly defined and we can figure it out. -func setIPImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValidator iputil.IPValidator) { +func setIPImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest, ipValidator iputil.IPValidator) { if bidReq.Device == nil || (bidReq.Device.IP == "" && bidReq.Device.IPv6 == "") { if ip, ver := httputil.FindIP(httpReq, ipValidator); ip != nil { switch ver { case iputil.IPv4: if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} + bidReq.Device = &openrtb2.Device{} } bidReq.Device.IP = ip.String() case iputil.IPv6: if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} + bidReq.Device = &openrtb2.Device{} } bidReq.Device.IPv6 = ip.String() } @@ -1307,23 +1441,23 @@ func setIPImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest, ipValida } // setUAImplicitly sets the User Agent on bidReq, if it's not explicitly defined and it's defined on the request. -func setUAImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { +func setUAImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { if bidReq.Device == nil || bidReq.Device.UA == "" { if ua := httpReq.UserAgent(); ua != "" { if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} + bidReq.Device = &openrtb2.Device{} } bidReq.Device.UA = ua } } } -func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb.BidRequest) { +func setDoNotTrackImplicitly(httpReq *http.Request, bidReq *openrtb2.BidRequest) { if bidReq.Device == nil || bidReq.Device.DNT == nil { dnt := httpReq.Header.Get(dntKey) if dnt == "0" || dnt == "1" { if bidReq.Device == nil { - bidReq.Device = &openrtb.Device{} + bidReq.Device = &openrtb2.Device{} } switch dnt { @@ -1370,7 +1504,7 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo } // Returns the account ID for the request -func getAccountID(pub *openrtb.Publisher) string { +func getAccountID(pub *openrtb2.Publisher) string { if pub != nil { if pub.Ext != nil { var pubExt openrtb_ext.ExtPublisher diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go index 89965db0430..e55ffd11093 100644 --- a/endpoints/openrtb2/auction_benchmark_test.go +++ b/endpoints/openrtb2/auction_benchmark_test.go @@ -7,15 +7,14 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/currency" + "github.com/prebid/prebid-server/currency" - analyticsConf "github.com/PubMatic-OpenWrap/prebid-server/analytics/config" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/empty_fetcher" + analyticsConf "github.com/prebid/prebid-server/analytics/config" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" ) // dummyServer returns the header bidding test ad. This response was scraped from a real appnexus server response. @@ -60,8 +59,8 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) { server := httptest.NewServer(http.HandlerFunc(dummyServer)) defer server.Close() - var infos adapters.BidderInfos - infos["appnexus"] = adapters.BidderInfo{Capabilities: &adapters.CapabilitiesInfo{Site: &adapters.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}} + var infos config.BidderInfos + infos["appnexus"] = config.BidderInfo{Capabilities: &config.CapabilitiesInfo{Site: &config.PlatformInfo{MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}}}} paramValidator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") if err != nil { return diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 7e67770173c..75d0610cb34 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -17,20 +17,19 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - - "github.com/PubMatic-OpenWrap/openrtb" - analyticsConf "github.com/PubMatic-OpenWrap/prebid-server/analytics/config" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/util/iputil" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/stored_requests" + "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" + analyticsConf "github.com/prebid/prebid-server/analytics/config" + "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/stored_requests/backends/empty_fetcher" + "github.com/prebid/prebid-server/util/iputil" "github.com/stretchr/testify/assert" ) @@ -49,7 +48,7 @@ type testConfigValues struct { AliasJSON string `json:"aliases"` BlacklistedAccounts []string `json:"blacklistedAccts"` BlacklistedApps []string `json:"blacklistedApps"` - AdapterList []string `json:"disabledAdapters"` + DisabledAdapters []string `json:"disabledAdapters"` } func TestJsonSampleRequests(t *testing.T) { @@ -153,8 +152,8 @@ func runTestCase(t *testing.T, fileData []byte, testFile string) { } if len(test.ExpectedBidResponse) > 0 { - var expectedBidResponse openrtb.BidResponse - var actualBidResponse openrtb.BidResponse + var expectedBidResponse openrtb2.BidResponse + var actualBidResponse openrtb2.BidResponse var err error err = json.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse) @@ -230,9 +229,9 @@ func (tc *testConfigValues) getBlackListedAccountMap() map[string]bool { func (tc *testConfigValues) getAdaptersConfigMap() map[string]config.Adapter { var adaptersConfig map[string]config.Adapter - if len(tc.AdapterList) > 0 { - adaptersConfig = make(map[string]config.Adapter, len(tc.AdapterList)) - for _, adapterName := range tc.AdapterList { + if len(tc.DisabledAdapters) > 0 { + adaptersConfig = make(map[string]config.Adapter, len(tc.DisabledAdapters)) + for _, adapterName := range tc.DisabledAdapters { adaptersConfig[adapterName] = config.Adapter{Disabled: true} } } @@ -242,7 +241,7 @@ func (tc *testConfigValues) getAdaptersConfigMap() map[string]config.Adapter { // Once unmarshalled, bidResponse objects can't simply be compared with an `assert.Equalf()` call // because tests fail if the elements inside the `bidResponse.SeatBid` and `bidResponse.SeatBid.Bid` // arrays, if any, are not listed in the exact same order in the actual version and in the expected version. -func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse openrtb.BidResponse, actualBidResponse openrtb.BidResponse) { +func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse openrtb2.BidResponse, actualBidResponse openrtb2.BidResponse) { //Assert non-array BidResponse fields assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile) @@ -253,12 +252,12 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid array doesn't match expected. Test: %s\n", testFile) //Given that bidResponses have the same length, compare them in an order-independent way using maps - var actualSeatBidsMap map[string]openrtb.SeatBid = make(map[string]openrtb.SeatBid, 0) + var actualSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0) for _, seatBid := range actualBidResponse.SeatBid { actualSeatBidsMap[seatBid.Seat] = seatBid } - var expectedSeatBidsMap map[string]openrtb.SeatBid = make(map[string]openrtb.SeatBid, 0) + var expectedSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0) for _, seatBid := range expectedBidResponse.SeatBid { expectedSeatBidsMap[seatBid.Seat] = seatBid } @@ -276,76 +275,76 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o } func TestBidRequestAssert(t *testing.T) { - appnexusB1 := openrtb.Bid{ID: "appnexus-bid-1", Price: 5.00} - appnexusB2 := openrtb.Bid{ID: "appnexus-bid-2", Price: 7.00} - rubiconB1 := openrtb.Bid{ID: "rubicon-bid-1", Price: 1.50} - rubiconB2 := openrtb.Bid{ID: "rubicon-bid-2", Price: 4.00} + appnexusB1 := openrtb2.Bid{ID: "appnexus-bid-1", Price: 5.00} + appnexusB2 := openrtb2.Bid{ID: "appnexus-bid-2", Price: 7.00} + rubiconB1 := openrtb2.Bid{ID: "rubicon-bid-1", Price: 1.50} + rubiconB2 := openrtb2.Bid{ID: "rubicon-bid-2", Price: 4.00} - sampleSeatBids := []openrtb.SeatBid{ + sampleSeatBids := []openrtb2.SeatBid{ { Seat: "appnexus-bids", - Bid: []openrtb.Bid{appnexusB1, appnexusB2}, + Bid: []openrtb2.Bid{appnexusB1, appnexusB2}, }, { Seat: "rubicon-bids", - Bid: []openrtb.Bid{rubiconB1, rubiconB2}, + Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, }, } testSuites := []struct { description string - expectedBidResponse openrtb.BidResponse - actualBidResponse openrtb.BidResponse + expectedBidResponse openrtb2.BidResponse + actualBidResponse openrtb2.BidResponse }{ { "identical SeatBids, exact same SeatBid and Bid arrays order", - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, }, { "identical SeatBids but Seatbid array elements come in different order", - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, - openrtb.BidResponse{ID: "anId", BidID: "bidId", - SeatBid: []openrtb.SeatBid{ + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", + SeatBid: []openrtb2.SeatBid{ { Seat: "rubicon-bids", - Bid: []openrtb.Bid{rubiconB1, rubiconB2}, + Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, }, { Seat: "appnexus-bids", - Bid: []openrtb.Bid{appnexusB1, appnexusB2}, + Bid: []openrtb2.Bid{appnexusB1, appnexusB2}, }, }, }, }, { "SeatBids seem to be identical except for the different order of Bid array elements in one of them", - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, - openrtb.BidResponse{ID: "anId", BidID: "bidId", - SeatBid: []openrtb.SeatBid{ + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", + SeatBid: []openrtb2.SeatBid{ { Seat: "appnexus-bids", - Bid: []openrtb.Bid{appnexusB2, appnexusB1}, + Bid: []openrtb2.Bid{appnexusB2, appnexusB1}, }, { Seat: "rubicon-bids", - Bid: []openrtb.Bid{rubiconB1, rubiconB2}, + Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, }, }, }, }, { "Both SeatBid elements and bid elements come in different order", - openrtb.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, - openrtb.BidResponse{ID: "anId", BidID: "bidId", - SeatBid: []openrtb.SeatBid{ + openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, + openrtb2.BidResponse{ID: "anId", BidID: "bidId", + SeatBid: []openrtb2.SeatBid{ { Seat: "rubicon-bids", - Bid: []openrtb.Bid{rubiconB2, rubiconB1}, + Bid: []openrtb2.Bid{rubiconB2, rubiconB1}, }, { Seat: "appnexus-bids", - Bid: []openrtb.Bid{appnexusB2, appnexusB1}, + Bid: []openrtb2.Bid{appnexusB2, appnexusB1}, }, }, }, @@ -627,7 +626,7 @@ func TestExchangeError(t *testing.T) { func TestUserAgentSetting(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("User-Agent", "foo") - bidReq := &openrtb.BidRequest{} + bidReq := &openrtb2.BidRequest{} setUAImplicitly(httpReq, bidReq) @@ -643,8 +642,8 @@ func TestUserAgentSetting(t *testing.T) { func TestUserAgentOverride(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("User-Agent", "foo") - bidReq := &openrtb.BidRequest{ - Device: &openrtb.Device{ + bidReq := &openrtb2.BidRequest{ + Device: &openrtb2.Device{ UA: "bar", }, } @@ -657,7 +656,7 @@ func TestUserAgentOverride(t *testing.T) { } func TestAuctionTypeDefault(t *testing.T) { - bidReq := &openrtb.BidRequest{} + bidReq := &openrtb2.BidRequest{} setAuctionTypeImplicitly(bidReq) if bidReq.AT != 1 { @@ -758,21 +757,21 @@ func TestImplicitDNT(t *testing.T) { testCases := []struct { description string dntHeader string - request openrtb.BidRequest - expectedRequest openrtb.BidRequest + request openrtb2.BidRequest + expectedRequest openrtb2.BidRequest }{ { description: "Device Missing - Not Set In Header", dntHeader: "", - request: openrtb.BidRequest{}, - expectedRequest: openrtb.BidRequest{}, + request: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{}, }, { description: "Device Missing - Set To 0 In Header", dntHeader: "0", - request: openrtb.BidRequest{}, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &disabled, }, }, @@ -780,9 +779,9 @@ func TestImplicitDNT(t *testing.T) { { description: "Device Missing - Set To 1 In Header", dntHeader: "1", - request: openrtb.BidRequest{}, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{}, + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -790,21 +789,21 @@ func TestImplicitDNT(t *testing.T) { { description: "Not Set In Request - Not Set In Header", dntHeader: "", - request: openrtb.BidRequest{ - Device: &openrtb.Device{}, + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{}, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{}, + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{}, }, }, { description: "Not Set In Request - Set To 0 In Header", dntHeader: "0", - request: openrtb.BidRequest{ - Device: &openrtb.Device{}, + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{}, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &disabled, }, }, @@ -812,11 +811,11 @@ func TestImplicitDNT(t *testing.T) { { description: "Not Set In Request - Set To 1 In Header", dntHeader: "1", - request: openrtb.BidRequest{ - Device: &openrtb.Device{}, + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{}, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -824,13 +823,13 @@ func TestImplicitDNT(t *testing.T) { { description: "Set In Request - Not Set In Header", dntHeader: "", - request: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -838,13 +837,13 @@ func TestImplicitDNT(t *testing.T) { { description: "Set In Request - Set To 0 In Header", dntHeader: "0", - request: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -852,13 +851,13 @@ func TestImplicitDNT(t *testing.T) { { description: "Set In Request - Set To 1 In Header", dntHeader: "1", - request: openrtb.BidRequest{ - Device: &openrtb.Device{ + request: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, - expectedRequest: openrtb.BidRequest{ - Device: &openrtb.Device{ + expectedRequest: openrtb2.BidRequest{ + Device: &openrtb2.Device{ DNT: &enabled, }, }, @@ -942,11 +941,12 @@ func TestImplicitDNTEndToEnd(t *testing.T) { assert.Equal(t, test.expectedDNT, result.Device.DNT, test.description+":dnt") } } + func TestImplicitSecure(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set(http.CanonicalHeaderKey("X-Forwarded-Proto"), "https") - imps := []openrtb.Imp{ + imps := []openrtb2.Imp{ {}, {}, } @@ -961,7 +961,7 @@ func TestImplicitSecure(t *testing.T) { func TestRefererParsing(t *testing.T) { httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) httpReq.Header.Set("Referer", "http://test.mysite.com") - bidReq := &openrtb.BidRequest{} + bidReq := &openrtb2.BidRequest{} setSiteImplicitly(httpReq, bidReq) @@ -1123,8 +1123,8 @@ func TestImplicitAMPNoExt(t *testing.T) { return } - bidReq := openrtb.BidRequest{ - Site: &openrtb.Site{}, + bidReq := openrtb2.BidRequest{ + Site: &openrtb2.Site{}, } setSiteImplicitly(httpReq, &bidReq) assert.JSONEq(t, `{"amp":0}`, string(bidReq.Site.Ext)) @@ -1136,8 +1136,8 @@ func TestImplicitAMPOtherExt(t *testing.T) { return } - bidReq := openrtb.BidRequest{ - Site: &openrtb.Site{ + bidReq := openrtb2.BidRequest{ + Site: &openrtb2.Site{ Ext: json.RawMessage(`{"other":true}`), }, } @@ -1151,8 +1151,8 @@ func TestExplicitAMP(t *testing.T) { return } - bidReq := openrtb.BidRequest{ - Site: &openrtb.Site{ + bidReq := openrtb2.BidRequest{ + Site: &openrtb2.Site{ Ext: json.RawMessage(`{"amp":1}`), }, } @@ -1394,7 +1394,7 @@ func TestValidateImpExt(t *testing.T) { for _, group := range testGroups { for _, test := range group.testCases { - imp := &openrtb.Imp{Ext: test.impExt} + imp := &openrtb2.Imp{Ext: test.impExt} errs := deps.validateImpExt(imp, nil, 0) @@ -1438,20 +1438,20 @@ func TestCurrencyTrunc(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, Cur: []string{"USD", "EUR"}, @@ -1482,30 +1482,32 @@ func TestCCPAInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy": "invalid by length"}`), }, } errL := deps.validateRequest(&req) - expectedWarning := errortypes.InvalidPrivacyConsent{Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)"} + expectedWarning := errortypes.Warning{ + Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode} assert.ElementsMatch(t, errL, []error{&expectedWarning}) assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") @@ -1530,23 +1532,23 @@ func TestNoSaleInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy": "1NYN"}`), }, Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`), @@ -1581,20 +1583,20 @@ func TestValidateSourceTID(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, } @@ -1622,20 +1624,20 @@ func TestSChainInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), @@ -1660,12 +1662,12 @@ func TestGetAccountID(t *testing.T) { testCases := []struct { description string - pub *openrtb.Publisher + pub *openrtb2.Publisher expectedAccID string }{ { description: "Publisher.ID and Publisher.Ext.Prebid.ParentAccount both present", - pub: &openrtb.Publisher{ + pub: &openrtb2.Publisher{ ID: testPubID, Ext: testPubExtJSON, }, @@ -1673,7 +1675,7 @@ func TestGetAccountID(t *testing.T) { }, { description: "Only Publisher.Ext.Prebid.ParentAccount present", - pub: &openrtb.Publisher{ + pub: &openrtb2.Publisher{ ID: "", Ext: testPubExtJSON, }, @@ -1681,14 +1683,14 @@ func TestGetAccountID(t *testing.T) { }, { description: "Only Publisher.ID present", - pub: &openrtb.Publisher{ + pub: &openrtb2.Publisher{ ID: testPubID, }, expectedAccID: testPubID, }, { description: "Neither Publisher.ID or Publisher.Ext.Prebid.ParentAccount present", - pub: &openrtb.Publisher{}, + pub: &openrtb2.Publisher{}, expectedAccID: metrics.PublisherUnknown, }, { @@ -1707,15 +1709,15 @@ func TestGetAccountID(t *testing.T) { func TestSanitizeRequest(t *testing.T) { testCases := []struct { description string - req *openrtb.BidRequest + req *openrtb2.BidRequest ipValidator iputil.IPValidator expectedIPv4 string expectedIPv6 string }{ { description: "Empty", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "", IPv6: "", }, @@ -1725,8 +1727,8 @@ func TestSanitizeRequest(t *testing.T) { }, { description: "Valid", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "1.1.1.1", IPv6: "1111::", }, @@ -1737,8 +1739,8 @@ func TestSanitizeRequest(t *testing.T) { }, { description: "Invalid", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "1.1.1.1", IPv6: "1111::", }, @@ -1749,8 +1751,8 @@ func TestSanitizeRequest(t *testing.T) { }, { description: "Invalid - Wrong IP Types", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "1111::", IPv6: "1.1.1.1", }, @@ -1761,8 +1763,8 @@ func TestSanitizeRequest(t *testing.T) { }, { description: "Malformed", - req: &openrtb.BidRequest{ - Device: &openrtb.Device{ + req: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ IP: "malformed", IPv6: "malformed", }, @@ -1783,26 +1785,26 @@ func TestValidateAndFillSourceTID(t *testing.T) { testTID := "some-tid" testCases := []struct { description string - req *openrtb.BidRequest + req *openrtb2.BidRequest expectRandTID bool expectedTID string }{ { description: "req.Source not present. Expecting a randomly generated TID value", - req: &openrtb.BidRequest{}, + req: &openrtb2.BidRequest{}, expectRandTID: true, }, { description: "req.Source.TID not present. Expecting a randomly generated TID value", - req: &openrtb.BidRequest{ - Source: &openrtb.Source{}, + req: &openrtb2.BidRequest{ + Source: &openrtb2.Source{}, }, expectRandTID: true, }, { description: "req.Source.TID present. Expecting no change", - req: &openrtb.BidRequest{ - Source: &openrtb.Source{ + req: &openrtb2.BidRequest{ + Source: &openrtb2.Source{ TID: testTID, }, }, @@ -1841,20 +1843,20 @@ func TestEidPermissionsInvalid(t *testing.T) { hardcodedResponseIPValidator{response: true}, } - ui := uint64(1) - req := openrtb.BidRequest{ + ui := int64(1) + req := openrtb2.BidRequest{ ID: "anyRequestID", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "anyImpID", - Banner: &openrtb.Banner{ + Banner: &openrtb2.Banner{ W: &ui, H: &ui, }, Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "anySiteID", }, Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`), @@ -2076,34 +2078,111 @@ func TestValidateBidders(t *testing.T) { } } +func TestIOS14EndToEnd(t *testing.T) { + exchange := &nobidExchange{} + + endpoint, _ := NewEndpoint( + exchange, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: maxSize}, + newTestMetrics(), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + []byte{}, + openrtb_ext.BuildBidderMap()) + + httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "app-ios140-no-ifa.json"))) + + endpoint(httptest.NewRecorder(), httpReq, nil) + + result := exchange.gotRequest + if !assert.NotEmpty(t, result, "request received by the exchange.") { + t.FailNow() + } + + var lmtOne int8 = 1 + assert.Equal(t, &lmtOne, result.Device.Lmt) +} + +func TestAuctionWarnings(t *testing.T) { + reqBody := validRequest(t, "us-privacy-invalid.json") + deps := &endpointDeps{ + &warningsCheckExchange{}, + newParamsValidator(t), + &mockStoredReqFetcher{}, + empty_fetcher.EmptyFetcher{}, + empty_fetcher.EmptyFetcher{}, + &config.Configuration{MaxRequestSize: int64(len(reqBody))}, + newTestMetrics(), + analyticsConf.NewPBSAnalytics(&config.Analytics{}), + map[string]string{}, + false, + []byte{}, + openrtb_ext.BuildBidderMap(), + nil, + nil, + hardcodedResponseIPValidator{response: true}, + } + + req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) + recorder := httptest.NewRecorder() + + deps.Auction(recorder, req, nil) + + if recorder.Code != http.StatusOK { + t.Errorf("Endpoint should return a 200") + } + warnings := deps.ex.(*warningsCheckExchange).auctionRequest.Warnings + if !assert.Len(t, warnings, 1, "One warning should be returned from exchange") { + t.FailNow() + } + actualWarning := warnings[0].(*errortypes.Warning) + expectedMessage := "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)" + assert.Equal(t, expectedMessage, actualWarning.Message, "Warning message is incorrect") + + assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect") +} + +// warningsCheckExchange is a well-behaved exchange which stores all incoming warnings. +type warningsCheckExchange struct { + auctionRequest exchange.AuctionRequest +} + +func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { + e.auctionRequest = r + return nil, nil +} + // nobidExchange is a well-behaved exchange which always bids "no bid". type nobidExchange struct { - gotRequest *openrtb.BidRequest + gotRequest *openrtb2.BidRequest } -func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { e.gotRequest = r.BidRequest - return &openrtb.BidResponse{ + return &openrtb2.BidResponse{ ID: r.BidRequest.ID, BidID: "test bid id", - NBR: openrtb.NoBidReasonCodeUnknownError.Ptr(), + NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(), }, nil } type mockBidExchange struct { - gotRequest *openrtb.BidRequest + gotRequest *openrtb2.BidRequest } // mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext // into the bidResponse.Ext to assert the bidder adapters that were not filtered out in the validation process -func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { - bidResponse := &openrtb.BidResponse{ +func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { + bidResponse := &openrtb2.BidResponse{ ID: r.BidRequest.ID, BidID: "test bid id", - NBR: openrtb.NoBidReasonCodeUnknownError.Ptr(), + NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(), } if len(r.BidRequest.Imp) > 0 { - var SeatBidMap = make(map[string]openrtb.SeatBid, 0) + var SeatBidMap = make(map[string]openrtb2.SeatBid, 0) for _, imp := range r.BidRequest.Imp { var bidderExts map[string]json.RawMessage if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil { @@ -2126,9 +2205,9 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq for bidderNameOrAlias := range bidderExts { if isBidderToValidate(bidderNameOrAlias) { if val, ok := SeatBidMap[bidderNameOrAlias]; ok { - val.Bid = append(val.Bid, openrtb.Bid{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}) + val.Bid = append(val.Bid, openrtb2.Bid{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}) } else { - SeatBidMap[bidderNameOrAlias] = openrtb.SeatBid{Seat: fmt.Sprintf("%s-bids", bidderNameOrAlias), Bid: []openrtb.Bid{{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}}} + SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{Seat: fmt.Sprintf("%s-bids", bidderNameOrAlias), Bid: []openrtb2.Bid{{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}}} } } } @@ -2143,7 +2222,7 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq type brokenExchange struct{} -func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { return nil, errors.New("Critical, unrecoverable error.") } @@ -2506,22 +2585,22 @@ func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string) } type mockExchange struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest } -func (m *mockExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ - Bid: []openrtb.Bid{{ + return &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ + Bid: []openrtb2.Bid{{ AdM: "", }}, }}, }, nil } -func getBidderInfos(cfg map[string]config.Adapter, biddersNames []openrtb_ext.BidderName) adapters.BidderInfos { - biddersInfos := make(adapters.BidderInfos) +func getBidderInfos(cfg map[string]config.Adapter, biddersNames []openrtb_ext.BidderName) config.BidderInfos { + biddersInfos := make(config.BidderInfos) for _, name := range biddersNames { adapterConfig, ok := cfg[string(name)] if !ok { @@ -2532,14 +2611,9 @@ func getBidderInfos(cfg map[string]config.Adapter, biddersNames []openrtb_ext.Bi return biddersInfos } -func newBidderInfo(cfg config.Adapter) adapters.BidderInfo { - status := adapters.StatusActive - if cfg.Disabled == true { - status = adapters.StatusDisabled - } - - return adapters.BidderInfo{ - Status: status, +func newBidderInfo(cfg config.Adapter) config.BidderInfo { + return config.BidderInfo{ + Enabled: !cfg.Disabled, } } diff --git a/endpoints/openrtb2/ctv/combination/adslot_combination_generator.go b/endpoints/openrtb2/ctv/combination/adslot_combination_generator.go index 8778cfe55e0..81a5a923eb6 100644 --- a/endpoints/openrtb2/ctv/combination/adslot_combination_generator.go +++ b/endpoints/openrtb2/ctv/combination/adslot_combination_generator.go @@ -3,7 +3,7 @@ package combination import ( "math/big" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) //generator holds all the combinations based diff --git a/endpoints/openrtb2/ctv/combination/adslot_combination_generator_test.go b/endpoints/openrtb2/ctv/combination/adslot_combination_generator_test.go index a9c72a8a5a8..b701a7f1c1f 100644 --- a/endpoints/openrtb2/ctv/combination/adslot_combination_generator_test.go +++ b/endpoints/openrtb2/ctv/combination/adslot_combination_generator_test.go @@ -6,8 +6,8 @@ import ( "os" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/util" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/ctv/combination/combination.go b/endpoints/openrtb2/ctv/combination/combination.go index e5c5e0c4b79..4f4f1987354 100644 --- a/endpoints/openrtb2/ctv/combination/combination.go +++ b/endpoints/openrtb2/ctv/combination/combination.go @@ -8,8 +8,8 @@ package combination import ( - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/openrtb_ext" ) // ICombination ... diff --git a/endpoints/openrtb2/ctv/combination/combination_test.go b/endpoints/openrtb2/ctv/combination/combination_test.go index 029cad0819b..14f32eddcb6 100644 --- a/endpoints/openrtb2/ctv/combination/combination_test.go +++ b/endpoints/openrtb2/ctv/combination/combination_test.go @@ -3,8 +3,8 @@ package combination import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/ctv/impressions/helper.go b/endpoints/openrtb2/ctv/impressions/helper.go index 23e57a3b349..7fba95d704d 100644 --- a/endpoints/openrtb2/ctv/impressions/helper.go +++ b/endpoints/openrtb2/ctv/impressions/helper.go @@ -3,8 +3,8 @@ package impressions import ( "math" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/util" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/openrtb_ext" ) // newConfig initializes the generator instance diff --git a/endpoints/openrtb2/ctv/impressions/impression_generator.go b/endpoints/openrtb2/ctv/impressions/impression_generator.go index eb195b39f56..5488a8dd6a6 100644 --- a/endpoints/openrtb2/ctv/impressions/impression_generator.go +++ b/endpoints/openrtb2/ctv/impressions/impression_generator.go @@ -1,7 +1,7 @@ package impressions import ( - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/util" ) // generator contains Pod Minimum Duration, Pod Maximum Duration, Slot Minimum Duration and Slot Maximum Duration diff --git a/endpoints/openrtb2/ctv/impressions/impressions.go b/endpoints/openrtb2/ctv/impressions/impressions.go index ab8294afc81..f810d9fd2a8 100644 --- a/endpoints/openrtb2/ctv/impressions/impressions.go +++ b/endpoints/openrtb2/ctv/impressions/impressions.go @@ -4,8 +4,8 @@ package impressions import ( - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/util" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/openrtb_ext" ) // Algorithm indicates type of algorithms supported diff --git a/endpoints/openrtb2/ctv/impressions/maximize_for_duration.go b/endpoints/openrtb2/ctv/impressions/maximize_for_duration.go index 77cd5a334c7..b51116744b6 100644 --- a/endpoints/openrtb2/ctv/impressions/maximize_for_duration.go +++ b/endpoints/openrtb2/ctv/impressions/maximize_for_duration.go @@ -1,8 +1,8 @@ package impressions import ( - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/util" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/openrtb_ext" ) // newMaximizeForDuration Constucts the generator object from openrtb_ext.VideoAdPod diff --git a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go index 84f3304fb6d..2bb7323b0c1 100644 --- a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go +++ b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go @@ -3,8 +3,8 @@ package impressions import ( "testing" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/impressions/testdata" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/impressions/testdata" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/ctv/impressions/min_max_algorithm.go b/endpoints/openrtb2/ctv/impressions/min_max_algorithm.go index b25d0783230..0d2f43301f2 100644 --- a/endpoints/openrtb2/ctv/impressions/min_max_algorithm.go +++ b/endpoints/openrtb2/ctv/impressions/min_max_algorithm.go @@ -7,8 +7,8 @@ import ( "strings" "sync" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/util" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/openrtb_ext" ) // keyDelim used as separator in forming key of maxExpectedDurationMap diff --git a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go index 5928b430924..332e5d78e4c 100644 --- a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go +++ b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go @@ -4,7 +4,7 @@ import ( "sort" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/impressions/testdata" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/impressions/testdata" "github.com/stretchr/testify/assert" ) diff --git a/endpoints/openrtb2/ctv/response/adpod_generator copy.go.bak b/endpoints/openrtb2/ctv/response/adpod_generator copy.go.bak index 0c3fc4b1a69..df5b7e36be0 100644 --- a/endpoints/openrtb2/ctv/response/adpod_generator copy.go.bak +++ b/endpoints/openrtb2/ctv/response/adpod_generator copy.go.bak @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" ) /********************* AdPodGenerator Functions *********************/ diff --git a/endpoints/openrtb2/ctv/response/adpod_generator.go b/endpoints/openrtb2/ctv/response/adpod_generator.go index 1f5eb3d3d2b..37758f7bbf6 100644 --- a/endpoints/openrtb2/ctv/response/adpod_generator.go +++ b/endpoints/openrtb2/ctv/response/adpod_generator.go @@ -5,13 +5,13 @@ import ( "sync" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/combination" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/constant" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/util" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/combination" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/util" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" ) /********************* AdPodGenerator Functions *********************/ @@ -40,7 +40,7 @@ type highestCombination struct { //AdPodGenerator AdPodGenerator type AdPodGenerator struct { IAdPodGenerator - request *openrtb.BidRequest + request *openrtb2.BidRequest impIndex int buckets types.BidsBuckets comb combination.ICombination @@ -49,7 +49,7 @@ type AdPodGenerator struct { } //NewAdPodGenerator will generate adpod based on configuration -func NewAdPodGenerator(request *openrtb.BidRequest, impIndex int, buckets types.BidsBuckets, comb combination.ICombination, adpod *openrtb_ext.VideoAdPod, met metrics.MetricsEngine) *AdPodGenerator { +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, diff --git a/endpoints/openrtb2/ctv/response/adpod_generator_test.go b/endpoints/openrtb2/ctv/response/adpod_generator_test.go index 932de32d6e0..f387d5bbe24 100644 --- a/endpoints/openrtb2/ctv/response/adpod_generator_test.go +++ b/endpoints/openrtb2/ctv/response/adpod_generator_test.go @@ -1,13 +1,12 @@ package response import ( - "sort" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/constant" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types" "github.com/stretchr/testify/assert" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/types" ) func Test_findUniqueCombinations(t *testing.T) { @@ -28,38 +27,38 @@ func Test_findUniqueCombinations(t *testing.T) { data: [][]*types.Bid{ { { - Bid: &openrtb.Bid{ID: "3-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 6.339115524232314}, + Bid: &openrtb2.Bid{ID: "3-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 6.339115524232314}, DealTierSatisfied: true, }, { - Bid: &openrtb.Bid{ID: "4-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.532468782358357}, + Bid: &openrtb2.Bid{ID: "4-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.532468782358357}, DealTierSatisfied: true, }, { - Bid: &openrtb.Bid{ID: "7-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, + Bid: &openrtb2.Bid{ID: "7-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, DealTierSatisfied: false, }, { - Bid: &openrtb.Bid{ID: "8-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, + Bid: &openrtb2.Bid{ID: "8-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, DealTierSatisfied: false, }, }, //20 { { - Bid: &openrtb.Bid{ID: "2-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.4502433547413878}, + Bid: &openrtb2.Bid{ID: "2-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.4502433547413878}, DealTierSatisfied: true, }, { - Bid: &openrtb.Bid{ID: "1-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.329644588311827}, + Bid: &openrtb2.Bid{ID: "1-ed72b572-ba62-4220-abba-c19c0bf6346b", Price: 3.329644588311827}, DealTierSatisfied: true, }, { - Bid: &openrtb.Bid{ID: "5-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, + Bid: &openrtb2.Bid{ID: "5-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, DealTierSatisfied: false, }, { - Bid: &openrtb.Bid{ID: "6-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, + Bid: &openrtb2.Bid{ID: "6-VIDEO12-89A1-41F1-8708-978FD3C0912A", Price: 5}, DealTierSatisfied: false, }, }, //25 @@ -86,311 +85,3 @@ func Test_findUniqueCombinations(t *testing.T) { }) } } - -func TestAdPodGenerator_getMaxAdPodBid(t *testing.T) { - type fields struct { - request *openrtb.BidRequest - impIndex int - } - type args struct { - results []*highestCombination - } - tests := []struct { - name string - fields fields - args args - want *types.AdPodBid - }{ - { - name: `EmptyResults`, - fields: fields{ - request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, - impIndex: 0, - }, - args: args{ - results: nil, - }, - want: nil, - }, - { - name: `AllBidsFiltered`, - fields: fields{ - request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, - impIndex: 0, - }, - args: args{ - results: []*highestCombination{ - { - filteredBids: map[string]*filteredBid{ - `bid-1`: {bid: &types.Bid{Bid: &openrtb.Bid{ID: `bid-1`}}, reasonCode: constant.CTVRCCategoryExclusion}, - `bid-2`: {bid: &types.Bid{Bid: &openrtb.Bid{ID: `bid-2`}}, reasonCode: constant.CTVRCCategoryExclusion}, - `bid-3`: {bid: &types.Bid{Bid: &openrtb.Bid{ID: `bid-3`}}, reasonCode: constant.CTVRCCategoryExclusion}, - }, - }, - }, - }, - want: nil, - }, - { - name: `SingleResponse`, - fields: fields{ - request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, - impIndex: 0, - }, - args: args{ - results: []*highestCombination{ - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-1`}}, - {Bid: &openrtb.Bid{ID: `bid-2`}}, - {Bid: &openrtb.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: &openrtb.Bid{ID: `bid-4`}}, reasonCode: constant.CTVRCCategoryExclusion}, - }, - }, - }, - }, - want: &types.AdPodBid{ - Bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-1`}}, - {Bid: &openrtb.Bid{ID: `bid-2`}}, - {Bid: &openrtb.Bid{ID: `bid-3`}}, - }, - Cat: []string{`cat-1`, `cat-2`}, - ADomain: []string{`domain-1`, `domain-2`}, - Price: 20, - }, - }, - { - name: `MultiResponse-AllNonDealBids`, - fields: fields{ - request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, - impIndex: 0, - }, - args: args{ - results: []*highestCombination{ - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-11`}}, - }, - bidIDs: []string{`bid-11`}, - price: 10, - nDealBids: 0, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-21`}}, - }, - bidIDs: []string{`bid-21`}, - price: 20, - nDealBids: 0, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-31`}}, - }, - bidIDs: []string{`bid-31`}, - price: 10, - nDealBids: 0, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-41`}}, - }, - bidIDs: []string{`bid-41`}, - price: 15, - nDealBids: 0, - }, - }, - }, - want: &types.AdPodBid{ - Bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-21`}}, - }, - Cat: []string{}, - ADomain: []string{}, - Price: 20, - }, - }, - { - name: `MultiResponse-AllDealBids-SameCount`, - fields: fields{ - request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, - impIndex: 0, - }, - args: args{ - results: []*highestCombination{ - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-11`}}, - }, - bidIDs: []string{`bid-11`}, - price: 10, - nDealBids: 1, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-21`}}, - }, - bidIDs: []string{`bid-21`}, - price: 20, - nDealBids: 1, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-31`}}, - }, - bidIDs: []string{`bid-31`}, - price: 10, - nDealBids: 1, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-41`}}, - }, - bidIDs: []string{`bid-41`}, - price: 15, - nDealBids: 1, - }, - }, - }, - want: &types.AdPodBid{ - Bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-21`}}, - }, - Cat: []string{}, - ADomain: []string{}, - Price: 20, - }, - }, - { - name: `MultiResponse-AllDealBids-DifferentCount`, - fields: fields{ - request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, - impIndex: 0, - }, - args: args{ - results: []*highestCombination{ - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-11`}}, - }, - bidIDs: []string{`bid-11`}, - price: 10, - nDealBids: 2, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-21`}}, - }, - bidIDs: []string{`bid-21`}, - price: 20, - nDealBids: 1, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-31`}}, - }, - bidIDs: []string{`bid-31`}, - price: 10, - nDealBids: 3, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-41`}}, - }, - bidIDs: []string{`bid-41`}, - price: 15, - nDealBids: 2, - }, - }, - }, - want: &types.AdPodBid{ - Bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-31`}}, - }, - Cat: []string{}, - ADomain: []string{}, - Price: 10, - }, - }, - { - name: `MultiResponse-Mixed-DealandNonDealBids`, - fields: fields{ - request: &openrtb.BidRequest{ID: `req-1`, Imp: []openrtb.Imp{{ID: `imp-1`}}}, - impIndex: 0, - }, - args: args{ - results: []*highestCombination{ - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-11`}}, - }, - bidIDs: []string{`bid-11`}, - price: 10, - nDealBids: 2, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-21`}}, - }, - bidIDs: []string{`bid-21`}, - price: 20, - nDealBids: 0, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-31`}}, - }, - bidIDs: []string{`bid-31`}, - price: 10, - nDealBids: 3, - }, - { - bids: []*types.Bid{ - {Bid: &openrtb.Bid{ID: `bid-41`}}, - }, - bidIDs: []string{`bid-41`}, - price: 15, - nDealBids: 0, - }, - }, - }, - want: &types.AdPodBid{ - Bids: []*types.Bid{ - {Bid: &openrtb.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/types/adpod_types.go b/endpoints/openrtb2/ctv/types/adpod_types.go index a3348d7d816..6e46929547d 100644 --- a/endpoints/openrtb2/ctv/types/adpod_types.go +++ b/endpoints/openrtb2/ctv/types/adpod_types.go @@ -1,14 +1,14 @@ package types import ( - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/constant" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/openrtb_ext" ) //Bid openrtb bid object with extra parameters type Bid struct { - *openrtb.Bid + *openrtb2.Bid Duration int FilterReasonCode constant.FilterReasonCode DealTierSatisfied bool @@ -22,8 +22,8 @@ type ExtCTVBidResponse struct { //BidResponseAdPodExt object for ctv bidresponse adpod object type BidResponseAdPodExt struct { - Response openrtb.BidResponse `json:"bidresponse,omitempty"` - Config map[string]*ImpData `json:"config,omitempty"` + Response openrtb2.BidResponse `json:"bidresponse,omitempty"` + Config map[string]*ImpData `json:"config,omitempty"` } //AdPodBid combination contains ImpBid diff --git a/endpoints/openrtb2/ctv/util/util.go b/endpoints/openrtb2/ctv/util/util.go index d80c89300cf..523fcf7ed61 100644 --- a/endpoints/openrtb2/ctv/util/util.go +++ b/endpoints/openrtb2/ctv/util/util.go @@ -8,13 +8,13 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/openrtb" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/constant" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/golang/glog" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/openrtb_ext" ) func GetDurationWiseBidsBucket(bids []*types.Bid) types.BidsBuckets { @@ -93,7 +93,7 @@ func TimeTrack(start time.Time, name string) { // 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 openrtb.Bid) (string, 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) } diff --git a/endpoints/openrtb2/ctv/util/util_test.go b/endpoints/openrtb2/ctv/util/util_test.go index 0f49b04f6bb..8a351caad86 100644 --- a/endpoints/openrtb2/ctv/util/util_test.go +++ b/endpoints/openrtb2/ctv/util/util_test.go @@ -4,11 +4,11 @@ import ( "fmt" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/types" "github.com/stretchr/testify/assert" ) @@ -144,7 +144,7 @@ func TestSortByDealPriority(t *testing.T) { newBid := func(bid testbid) *types.Bid { return &types.Bid{ - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ ID: bid.id, Price: bid.price, //Ext: json.RawMessage(`{"prebid":{ "dealTierSatisfied" : ` + bid.isDealBid + ` }}`), @@ -193,7 +193,7 @@ func TestGetTargeting(t *testing.T) { for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - bid := new(openrtb.Bid) + 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 { diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go index 4e9d5b46438..c28904591c7 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -7,44 +7,42 @@ import ( "fmt" "math" "net/http" - "net/url" "sort" "strconv" "strings" "time" "github.com/PubMatic-OpenWrap/etree" - "github.com/PubMatic-OpenWrap/openrtb" - accountService "github.com/PubMatic-OpenWrap/prebid-server/account" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/events" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/combination" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/constant" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/impressions" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/response" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/types" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2/ctv/util" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" - "github.com/PubMatic-OpenWrap/prebid-server/util/iputil" "github.com/buger/jsonparser" uuid "github.com/gofrs/uuid" "github.com/golang/glog" "github.com/julienschmidt/httprouter" + "github.com/mxmCherry/openrtb/v15/openrtb2" + accountService "github.com/prebid/prebid-server/account" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/combination" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/constant" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/impressions" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/response" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/types" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/util" + "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/stored_requests" + "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/iputil" ) //CTV Specific Endpoint type ctvEndpointDeps struct { endpointDeps - request *openrtb.BidRequest + request *openrtb2.BidRequest reqExt *openrtb_ext.ExtRequestAdPod impData []*types.ImpData - videoSeats []*openrtb.SeatBid //stores pure video impression bids + videoSeats []*openrtb2.SeatBid //stores pure video impression bids impIndices map[string]int isAdPodRequest bool @@ -102,8 +100,8 @@ func NewCTVEndpoint( func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { defer util.TimeTrack(time.Now(), "CTVAuctionEndpoint") - var request *openrtb.BidRequest - var response *openrtb.BidResponse + var request *openrtb2.BidRequest + var response *openrtb2.BidResponse var err error var errL []error @@ -246,13 +244,13 @@ func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.R } } -func (deps *ctvEndpointDeps) holdAuction(request *openrtb.BidRequest, usersyncs *usersync.PBSCookie, account *config.Account, startTime time.Time) (*openrtb.BidResponse, error) { +func (deps *ctvEndpointDeps) holdAuction(request *openrtb2.BidRequest, usersyncs *usersync.PBSCookie, account *config.Account, startTime time.Time) (*openrtb2.BidResponse, error) { defer util.TimeTrack(time.Now(), fmt.Sprintf("Tid:%v CTVHoldAuction", deps.request.ID)) //Hold OpenRTB Standard Auction if len(request.Imp) == 0 { //Dummy Response Object - return &openrtb.BidResponse{ID: request.ID}, nil + return &openrtb2.BidResponse{ID: request.ID}, nil } auctionRequest := exchange.AuctionRequest{ @@ -269,7 +267,7 @@ func (deps *ctvEndpointDeps) holdAuction(request *openrtb.BidRequest, usersyncs /********************* BidRequest Processing *********************/ -func (deps *ctvEndpointDeps) init(req *openrtb.BidRequest) { +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)) @@ -412,7 +410,7 @@ func (deps *ctvEndpointDeps) validateBidRequest() (err []error) { /********************* Creating CTV BidRequest *********************/ //createBidRequest will return new bid request with all things copy from bid request except impression objects -func (deps *ctvEndpointDeps) createBidRequest(req *openrtb.BidRequest) *openrtb.BidRequest { +func (deps *ctvEndpointDeps) createBidRequest(req *openrtb2.BidRequest) *openrtb2.BidRequest { ctvRequest := *req //get configurations for all impressions @@ -441,7 +439,7 @@ func (deps *ctvEndpointDeps) getAllAdPodImpsConfigs() { } //getAdPodImpsConfigs will return number of impressions configurations within adpod -func (deps *ctvEndpointDeps) getAdPodImpsConfigs(imp *openrtb.Imp, adpod *openrtb_ext.VideoAdPod) []*types.ImpAdPodConfig { +func (deps *ctvEndpointDeps) getAdPodImpsConfigs(imp *openrtb2.Imp, adpod *openrtb_ext.VideoAdPod) []*types.ImpAdPodConfig { selectedAlgorithm := impressions.MinMaxAlgorithm labels := metrics.PodLabels{AlgorithmName: impressions.MonitorKey[selectedAlgorithm], NoOfImpressions: new(int)} @@ -465,7 +463,7 @@ func (deps *ctvEndpointDeps) getAdPodImpsConfigs(imp *openrtb.Imp, adpod *openrt } //createImpressions will create multiple impressions based on adpod configurations -func (deps *ctvEndpointDeps) createImpressions() []openrtb.Imp { +func (deps *ctvEndpointDeps) createImpressions() []openrtb2.Imp { impCount := 0 for _, imp := range deps.impData { if nil == imp.ErrorCode { @@ -478,7 +476,7 @@ func (deps *ctvEndpointDeps) createImpressions() []openrtb.Imp { } count := 0 - imps := make([]openrtb.Imp, impCount) + imps := make([]openrtb2.Imp, impCount) for index, imp := range deps.request.Imp { if nil == deps.impData[index].ErrorCode { adPodConfig := deps.impData[index].Config @@ -499,7 +497,7 @@ func (deps *ctvEndpointDeps) createImpressions() []openrtb.Imp { } //newImpression will clone existing impression object and create video object with ImpAdPodConfig. -func newImpression(imp *openrtb.Imp, config *types.ImpAdPodConfig) *openrtb.Imp { +func newImpression(imp *openrtb2.Imp, config *types.ImpAdPodConfig) *openrtb2.Imp { video := *imp.Video video.MinDuration = config.MinDuration video.MaxDuration = config.MaxDuration @@ -517,15 +515,15 @@ func newImpression(imp *openrtb.Imp, config *types.ImpAdPodConfig) *openrtb.Imp /********************* Prebid BidResponse Processing *********************/ //validateBidResponse -func (deps *ctvEndpointDeps) validateBidResponse(req *openrtb.BidRequest, resp *openrtb.BidResponse) error { +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 *openrtb.BidResponse) { - var vseat *openrtb.SeatBid +func (deps *ctvEndpointDeps) getBids(resp *openrtb2.BidResponse) { + var vseat *openrtb2.SeatBid result := make(map[string]*types.AdPodBid) for i := range resp.SeatBid { @@ -569,7 +567,7 @@ func (deps *ctvEndpointDeps) getBids(resp *openrtb.BidResponse) { if len(deps.impData[index].Config) == 0 { //adding pure video bids if vseat == nil { - vseat = &openrtb.SeatBid{ + vseat = &openrtb2.SeatBid{ Seat: seat.Seat, Group: seat.Group, Ext: seat.Ext, @@ -677,10 +675,10 @@ func (deps *ctvEndpointDeps) doAdPodExclusions() types.AdPodBids { /********************* Creating CTV BidResponse *********************/ //createBidResponse -func (deps *ctvEndpointDeps) createBidResponse(resp *openrtb.BidResponse, adpods types.AdPodBids) *openrtb.BidResponse { +func (deps *ctvEndpointDeps) createBidResponse(resp *openrtb2.BidResponse, adpods types.AdPodBids) *openrtb2.BidResponse { defer util.TimeTrack(time.Now(), fmt.Sprintf("Tid:%v createBidResponse", deps.request.ID)) - bidResp := &openrtb.BidResponse{ + bidResp := &openrtb2.BidResponse{ ID: resp.ID, Cur: resp.Cur, CustomData: resp.CustomData, @@ -692,15 +690,15 @@ func (deps *ctvEndpointDeps) createBidResponse(resp *openrtb.BidResponse, adpods return bidResp } -func (deps *ctvEndpointDeps) getBidResponseSeatBids(adpods types.AdPodBids) []openrtb.SeatBid { - seats := []openrtb.SeatBid{} +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 *openrtb.SeatBid + var adpodSeat *openrtb2.SeatBid for _, adpod := range adpods { if len(adpod.Bids) == 0 { continue @@ -709,7 +707,7 @@ func (deps *ctvEndpointDeps) getBidResponseSeatBids(adpods types.AdPodBids) []op bid := deps.getAdPodBid(adpod) if bid != nil { if nil == adpodSeat { - adpodSeat = &openrtb.SeatBid{ + adpodSeat = &openrtb2.SeatBid{ Seat: adpod.SeatName, } } @@ -723,7 +721,7 @@ func (deps *ctvEndpointDeps) getBidResponseSeatBids(adpods types.AdPodBids) []op } //getBidResponseExt will return extension object -func (deps *ctvEndpointDeps) getBidResponseExt(resp *openrtb.BidResponse) (data json.RawMessage) { +func (deps *ctvEndpointDeps) getBidResponseExt(resp *openrtb2.BidResponse) (data json.RawMessage) { var err error adpodExt := types.BidResponseAdPodExt{ @@ -789,7 +787,7 @@ func (deps *ctvEndpointDeps) getBidResponseExt(resp *openrtb.BidResponse) (data //getAdPodBid func (deps *ctvEndpointDeps) getAdPodBid(adpod *types.AdPodBid) *types.Bid { bid := types.Bid{ - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, } //TODO: Write single for loop to get all details @@ -810,7 +808,7 @@ func (deps *ctvEndpointDeps) getAdPodBid(adpod *types.AdPodBid) *types.Bid { } //getAdPodBidCreative get commulative adpod bid details -func getAdPodBidCreative(video *openrtb.Video, adpod *types.AdPodBid) *string { +func getAdPodBidCreative(video *openrtb2.Video, adpod *types.AdPodBid) *string { doc := etree.NewDocument() vast := doc.CreateElement(constant.VASTElement) sequenceNumber := 1 @@ -830,15 +828,6 @@ func getAdPodBidCreative(video *openrtb.Video, adpod *types.AdPodBid) *string { continue } - // 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 @@ -864,7 +853,6 @@ func getAdPodBidCreative(video *openrtb.Video, adpod *types.AdPodBid) *string { } vast.CreateAttr(constant.VASTVersionAttribute, constant.VASTVersionsStr[int(version)]) - bidAdM, err := doc.WriteToString() if nil != err { fmt.Printf("ERROR, %v", err.Error()) @@ -900,7 +888,7 @@ func getAdPodBidExtension(adpod *types.AdPodBid) json.RawMessage { //it will try to get the actual ad duration returned by the bidder using prebid.video.duration //if prebid.video.duration = 0 or there is error occured in determing it then //impress -func getAdDuration(bid openrtb.Bid, defaultDuration int64) int { +func getAdDuration(bid openrtb2.Bid, defaultDuration int64) int { duration, err := jsonparser.GetInt(bid.Ext, "prebid", "video", "duration") if nil != err || duration <= 0 { duration = defaultDuration @@ -908,7 +896,7 @@ func getAdDuration(bid openrtb.Bid, defaultDuration int64) int { return int(duration) } -func addTargetingKey(bid *openrtb.Bid, key openrtb_ext.TargetingKey, value string) error { +func addTargetingKey(bid *openrtb2.Bid, key openrtb_ext.TargetingKey, value string) error { if nil == bid { return errors.New("Invalid bid") } @@ -919,38 +907,3 @@ func addTargetingKey(bid *openrtb.Bid, key openrtb_ext.TargetingKey, value strin } return err } - -func adjustBidIDInVideoEventTrackers(doc *etree.Document, bid *openrtb.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()) - } - } - } - } -} diff --git a/endpoints/openrtb2/ctv_auction_test.go b/endpoints/openrtb2/ctv_auction_test.go index db3438accfb..7284f856ea3 100644 --- a/endpoints/openrtb2/ctv_auction_test.go +++ b/endpoints/openrtb2/ctv_auction_test.go @@ -2,14 +2,10 @@ package openrtb2 import ( "encoding/json" - "fmt" - "net/url" - "strings" "testing" - "github.com/PubMatic-OpenWrap/etree" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -28,7 +24,7 @@ func TestGetAdDuration(t *testing.T) { } for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - bid := openrtb.Bid{ + bid := openrtb2.Bid{ Ext: []byte(`{"prebid" : {"video" : {"duration" : ` + test.adDuration + `}}}`), } assert.Equal(t, test.expect, getAdDuration(bid, int64(test.maxAdDuration))) @@ -49,7 +45,7 @@ func TestAddTargetingKeys(t *testing.T) { } for _, test := range tests { t.Run(test.scenario, func(t *testing.T) { - bid := new(openrtb.Bid) + bid := new(openrtb2.Bid) bid.Ext = []byte(test.bidExt) key := openrtb_ext.TargetingKey(test.key) assert.Nil(t, addTargetingKey(bid, key, test.value)) @@ -60,92 +56,3 @@ func TestAddTargetingKeys(t *testing.T) { } assert.Equal(t, "Invalid bid", addTargetingKey(nil, openrtb_ext.HbCategoryDurationKey, "some value").Error()) } - -func TestAdjustBidIDInVideoEventTrackers(t *testing.T) { - type args struct { - modifiedBid *openrtb.Bid - } - type want struct { - eventURLMap map[string]string - } - - tests := []struct { - name string - args args - want want - }{ - { - name: "replace_with_custom_ctv_bid_id", - want: want{ - eventURLMap: map[string]string{ - "thirdQuartile": "https://thirdQuartile.com?operId=8&key1=value1&bidid=1-bid_123", - "complete": "https://complete.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", - "firstQuartile": "https://firstQuartile.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", - "midpoint": "https://midpoint.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", - "someevent": "https://othermacros?bidid=bid_123&abc=pqr", - }, - }, - args: args{ - modifiedBid: &openrtb.Bid{ - ID: "1-bid_123", - AdM: ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - `, - }, - }, - }, - } - for _, test := range tests { - doc := etree.NewDocument() - doc.ReadFromString(test.args.modifiedBid.AdM) - adjustBidIDInVideoEventTrackers(doc, test.args.modifiedBid) - events := doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/Linear/TrackingEvents/Tracking") - for _, event := range events { - evntName := event.SelectAttr("event").Value - expectedURL, _ := url.Parse(test.want.eventURLMap[evntName]) - expectedValues := expectedURL.Query() - actualURL, _ := url.Parse(event.Text()) - actualValues := actualURL.Query() - 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' [Event = %v]. but found %v", ev[i], k, evntName, av[i])) - } - } - - // check if operId=8 is first param - if evntName != "someevent" { - assert.True(t, strings.HasPrefix(actualURL.RawQuery, "operId=8"), "operId=8 must be first query param") - } - } - } -} diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 2f9c48fcd79..1aa2a7fc890 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -4,13 +4,13 @@ import ( "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" ) -func processInterstitials(req *openrtb.BidRequest) error { +func processInterstitials(req *openrtb2.BidRequest) error { var devExt openrtb_ext.ExtDevice unmarshalled := true for i := range req.Imp { @@ -38,8 +38,8 @@ func processInterstitials(req *openrtb.BidRequest) error { return nil } -func processInterstitialsForImp(imp *openrtb.Imp, devExt *openrtb_ext.ExtDevice, device *openrtb.Device) error { - var maxWidth, maxHeight, minWidth, minHeight uint64 +func processInterstitialsForImp(imp *openrtb2.Imp, devExt *openrtb_ext.ExtDevice, device *openrtb2.Device) error { + var maxWidth, maxHeight, minWidth, minHeight int64 if imp.Banner == nil { // custom interstitial support is only available for banner requests. return nil @@ -56,8 +56,8 @@ func processInterstitialsForImp(imp *openrtb.Imp, devExt *openrtb_ext.ExtDevice, maxWidth = device.W maxHeight = device.H } - minWidth = (maxWidth * devExt.Prebid.Interstitial.MinWidthPerc) / 100 - minHeight = (maxHeight * devExt.Prebid.Interstitial.MinHeightPerc) / 100 + minWidth = (maxWidth * int64(devExt.Prebid.Interstitial.MinWidthPerc)) / 100 + minHeight = (maxHeight * int64(devExt.Prebid.Interstitial.MinHeightPerc)) / 100 imp.Banner.Format = genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight) if len(imp.Banner.Format) == 0 { return &errortypes.BadInput{Message: fmt.Sprintf("Unable to set interstitial size list for Imp id=%s (No valid sizes between %dx%d and %dx%d)", imp.ID, minWidth, minHeight, maxWidth, maxHeight)} @@ -65,10 +65,10 @@ func processInterstitialsForImp(imp *openrtb.Imp, devExt *openrtb_ext.ExtDevice, return nil } -func genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight uint64) []openrtb.Format { +func genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight int64) []openrtb2.Format { sizes := make([]config.InterstitialSize, 0, 10) for _, size := range config.ResolvedInterstitialSizes { - if size.Width >= minWidth && size.Width <= maxWidth && size.Height >= minHeight && size.Height <= maxHeight { + if int64(size.Width) >= minWidth && int64(size.Width) <= maxWidth && int64(size.Height) >= minHeight && int64(size.Height) <= maxHeight { sizes = append(sizes, size) if len(sizes) >= 10 { // we have enough sizes @@ -76,9 +76,9 @@ func genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight uint64) []op } } } - formatList := make([]openrtb.Format, 0, len(sizes)) + formatList := make([]openrtb2.Format, 0, len(sizes)) for _, size := range sizes { - formatList = append(formatList, openrtb.Format{W: size.Width, H: size.Height}) + formatList = append(formatList, openrtb2.Format{W: int64(size.Width), H: int64(size.Height)}) } return formatList } diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index 93d310525c8..1d7ad9e3d6b 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -4,17 +4,17 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) -var request = &openrtb.BidRequest{ +var request = &openrtb2.BidRequest{ ID: "some-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "my-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ { W: 300, H: 600, @@ -25,7 +25,7 @@ var request = &openrtb.BidRequest{ Ext: json.RawMessage(`{"appnexus": {"placementId": 12883451}}`), }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ H: 640, W: 320, Ext: json.RawMessage(`{"prebid": {"interstitial": {"minwidthperc": 60, "minheightperc": 60}}}`), @@ -37,7 +37,7 @@ func TestInterstitial(t *testing.T) { if err := processInterstitials(myRequest); err != nil { t.Fatalf("Error processing interstitials: %v", err) } - targetFormat := []openrtb.Format{ + targetFormat := []openrtb2.Format{ { W: 300, H: 600, diff --git a/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json b/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json index f4379dc09a2..862393081e2 100644 --- a/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json +++ b/endpoints/openrtb2/sample-requests/disabled/bad/bad-alias.json @@ -1,7 +1,7 @@ { "description": "Request comes with an alias to a disabled bidder, we should throw error", "config": { - "disabledAdapters": ["appnexus", "rubicon"] + "disabledAdapters": ["appnexus"] }, "mockBidRequest": { "id": "some-request-id", @@ -32,5 +32,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.ext.prebid.aliases.test1 refers to unknown bidder: appnexus\n" + "expectedErrorMessage": "Invalid request: request.ext.prebid.aliases.test1 refers to disabled bidder: appnexus\n" } diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json similarity index 64% rename from endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json rename to endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json index 74dede0857f..c36ae0cd41d 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-ext-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json @@ -21,30 +21,29 @@ "appnexus": { "placementId": 12883451 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } }] }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ], - "bidid":"test bid id", - "nbr":0 + "id": "some-request-id", + "seatbid": [{ + "bid": [{ + "id": "appnexus-bid", + "impid": "", + "price": 0 + }], + "seat": "appnexus-bids" + }], + "bidid": "test bid id", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json similarity index 67% rename from endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json rename to endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json index 41461813c40..ad6298db39a 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-context-allowed-with-prebid-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json @@ -25,30 +25,29 @@ } } }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } }] }, "expectedBidResponse": { - "id":"some-request-id", - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ], - "bidid":"test bid id", - "nbr":0 + "id": "some-request-id", + "seatbid": [{ + "bid": [{ + "id": "appnexus-bid", + "impid": "", + "price": 0 + }], + "seat": "appnexus-bids" + }], + "bidid": "test bid id", + "nbr": 0 }, "expectedReturnCode": 200 -} +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json new file mode 100644 index 00000000000..4297aa7596b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-content-incompatible-subtype.json @@ -0,0 +1,25 @@ +{ + "description": "Native bid request. Context type content (1) is incompatible with 'social' subcontext types (20~29). Return error", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":1,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request" +} diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json new file mode 100644 index 00000000000..3193833658b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-product-incompatible-subtype.json @@ -0,0 +1,27 @@ +{ + "description": "Native bid request. Context type product (3) is incompatible with 'social' subcontext types (10~19). Return error", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":3,\"contextsubtype\":11,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request" +} + + diff --git a/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json b/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json new file mode 100644 index 00000000000..4ecbf4498fb --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-native/context-type-social-incompatible-subtype.json @@ -0,0 +1,27 @@ +{ + "description": "Native bid request. Context type social (2) is incompatible with 'product' subcontext types (30~39). Return error", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":2,\"contextsubtype\":31,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request" +} + + diff --git a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json new file mode 100644 index 00000000000..9b422380edf --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json @@ -0,0 +1,26 @@ +{ + "description": "Native bid request comes with a subcontext type greater than 500. Return error", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":1,\"contextsubtype\":550,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request" +} + diff --git a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json index 395e7034b0c..cdf4ef0c075 100644 --- a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json +++ b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-invalid.json @@ -1,5 +1,5 @@ { - "description": "Bid request with invalid contextsubtype value inside the native.request field in its only imp element", + "description": "Native bid request comes with a contextsubtype greater than 32, 'ContextSubTypeProductReview'", "mockBidRequest": { "id": "req-id", "site": { @@ -10,7 +10,7 @@ { "id": "some-imp", "native": { - "request": "{\"context\":1,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + "request": "{\"context\":1,\"contextsubtype\":41,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" }, "ext": { "appnexus": { diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxbitrate-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxbitrate-negative.json new file mode 100644 index 00000000000..62a96050121 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxbitrate-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative audio max bitrate.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "maxbitrate": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].audio.maxbitrate must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxseq-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxseq-negative.json new file mode 100644 index 00000000000..5eb48e0c396 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-maxseq-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative audio max sequence.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "maxseq": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].audio.maxseq must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-minbitrate-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-minbitrate-negative.json new file mode 100644 index 00000000000..304b77e2165 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-minbitrate-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative audio min bitrate.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "minbitrate": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].audio.minbitrate must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-sequence-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-sequence-negative.json new file mode 100644 index 00000000000..2c1e181d96b --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-sequence-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative audio sequence.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "audio": { + "mimes": ["audio/mp4"], + "sequence": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].audio.sequence must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-h-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-h-negative.json new file mode 100644 index 00000000000..b4ab8eefadb --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-h-negative.json @@ -0,0 +1,23 @@ +{ + "description": "Request has a negative banner height.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": -1 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.h must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-w-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-w-negative.json new file mode 100644 index 00000000000..968cc366b8f --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-w-negative.json @@ -0,0 +1,23 @@ +{ + "description": "Request has a negative banner width.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": -1, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.w must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/device-geo-accuracy-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/device-geo-accuracy-negative.json new file mode 100644 index 00000000000..ce9cf24a860 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/device-geo-accuracy-negative.json @@ -0,0 +1,30 @@ +{ + "description": "Request has a negative device geography accuracy.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "device": { + "w": 50, + "h": 50, + "geo": { + "accuracy": -1 + } + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.device.geo.accuracy must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/device-h-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/device-h-negative.json new file mode 100644 index 00000000000..12a0b6cb662 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/device-h-negative.json @@ -0,0 +1,27 @@ +{ + "description": "Request has a negative device height.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "device": { + "w": 50, + "h": -1 + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.device.h must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/device-ppi-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/device-ppi-negative.json new file mode 100644 index 00000000000..8ca2c03c198 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/device-ppi-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative PPI height.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "device": { + "w": 50, + "h": 50, + "ppi": -1 + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.device.ppi must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/device-w-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/device-w-negative.json new file mode 100644 index 00000000000..f3c17cee1f8 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/device-w-negative.json @@ -0,0 +1,27 @@ +{ + "description": "Request has a negative device width.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "device": { + "w": -1, + "h": 50 + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.device.w must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-h-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-h-negative.json new file mode 100644 index 00000000000..3d47c9a00ec --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-h-negative.json @@ -0,0 +1,20 @@ +{ + "description": "Request has a negative banner format height.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": 50, + "h": -1 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].h must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-hratio-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-hratio-negative.json new file mode 100644 index 00000000000..93832f566ae --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-hratio-negative.json @@ -0,0 +1,21 @@ +{ + "description": "Request has a negative banner format height ratio.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": 50, + "h": 50, + "hratio": -1 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].hratio must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-w-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-w-negative.json new file mode 100644 index 00000000000..7e845c63bcd --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-w-negative.json @@ -0,0 +1,20 @@ +{ + "description": "Request has a negative banner format width.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": -1, + "h": 50 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].w must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-wmin-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-wmin-negative.json new file mode 100644 index 00000000000..ed618c0f459 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-wmin-negative.json @@ -0,0 +1,21 @@ +{ + "description": "Request has a negative banner format width minimum.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": 50, + "h": 50, + "wmin": -1 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].wmin must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-wratio-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-wratio-negative.json new file mode 100644 index 00000000000..84c72b47d69 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-wratio-negative.json @@ -0,0 +1,21 @@ +{ + "description": "Request has a negative banner format width ratio.", + "mockBidRequest": { + "id": "req-id", + "imp": [{ + "id": "imp-id", + "banner": { + "format": [{ + "w": 50, + "h": 50, + "wratio": -1 + }] + } + }], + "app": { + "id": "app_001" + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].banner.format[0].wratio must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/user-geo-accuracy-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/user-geo-accuracy-negative.json new file mode 100644 index 00000000000..163a88eeb79 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/user-geo-accuracy-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative user geography accuracy.", + "mockBidRequest": { + "id": "req-id", + "site": { + "id": "some-site" + }, + "imp": [{ + "id": "imp-id", + "banner": { + "w": 50, + "h": 50 + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }], + "user": { + "geo": { + "accuracy": -1 + } + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.user.geo.accuracy must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-h-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-h-negative.json new file mode 100644 index 00000000000..2534dd626ea --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-h-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative video height.", + + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "imp-id", + "video": { + "mimes": ["video/mp4"], + "h": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].video.h must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-maxbitrate-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-maxbitrate-negative.json new file mode 100644 index 00000000000..07c1af1d9a1 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-maxbitrate-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative video max bitrate.", + + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "imp-id", + "video": { + "mimes": ["video/mp4"], + "maxbitrate": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].video.maxbitrate must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-minbitrate-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-minbitrate-negative.json new file mode 100644 index 00000000000..54ead28e8e2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-minbitrate-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative video min bitrate.", + + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "imp-id", + "video": { + "mimes": ["video/mp4"], + "minbitrate": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].video.minbitrate must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-w-negative.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-w-negative.json new file mode 100644 index 00000000000..cf50e8eddd7 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-w-negative.json @@ -0,0 +1,28 @@ +{ + "description": "Request has a negative video width.", + + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "imp-id", + "video": { + "mimes": ["video/mp4"], + "w": -1 + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12345 + } + } + } + } + }] + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: request.imp[0].video.w must be a positive number" +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json new file mode 100644 index 00000000000..dbf7b9c5e0d --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json @@ -0,0 +1,41 @@ +{ + "description": "Native bid request comes with a context type product (3) and compatible subcontext type", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":3,\"contextsubtype\":31,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedBidResponse": { + "id": "req-id", + "bidid": "test bid id", + "nbr": 0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json new file mode 100644 index 00000000000..41fb833d770 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json @@ -0,0 +1,41 @@ +{ + "description": "Native bid request comes with a context type social (2) and compatible subcontext type", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [ + { + "id": "some-imp", + "native": { + "request": "{\"context\":2,\"contextsubtype\":21,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedBidResponse": { + "id": "req-id", + "bidid": "test bid id", + "nbr": 0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json new file mode 100644 index 00000000000..e238f3c07c7 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json @@ -0,0 +1,48 @@ +{ + "description": "The imp.ext.skadn field is valid for Apple's SKAdNetwork and should be exempt from bidder name validation.", + + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "some-imp-id", + "banner": { + "format": [{ + "w": 600, + "h": 500 + }, { + "w": 300, + "h": 600 + }] + }, + "ext": { + "prebid": { + "bidder": { + "appnexus": { + "placementId": 12883451 + } + } + }, + "skadn": { + "anyMember": "anyValue" + } + } + }] + }, + "expectedBidResponse": { + "id": "some-request-id", + "seatbid": [{ + "bid": [{ + "id": "appnexus-bid", + "impid": "", + "price": 0 + }], + "seat": "appnexus-bids" + }], + "bidid": "test bid id", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/app-ios140-no-ifa.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/app-ios140-no-ifa.json new file mode 100644 index 00000000000..41c27877f4f --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/app-ios140-no-ifa.json @@ -0,0 +1,34 @@ +{ + "description": "Well Formed iOS 14.0 App Request With Unspecified LMT + Empty IFA", + "mockBidRequest": { + "id": "some-request-id", + "app": { + "id": "some-app-id" + }, + "device": { + "os": "iOS", + "osv": "14.0", + "ifa": "" + }, + "imp": [{ + "id": "my-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 600 + }] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedBidResponse": { + "id": "some-request-id", + "bidid": "test bid id", + "nbr": 0 + }, + "expectedReturnCode": 200 +} \ No newline at end of file diff --git a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json new file mode 100644 index 00000000000..2ccdfb7ccdc --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/us-privacy-invalid.json @@ -0,0 +1,52 @@ +{ + "description": "Well formed amp request with invalid CCPA consent value", + "mockBidRequest": { + "id": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "site": { + "page": "prebid.org", + "publisher": { + "id": "a3de7af2-a86a-4043-a77b-c7e86744155e" + } + }, + "source": { + "tid": "b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5" + }, + "tmax": 1000, + "imp": [ + { + "id": "/19968336/header-bid-tag-0", + "ext": { + "appnexus": { + "placementId": 12883451 + } + }, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ] + } + } + ], + "regs": { + "ext": { + "us_privacy": "{invalid}" + } + }, + "user": { + "ext": {} + } + }, + "expectedBidResponse": { + "id":"b9c97a4b-cbc4-483d-b2c4-58a19ed5cfc5", + "bidid":"test bid id", + "nbr":0 + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 1c1846a7c9d..2ab3bbb0829 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -14,24 +14,24 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/util/iputil" "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" "github.com/gofrs/uuid" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/util/iputil" - "github.com/PubMatic-OpenWrap/openrtb" - accountService "github.com/PubMatic-OpenWrap/prebid-server/account" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "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/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/usersync" ) var defaultRequestTimeout int64 = 5000 @@ -197,7 +197,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re vo.VideoRequest = videoBidReq - var bidReq = &openrtb.BidRequest{} + var bidReq = &openrtb2.BidRequest{} if deps.defaultRequest { if err := json.Unmarshal(deps.defReqJSON, bidReq); err != nil { err = fmt.Errorf("Invalid JSON in Default Request Settings: %s", err) @@ -370,13 +370,13 @@ func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo vo.Errors = append(vo.Errors, errL...) } -func (deps *endpointDeps) createImpressions(videoReq *openrtb_ext.BidRequestVideo, podErrors []PodError) ([]openrtb.Imp, []PodError) { +func (deps *endpointDeps) createImpressions(videoReq *openrtb_ext.BidRequestVideo, podErrors []PodError) ([]openrtb2.Imp, []PodError) { videoDur := videoReq.PodConfig.DurationRangeSec minDuration, maxDuration := minMax(videoDur) reqExactDur := videoReq.PodConfig.RequireExactDuration videoData := videoReq.Video - finalImpsArray := make([]openrtb.Imp, 0) + finalImpsArray := make([]openrtb2.Imp, 0) for ind, pod := range videoReq.PodConfig.Pods { //load stored impression @@ -400,7 +400,7 @@ func (deps *endpointDeps) createImpressions(videoReq *openrtb_ext.BidRequestVide } impDivNumber := numImps / len(videoDur) - impsArray := make([]openrtb.Imp, numImps) + impsArray := make([]openrtb2.Imp, numImps) for impInd := range impsArray { newImp := createImpressionTemplate(storedImp, videoData) impsArray[impInd] = newImp @@ -432,18 +432,18 @@ func max(a, b int) int { return b } -func createImpressionTemplate(imp openrtb.Imp, video *openrtb.Video) openrtb.Imp { +func createImpressionTemplate(imp openrtb2.Imp, video *openrtb2.Video) openrtb2.Imp { //for every new impression we need to have it's own copy of video object, because we customize it in further processing newVideo := *video imp.Video = &newVideo return imp } -func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb.Imp, []error) { +func (deps *endpointDeps) loadStoredImp(storedImpId string) (openrtb2.Imp, []error) { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(storedRequestTimeoutMillis)*time.Millisecond) defer cancel() - impr := openrtb.Imp{} + impr := openrtb2.Imp{} _, imp, err := deps.storedReqFetcher.FetchRequests(ctx, []string{}, []string{storedImpId}) if err != nil { return impr, err @@ -469,7 +469,7 @@ func minMax(array []int) (int, int) { return min, max } -func buildVideoResponse(bidresponse *openrtb.BidResponse, podErrors []PodError) (*openrtb_ext.BidResponseVideo, error) { +func buildVideoResponse(bidresponse *openrtb2.BidResponse, podErrors []PodError) (*openrtb_ext.BidResponseVideo, error) { adPods := make([]*openrtb_ext.AdPod, 0) anyBidsReturned := false @@ -561,7 +561,7 @@ func getVideoStoredRequestId(request []byte) (string, error) { return string(value), nil } -func mergeData(videoRequest *openrtb_ext.BidRequestVideo, bidRequest *openrtb.BidRequest) error { +func mergeData(videoRequest *openrtb_ext.BidRequestVideo, bidRequest *openrtb2.BidRequest) error { if videoRequest.Site != nil { bidRequest.Site = videoRequest.Site diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 9bcf6522ec6..9ede7147686 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -12,15 +12,15 @@ import ( "strings" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - analyticsConf "github.com/PubMatic-OpenWrap/prebid-server/analytics/config" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/empty_fetcher" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/analytics" + analyticsConf "github.com/prebid/prebid-server/analytics/config" + "github.com/prebid/prebid-server/config" + "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/backends/empty_fetcher" "github.com/stretchr/testify/assert" ) @@ -364,7 +364,7 @@ func TestVideoEndpointValidationsPositive(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) videoProtocols = append(videoProtocols, 15) videoProtocols = append(videoProtocols, 30) @@ -375,13 +375,13 @@ func TestVideoEndpointValidationsPositive(t *testing.T) { RequireExactDuration: true, Pods: pods, }, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -407,7 +407,7 @@ func TestVideoEndpointValidationsCritical(t *testing.T) { mimes = append(mimes, "") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) req := openrtb_ext.BidRequestVideo{ StoredRequestId: "", @@ -419,7 +419,7 @@ func TestVideoEndpointValidationsCritical(t *testing.T) { IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 0, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -476,7 +476,7 @@ func TestVideoEndpointValidationsPodErrors(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) videoProtocols = append(videoProtocols, 15) videoProtocols = append(videoProtocols, 30) @@ -487,13 +487,13 @@ func TestVideoEndpointValidationsPodErrors(t *testing.T) { RequireExactDuration: true, Pods: pods, }, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -546,7 +546,7 @@ func TestVideoEndpointValidationsSiteAndApp(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) videoProtocols = append(videoProtocols, 15) videoProtocols = append(videoProtocols, 30) @@ -557,16 +557,16 @@ func TestVideoEndpointValidationsSiteAndApp(t *testing.T) { RequireExactDuration: true, Pods: pods, }, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "pbs.com", }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ ID: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -604,7 +604,7 @@ func TestVideoEndpointValidationsSiteMissingRequiredField(t *testing.T) { mimes = append(mimes, "mp4") mimes = append(mimes, "") - videoProtocols := make([]openrtb.Protocol, 0) + videoProtocols := make([]openrtb2.Protocol, 0) videoProtocols = append(videoProtocols, 15) videoProtocols = append(videoProtocols, 30) @@ -615,13 +615,13 @@ func TestVideoEndpointValidationsSiteMissingRequiredField(t *testing.T) { RequireExactDuration: true, Pods: pods, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Domain: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ PrimaryAdserver: 1, }, - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: mimes, Protocols: videoProtocols, }, @@ -655,7 +655,7 @@ func TestVideoEndpointValidationsMissingVideo(t *testing.T) { }, }, }, - App: &openrtb.App{ + App: &openrtb2.App{ Bundle: "pbs.com", }, IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{ @@ -670,16 +670,16 @@ func TestVideoEndpointValidationsMissingVideo(t *testing.T) { } func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) { - openRtbBidResp := openrtb.BidResponse{} + openRtbBidResp := openrtb2.BidResponse{} podErrors := make([]PodError, 0) - seatBids := make([]openrtb.SeatBid, 0) - seatBid := openrtb.SeatBid{} + seatBids := make([]openrtb2.SeatBid, 0) + seatBid := openrtb2.SeatBid{} - bids := make([]openrtb.Bid, 0) - bid1 := openrtb.Bid{} - bid2 := openrtb.Bid{} - bid3 := openrtb.Bid{} + bids := make([]openrtb2.Bid, 0) + bid1 := openrtb2.Bid{} + bid2 := openrtb2.Bid{} + bid3 := openrtb2.Bid{} extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) @@ -708,16 +708,16 @@ func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) { } func TestVideoBuildVideoResponseMissedCacheForAllBids(t *testing.T) { - openRtbBidResp := openrtb.BidResponse{} + openRtbBidResp := openrtb2.BidResponse{} podErrors := make([]PodError, 0) - seatBids := make([]openrtb.SeatBid, 0) - seatBid := openrtb.SeatBid{} + seatBids := make([]openrtb2.SeatBid, 0) + seatBid := openrtb2.SeatBid{} - bids := make([]openrtb.Bid, 0) - bid1 := openrtb.Bid{} - bid2 := openrtb.Bid{} - bid3 := openrtb.Bid{} + bids := make([]openrtb2.Bid, 0) + bid1 := openrtb2.Bid{} + bid2 := openrtb2.Bid{} + bid3 := openrtb2.Bid{} extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_123_30s","hb_size":"1x1"}}}`) extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_456_30s","hb_size":"1x1"}}}`) @@ -742,15 +742,15 @@ func TestVideoBuildVideoResponseMissedCacheForAllBids(t *testing.T) { } func TestVideoBuildVideoResponsePodErrors(t *testing.T) { - openRtbBidResp := openrtb.BidResponse{} + openRtbBidResp := openrtb2.BidResponse{} podErrors := make([]PodError, 0, 2) - seatBids := make([]openrtb.SeatBid, 0) - seatBid := openrtb.SeatBid{} + seatBids := make([]openrtb2.SeatBid, 0) + seatBid := openrtb2.SeatBid{} - bids := make([]openrtb.Bid, 0) - bid1 := openrtb.Bid{} - bid2 := openrtb.Bid{} + bids := make([]openrtb2.Bid, 0) + bid1 := openrtb2.Bid{} + bid2 := openrtb2.Bid{} extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`) @@ -785,30 +785,30 @@ func TestVideoBuildVideoResponsePodErrors(t *testing.T) { } func TestVideoBuildVideoResponseNoBids(t *testing.T) { - openRtbBidResp := openrtb.BidResponse{} + openRtbBidResp := openrtb2.BidResponse{} podErrors := make([]PodError, 0, 0) - openRtbBidResp.SeatBid = make([]openrtb.SeatBid, 0) + openRtbBidResp.SeatBid = make([]openrtb2.SeatBid, 0) bidRespVideo, err := buildVideoResponse(&openRtbBidResp, podErrors) assert.NoError(t, err, "Error should be nil") assert.Len(t, bidRespVideo.AdPods, 0, "AdPods length should be 0") } func TestMergeOpenRTBToVideoRequest(t *testing.T) { - var bidReq = &openrtb.BidRequest{} + var bidReq = &openrtb2.BidRequest{} var videoReq = &openrtb_ext.BidRequestVideo{} - videoReq.App = &openrtb.App{ + videoReq.App = &openrtb2.App{ Domain: "test.com", Bundle: "test.bundle", } - videoReq.Site = &openrtb.Site{ + videoReq.Site = &openrtb2.Site{ Page: "site.com/index", } var dnt int8 = 4 var lmt int8 = 5 - videoReq.Device = openrtb.Device{ + videoReq.Device = openrtb2.Device{ DNT: &dnt, Lmt: &lmt, } @@ -816,11 +816,11 @@ func TestMergeOpenRTBToVideoRequest(t *testing.T) { videoReq.BCat = []string{"test1", "test2"} videoReq.BAdv = []string{"test3", "test4"} - videoReq.Regs = &openrtb.Regs{ + videoReq.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"1NYY","existing":"any","consent":"anyConsent"}`), } - videoReq.User = &openrtb.User{ + videoReq.User = &openrtb2.User{ BuyerUID: "test UID", Yob: 1980, Keywords: "test keywords", @@ -1057,27 +1057,27 @@ func TestHandleErrorDebugLog(t *testing.T) { func TestCreateImpressionTemplate(t *testing.T) { - imp := openrtb.Imp{} - imp.Video = &openrtb.Video{} - imp.Video.Protocols = []openrtb.Protocol{1, 2} + imp := openrtb2.Imp{} + imp.Video = &openrtb2.Video{} + imp.Video.Protocols = []openrtb2.Protocol{1, 2} imp.Video.MIMEs = []string{"video/mp4"} imp.Video.H = 200 imp.Video.W = 400 - imp.Video.PlaybackMethod = []openrtb.PlaybackMethod{5, 6} + imp.Video.PlaybackMethod = []openrtb2.PlaybackMethod{5, 6} - video := openrtb.Video{} - video.Protocols = []openrtb.Protocol{3, 4} + video := openrtb2.Video{} + video.Protocols = []openrtb2.Protocol{3, 4} video.MIMEs = []string{"video/flv"} video.H = 300 video.W = 0 - video.PlaybackMethod = []openrtb.PlaybackMethod{7, 8} + video.PlaybackMethod = []openrtb2.PlaybackMethod{7, 8} res := createImpressionTemplate(imp, &video) - assert.Equal(t, res.Video.Protocols, []openrtb.Protocol{3, 4}, "Incorrect video protocols") + assert.Equal(t, res.Video.Protocols, []openrtb2.Protocol{3, 4}, "Incorrect video protocols") assert.Equal(t, res.Video.MIMEs, []string{"video/flv"}, "Incorrect video MIMEs") assert.Equal(t, int(res.Video.H), 300, "Incorrect video height") assert.Equal(t, int(res.Video.W), 0, "Incorrect video width") - assert.Equal(t, res.Video.PlaybackMethod, []openrtb.PlaybackMethod{7, 8}, "Incorrect video playback method") + assert.Equal(t, res.Video.PlaybackMethod, []openrtb2.PlaybackMethod{7, 8}, "Incorrect video playback method") } func TestCCPA(t *testing.T) { @@ -1331,20 +1331,20 @@ func (cf mockVideoStoredReqFetcher) FetchRequests(ctx context.Context, requestID } type mockExchangeVideo struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest cache *mockCacheClient } -func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest if debugLog != nil && debugLog.Enabled { m.cache.called = true } ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video","dealpriority":0,"dealtiersatisfied":false},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ + return &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ Seat: "appnexus", - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ {ID: "01", ImpID: "1_0", Ext: ext}, {ID: "02", ImpID: "1_1", Ext: ext}, {ID: "03", ImpID: "1_2", Ext: ext}, @@ -1367,20 +1367,20 @@ func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r exchange.AuctionR } type mockExchangeAppendBidderNames struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest cache *mockCacheClient } -func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest if debugLog != nil && debugLog.Enabled { m.cache.called = true } ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s_appnexus","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`) - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{ + return &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{ Seat: "appnexus", - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ {ID: "01", ImpID: "1_0", Ext: ext}, {ID: "02", ImpID: "1_1", Ext: ext}, {ID: "03", ImpID: "1_2", Ext: ext}, @@ -1403,14 +1403,14 @@ func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r excha } type mockExchangeVideoNoBids struct { - lastRequest *openrtb.BidRequest + lastRequest *openrtb2.BidRequest cache *mockCacheClient } -func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb.BidResponse, error) { +func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { m.lastRequest = r.BidRequest - return &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{{}}, + return &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{{}}, }, nil } diff --git a/endpoints/setuid.go b/endpoints/setuid.go index b6832951625..4bff02acf37 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -9,13 +9,13 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/usersync" ) const ( diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 2b46fddecd0..0d68c15bea8 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -10,17 +10,17 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/gdpr" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/usersync" "github.com/stretchr/testify/assert" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" - analyticsConf "github.com/PubMatic-OpenWrap/prebid-server/analytics/config" - metricsConf "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" + analyticsConf "github.com/prebid/prebid-server/analytics/config" + metricsConf "github.com/prebid/prebid-server/metrics/config" ) func TestSetUIDEndpoint(t *testing.T) { @@ -439,7 +439,7 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string) (bool, bool, bool, error) { +func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return g.allowPI, g.allowPI, g.allowPI, nil } @@ -462,8 +462,3 @@ func (s fakeSyncer) FamilyName() string { func (s fakeSyncer) GetUsersyncInfo(privacyPolicies privacy.Policies) (*usersync.UsersyncInfo, error) { return nil, nil } - -// GDPRVendorID implements the Usersyncer interface with a no-op. -func (s fakeSyncer) GDPRVendorID() uint16 { - return 0 -} diff --git a/errortypes/aggregate.go b/errortypes/aggregate.go index d58bcea7c25..272e255c246 100644 --- a/errortypes/aggregate.go +++ b/errortypes/aggregate.go @@ -5,22 +5,22 @@ import ( "strconv" ) -// AggregateErrors represents one or more errors. -type AggregateErrors struct { +// AggregateError represents one or more errors. +type AggregateError struct { Message string Errors []error } -// NewAggregateErrors builds a AggregateErrors struct. -func NewAggregateErrors(msg string, errs []error) AggregateErrors { - return AggregateErrors{ +// NewAggregateError builds a AggregateError struct. +func NewAggregateError(msg string, errs []error) AggregateError { + return AggregateError{ Message: msg, Errors: errs, } } // Error implements the standard error interface. -func (e AggregateErrors) Error() string { +func (e AggregateError) Error() string { if len(e.Errors) == 0 { return "" } diff --git a/errortypes/aggregate_test.go b/errortypes/aggregate_test.go index 82af9b8901d..2d4ce21b493 100644 --- a/errortypes/aggregate_test.go +++ b/errortypes/aggregate_test.go @@ -7,7 +7,7 @@ import ( "github.com/influxdata/influxdb/pkg/testing/assert" ) -func TestAggregateErrors(t *testing.T) { +func TestAggregateError(t *testing.T) { var testCases = []struct { description string message string @@ -35,7 +35,7 @@ func TestAggregateErrors(t *testing.T) { } for _, test := range testCases { - err := NewAggregateErrors(test.message, test.errors) + err := NewAggregateError(test.message, test.errors) assert.Equal(t, test.expected, err.Error(), test.description) } } diff --git a/errortypes/code.go b/errortypes/code.go index 7f3833a46f1..2749b978006 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -11,13 +11,14 @@ const ( BidderTemporarilyDisabledErrorCode BlacklistedAcctErrorCode AcctRequiredErrorCode - BidderFailedSchemaValidationErrorCode ) // Defines numeric codes for well-known warnings. const ( UnknownWarningCode = 10999 InvalidPrivacyConsentWarningCode = iota + 10000 + AccountLevelDebugDisabledWarningCode + BidderLevelDebugDisabledWarningCode ) // Coder provides an error or warning code with severity. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index 353463611b7..1fed2d7da6e 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -167,7 +167,8 @@ func (err *BidderTemporarilyDisabled) Severity() Severity { // Warning is a generic non-fatal error. type Warning struct { - Message string + Message string + WarningCode int } func (err *Warning) Error() string { @@ -175,45 +176,9 @@ func (err *Warning) Error() string { } func (err *Warning) Code() int { - return UnknownWarningCode + return err.WarningCode } func (err *Warning) Severity() Severity { return SeverityWarning } - -// InvalidPrivacyConsent is a warning for when the privacy consent string is invalid and is ignored. -type InvalidPrivacyConsent struct { - Message string -} - -func (err *InvalidPrivacyConsent) Error() string { - return err.Message -} - -func (err *InvalidPrivacyConsent) Code() int { - return InvalidPrivacyConsentWarningCode -} - -func (err *InvalidPrivacyConsent) Severity() Severity { - return SeverityWarning -} - -// 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 -} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index ca6f1fb0868..e3924e5b8cc 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -1,103 +1,113 @@ package exchange import ( - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - ttx "github.com/PubMatic-OpenWrap/prebid-server/adapters/33across" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/acuityads" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adform" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adgeneration" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adhese" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adkernel" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adkernelAdn" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adman" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/admixer" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adocean" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adoppler" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adot" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adpone" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adprime" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adtarget" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adtelligent" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/advangelists" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/aja" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/amx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/applogy" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/appnexus" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/audienceNetwork" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/avocet" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/beachfront" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/beintoo" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/between" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/brightroll" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/colossus" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/connectad" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/consumable" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/conversant" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/cpmstar" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/datablocks" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/decenterads" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/deepintent" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/dmx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/emx_digital" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/engagebdr" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/eplanning" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/gamma" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/gamoshi" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/grid" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/gumgum" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/improvedigital" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/inmobi" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/invibes" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/ix" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/kidoz" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/krushmedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/kubient" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lockerdome" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/logicad" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lunamedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/marsmedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/mgid" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/mobfoxpb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/mobilefuse" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/nanointeractive" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/ninthdecimal" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/nobid" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/openx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/orbidder" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/pubmatic" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/pubnative" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/pulsepoint" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/revcontent" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/rhythmone" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/rtbhouse" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/rubicon" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/sharethrough" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/silvermob" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/smaato" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/smartadserver" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/smartrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/smartyads" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/somoaudience" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/sonobi" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/sovrn" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/spotx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/synacormedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/tappx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/telaria" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/triplelift" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/triplelift_native" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/ucfunnel" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/unruly" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/valueimpression" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/verizonmedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/visx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/vrtcal" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/yeahmobi" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/yieldlab" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/yieldmo" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/yieldone" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/zeroclickfraud" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters" + ttx "github.com/prebid/prebid-server/adapters/33across" + "github.com/prebid/prebid-server/adapters/acuityads" + "github.com/prebid/prebid-server/adapters/adform" + "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/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/adtarget" + "github.com/prebid/prebid-server/adapters/adtelligent" + "github.com/prebid/prebid-server/adapters/advangelists" + "github.com/prebid/prebid-server/adapters/adxcg" + "github.com/prebid/prebid-server/adapters/adyoulike" + "github.com/prebid/prebid-server/adapters/aja" + "github.com/prebid/prebid-server/adapters/amx" + "github.com/prebid/prebid-server/adapters/applogy" + "github.com/prebid/prebid-server/adapters/appnexus" + "github.com/prebid/prebid-server/adapters/audienceNetwork" + "github.com/prebid/prebid-server/adapters/avocet" + "github.com/prebid/prebid-server/adapters/beachfront" + "github.com/prebid/prebid-server/adapters/beintoo" + "github.com/prebid/prebid-server/adapters/between" + "github.com/prebid/prebid-server/adapters/bidmachine" + "github.com/prebid/prebid-server/adapters/brightroll" + "github.com/prebid/prebid-server/adapters/colossus" + "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/datablocks" + "github.com/prebid/prebid-server/adapters/decenterads" + "github.com/prebid/prebid-server/adapters/deepintent" + "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/emx_digital" + "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/gamma" + "github.com/prebid/prebid-server/adapters/gamoshi" + "github.com/prebid/prebid-server/adapters/grid" + "github.com/prebid/prebid-server/adapters/gumgum" + "github.com/prebid/prebid-server/adapters/improvedigital" + "github.com/prebid/prebid-server/adapters/inmobi" + "github.com/prebid/prebid-server/adapters/invibes" + "github.com/prebid/prebid-server/adapters/ix" + "github.com/prebid/prebid-server/adapters/jixie" + "github.com/prebid/prebid-server/adapters/kidoz" + "github.com/prebid/prebid-server/adapters/krushmedia" + "github.com/prebid/prebid-server/adapters/kubient" + "github.com/prebid/prebid-server/adapters/lockerdome" + "github.com/prebid/prebid-server/adapters/logicad" + "github.com/prebid/prebid-server/adapters/lunamedia" + "github.com/prebid/prebid-server/adapters/marsmedia" + "github.com/prebid/prebid-server/adapters/mgid" + "github.com/prebid/prebid-server/adapters/mobfoxpb" + "github.com/prebid/prebid-server/adapters/mobilefuse" + "github.com/prebid/prebid-server/adapters/nanointeractive" + "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/openx" + "github.com/prebid/prebid-server/adapters/orbidder" + "github.com/prebid/prebid-server/adapters/outbrain" + "github.com/prebid/prebid-server/adapters/pangle" + "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/revcontent" + "github.com/prebid/prebid-server/adapters/rhythmone" + "github.com/prebid/prebid-server/adapters/rtbhouse" + "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sharethrough" + "github.com/prebid/prebid-server/adapters/silvermob" + "github.com/prebid/prebid-server/adapters/smaato" + "github.com/prebid/prebid-server/adapters/smartadserver" + "github.com/prebid/prebid-server/adapters/smartrtb" + "github.com/prebid/prebid-server/adapters/smartyads" + "github.com/prebid/prebid-server/adapters/somoaudience" + "github.com/prebid/prebid-server/adapters/sonobi" + "github.com/prebid/prebid-server/adapters/sovrn" + "github.com/prebid/prebid-server/adapters/spotx" + "github.com/prebid/prebid-server/adapters/synacormedia" + "github.com/prebid/prebid-server/adapters/tappx" + "github.com/prebid/prebid-server/adapters/telaria" + "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/unicorn" + "github.com/prebid/prebid-server/adapters/unruly" + "github.com/prebid/prebid-server/adapters/valueimpression" + "github.com/prebid/prebid-server/adapters/verizonmedia" + "github.com/prebid/prebid-server/adapters/visx" + "github.com/prebid/prebid-server/adapters/vrtcal" + "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/openrtb_ext" ) // Adapter registration is kept in this separate file for ease of use and to aid @@ -122,6 +132,8 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderAdtarget: adtarget.Builder, openrtb_ext.BidderAdtelligent: adtelligent.Builder, openrtb_ext.BidderAdvangelists: advangelists.Builder, + openrtb_ext.BidderAdxcg: adxcg.Builder, + openrtb_ext.BidderAdyoulike: adyoulike.Builder, openrtb_ext.BidderAJA: aja.Builder, openrtb_ext.BidderAMX: amx.Builder, openrtb_ext.BidderApplogy: applogy.Builder, @@ -131,12 +143,14 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderBeachfront: beachfront.Builder, openrtb_ext.BidderBeintoo: beintoo.Builder, openrtb_ext.BidderBetween: between.Builder, + openrtb_ext.BidderBidmachine: bidmachine.Builder, openrtb_ext.BidderBrightroll: brightroll.Builder, openrtb_ext.BidderColossus: colossus.Builder, openrtb_ext.BidderConnectAd: connectad.Builder, openrtb_ext.BidderConsumable: consumable.Builder, openrtb_ext.BidderConversant: conversant.Builder, openrtb_ext.BidderCpmstar: cpmstar.Builder, + openrtb_ext.BidderCriteo: criteo.Builder, openrtb_ext.BidderDatablocks: datablocks.Builder, openrtb_ext.BidderDecenterAds: decenterads.Builder, openrtb_ext.BidderDeepintent: deepintent.Builder, @@ -144,6 +158,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderEmxDigital: emx_digital.Builder, openrtb_ext.BidderEngageBDR: engagebdr.Builder, openrtb_ext.BidderEPlanning: eplanning.Builder, + openrtb_ext.BidderEpom: epom.Builder, openrtb_ext.BidderGamma: gamma.Builder, openrtb_ext.BidderGamoshi: gamoshi.Builder, openrtb_ext.BidderGrid: grid.Builder, @@ -152,6 +167,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderInMobi: inmobi.Builder, openrtb_ext.BidderInvibes: invibes.Builder, openrtb_ext.BidderIx: ix.Builder, + openrtb_ext.BidderJixie: jixie.Builder, openrtb_ext.BidderKidoz: kidoz.Builder, openrtb_ext.BidderKrushmedia: krushmedia.Builder, openrtb_ext.BidderKubient: kubient.Builder, @@ -166,8 +182,11 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, openrtb_ext.BidderNoBid: nobid.Builder, + openrtb_ext.BidderOneTag: onetag.Builder, openrtb_ext.BidderOpenx: openx.Builder, openrtb_ext.BidderOrbidder: orbidder.Builder, + openrtb_ext.BidderOutbrain: outbrain.Builder, + openrtb_ext.BidderPangle: pangle.Builder, openrtb_ext.BidderPubmatic: pubmatic.Builder, openrtb_ext.BidderPubnative: pubnative.Builder, openrtb_ext.BidderPulsepoint: pulsepoint.Builder, @@ -190,7 +209,9 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderTelaria: telaria.Builder, openrtb_ext.BidderTriplelift: triplelift.Builder, openrtb_ext.BidderTripleliftNative: triplelift_native.Builder, + openrtb_ext.BidderTrustX: grid.Builder, openrtb_ext.BidderUcfunnel: ucfunnel.Builder, + openrtb_ext.BidderUnicorn: unicorn.Builder, openrtb_ext.BidderUnruly: unruly.Builder, openrtb_ext.BidderValueImpression: valueimpression.Builder, openrtb_ext.BidderVerizonMedia: verizonmedia.Builder, diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 4cd9c6ddafd..8af6d11ad60 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -4,15 +4,15 @@ import ( "fmt" "net/http" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" + "github.com/prebid/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lifestreet" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/lifestreet" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) -func BuildAdapters(client *http.Client, cfg *config.Configuration, infos adapters.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) { +func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) { exchangeBidders := buildExchangeBiddersLegacy(cfg.Adapters, infos) exchangeBiddersModern, errs := buildExchangeBidders(cfg, infos, client, me) @@ -30,7 +30,7 @@ func BuildAdapters(client *http.Client, cfg *config.Configuration, infos adapter return exchangeBidders, nil } -func buildExchangeBidders(cfg *config.Configuration, infos adapters.BidderInfos, client *http.Client, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) { +func buildExchangeBidders(cfg *config.Configuration, infos config.BidderInfos, client *http.Client, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) { bidders, errs := buildBidders(cfg.Adapters, infos, newAdapterBuilders()) if len(errs) > 0 { return nil, errs @@ -50,7 +50,7 @@ func buildExchangeBidders(cfg *config.Configuration, infos adapters.BidderInfos, } -func buildBidders(adapterConfig map[string]config.Adapter, infos adapters.BidderInfos, builders map[openrtb_ext.BidderName]adapters.Builder) (map[openrtb_ext.BidderName]adapters.Bidder, []error) { +func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderInfos, builders map[openrtb_ext.BidderName]adapters.Builder) (map[openrtb_ext.BidderName]adapters.Bidder, []error) { bidders := make(map[openrtb_ext.BidderName]adapters.Bidder) var errs []error @@ -78,14 +78,14 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos adapters.Bidder continue } - if info.Status == adapters.StatusActive { + if info.Enabled { bidderInstance, builderErr := builder(bidderName, cfg) if builderErr != nil { errs = append(errs, fmt.Errorf("%v: %v", bidder, builderErr)) continue } - bidderWithInfoEnforcement := adapters.EnforceBidderInfo(bidderInstance, info) + bidderWithInfoEnforcement := adapters.BuildInfoAwareBidder(bidderInstance, info) bidders[bidderName] = bidderWithInfoEnforcement } @@ -94,11 +94,11 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos adapters.Bidder return bidders, errs } -func buildExchangeBiddersLegacy(adapterConfig map[string]config.Adapter, infos adapters.BidderInfos) map[openrtb_ext.BidderName]adaptedBidder { +func buildExchangeBiddersLegacy(adapterConfig map[string]config.Adapter, infos config.BidderInfos) map[openrtb_ext.BidderName]adaptedBidder { bidders := make(map[openrtb_ext.BidderName]adaptedBidder, 2) // Lifestreet - if infos[string(openrtb_ext.BidderLifestreet)].Status == adapters.StatusActive { + if infos[string(openrtb_ext.BidderLifestreet)].Enabled { adapter := lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, adapterConfig[string(openrtb_ext.BidderLifestreet)].Endpoint) bidders[openrtb_ext.BidderLifestreet] = adaptLegacyAdapter(adapter) } @@ -113,11 +113,11 @@ func wrapWithMiddleware(bidders map[openrtb_ext.BidderName]adaptedBidder) { } // GetActiveBidders returns a map of all active bidder names. -func GetActiveBidders(infos adapters.BidderInfos) map[string]openrtb_ext.BidderName { +func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderName { activeBidders := make(map[string]openrtb_ext.BidderName) for name, info := range infos { - if info.Status != adapters.StatusDisabled { + if info.Enabled { activeBidders[name] = openrtb_ext.BidderName(name) } } @@ -126,13 +126,13 @@ func GetActiveBidders(infos adapters.BidderInfos) map[string]openrtb_ext.BidderN } // GetDisabledBiddersErrorMessages returns a map of error messages for disabled bidders. -func GetDisabledBiddersErrorMessages(infos adapters.BidderInfos) map[string]string { +func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string { disabledBidders := make(map[string]string) - for bidderName, bidderInfo := range infos { - if bidderInfo.Status == adapters.StatusDisabled { - msg := fmt.Sprintf(`Bidder "%s" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, bidderName) - disabledBidders[bidderName] = msg + for name, info := range infos { + if !info.Enabled { + msg := fmt.Sprintf(`Bidder "%s" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, name) + disabledBidders[name] = msg } } diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index 05cdd7e9648..c9f1907d314 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -6,22 +6,21 @@ import ( "net/http" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/appnexus" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lifestreet" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/rubicon" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - metrics "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/appnexus" + "github.com/prebid/prebid-server/adapters/lifestreet" + "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" + metrics "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) var ( - infoActive = adapters.BidderInfo{Status: adapters.StatusActive} - infoDisabled = adapters.BidderInfo{Status: adapters.StatusDisabled} - infoUnknown = adapters.BidderInfo{Status: adapters.StatusUnknown} + infoEnabled = config.BidderInfo{Enabled: true} + infoDisabled = config.BidderInfo{Enabled: false} ) func TestBuildAdaptersSuccess(t *testing.T) { @@ -30,16 +29,16 @@ func TestBuildAdaptersSuccess(t *testing.T) { "appnexus": {}, "lifestreet": {Endpoint: "anyEndpoint"}, }} - infos := map[string]adapters.BidderInfo{ - "appnexus": infoActive, - "lifestreet": infoActive, + infos := map[string]config.BidderInfo{ + "appnexus": infoEnabled, + "lifestreet": infoEnabled, } metricEngine := &metrics.DummyMetricsEngine{} bidders, errs := BuildAdapters(client, cfg, infos, metricEngine) appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}) - appnexusBidderWithInfo := adapters.EnforceBidderInfo(appnexusBidder, infoActive) + appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled) appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil) appnexusBidderValidated := addValidatedBidderMiddleware(appnexusBidderAdapted) @@ -58,7 +57,7 @@ func TestBuildAdaptersSuccess(t *testing.T) { func TestBuildAdaptersErrors(t *testing.T) { client := &http.Client{} cfg := &config.Configuration{Adapters: map[string]config.Adapter{"unknown": {}}} - infos := map[string]adapters.BidderInfo{} + infos := map[string]config.BidderInfo{} metricEngine := &metrics.DummyMetricsEngine{} bidders, errs := BuildAdapters(client, cfg, infos, metricEngine) @@ -76,24 +75,24 @@ func TestBuildExchangeBidders(t *testing.T) { metricEngine := &metrics.DummyMetricsEngine{} appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}) - appnexusBidderWithInfo := adapters.EnforceBidderInfo(appnexusBidder, infoActive) + appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled) appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil) rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{}) - rubiconBidderWithInfo := adapters.EnforceBidderInfo(rubiconBidder, infoActive) + rubiconBidderWithInfo := adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled) rubiconBidderAdapted := adaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil) testCases := []struct { description string adapterConfig map[string]config.Adapter - bidderInfos map[string]adapters.BidderInfo + bidderInfos map[string]config.BidderInfo expectedBidders map[openrtb_ext.BidderName]adaptedBidder expectedErrors []error }{ { description: "Invalid - Builder Errors", adapterConfig: map[string]config.Adapter{"appnexus": {}, "unknown": {}}, - bidderInfos: map[string]adapters.BidderInfo{}, + bidderInfos: map[string]config.BidderInfo{}, expectedErrors: []error{ errors.New("appnexus: bidder info not found"), errors.New("unknown: unknown bidder"), @@ -102,13 +101,13 @@ func TestBuildExchangeBidders(t *testing.T) { { description: "Success - None", adapterConfig: map[string]config.Adapter{}, - bidderInfos: map[string]adapters.BidderInfo{}, + bidderInfos: map[string]config.BidderInfo{}, expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{}, }, { description: "Success - One", adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{ openrtb_ext.BidderAppnexus: appnexusBidderAdapted, }, @@ -116,7 +115,7 @@ func TestBuildExchangeBidders(t *testing.T) { { description: "Success - Many", adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive, "rubicon": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled}, expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{ openrtb_ext.BidderAppnexus: appnexusBidderAdapted, openrtb_ext.BidderRubicon: rubiconBidderAdapted, @@ -145,7 +144,7 @@ func TestBuildBidders(t *testing.T) { testCases := []struct { description string adapterConfig map[string]config.Adapter - bidderInfos map[string]adapters.BidderInfo + bidderInfos map[string]config.BidderInfo builders map[openrtb_ext.BidderName]adapters.Builder expectedBidders map[openrtb_ext.BidderName]adapters.Bidder expectedErrors []error @@ -153,7 +152,7 @@ func TestBuildBidders(t *testing.T) { { description: "Invalid - Unknown Bidder", adapterConfig: map[string]config.Adapter{"unknown": {}}, - bidderInfos: map[string]adapters.BidderInfo{"unknown": infoActive}, + bidderInfos: map[string]config.BidderInfo{"unknown": infoEnabled}, builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, expectedErrors: []error{ errors.New("unknown: unknown bidder"), @@ -162,7 +161,7 @@ func TestBuildBidders(t *testing.T) { { description: "Invalid - No Bidder Info", adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]adapters.BidderInfo{}, + bidderInfos: map[string]config.BidderInfo{}, builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, expectedErrors: []error{ errors.New("appnexus: bidder info not found"), @@ -171,7 +170,7 @@ func TestBuildBidders(t *testing.T) { { description: "Invalid - No Builder", adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, builders: map[openrtb_ext.BidderName]adapters.Builder{}, expectedErrors: []error{ errors.New("appnexus: builder not registered"), @@ -180,7 +179,7 @@ func TestBuildBidders(t *testing.T) { { description: "Success - Builder Error", adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilderWithError}, expectedErrors: []error{ errors.New("appnexus: anyError"), @@ -189,62 +188,53 @@ func TestBuildBidders(t *testing.T) { { description: "Success - None", adapterConfig: map[string]config.Adapter{}, - bidderInfos: map[string]adapters.BidderInfo{}, + bidderInfos: map[string]config.BidderInfo{}, builders: map[openrtb_ext.BidderName]adapters.Builder{}, }, { description: "Success - One", adapterConfig: map[string]config.Adapter{"appnexus": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ - openrtb_ext.BidderAppnexus: adapters.EnforceBidderInfo(appnexusBidder, infoActive), + openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled), }, }, { description: "Success - Many", adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive, "rubicon": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled}, builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderRubicon: rubiconBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ - openrtb_ext.BidderAppnexus: adapters.EnforceBidderInfo(appnexusBidder, infoActive), - openrtb_ext.BidderRubicon: adapters.EnforceBidderInfo(rubiconBidder, infoActive), + openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled), + openrtb_ext.BidderRubicon: adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled), }, }, { description: "Success - Ignores Legacy", adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive, "lifestreet": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "lifestreet": infoEnabled}, builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ - openrtb_ext.BidderAppnexus: adapters.EnforceBidderInfo(appnexusBidder, infoActive), + openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled), }, }, { description: "Success - Ignores Disabled", adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoDisabled, "rubicon": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "rubicon": infoEnabled}, builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderRubicon: rubiconBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ - openrtb_ext.BidderRubicon: adapters.EnforceBidderInfo(rubiconBidder, infoActive), - }, - }, - { - description: "Success - Ignores Unknown State", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoUnknown, "rubicon": infoActive}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderRubicon: rubiconBuilder}, - expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ - openrtb_ext.BidderRubicon: adapters.EnforceBidderInfo(rubiconBidder, infoActive), + openrtb_ext.BidderRubicon: adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled), }, }, { description: "Success - Ignores Adapter Config Case", adapterConfig: map[string]config.Adapter{"AppNexus": {}}, - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder}, expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ - openrtb_ext.BidderAppnexus: adapters.EnforceBidderInfo(appnexusBidder, infoActive), + openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled), }, }, } @@ -270,25 +260,19 @@ func TestBuildExchangeBiddersLegacy(t *testing.T) { testCases := []struct { description string adapterConfig map[string]config.Adapter - bidderInfos map[string]adapters.BidderInfo + bidderInfos map[string]config.BidderInfo expected map[openrtb_ext.BidderName]adaptedBidder }{ { description: "Active", adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoActive}, + bidderInfos: map[string]config.BidderInfo{"lifestreet": infoEnabled}, expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet}, }, { description: "Disabled", adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoDisabled}, - expected: map[openrtb_ext.BidderName]adaptedBidder{}, - }, - { - description: "Unknown", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, - bidderInfos: map[string]adapters.BidderInfo{"lifestreet": infoUnknown}, + bidderInfos: map[string]config.BidderInfo{"lifestreet": infoDisabled}, expected: map[openrtb_ext.BidderName]adaptedBidder{}, }, } @@ -318,33 +302,28 @@ func TestWrapWithMiddleware(t *testing.T) { func TestGetActiveBidders(t *testing.T) { testCases := []struct { description string - bidderInfos map[string]adapters.BidderInfo + bidderInfos map[string]config.BidderInfo expected map[string]openrtb_ext.BidderName }{ { description: "None", - bidderInfos: map[string]adapters.BidderInfo{}, + bidderInfos: map[string]config.BidderInfo{}, expected: map[string]openrtb_ext.BidderName{}, }, { - description: "Active", - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive}, + description: "Enabled", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, expected: map[string]openrtb_ext.BidderName{"appnexus": openrtb_ext.BidderAppnexus}, }, { description: "Disabled", - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoDisabled}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled}, expected: map[string]openrtb_ext.BidderName{}, }, - { - description: "Unknown", - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoUnknown}, - expected: map[string]openrtb_ext.BidderName{"appnexus": openrtb_ext.BidderAppnexus}, - }, { description: "Mixed", - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoDisabled, "openx": infoActive, "rubicon": infoUnknown}, - expected: map[string]openrtb_ext.BidderName{"openx": openrtb_ext.BidderOpenx, "rubicon": openrtb_ext.BidderRubicon}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "openx": infoEnabled}, + expected: map[string]openrtb_ext.BidderName{"openx": openrtb_ext.BidderOpenx}, }, } @@ -357,34 +336,29 @@ func TestGetActiveBidders(t *testing.T) { func TestGetDisabledBiddersErrorMessages(t *testing.T) { testCases := []struct { description string - bidderInfos map[string]adapters.BidderInfo + bidderInfos map[string]config.BidderInfo expected map[string]string }{ { description: "None", - bidderInfos: map[string]adapters.BidderInfo{}, + bidderInfos: map[string]config.BidderInfo{}, expected: map[string]string{}, }, { - description: "Active", - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoActive}, + description: "Enabled", + bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, expected: map[string]string{}, }, { description: "Disabled", - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoDisabled}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled}, expected: map[string]string{ "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, }, }, - { - description: "Unknown", - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoUnknown}, - expected: map[string]string{}, - }, { description: "Mixed", - bidderInfos: map[string]adapters.BidderInfo{"appnexus": infoDisabled, "openx": infoActive, "rubicon": infoUnknown}, + bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "openx": infoEnabled}, expected: map[string]string{"appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`}, }, } @@ -397,7 +371,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) { type fakeAdaptedBidder struct{} -func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return nil, nil } @@ -405,11 +379,11 @@ type fakeBidder struct { name string } -func (fakeBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (fakeBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { return nil, nil } -func (fakeBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (fakeBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { return nil, nil } diff --git a/exchange/auction.go b/exchange/auction.go index 43fea247950..3d733daaff8 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -10,11 +10,11 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" uuid "github.com/gofrs/uuid" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/prebid_cache_client" ) type DebugLog struct { @@ -125,7 +125,7 @@ func newAuction(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, numImps int } // isNewWinningBid calculates if the new bid (nbid) will win against the current winning bid (wbid) given preferDeals. -func isNewWinningBid(bid, wbid *openrtb.Bid, preferDeals bool) bool { +func isNewWinningBid(bid, wbid *openrtb2.Bid, preferDeals bool) bool { if preferDeals { if len(wbid.DealID) > 0 && len(bid.DealID) == 0 { return false @@ -147,7 +147,7 @@ func (a *auction) setRoundedPrices(priceGranularity openrtb_ext.PriceGranularity a.roundedPrices = roundedPrices } -func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, evTracking *eventTracking, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string, debugLog *DebugLog) []error { +func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, evTracking *eventTracking, bidRequest *openrtb2.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string, debugLog *DebugLog) []error { var bids, vast, includeBidderKeys, includeWinners bool = targData.includeCacheBids, targData.includeCacheVast, targData.includeBidderKeys, targData.includeWinners if !((bids || vast) && (includeBidderKeys || includeWinners)) { return nil @@ -155,8 +155,8 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, var errs []error expectNumBids := valOrZero(bids, len(a.roundedPrices)) expectNumVast := valOrZero(vast, len(a.roundedPrices)) - bidIndices := make(map[int]*openrtb.Bid, expectNumBids) - vastIndices := make(map[int]*openrtb.Bid, expectNumVast) + bidIndices := make(map[int]*openrtb2.Bid, expectNumBids) + vastIndices := make(map[int]*openrtb2.Bid, expectNumVast) toCache := make([]prebid_cache_client.Cacheable, 0, expectNumBids+expectNumVast) expByImp := make(map[string]int64) competitiveExclusion := false @@ -257,7 +257,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } if bids { - a.cacheIds = make(map[*openrtb.Bid]string, len(bidIndices)) + a.cacheIds = make(map[*openrtb2.Bid]string, len(bidIndices)) for index, bid := range bidIndices { if ids[index] != "" { a.cacheIds[bid] = ids[index] @@ -265,7 +265,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } if vast { - a.vastCacheIds = make(map[*openrtb.Bid]string, len(vastIndices)) + a.vastCacheIds = make(map[*openrtb2.Bid]string, len(vastIndices)) for index, bid := range vastIndices { if ids[index] != "" { if competitiveExclusion && strings.HasSuffix(ids[index], hbCacheID) { @@ -282,20 +282,13 @@ 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 *openrtb.Bid) string { - wrapperVASTTemplate := `` + - `prebid.org wrapper` + - `` + - `` + - `` - adm := bid.AdM - - if adm == "" { - return fmt.Sprintf(wrapperVASTTemplate, bid.NURL) // set nurl as VASTAdTagURI - } - - if strings.HasPrefix(adm, "http") { // check if it contains URL - return fmt.Sprintf(wrapperVASTTemplate, adm) // set adm as VASTAdTagURI +func makeVAST(bid *openrtb2.Bid) string { + if bid.AdM == "" { + return `` + + `prebid.org wrapper` + + `` + + `` + + `` } return bid.AdM } @@ -362,7 +355,7 @@ type auction struct { // roundedPrices stores the price strings rounded for each bid according to the price granularity. roundedPrices map[*pbsOrtbBid]string // cacheIds stores the UUIDs from Prebid Cache for fetching the full bid JSON. - cacheIds map[*openrtb.Bid]string + cacheIds map[*openrtb2.Bid]string // vastCacheIds stores UUIDS from Prebid cache for fetching the VAST markup to video bids. - vastCacheIds map[*openrtb.Bid]string + vastCacheIds map[*openrtb2.Bid]string } diff --git a/exchange/auction_test.go b/exchange/auction_test.go index a3ef7eab077..54f67eb8177 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -11,17 +11,17 @@ import ( "strconv" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/openrtb" "github.com/stretchr/testify/assert" ) func TestMakeVASTGiven(t *testing.T) { const expect = `` - bid := &openrtb.Bid{ + bid := &openrtb2.Bid{ AdM: expect, } vast := makeVAST(bid) @@ -35,27 +35,13 @@ func TestMakeVASTNurl(t *testing.T) { `` + `` + `` - bid := &openrtb.Bid{ + bid := &openrtb2.Bid{ NURL: url, } vast := makeVAST(bid) assert.Equal(t, expect, vast) } -func TestMakeVASTAdmContainsURI(t *testing.T) { - const url = "http://myvast.com/1.xml" - const expect = `` + - `prebid.org wrapper` + - `` + - `` + - `` - bid := &openrtb.Bid{ - AdM: url, - } - vast := makeVAST(bid) - assert.Equal(t, expect, vast) -} - func TestBuildCacheString(t *testing.T) { testCases := []struct { description string @@ -282,45 +268,45 @@ func runCacheSpec(t *testing.T, fileDisplayName string, specData *cacheSpec) { func TestNewAuction(t *testing.T) { bid1p077 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 0.77, }, } bid1p123 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 1.23, }, } bid1p230 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 2.30, }, } bid1p088d := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 0.88, DealID: "SpecialDeal", }, } bid1p166d := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp1", Price: 1.66, DealID: "BigDeal", }, } bid2p123 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp2", Price: 1.23, }, } bid2p144 := pbsOrtbBid{ - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "imp2", Price: 1.44, }, @@ -500,7 +486,7 @@ func TestNewAuction(t *testing.T) { } type cacheSpec struct { - BidRequest openrtb.BidRequest `json:"bidRequest"` + BidRequest openrtb2.BidRequest `json:"bidRequest"` PbsBids []pbsBid `json:"pbsBids"` ExpectedCacheables []prebid_cache_client.Cacheable `json:"expectedCacheables"` DefaultTTLs config.DefaultTTLs `json:"defaultTTLs"` @@ -514,7 +500,7 @@ type cacheSpec struct { } type pbsBid struct { - Bid *openrtb.Bid `json:"bid"` + Bid *openrtb2.Bid `json:"bid"` BidType openrtb_ext.BidType `json:"bidType"` Bidder openrtb_ext.BidderName `json:"bidder"` } diff --git a/exchange/bidder.go b/exchange/bidder.go index b426fce9322..c8f319b231c 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -12,18 +12,18 @@ import ( "net/http/httptrace" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config/util" - "github.com/PubMatic-OpenWrap/prebid-server/currency" "github.com/golang/glog" - - "github.com/PubMatic-OpenWrap/openrtb" - nativeRequests "github.com/PubMatic-OpenWrap/openrtb/native/request" - nativeResponse "github.com/PubMatic-OpenWrap/openrtb/native/response" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config/util" + "github.com/prebid/prebid-server/currency" + + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" + "github.com/mxmCherry/openrtb/v15/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" "golang.org/x/net/context/ctxhttp" ) @@ -49,7 +49,7 @@ type adaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) } // pbsOrtbBid is a Bid returned by an adaptedBidder. @@ -61,19 +61,21 @@ type adaptedBidder interface { // pbsOrtbBid.bidEvents is set by exchange when event tracking is enabled // 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 type pbsOrtbBid struct { - bid *openrtb.Bid + bid *openrtb2.Bid bidType openrtb_ext.BidType bidTargets map[string]string bidVideo *openrtb_ext.ExtBidPrebidVideo bidEvents *openrtb_ext.ExtBidPrebidEvents dealPriority int dealTierSatisfied bool + generatedBidID string } // pbsOrtbSeatBid is a SeatBid returned by an adaptedBidder. // -// This is distinct from the openrtb.SeatBid so that the prebid-server ext can be passed back with typesafety. +// This is distinct from the openrtb2.SeatBid so that the prebid-server ext can be passed back with typesafety. type pbsOrtbSeatBid struct { // bids is the list of bids which this adaptedBidder wishes to make. bids []*pbsOrtbBid @@ -83,17 +85,13 @@ type pbsOrtbSeatBid struct { // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. // This will become response.ext.debug.httpcalls.{bidder} on the final Response. httpCalls []*openrtb_ext.ExtHttpCall - // ext contains the extension for this seatbid. - // if len(bids) > 0, this will become response.seatbid[i].ext.{bidder} on the final OpenRTB response. - // if len(bids) == 0, this will be ignored because the OpenRTB spec doesn't allow a SeatBid with 0 Bids. - ext json.RawMessage } // 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" // (which is being phased out and replaced by Bidder for OpenRTB auctions) -func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *adapters.DebugInfo) adaptedBidder { +func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Configuration, me metrics.MetricsEngine, name openrtb_ext.BidderName, debugInfo *config.DebugInfo) adaptedBidder { return &bidderAdapter{ Bidder: bidder, BidderName: name, @@ -102,12 +100,12 @@ func adaptBidder(bidder adapters.Bidder, client *http.Client, cfg *config.Config config: bidderAdapterConfig{ Debug: cfg.Debug, DisableConnMetrics: cfg.Metrics.Disabled.AdapterConnectionMetrics, - DebugInfo: adapters.DebugInfo{Allow: parseDebugInfo(debugInfo)}, + DebugInfo: config.DebugInfo{Allow: parseDebugInfo(debugInfo)}, }, } } -func parseDebugInfo(info *adapters.DebugInfo) bool { +func parseDebugInfo(info *config.DebugInfo) bool { if info == nil { return true } @@ -125,10 +123,10 @@ type bidderAdapter struct { type bidderAdapterConfig struct { Debug config.Debug DisableConnMetrics bool - DebugInfo adapters.DebugInfo + DebugInfo config.DebugInfo } -func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo) if len(reqData) == 0 { @@ -168,9 +166,17 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi // - debugContextKey (url param) in true // - account debug is allowed // - bidder debug is allowed - if accountDebugAllowed && bidder.config.DebugInfo.Allow { - if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { + if accountDebugAllowed { + if bidder.config.DebugInfo.Allow { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugDisabledWarning := errortypes.Warning{ + WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, + Message: "debug turned off for bidder", + } + errs = append(errs, &debugDisabledWarning) + } } } @@ -227,8 +233,8 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi seatBid.bids = append(seatBid.bids, &pbsOrtbBid{ bid: bidResponse.Bids[i].Bid, bidType: bidResponse.Bids[i].BidType, - bidVideo: bidResponse.Bids[i].BidVideo, bidTargets: bidResponse.Bids[i].BidTargets, + bidVideo: bidResponse.Bids[i].BidVideo, dealPriority: bidResponse.Bids[i].DealPriority, }) } @@ -245,7 +251,7 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb.Bi return seatBid, errs } -func addNativeTypes(bid *openrtb.Bid, request *openrtb.BidRequest) (*nativeResponse.Response, []error) { +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 { @@ -275,13 +281,16 @@ func addNativeTypes(bid *openrtb.Bid, request *openrtb.BidRequest) (*nativeRespo func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Request) error { if asset.Img != nil { - if tempAsset, err := getAssetByID(asset.ID, nativePayload.Assets); err == nil { + if asset.ID == nil { + return errors.New("Response Image asset doesn't have an ID") + } + if tempAsset, err := getAssetByID(*asset.ID, nativePayload.Assets); err == nil { if tempAsset.Img != nil { if tempAsset.Img.Type != 0 { asset.Img.Type = tempAsset.Img.Type } } else { - return fmt.Errorf("Response has an Image asset with ID:%d present that doesn't exist in the request", asset.ID) + return fmt.Errorf("Response has an Image asset with ID:%d present that doesn't exist in the request", *asset.ID) } } else { return err @@ -289,13 +298,16 @@ func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Requ } if asset.Data != nil { - if tempAsset, err := getAssetByID(asset.ID, nativePayload.Assets); err == nil { + if asset.ID == nil { + return errors.New("Response Data asset doesn't have an ID") + } + if tempAsset, err := getAssetByID(*asset.ID, nativePayload.Assets); err == nil { if tempAsset.Data != nil { if tempAsset.Data.Type != 0 { asset.Data.Type = tempAsset.Data.Type } } else { - return fmt.Errorf("Response has a Data asset with ID:%d present that doesn't exist in the request", asset.ID) + return fmt.Errorf("Response has a Data asset with ID:%d present that doesn't exist in the request", *asset.ID) } } else { return err @@ -304,7 +316,7 @@ func setAssetTypes(asset nativeResponse.Asset, nativePayload nativeRequests.Requ return nil } -func getNativeImpByImpID(impID string, request *openrtb.BidRequest) (*openrtb.Native, error) { +func getNativeImpByImpID(impID string, request *openrtb2.BidRequest) (*openrtb2.Native, error) { for _, impInRequest := range request.Imp { if impInRequest.ID == impID && impInRequest.Native != nil { return impInRequest.Native, nil @@ -322,25 +334,30 @@ func getAssetByID(id int64, assets []nativeRequests.Asset) (nativeRequests.Asset return nativeRequests.Asset{}, fmt.Errorf("Unable to find asset with ID:%d in the request", id) } +var authorizationHeader = http.CanonicalHeaderKey("authorization") + +func filterHeader(h http.Header) http.Header { + clone := h.Clone() + clone.Del(authorizationHeader) + return clone +} + // makeExt transforms information about the HTTP call into the contract class for the PBS response. func makeExt(httpInfo *httpCallInfo) *openrtb_ext.ExtHttpCall { - if httpInfo.err == nil { - return &openrtb_ext.ExtHttpCall{ - Uri: httpInfo.request.Uri, - RequestBody: string(httpInfo.request.Body), - ResponseBody: string(httpInfo.response.Body), - Status: httpInfo.response.StatusCode, - RequestHeaders: httpInfo.request.Headers, - } - } else if httpInfo.request == nil { - return &openrtb_ext.ExtHttpCall{} - } else { - return &openrtb_ext.ExtHttpCall{ - Uri: httpInfo.request.Uri, - RequestBody: string(httpInfo.request.Body), - RequestHeaders: httpInfo.request.Headers, + ext := &openrtb_ext.ExtHttpCall{} + + if httpInfo != nil && httpInfo.request != nil { + ext.Uri = httpInfo.request.Uri + ext.RequestBody = string(httpInfo.request.Body) + ext.RequestHeaders = filterHeader(httpInfo.request.Headers) + + if httpInfo.err == nil && httpInfo.response != nil { + ext.ResponseBody = string(httpInfo.response.Body) + ext.Status = httpInfo.response.StatusCode } } + + return ext } // doRequest makes a request, handles the response, and returns the data needed by the diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index 66f976c0b66..6db249ec6ed 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -15,19 +15,19 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - metricsConfig "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/golang/glog" + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + nativeResponse "github.com/mxmCherry/openrtb/v15/native1/response" + "github.com/mxmCherry/openrtb/v15/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/metrics" + metricsConfig "github.com/prebid/prebid-server/metrics/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - - nativeRequests "github.com/PubMatic-OpenWrap/openrtb/native/request" - nativeResponse "github.com/PubMatic-OpenWrap/openrtb/native/response" ) // TestSingleBidder makes sure that the following things work if the Bidder needs only one request. @@ -36,13 +36,13 @@ import ( // 2. The returned values are correct for a non-test bid. func TestSingleBidder(t *testing.T) { type aTest struct { - debugInfo *adapters.DebugInfo + debugInfo *config.DebugInfo httpCallsLen int } testCases := []*aTest{ - {&adapters.DebugInfo{Allow: false}, 0}, - {&adapters.DebugInfo{Allow: true}, 1}, + {&config.DebugInfo{Allow: false}, 0}, + {&config.DebugInfo{Allow: true}, 1}, } respStatus := 200 @@ -71,18 +71,17 @@ func TestSingleBidder(t *testing.T) { ctx = context.WithValue(ctx, DebugContextKey, true) for _, test := range testCases { - mockBidderResponse := &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ Price: firstInitialPrice, }, BidType: openrtb_ext.BidTypeBanner, DealPriority: 4, }, { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ Price: secondInitialPrice, }, BidType: openrtb_ext.BidTypeVideo, @@ -95,7 +94,7 @@ func TestSingleBidder(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) // Make sure the goodSingleBidder was called with the expected arguments. if bidderImpl.httpResponse == nil { @@ -109,9 +108,13 @@ func TestSingleBidder(t *testing.T) { } // Make sure the returned values are what we expect - if len(errs) != 0 { + if len(errortypes.FatalOnly(errs)) != 0 { t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs)) } + + if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 { + t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs)) + } if len(seatBid.bids) != len(mockBidderResponse.Bids) { t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.bids)) } @@ -135,11 +138,49 @@ func TestSingleBidder(t *testing.T) { if len(seatBid.httpCalls) != test.httpCallsLen { t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.httpCalls)) } + } +} - if len(seatBid.ext) != 0 { - t.Errorf("The bidder shouldn't define any seatBid.ext. Got %s", string(seatBid.ext)) - } +func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { + server := httptest.NewServer(mockHandler(200, "getBody", "responseJson")) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + requestHeaders.Add("Authorization", "anySecret") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("requestJson"), + Headers: requestHeaders, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{}, + }, } + + debugInfo := &config.DebugInfo{Allow: true} + ctx := context.Background() + ctx = context.WithValue(ctx, DebugContextKey, true) + + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + + expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ + { + Uri: server.URL, + RequestBody: "requestJson", + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}}, + ResponseBody: "responseJson", + Status: 200, + }, + } + + assert.Empty(t, errs) + assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCalls) } // TestMultiBidder makes sure all the requests get sent, and the responses processed. @@ -157,11 +198,11 @@ func TestMultiBidder(t *testing.T) { mockBidderResponse := &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, BidType: openrtb_ext.BidTypeBanner, }, { - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, BidType: openrtb_ext.BidTypeVideo, }, }, @@ -184,7 +225,7 @@ func TestMultiBidder(t *testing.T) { } bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if seatBid == nil { t.Fatalf("SeatBid should exist, because bids exist.") @@ -514,7 +555,7 @@ func TestMultiCurrencies(t *testing.T) { mockBidderResponses[i] = &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ Price: bid.price, }, BidType: openrtb_ext.BidTypeBanner, @@ -555,7 +596,7 @@ func TestMultiCurrencies(t *testing.T) { seatBid, errs := bidder.requestBid( context.Background(), - &openrtb.BidRequest{}, + &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), @@ -680,7 +721,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { mockBidderResponses[i] = &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, BidType: openrtb_ext.BidTypeBanner, }, }, @@ -700,7 +741,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) seatBid, errs := bidder.requestBid( context.Background(), - &openrtb.BidRequest{}, + &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), @@ -843,7 +884,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { { Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{}, + Bid: &openrtb2.Bid{}, BidType: openrtb_ext.BidTypeBanner, }, }, @@ -871,7 +912,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { ) seatBid, errs := bidder.requestBid( context.Background(), - &openrtb.BidRequest{ + &openrtb2.BidRequest{ Cur: tc.bidRequestCurrencies, }, "test", @@ -891,86 +932,204 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { } } -// TestBadResponseLogging makes sure that openrtb_ext works properly on malformed HTTP requests. -func TestBadRequestLogging(t *testing.T) { - info := &httpCallInfo{ - err: errors.New("Bad request"), - } - ext := makeExt(info) - if ext.Uri != "" { - t.Errorf("The URI should be empty. Got %s", ext.Uri) - } - if ext.RequestBody != "" { - t.Errorf("The request body should be empty. Got %s", ext.RequestBody) - } - if ext.ResponseBody != "" { - t.Errorf("The response body should be empty. Got %s", ext.ResponseBody) - } - if ext.Status != 0 { - t.Errorf("The Status code should be 0. Got %d", ext.Status) - } - if len(ext.RequestHeaders) > 0 { - t.Errorf("The request headers should be empty. Got %s", ext.RequestHeaders) - } -} - -// TestBadResponseLogging makes sure that openrtb_ext works properly if we don't get a sensible HTTP response. -func TestBadResponseLogging(t *testing.T) { - info := &httpCallInfo{ - request: &adapters.RequestData{ - Uri: "test.com", - Body: []byte("request body"), - Headers: http.Header{ - "header-1": []string{"value-1"}, +func TestMakeExt(t *testing.T) { + testCases := []struct { + description string + given *httpCallInfo + expected *openrtb_ext.ExtHttpCall + }{ + { + description: "Nil", + given: nil, + expected: &openrtb_ext.ExtHttpCall{}, + }, + { + description: "Empty", + given: &httpCallInfo{ + err: nil, + response: nil, + request: nil, }, + expected: &openrtb_ext.ExtHttpCall{}, + }, + { + description: "Request & Response - No Error", + given: &httpCallInfo{ + err: nil, + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}), + }, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}}, + ResponseBody: "responseBody", + Status: 999, + }, + }, + { + description: "Request & Response - No Error with Authorization removal", + given: &httpCallInfo{ + err: nil, + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}, "Authorization": {"secret"}}), + }, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}}, + ResponseBody: "responseBody", + Status: 999, + }, + }, + { + description: "Request & Response - No Error with nil header", + given: &httpCallInfo{ + err: nil, + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: nil, + }, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: nil, + ResponseBody: "responseBody", + Status: 999, + }, + }, + { + description: "Request & Response - Error", + given: &httpCallInfo{ + err: errors.New("error"), + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}), + }, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}}, + }, + }, + { + description: "Request Only", + given: &httpCallInfo{ + err: nil, + request: &adapters.RequestData{ + Uri: "requestUri", + Body: []byte("requestBody"), + Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}), + }, + response: nil, + }, + expected: &openrtb_ext.ExtHttpCall{ + Uri: "requestUri", + RequestBody: "requestBody", + RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}}, + }, + }, { + description: "Response Only", + given: &httpCallInfo{ + err: nil, + response: &adapters.ResponseData{ + Body: []byte("responseBody"), + StatusCode: 999, + }, + }, + expected: &openrtb_ext.ExtHttpCall{}, }, - err: errors.New("Bad response"), - } - ext := makeExt(info) - if ext.Uri != info.request.Uri { - t.Errorf("The URI should be test.com. Got %s", ext.Uri) - } - if ext.RequestBody != string(info.request.Body) { - t.Errorf("The request body should be empty. Got %s", ext.RequestBody) - } - if ext.ResponseBody != "" { - t.Errorf("The response body should be empty. Got %s", ext.ResponseBody) } - if ext.Status != 0 { - t.Errorf("The Status code should be 0. Got %d", ext.Status) + + for _, test := range testCases { + result := makeExt(test.given) + assert.Equal(t, test.expected, result, test.description) } - assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"header-1:value-1\"") } -// TestSuccessfulResponseLogging makes sure that openrtb_ext works properly if the HTTP request is successful. -func TestSuccessfulResponseLogging(t *testing.T) { - info := &httpCallInfo{ - request: &adapters.RequestData{ - Uri: "test.com", - Body: []byte("request body"), - Headers: http.Header{ - "header-1": []string{"value-1", "value-2"}, - }, +func TestFilterHeader(t *testing.T) { + testCases := []struct { + description string + given http.Header + expected http.Header + }{ + { + description: "Nil", + given: nil, + expected: nil, }, - response: &adapters.ResponseData{ - StatusCode: 200, - Body: []byte("response body"), + { + description: "Empty", + given: http.Header{}, + expected: http.Header{}, + }, + { + description: "One", + given: makeHeader(map[string][]string{"Key1": {"value1"}}), + expected: makeHeader(map[string][]string{"Key1": {"value1"}}), + }, + { + description: "Many", + given: makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}), + expected: makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}), + }, + { + description: "Authorization Header Omitted", + given: makeHeader(map[string][]string{"authorization": {"secret"}}), + expected: http.Header{}, + }, + { + description: "Authorization Header Omitted - Case Insensitive", + given: makeHeader(map[string][]string{"AuThOrIzAtIoN": {"secret"}}), + expected: http.Header{}, + }, + { + description: "Authorization Header Omitted + Other Keys", + given: makeHeader(map[string][]string{"authorization": {"secret"}, "Key1": {"value1"}}), + expected: makeHeader(map[string][]string{"Key1": {"value1"}}), }, } - ext := makeExt(info) - if ext.Uri != info.request.Uri { - t.Errorf("The URI should be test.com. Got %s", ext.Uri) - } - if ext.RequestBody != string(info.request.Body) { - t.Errorf("The request body should be \"request body\". Got %s", ext.RequestBody) - } - if ext.ResponseBody != string(info.response.Body) { - t.Errorf("The response body should be \"response body\". Got %s", ext.ResponseBody) + + for _, test := range testCases { + result := filterHeader(test.given) + assert.Equal(t, test.expected, result, test.description) } - if ext.Status != info.response.StatusCode { - t.Errorf("The Status code should be 0. Got %d", ext.Status) +} + +func makeHeader(v map[string][]string) http.Header { + h := http.Header{} + for key, values := range v { + for _, value := range values { + h.Add(key, value) + } } - assert.Equal(t, info.request.Headers, http.Header(ext.RequestHeaders), "The request headers should be \"%s\". Got %s", info.request.Headers, ext.RequestHeaders) + return h } func TestMobileNativeTypes(t *testing.T) { @@ -983,27 +1142,27 @@ func TestMobileNativeTypes(t *testing.T) { reqURL := server.URL testCases := []struct { - mockBidderRequest *openrtb.BidRequest + mockBidderRequest *openrtb2.BidRequest mockBidderResponse *adapters.BidderResponse expectedValue string description string }{ { - mockBidderRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{ + mockBidderRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ { ID: "some-imp-id", - Native: &openrtb.Native{ + 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: &openrtb.App{}, + App: &openrtb2.App{}, }, mockBidderResponse: &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ ImpID: "some-imp-id", AdM: "{\"assets\":[{\"id\":2,\"img\":{\"url\":\"http://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg\",\"w\":989,\"h\":742,\"ext\":{\"appnexus\":{\"prevent_crop\":0}}}},{\"id\":1,\"title\":{\"text\":\"This is a 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://some-url.com\"},\"imptrackers\":[\"http://someimptracker.com\"],\"jstracker\":\"some-js-tracker\"}", Price: 10, @@ -1016,21 +1175,21 @@ func TestMobileNativeTypes(t *testing.T) { description: "Checks types in response", }, { - mockBidderRequest: &openrtb.BidRequest{ - Imp: []openrtb.Imp{ + mockBidderRequest: &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ { ID: "some-imp-id", - Native: &openrtb.Native{ + 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: &openrtb.App{}, + App: &openrtb2.App{}, }, mockBidderResponse: &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{ + Bid: &openrtb2.Bid{ ImpID: "some-imp-id", AdM: "{\"some-diff-markup\":\"creative\"}", Price: 10, @@ -1078,7 +1237,7 @@ func TestMobileNativeTypes(t *testing.T) { func TestErrorReporting(t *testing.T) { bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bids, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1099,7 +1258,7 @@ func TestSetAssetTypes(t *testing.T) { }{ { respAsset: nativeResponse.Asset{ - ID: 1, + ID: openrtb2.Int64Ptr(1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -1125,7 +1284,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: 2, + ID: openrtb2.Int64Ptr(2), Data: &nativeResponse.Data{ Label: "some label", }, @@ -1151,7 +1310,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: 1, + ID: openrtb2.Int64Ptr(1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -1171,7 +1330,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: 2, + ID: openrtb2.Int64Ptr(2), Data: &nativeResponse.Data{ Label: "some label", }, @@ -1191,7 +1350,7 @@ func TestSetAssetTypes(t *testing.T) { }, { respAsset: nativeResponse.Asset{ - ID: 1, + ID: openrtb2.Int64Ptr(1), Img: &nativeResponse.Image{ URL: "http://some-url", }, @@ -1209,6 +1368,44 @@ func TestSetAssetTypes(t *testing.T) { expectedErr: "Response has an Image asset with ID:1 present that doesn't exist in the request", desc: "Assets with same ID in the req and resp are of different types", }, + { + respAsset: nativeResponse.Asset{ + Img: &nativeResponse.Image{ + URL: "http://some-url", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 1, + Img: &nativeRequests.Image{ + Type: 2, + }, + }, + }, + }, + expectedErr: "Response Image asset doesn't have an ID", + desc: "Response Image without an ID", + }, + { + respAsset: nativeResponse.Asset{ + Data: &nativeResponse.Data{ + Label: "some label", + }, + }, + nativeReq: nativeRequests.Request{ + Assets: []nativeRequests.Asset{ + { + ID: 1, + Data: &nativeRequests.Data{ + Type: 2, + }, + }, + }, + }, + expectedErr: "Response Data asset doesn't have an ID", + desc: "Response Data asset without an ID", + }, } for _, test := range testCases { @@ -1261,7 +1458,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { // Run requestBid using an http.Client with a mock handler bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) @@ -1437,13 +1634,13 @@ func TestTimeoutNotificationOn(t *testing.T) { } func TestParseDebugInfoTrue(t *testing.T) { - debugInfo := &adapters.DebugInfo{Allow: true} + debugInfo := &config.DebugInfo{Allow: true} resDebugInfo := parseDebugInfo(debugInfo) assert.True(t, resDebugInfo, "Debug Allow value should be true") } func TestParseDebugInfoFalse(t *testing.T) { - debugInfo := &adapters.DebugInfo{Allow: false} + debugInfo := &config.DebugInfo{Allow: false} resDebugInfo := parseDebugInfo(debugInfo) assert.False(t, resDebugInfo, "Debug Allow value should be false") } @@ -1454,43 +1651,43 @@ func TestParseDebugInfoIsNil(t *testing.T) { } func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder { - bidderInfo := adapters.BidderInfo{ - Status: adapters.StatusActive, - Capabilities: &adapters.CapabilitiesInfo{ - App: &adapters.PlatformInfo{ + bidderInfo := config.BidderInfo{ + Enabled: true, + Capabilities: &config.CapabilitiesInfo{ + App: &config.PlatformInfo{ MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner}, }, }, } - return adapters.EnforceBidderInfo(bidder, bidderInfo) + return adapters.BuildInfoAwareBidder(bidder, bidderInfo) } type goodSingleBidder struct { - bidRequest *openrtb.BidRequest + bidRequest *openrtb2.BidRequest httpRequest *adapters.RequestData httpResponse *adapters.ResponseData bidResponse *adapters.BidderResponse } -func (bidder *goodSingleBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *goodSingleBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { bidder.bidRequest = request return []*adapters.RequestData{bidder.httpRequest}, nil } -func (bidder *goodSingleBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *goodSingleBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { bidder.httpResponse = response return bidder.bidResponse, nil } type goodMultiHTTPCallsBidder struct { - bidRequest *openrtb.BidRequest + bidRequest *openrtb2.BidRequest httpRequest []*adapters.RequestData httpResponses []*adapters.ResponseData bidResponses []*adapters.BidderResponse bidResponseNumber int } -func (bidder *goodMultiHTTPCallsBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *goodMultiHTTPCallsBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { bidder.bidRequest = request response := make([]*adapters.RequestData, len(bidder.httpRequest)) @@ -1500,7 +1697,7 @@ func (bidder *goodMultiHTTPCallsBidder) MakeRequests(request *openrtb.BidRequest return response, nil } -func (bidder *goodMultiHTTPCallsBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *goodMultiHTTPCallsBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { br := bidder.bidResponses[bidder.bidResponseNumber] bidder.bidResponseNumber++ bidder.httpResponses = append(bidder.httpResponses, response) @@ -1509,18 +1706,18 @@ func (bidder *goodMultiHTTPCallsBidder) MakeBids(internalRequest *openrtb.BidReq } type mixedMultiBidder struct { - bidRequest *openrtb.BidRequest + bidRequest *openrtb2.BidRequest httpRequests []*adapters.RequestData httpResponses []*adapters.ResponseData bidResponse *adapters.BidderResponse } -func (bidder *mixedMultiBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *mixedMultiBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { bidder.bidRequest = request return bidder.httpRequests, []error{errors.New("The requests weren't ideal.")} } -func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { bidder.httpResponses = append(bidder.httpResponses, response) return bidder.bidResponse, []error{errors.New("The bidResponse weren't ideal.")} } @@ -1530,11 +1727,11 @@ type bidRejector struct { httpResponse *adapters.ResponseData } -func (bidder *bidRejector) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *bidRejector) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { return nil, []error{errors.New("Invalid params on BidRequest.")} } -func (bidder *bidRejector) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *bidRejector) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { bidder.httpResponse = response return nil, []error{errors.New("Can't make a response.")} } @@ -1544,11 +1741,11 @@ type notifyingBidder struct { notifyRequest adapters.RequestData } -func (bidder *notifyingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (bidder *notifyingBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { return bidder.requests, nil } -func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { return nil, nil } diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 9ea357336fa..3d2eb0b8e42 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -6,10 +6,10 @@ import ( "fmt" "strings" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/openrtb_ext" goCurrency "golang.org/x/text/currency" ) @@ -28,7 +28,7 @@ type validatedBidder struct { bidder adaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed) if validationErrors := removeInvalidBids(request, seatBid); len(validationErrors) > 0 { errs = append(errs, validationErrors...) @@ -37,7 +37,7 @@ func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb.BidRe } // validateBids will run some validation checks on the returned bids and excise any invalid bids -func removeInvalidBids(request *openrtb.BidRequest, seatBid *pbsOrtbSeatBid) []error { +func removeInvalidBids(request *openrtb2.BidRequest, seatBid *pbsOrtbSeatBid) []error { // Exit early if there is nothing to do. if seatBid == nil || len(seatBid.bids) == 0 { return nil diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index a2ec026f5d7..3bb43559856 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -4,10 +4,10 @@ import ( "context" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -16,7 +16,7 @@ func TestAllValidBids(t *testing.T) { bidResponse: &pbsOrtbSeatBid{ bids: []*pbsOrtbBid{ { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "one-bid", ImpID: "thisImp", Price: 0.45, @@ -24,7 +24,7 @@ func TestAllValidBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "thatBid", ImpID: "thatImp", Price: 0.40, @@ -32,7 +32,7 @@ func TestAllValidBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "123", ImpID: "456", Price: 0.44, @@ -42,7 +42,7 @@ func TestAllValidBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 3) assert.Len(t, errs, 0) } @@ -52,28 +52,28 @@ func TestAllBadBids(t *testing.T) { bidResponse: &pbsOrtbSeatBid{ bids: []*pbsOrtbBid{ { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "one-bid", Price: 0.45, CrID: "thisCreative", }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "thatBid", ImpID: "thatImp", CrID: "thatCreative", }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "123", ImpID: "456", Price: 0.44, }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "456", Price: 0.44, CrID: "blah", @@ -83,7 +83,7 @@ func TestAllBadBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 0) assert.Len(t, errs, 5) } @@ -93,7 +93,7 @@ func TestMixedBids(t *testing.T) { bidResponse: &pbsOrtbSeatBid{ bids: []*pbsOrtbBid{ { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "one-bid", ImpID: "thisImp", Price: 0.45, @@ -101,14 +101,14 @@ func TestMixedBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "thatBid", ImpID: "thatImp", CrID: "thatCreative", }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "123", ImpID: "456", Price: 0.44, @@ -116,7 +116,7 @@ func TestMixedBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ImpID: "456", Price: 0.44, CrID: "blah", @@ -126,7 +126,7 @@ func TestMixedBids(t *testing.T) { }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) assert.Len(t, seatBid.bids, 2) assert.Len(t, errs, 3) } @@ -210,7 +210,7 @@ func TestCurrencyBids(t *testing.T) { for _, tc := range currencyTestCases { bids := []*pbsOrtbBid{ { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "one-bid", ImpID: "thisImp", Price: 0.45, @@ -218,7 +218,7 @@ func TestCurrencyBids(t *testing.T) { }, }, { - bid: &openrtb.Bid{ + bid: &openrtb2.Bid{ ID: "thatBid", ImpID: "thatImp", Price: 0.44, @@ -242,7 +242,7 @@ func TestCurrencyBids(t *testing.T) { expectedValidBids = 0 } - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ Cur: tc.brqCur, } @@ -257,6 +257,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/events.go b/exchange/events.go index 128b6778364..bedafddf5a0 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -4,14 +4,12 @@ import ( "encoding/json" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/events" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - jsonpatch "github.com/evanphx/json-patch" + "github.com/evanphx/json-patch" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/endpoints/events" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" ) // eventTracking has configuration fields needed for adding event tracking to an auction response @@ -21,12 +19,12 @@ type eventTracking struct { enabledForRequest bool auctionTimestampMs int64 integration metrics.DemandSource // web app amp - bidderInfos adapters.BidderInfos + bidderInfos config.BidderInfos externalURL string } // getEventTracking creates an eventTracking object from the different configuration sources -func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Time, account *config.Account, bidderInfos adapters.BidderInfos, externalURL string) *eventTracking { +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.EventsEnabled, @@ -39,13 +37,13 @@ func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Ti } // modifyBidsForEvents adds bidEvents and modifies VAST AdM if necessary. -func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, req *openrtb.BidRequest, trackerURL string) map[openrtb_ext.BidderName]*pbsOrtbSeatBid { +func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid) map[openrtb_ext.BidderName]*pbsOrtbSeatBid { for bidderName, seatBid := range seatBids { - // modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String()) + modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String()) for _, pbsBid := range seatBid.bids { - // if modifyingVastXMLAllowed { - ev.modifyBidVAST(pbsBid, bidderName, req, trackerURL) - // } + if modifyingVastXMLAllowed { + ev.modifyBidVAST(pbsBid, bidderName) + } pbsBid.bidEvents = ev.makeBidExtEvents(pbsBid, bidderName) } } @@ -58,20 +56,18 @@ func (ev *eventTracking) isModifyingVASTXMLAllowed(bidderName string) bool { } // modifyBidVAST injects event Impression url if needed, otherwise returns original VAST string -func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName, req *openrtb.BidRequest, trackerURL string) { +func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName) { bid := pbsBid.bid if pbsBid.bidType != openrtb_ext.BidTypeVideo || len(bid.AdM) == 0 && len(bid.NURL) == 0 { return } vastXML := makeVAST(bid) - if ev.isModifyingVASTXMLAllowed(bidderName.String()) { // condition added for ow fork - if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bid.ID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); ok { - bid.AdM = newVastXML - } + bidID := bid.ID + if len(pbsBid.generatedBidID) > 0 { + bidID = pbsBid.generatedBidID } - // always inject event trackers without checkign isModifyingVASTXMLAllowed - if newVastXML, injected, _ := events.InjectVideoEventTrackers(trackerURL, vastXML, bid, bidderName.String(), ev.accountID, ev.auctionTimestampMs, req); injected { - bid.AdM = string(newVastXML) + if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bidID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); ok { + bid.AdM = newVastXML } } @@ -111,10 +107,14 @@ func (ev *eventTracking) makeBidExtEvents(pbsBid *pbsOrtbBid, bidderName openrtb // makeEventURL returns an analytics event url for the requested type (win or imp) func (ev *eventTracking) makeEventURL(evType analytics.EventType, pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName) string { + bidId := pbsBid.bid.ID + if len(pbsBid.generatedBidID) > 0 { + bidId = pbsBid.generatedBidID + } return events.EventRequestToUrl(ev.externalURL, &analytics.EventRequest{ Type: evType, - BidID: pbsBid.bid.ID, + BidID: bidId, Bidder: string(bidderName), AccountID: ev.accountID, Timestamp: ev.auctionTimestampMs, diff --git a/exchange/events_test.go b/exchange/events_test.go index a5e7d719510..887122a687e 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -1,12 +1,10 @@ package exchange import ( - "strings" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -15,6 +13,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { enabledForAccount bool enabledForRequest bool bidType openrtb_ext.BidType + generatedBidId string } tests := []struct { name string @@ -23,7 +22,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { }{ { name: "banner: events enabled for request, disabled for account", - args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, want: &openrtb_ext.ExtBidPrebidEvents{ Win: "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890", Imp: "http://localhost/event?t=imp&b=BID-1&a=123456&bidder=openx&ts=1234567890", @@ -31,7 +30,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { }, { name: "banner: events enabled for account, disabled for request", - args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, want: &openrtb_ext.ExtBidPrebidEvents{ Win: "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890", Imp: "http://localhost/event?t=imp&b=BID-1&a=123456&bidder=openx&ts=1234567890", @@ -39,19 +38,27 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { }, { name: "banner: events disabled for account and request", - args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, want: nil, }, { name: "video: events enabled for account and request", - args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo}, + args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""}, want: nil, }, { name: "video: events disabled for account and request", - args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo}, + args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""}, want: nil, }, + { + name: "banner: use generated bid id", + args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: "randomId"}, + want: &openrtb_ext.ExtBidPrebidEvents{ + Win: "http://localhost/event?t=win&b=randomId&a=123456&bidder=openx&ts=1234567890", + Imp: "http://localhost/event?t=imp&b=randomId&a=123456&bidder=openx&ts=1234567890", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -62,7 +69,7 @@ func Test_eventsData_makeBidExtEvents(t *testing.T) { auctionTimestampMs: 1234567890, externalURL: "http://localhost", } - bid := &pbsOrtbBid{bid: &openrtb.Bid{ID: "BID-1"}, bidType: tt.args.bidType} + bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType, generatedBidID: tt.args.generatedBidId} assert.Equal(t, tt.want, evData.makeBidExtEvents(bid, openrtb_ext.BidderOpenx)) }) } @@ -73,6 +80,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { enabledForAccount bool enabledForRequest bool bidType openrtb_ext.BidType + generatedBidId string } tests := []struct { name string @@ -82,40 +90,46 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { }{ { name: "banner: events enabled for request, disabled for account", - args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something", "wurl": "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890"}`), }, { name: "banner: events enabled for account, disabled for request", - args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: true, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something", "wurl": "http://localhost/event?t=win&b=BID-1&a=123456&bidder=openx&ts=1234567890"}`), }, { name: "banner: events disabled for account and request", - args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something"}`), }, { name: "video: events disabled for account and request", - args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo}, + args: args{enabledForAccount: false, enabledForRequest: false, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something"}`), }, { name: "video: events enabled for account and request", - args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo}, + args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeVideo, generatedBidId: ""}, jsonBytes: []byte(`{"ID": "something"}`), want: []byte(`{"ID": "something"}`), }, { name: "banner: broken json expected to fail patching", - args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner}, + args: args{enabledForAccount: true, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: ""}, jsonBytes: []byte(`broken json`), want: nil, }, + { + name: "banner: generate bid id enabled", + args: args{enabledForAccount: false, enabledForRequest: true, bidType: openrtb_ext.BidTypeBanner, generatedBidId: "randomID"}, + jsonBytes: []byte(`{"ID": "something"}`), + want: []byte(`{"ID": "something", "wurl":"http://localhost/event?t=win&b=randomID&a=123456&bidder=openx&ts=1234567890"}`), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -126,7 +140,7 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { auctionTimestampMs: 1234567890, externalURL: "http://localhost", } - bid := &pbsOrtbBid{bid: &openrtb.Bid{ID: "BID-1"}, bidType: tt.args.bidType} + bid := &pbsOrtbBid{bid: &openrtb2.Bid{ID: "BID-1"}, bidType: tt.args.bidType, generatedBidID: tt.args.generatedBidId} modifiedJSON, err := evData.modifyBidJSON(bid, openrtb_ext.BidderOpenx, tt.jsonBytes) if tt.want != nil { assert.NoError(t, err, "Unexpected error") @@ -138,108 +152,3 @@ func Test_eventsData_modifyBidJSON(t *testing.T) { }) } } - -func TestModifyBidVAST(t *testing.T) { - type args struct { - bidReq *openrtb.BidRequest - bid *openrtb.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: &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ID: "123", Video: &openrtb.Video{}}}, - }, - bid: &openrtb.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: &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ID: "123", Video: &openrtb.Video{}}}, - }, - bid: &openrtb.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: adapters.BidderInfos{ - "somebidder": adapters.BidderInfo{ - ModifyingVastXmlAllowed: false, - }, - }, - } - ev.modifyBidVAST(&pbsOrtbBid{ - bid: tc.args.bid, - bidType: openrtb_ext.BidTypeVideo, - }, "somebidder", tc.args.bidReq, "http://company.tracker.com?e=[EVENT_ID]") - validator(t, tc.args.bid, tc.want.tags) - }) - } -} - -func validator(t *testing.T, b *openrtb.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 f474531523e..eaff759f619 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -14,18 +14,19 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/stored_requests" + + "github.com/gofrs/uuid" "github.com/golang/glog" + "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/gdpr" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/prebid_cache_client" ) type ContextKey string @@ -39,7 +40,7 @@ type extCacheInstructions struct { // Exchange runs Auctions. Implementations must be threadsafe, and will be shared across many goroutines. type Exchange interface { // HoldAuction executes an OpenRTB v2.5 Auction. - HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb.BidResponse, error) + HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) } // IdFetcher can find the user's ID for a specific Bidder. @@ -51,7 +52,7 @@ type IdFetcher interface { type exchange struct { adapterMap map[openrtb_ext.BidderName]adaptedBidder - bidderInfo adapters.BidderInfos + bidderInfo config.BidderInfos me metrics.MetricsEngine cache prebid_cache_client.Client cacheTime time.Duration @@ -61,13 +62,14 @@ type exchange struct { UsersyncIfAmbiguous bool privacyConfig config.Privacy categoriesFetcher stored_requests.CategoryFetcher - trakerURL string + bidIDGenerator BidIDGenerator } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread type seatResponseExtra struct { ResponseTimeMillis int - Errors []openrtb_ext.ExtBidderError + Errors []openrtb_ext.ExtBidderMessage + Warnings []openrtb_ext.ExtBidderMessage // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. // This will become response.ext.debug.httpcalls.{bidder} on the final Response. HttpCalls []*openrtb_ext.ExtHttpCall @@ -79,7 +81,25 @@ type bidResponseWrapper struct { bidder openrtb_ext.BidderName } -func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, infos adapters.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { +type BidIDGenerator interface { + New() (string, error) + Enabled() bool +} + +type bidIDGenerator struct { + enabled bool +} + +func (big *bidIDGenerator) Enabled() bool { + return big.enabled +} + +func (big *bidIDGenerator) New() (string, error) { + rawUuid, err := uuid.NewV4() + return rawUuid.String(), err +} + +func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { return &exchange{ adapterMap: adapters, bidderInfo: infos, @@ -96,18 +116,19 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid GDPR: cfg.GDPR, LMT: cfg.LMT, }, - trakerURL: cfg.TrackerURL, + bidIDGenerator: &bidIDGenerator{cfg.GenerateBidID}, } } // AuctionRequest holds the bid request for the auction // and all other information needed to process that request type AuctionRequest struct { - BidRequest *openrtb.BidRequest + BidRequest *openrtb2.BidRequest Account config.Account UserSyncs IdFetcher RequestType metrics.RequestType StartTime time.Time + Warnings []error // LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. @@ -117,13 +138,13 @@ type AuctionRequest struct { // BidderRequest holds the bidder specific request and all other // information needed to process that bidder request. type BidderRequest struct { - BidRequest *openrtb.BidRequest + BidRequest *openrtb2.BidRequest BidderName openrtb_ext.BidderName BidderCoreName openrtb_ext.BidderName BidderLabels metrics.AdapterLabels } -func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb.BidResponse, error) { +func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *DebugLog) (*openrtb2.BidResponse, error) { var err error requestExt, err := extractBidRequestExt(r.BidRequest) if err != nil { @@ -140,9 +161,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * debugLog = &DebugLog{Enabled: false} } - debugInfo := getDebugInfo(r.BidRequest, requestExt) + requestDebugInfo := getDebugInfo(r.BidRequest, requestExt) - debugInfo = debugInfo && r.Account.DebugAllow + debugInfo := requestDebugInfo && r.Account.DebugAllow debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow if debugInfo { @@ -157,7 +178,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(r.BidRequest) // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, usersyncIfAmbiguous, e.privacyConfig) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, usersyncIfAmbiguous, e.privacyConfig, &r.Account) e.me.RecordRequestPrivacy(privacyLabels) @@ -183,7 +204,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * //If includebrandcategory is present in ext then CE feature is on. if requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil { var rejections []string - bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r.BidRequest, requestExt, adapterBids, e.categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, requestExt, adapterBids, e.categoriesFetcher, targData) if err != nil { return nil, fmt.Errorf("Error in category mapping : %s", err.Error()) } @@ -192,8 +213,19 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } + if e.bidIDGenerator.Enabled() { + for _, seatBid := range adapterBids { + for _, pbsBid := range seatBid.bids { + pbsBid.generatedBidID, err = e.bidIDGenerator.New() + if err != nil { + errs = append(errs, errors.New("Error generating bid.ext.prebid.bidid")) + } + } + } + } + evTracking := getEventTracking(&requestExt.Prebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL) - adapterBids = evTracking.modifyBidsForEvents(adapterBids, r.BidRequest, e.trakerURL) + adapterBids = evTracking.modifyBidsForEvents(adapterBids) if targData != nil { // A non-nil auction is only needed if targeting is active. (It is used below this block to extract cache keys) @@ -238,13 +270,29 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } + if !r.Account.DebugAllow && requestDebugInfo { + accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{ + Code: errortypes.AccountLevelDebugDisabledWarningCode, + Message: "debug turned off for account", + } + bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], accountDebugDisabledWarning) + } + + for _, warning := range r.Warnings { + generalWarning := openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(warning), + Message: warning.Error(), + } + bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], generalWarning) + } + // Build the response return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) } -func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb.BidRequest) bool { +func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) bool { usersyncIfAmbiguous := e.UsersyncIfAmbiguous - var geo *openrtb.Geo = nil + var geo *openrtb2.Geo = nil if bidRequest.User != nil && bidRequest.User.Geo != nil { geo = bidRequest.User.Geo @@ -265,7 +313,7 @@ func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb.BidRequest) bool return usersyncIfAmbiguous } -func recordImpMetrics(bidRequest *openrtb.BidRequest, metricsEngine metrics.MetricsEngine) { +func recordImpMetrics(bidRequest *openrtb2.BidRequest, metricsEngine metrics.MetricsEngine) { for _, impInRequest := range bidRequest.Imp { var impLabels metrics.ImpLabels = metrics.ImpLabels{ BannerImps: impInRequest.Banner != nil, @@ -278,7 +326,7 @@ func recordImpMetrics(bidRequest *openrtb.BidRequest, metricsEngine metrics.Metr } // applyDealSupport updates targeting keys with deal prefixes if minimum deal tier exceeded -func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction, bidCategory map[string]string) []error { +func applyDealSupport(bidRequest *openrtb2.BidRequest, auc *auction, bidCategory map[string]string) []error { errs := []error{} impDealMap := getDealTiers(bidRequest) @@ -299,7 +347,7 @@ func applyDealSupport(bidRequest *openrtb.BidRequest, auc *auction, bidCategory } // getDealTiers creates map of impression to bidder deal tier configuration -func getDealTiers(bidRequest *openrtb.BidRequest) map[string]openrtb_ext.DealTierBidderMap { +func getDealTiers(bidRequest *openrtb2.BidRequest) map[string]openrtb_ext.DealTierBidderMap { impDealMap := make(map[string]openrtb_ext.DealTierBidderMap) for _, imp := range bidRequest.Imp { @@ -362,7 +410,6 @@ func (e *exchange) getAllBids( adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, len(bidderRequests)) chBids := make(chan *bidResponseWrapper, len(bidderRequests)) bidsFound := false - bidIDsCollision := false for _, bidder := range bidderRequests { // Here we actually call the adapters and collect the bids. @@ -400,20 +447,17 @@ func (e *exchange) getAllBids( // Timing statistics e.me.RecordAdapterTime(bidderRequest.BidderLabels, time.Since(start)) - serr := errsToBidderErrors(err) bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterBids) bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err) // Append any bid validation errors to the error list - ae.Errors = serr + ae.Errors = errsToBidderErrors(err) + ae.Warnings = errsToBidderWarnings(err) brw.adapterExtra = ae if bids != nil { for _, bid := range bids.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) - } } } chBids <- brw @@ -433,14 +477,9 @@ func (e *exchange) getAllBids( if !bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].bids) > 0 { bidsFound = true - bidIDsCollision = recordAdaptorDuplicateBidIDs(e.me, adapterBids) } - - } - if bidIDsCollision { - // record this request count this request if bid collision is detected - e.me.RecordRequestHavingDuplicateBidID() } + return adapterBids, adapterExtra, bidsFound } @@ -505,29 +544,45 @@ func errorsToMetric(errs []error) map[metrics.AdapterError]struct{} { return ret } -func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderError { - serr := make([]openrtb_ext.ExtBidderError, len(errs)) - for i := 0; i < len(errs); i++ { - serr[i].Code = errortypes.ReadCode(errs[i]) - serr[i].Message = errs[i].Error() +func errsToBidderErrors(errs []error) []openrtb_ext.ExtBidderMessage { + sErr := make([]openrtb_ext.ExtBidderMessage, 0) + for _, err := range errortypes.FatalOnly(errs) { + newErr := openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(err), + Message: err.Error(), + } + sErr = append(sErr, newErr) } - return serr + + return sErr +} + +func errsToBidderWarnings(errs []error) []openrtb_ext.ExtBidderMessage { + sWarn := make([]openrtb_ext.ExtBidderMessage, 0) + for _, warn := range errortypes.WarningOnly(errs) { + newErr := openrtb_ext.ExtBidderMessage{ + Code: errortypes.ReadCode(warn), + Message: warn.Error(), + } + sWarn = append(sWarn, newErr) + } + return sWarn } // This piece takes all the bids supplied by the adapters and crafts an openRTB response to send back to the requester -func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, errList []error) (*openrtb.BidResponse, error) { - bidResponse := new(openrtb.BidResponse) +func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_ext.BidderName, adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, bidRequest *openrtb2.BidRequest, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, bidResponseExt *openrtb_ext.ExtBidResponse, returnCreative bool, errList []error) (*openrtb2.BidResponse, error) { + bidResponse := new(openrtb2.BidResponse) var err error bidResponse.ID = bidRequest.ID if len(liveAdapters) == 0 { // signal "Invalid Request" if no valid bidders. - bidResponse.NBR = openrtb.NoBidReasonCode.Ptr(openrtb.NoBidReasonCodeInvalidRequest) + bidResponse.NBR = openrtb2.NoBidReasonCode.Ptr(openrtb2.NoBidReasonCodeInvalidRequest) } // Create the SeatBids. We use a zero sized slice so that we can append non-zero seat bids, and not include seatBid // objects for seatBids without any bids. Preallocate the max possible size to avoid reallocating the array as we go. - seatBids := make([]openrtb.SeatBid, 0, len(liveAdapters)) + seatBids := make([]openrtb2.SeatBid, 0, len(liveAdapters)) for _, a := range liveAdapters { //while processing every single bib, do we need to handle categories here? if adapterBids[a] != nil && len(adapterBids[a].bids) > 0 { @@ -554,7 +609,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e return buffer.Bytes(), err } -func applyCategoryMapping(ctx context.Context, bidRequest *openrtb.BidRequest, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { +func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { res := make(map[string]string) type bidDedupe struct { @@ -566,8 +621,6 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb.BidRequest, r dedupe := make(map[string]bidDedupe) - impMap := make(map[string]*openrtb.Imp) - // applyCategoryMapping doesn't get called unless // requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil brandCatExt := requestExt.Prebid.Targeting.IncludeBrandCategory @@ -582,11 +635,6 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb.BidRequest, r 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 @@ -663,12 +711,6 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb.BidRequest, r break } } - } else if 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 @@ -685,51 +727,49 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb.BidRequest, r categoryDuration = fmt.Sprintf("%s_%s", categoryDuration, bidderName.String()) } - if false == brandCatExt.SkipDedup { - if dupe, ok := dedupe[dupeKey]; ok { + if dupe, ok := dedupe[dupeKey]; ok { - dupeBidPrice, err := strconv.ParseFloat(dupe.bidPrice, 64) - if err != nil { - dupeBidPrice = 0 - } - currBidPrice, err := strconv.ParseFloat(pb, 64) - if err != nil { - currBidPrice = 0 - } - if dupeBidPrice == currBidPrice { - if rand.Intn(100) < 50 { - dupeBidPrice = -1 - } else { - currBidPrice = -1 - } + dupeBidPrice, err := strconv.ParseFloat(dupe.bidPrice, 64) + if err != nil { + dupeBidPrice = 0 + } + currBidPrice, err := strconv.ParseFloat(pb, 64) + if err != nil { + currBidPrice = 0 + } + if dupeBidPrice == currBidPrice { + if rand.Intn(100) < 50 { + dupeBidPrice = -1 + } else { + currBidPrice = -1 } + } - if dupeBidPrice < currBidPrice { - if dupe.bidderName == bidderName { - // An older bid from the current bidder - bidsToRemove = append(bidsToRemove, dupe.bidIndex) + 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] + if len(oldSeatBid.bids) == 1 { + seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName) 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] - if len(oldSeatBid.bids) == 1 { - seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName) - rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") - } else { - oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...) - } + oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...) } - delete(res, dupe.bidID) - } else { - // Remove this bid - bidsToRemove = append(bidsToRemove, bidInd) - 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 } - dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } res[bidID] = categoryDuration + dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } if len(bidsToRemove) > 0 { @@ -775,7 +815,8 @@ func getPrimaryAdServer(adServerId int) (string, error) { func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, r AuctionRequest, debugInfo bool, errList []error) *openrtb_ext.ExtBidResponse { req := r.BidRequest bidResponseExt := &openrtb_ext.ExtBidResponse{ - Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderError, len(adapterBids)), + Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)), + Warnings: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage, len(adapterBids)), ResponseTimeMillis: make(map[openrtb_ext.BidderName]int, len(adapterBids)), RequestTimeoutMillis: req.TMax, } @@ -797,6 +838,9 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb if debugInfo && len(responseExtra.HttpCalls) > 0 { bidResponseExt.Debug.HttpCalls[bidderName] = responseExtra.HttpCalls } + if len(responseExtra.Warnings) > 0 { + bidResponseExt.Warnings[bidderName] = responseExtra.Warnings + } // Only make an entry for bidder errors if the bidder reported any. if len(responseExtra.Errors) > 0 { bidResponseExt.Errors[bidderName] = responseExtra.Errors @@ -813,26 +857,10 @@ func (e *exchange) makeExtBidResponse(adapterBids map[openrtb_ext.BidderName]*pb // Return an openrtb seatBid for a bidder // BuildBidResponse is responsible for ensuring nil bid seatbids are not included -func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool) *openrtb.SeatBid { - seatBid := new(openrtb.SeatBid) - seatBid.Seat = adapter.String() - // Prebid cannot support roadblocking - seatBid.Group = 0 - - if len(adapterBid.ext) > 0 { - sbExt := ExtSeatBid{ - Bidder: adapterBid.ext, - } - - ext, err := json.Marshal(sbExt) - if err != nil { - extError := openrtb_ext.ExtBidderError{ - Code: errortypes.ReadCode(err), - Message: fmt.Sprintf("Error writing SeatBid.Ext: %s", err.Error()), - } - adapterExtra[adapter].Errors = append(adapterExtra[adapter].Errors, extError) - } - seatBid.Ext = ext +func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.BidderName, adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra, auc *auction, returnCreative bool) *openrtb2.SeatBid { + seatBid := &openrtb2.SeatBid{ + Seat: adapter.String(), + Group: 0, // Prebid cannot support roadblocking } var errList []error @@ -844,42 +872,58 @@ func (e *exchange) makeSeatBid(adapterBid *pbsOrtbSeatBid, adapter openrtb_ext.B return seatBid } -// Create the Bid array inside of SeatBid -func (e *exchange) makeBid(Bids []*pbsOrtbBid, auc *auction, returnCreative bool) ([]openrtb.Bid, []error) { - bids := make([]openrtb.Bid, 0, len(Bids)) - errList := make([]error, 0, 1) - for _, thisBid := range Bids { - bidExt := &openrtb_ext.ExtBid{ - Bidder: thisBid.bid.Ext, - Prebid: &openrtb_ext.ExtBidPrebid{ - Targeting: thisBid.bidTargets, - Type: thisBid.bidType, - Video: thisBid.bidVideo, - Events: thisBid.bidEvents, - DealPriority: thisBid.dealPriority, - DealTierSatisfied: thisBid.dealTierSatisfied, - }, - } - if cacheInfo, found := e.getBidCacheInfo(thisBid, auc); found { - bidExt.Prebid.Cache = &openrtb_ext.ExtBidPrebidCache{ +func (e *exchange) makeBid(bids []*pbsOrtbBid, auc *auction, returnCreative bool) ([]openrtb2.Bid, []error) { + result := make([]openrtb2.Bid, 0, len(bids)) + errs := make([]error, 0, 1) + + for _, bid := range bids { + bidExtPrebid := &openrtb_ext.ExtBidPrebid{ + DealPriority: bid.dealPriority, + DealTierSatisfied: bid.dealTierSatisfied, + Events: bid.bidEvents, + Targeting: bid.bidTargets, + Type: bid.bidType, + Video: bid.bidVideo, + BidId: bid.generatedBidID, + } + + if cacheInfo, found := e.getBidCacheInfo(bid, auc); found { + bidExtPrebid.Cache = &openrtb_ext.ExtBidPrebidCache{ Bids: &cacheInfo, } } - ext, err := json.Marshal(bidExt) - if err != nil { - errList = append(errList, err) + + if bidExtJSON, err := makeBidExtJSON(bid.bid.Ext, bidExtPrebid); err != nil { + errs = append(errs, err) } else { - bids = append(bids, *thisBid.bid) - bids[len(bids)-1].Ext = ext + result = append(result, *bid.bid) + resultBid := &result[len(result)-1] + resultBid.Ext = bidExtJSON if !returnCreative { - bids[len(bids)-1].AdM = "" + resultBid.AdM = "" } } } - return bids, errList + return result, errs } -// If bid got cached inside `(a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, targData *targetData, bidRequest *openrtb.BidRequest, ttlBuffer int64, defaultTTLs *config.DefaultTTLs, bidCategory map[string]string)`, +func makeBidExtJSON(ext json.RawMessage, prebid *openrtb_ext.ExtBidPrebid) (json.RawMessage, error) { + // no existing bid.ext. generate a bid.ext with just our prebid section populated. + if len(ext) == 0 { + bidExt := &openrtb_ext.ExtBid{Prebid: prebid} + return json.Marshal(bidExt) + } + + // update existing bid.ext with our prebid section. if bid.ext.prebid already exists, it will be overwritten. + var extMap map[string]interface{} + if err := json.Unmarshal(ext, &extMap); err != nil { + return nil, err + } + extMap[openrtb_ext.PrebidExtKey] = prebid + return json.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)`, // a UUID should be found inside `a.cacheIds` or `a.vastCacheIds`. This function returns the UUID along with the internal cache URL func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo openrtb_ext.ExtBidPrebidCacheBids, found bool) { uuid, found := findCacheID(bid, auction) @@ -939,26 +983,3 @@ func listBiddersWithRequests(bidderRequests []BidderRequest) []openrtb_ext.Bidde return liveAdapters } - -// 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]*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 -} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 6362741a6b8..c18f4533966 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -15,19 +15,20 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - metricsConf "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - metricsConfig "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - pbc "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/file_fetcher" - - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/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/gdpr" + "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/stored_requests" + "github.com/prebid/prebid-server/stored_requests/backends/file_fetcher" + "github.com/buger/jsonparser" "github.com/stretchr/testify/assert" "github.com/yudai/gojsondiff" @@ -52,7 +53,11 @@ func TestNewExchange(t *testing.T) { }, } - biddersInfo := adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", knownAdapters) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + t.Fatal(err) + } + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) @@ -70,19 +75,19 @@ func TestNewExchange(t *testing.T) { } } -// The objective is to get to execute e.buildBidResponse(ctx.Background(), liveA... ) (*openrtb.BidResponse, error) +// The objective is to get to execute e.buildBidResponse(ctx.Background(), liveA... ) (*openrtb2.BidResponse, error) // 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/PubMatic-OpenWrap/prebid-server/issues/465 +// as specified in https://github.com/prebid/prebid-server/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/PubMatic-OpenWrap/prebid-server/issues/465 +// sample request as specified in https://github.com/prebid/prebid-server/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/PubMatic-OpenWrap/prebid-server/issues/465 */ + /* https://github.com/prebid/prebid-server/issues/465 */ cfg := &config.Configuration{ Adapters: make(map[string]config.Adapter, 1), } @@ -96,7 +101,11 @@ func TestCharacterEscape(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() - biddersInfo := adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.CoreBidderNames()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + t.Fatal(err) + } + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) @@ -114,16 +123,16 @@ func TestCharacterEscape(t *testing.T) { adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid, 1) adapterBids["appnexus"] = &pbsOrtbSeatBid{currency: "USD"} - //An openrtb.BidRequest struct as specified in https://github.com/PubMatic-OpenWrap/prebid-server/issues/465 - bidRequest := &openrtb.BidRequest{ + //An openrtb2.BidRequest struct as specified in https://github.com/prebid/prebid-server/issues/465 + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, AT: 1, TMax: 500, Ext: json.RawMessage(`{"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": 1}}}],"tmax": 500}`), @@ -133,7 +142,7 @@ func TestCharacterEscape(t *testing.T) { adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, 1) adapterExtra["appnexus"] = &seatResponseExtra{ ResponseTimeMillis: 5, - Errors: []openrtb_ext.ExtBidderError{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}}, + Errors: []openrtb_ext.ExtBidderMessage{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}}, } var errList []error @@ -154,7 +163,7 @@ func TestCharacterEscape(t *testing.T) { } // TestDebugBehaviour asserts the HttpCalls object is included inside the json "debug" field of the bidResponse extension when the -// openrtb.BidRequest "Test" value is set to 1 or the openrtb.BidRequest.Ext.Debug boolean field is set to true +// openrtb2.BidRequest "Test" value is set to 1 or the openrtb2.BidRequest.Ext.Debug boolean field is set to true func TestDebugBehaviour(t *testing.T) { // Define test cases @@ -172,59 +181,68 @@ func TestDebugBehaviour(t *testing.T) { } type aTest struct { - desc string - in inTest - out outTest - debugData debugData + desc string + in inTest + out outTest + debugData debugData + generateWarnings bool } testCases := []aTest{ { - desc: "test flag equals zero, ext debug flag false, no debug info expected", - in: inTest{test: 0, debug: false}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + desc: "test flag equals zero, ext debug flag false, no debug info expected", + in: inTest{test: 0, debug: false}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag equals zero, ext debug flag true, debug info expected", - in: inTest{test: 0, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals zero, ext debug flag true, debug info expected", + in: inTest{test: 0, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag equals 1, ext debug flag false, debug info expected", - in: inTest{test: 1, debug: false}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals 1, ext debug flag false, debug info expected", + in: inTest{test: 1, debug: false}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag equals 1, ext debug flag true, debug info expected", - in: inTest{test: 1, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag equals 1, ext debug flag true, debug info expected", + in: inTest{test: 1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", - in: inTest{test: 2, debug: false}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", + in: inTest{test: 2, debug: false}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, true}, + generateWarnings: false, }, { - desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true}, + generateWarnings: true, }, { - desc: "test account level debug disabled", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, false}, + desc: "test account level debug disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{true, false}, + generateWarnings: true, }, { - desc: "test bidder level debug disabled", - in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{false, true}, + desc: "test bidder level debug disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: false}, + debugData: debugData{false, true}, + generateWarnings: true, }, } @@ -235,20 +253,20 @@ func TestDebugBehaviour(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(noBidServer)) defer server.Close() - categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") - if error != nil { - t.Errorf("Failed to create a category Fetcher: %v", error) + categoriesFetcher, err := newCategoryFetcher("./test/category-mapping") + if err != nil { + t.Errorf("Failed to create a category Fetcher: %v", err) } - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, AT: 1, TMax: 500, } @@ -277,7 +295,7 @@ func TestDebugBehaviour(t *testing.T) { for _, test := range testCases { e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}), + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}), } //request level debug key @@ -297,6 +315,13 @@ func TestDebugBehaviour(t *testing.T) { UserSyncs: &emptyUsersync{}, StartTime: time.Now(), } + if test.generateWarnings { + var errL []error + errL = append(errL, &errortypes.Warning{ + Message: fmt.Sprintf("CCPA consent test warning."), + WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) + auctionRequest.Warnings = errL + } // Run test outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil) @@ -331,6 +356,35 @@ func TestDebugBehaviour(t *testing.T) { } else { assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") } + + if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed { + assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") + assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") + assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present") + } + + if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed { + if test.generateWarnings { + assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning") + } else { + assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") + } + assert.NotNil(t, actualExt.Warnings["appnexus"], "bidder warning should be present") + assert.Equal(t, "debug turned off for bidder", actualExt.Warnings["appnexus"][0].Message, "account debug disabled message should be present") + } + + if test.generateWarnings { + assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") + CCPAWarningPresent := false + for _, warn := range actualExt.Warnings["general"] { + if warn.Code == errortypes.InvalidPrivacyConsentWarningCode { + CCPAWarningPresent = true + break + } + } + assert.True(t, CCPAWarningPresent, "CCPA Warning should be present") + } + } } @@ -385,18 +439,18 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher - debugLog := DebugLog{} + debugLog := DebugLog{Enabled: true} for _, testCase := range testCases { - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"telaria": {"placementId": 1}, "appnexus": {"placementid": 2}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, AT: 1, TMax: 500, } @@ -411,8 +465,8 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { } e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: testCase.bidder1DebugEnabled}), - openrtb_ext.BidderTelaria: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &adapters.DebugInfo{Allow: testCase.bidder2DebugEnabled}), + openrtb_ext.BidderAppnexus: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}), + openrtb_ext.BidderTelaria: adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}), } // Run test outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog) @@ -583,7 +637,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) { bidResponse: &adapters.BidderResponse{ Bids: []*adapters.TypedBid{ { - Bid: &openrtb.Bid{AdM: sampleAd}, + Bid: &openrtb2.Bid{AdM: sampleAd}, }, }, }, @@ -598,16 +652,17 @@ func TestReturnCreativeEndToEnd(t *testing.T) { e.gDPR = gdpr.AlwaysAllow{} e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e.categoriesFetcher = categoriesFetcher + e.bidIDGenerator = &mockBidIDGenerator{false, false} // Define mock incoming bid requeset - mockBidRequest := &openrtb.BidRequest{ + mockBidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, } // Run tests @@ -680,7 +735,11 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() - biddersInfo := adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.CoreBidderNames()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + t.Fatal(err) + } + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) @@ -692,7 +751,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { liveAdapters := []openrtb_ext.BidderName{bidderName} //adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, - bids := []*openrtb.Bid{ + bids := []*openrtb2.Bid{ { ID: "some-imp-id", ImpID: "", @@ -724,7 +783,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }, } auc := &auction{ - cacheIds: map[*openrtb.Bid]string{ + cacheIds: map[*openrtb2.Bid]string{ bids[0]: testUUID, }, } @@ -750,7 +809,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{ bidderName: { ResponseTimeMillis: 5, - Errors: []openrtb_ext.ExtBidderError{ + Errors: []openrtb_ext.ExtBidderMessage{ { Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\"", @@ -758,14 +817,14 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }, }, } - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", TMax: 1000, - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "test-div", - Secure: openrtb.Int8Ptr(0), - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}}}, + Secure: openrtb2.Int8Ptr(0), + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}}, Ext: json.RawMessage(` { "rubicon": { "accountId": 1001, @@ -780,9 +839,9 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { }`), }, }, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Page: "http://rubitest.com/index.html", - Publisher: &openrtb.Publisher{ID: "1001"}, + Publisher: &openrtb2.Publisher{ID: "1001"}, }, Test: 1, Ext: json.RawMessage(`{"prebid": { "cache": { "bids": {}, "vastxml": {} }, "targeting": { "pricegranularity": "med", "includewinners": true, "includebidderkeys": false } }}`), @@ -796,11 +855,11 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { /* 5) Assert we have no errors and the bid response we expected*/ assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") - expectedBidResponse := &openrtb.BidResponse{ - SeatBid: []openrtb.SeatBid{ + expectedBidResponse := &openrtb2.BidResponse{ + SeatBid: []openrtb2.SeatBid{ { Seat: string(bidderName), - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ { Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheScheme + `://` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`), }, @@ -829,7 +888,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { func TestBidReturnsCreative(t *testing.T) { sampleAd := "" - sampleOpenrtbBid := &openrtb.Bid{ID: "some-bid-id", AdM: sampleAd} + sampleOpenrtbBid := &openrtb2.Bid{ID: "some-bid-id", AdM: sampleAd} // Define test cases testCases := []struct { @@ -852,12 +911,13 @@ func TestBidReturnsCreative(t *testing.T) { // Test set up sampleBids := []*pbsOrtbBid{ { - bid: sampleOpenrtbBid, - bidType: openrtb_ext.BidTypeBanner, - bidTargets: map[string]string{}, + bid: sampleOpenrtbBid, + bidType: openrtb_ext.BidTypeBanner, + bidTargets: map[string]string{}, + generatedBidID: "randomId", }, } - sampleAuction := &auction{cacheIds: map[*openrtb.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}} + sampleAuction := &auction{cacheIds: map[*openrtb2.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}} noBidHandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(noBidHandler)) @@ -896,7 +956,7 @@ func TestBidReturnsCreative(t *testing.T) { } func TestGetBidCacheInfo(t *testing.T) { - bid := &openrtb.Bid{ID: "42"} + bid := &openrtb2.Bid{ID: "42"} testCases := []struct { description string scheme string @@ -914,7 +974,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "https://prebid.org/cache?uuid=anyID", @@ -925,7 +985,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{vastCacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{vastCacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "https://prebid.org/cache?uuid=anyID", @@ -946,7 +1006,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "prebid.org/cache?uuid=anyID", @@ -954,7 +1014,7 @@ func TestGetBidCacheInfo(t *testing.T) { { description: "Host And Path Not Provided - Without Scheme", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "", @@ -963,7 +1023,7 @@ func TestGetBidCacheInfo(t *testing.T) { description: "Host And Path Not Provided - With Scheme", scheme: "https", bid: &pbsOrtbBid{bid: bid}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: true, expectedCacheID: "anyID", expectedCacheURL: "", @@ -974,7 +1034,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: nil, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: false, expectedCacheID: "", expectedCacheURL: "", @@ -985,7 +1045,7 @@ func TestGetBidCacheInfo(t *testing.T) { host: "prebid.org", path: "cache", bid: &pbsOrtbBid{bid: nil}, - auction: &auction{cacheIds: map[*openrtb.Bid]string{bid: "anyID"}}, + auction: &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}}, expectedFound: false, expectedCacheID: "", expectedCacheURL: "", @@ -1029,7 +1089,11 @@ func TestBidResponseCurrency(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() - biddersInfo := adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.CoreBidderNames()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + t.Fatal(err) + } + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) @@ -1041,15 +1105,15 @@ func TestBidResponseCurrency(t *testing.T) { liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-impression-id", - Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, Ext: json.RawMessage(`{"appnexus": {"placementId": 10433394}}`), }}, - Site: &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, - Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"}, AT: 1, TMax: 500, Ext: json.RawMessage(`{"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": 10433394}}}],"tmax": 500}`), @@ -1061,7 +1125,7 @@ func TestBidResponseCurrency(t *testing.T) { var errList []error - sampleBid := &openrtb.Bid{ + sampleBid := &openrtb2.Bid{ ID: "some-imp-id", Price: 9.517803, W: 300, @@ -1069,10 +1133,10 @@ func TestBidResponseCurrency(t *testing.T) { Ext: nil, } aPbsOrtbBidArr := []*pbsOrtbBid{{bid: sampleBid, bidType: openrtb_ext.BidTypeBanner}} - sampleSeatBid := []openrtb.SeatBid{ + sampleSeatBid := []openrtb2.SeatBid{ { Seat: "appnexus", - Bid: []openrtb.Bid{ + Bid: []openrtb2.Bid{ { ID: "some-imp-id", Price: 9.517803, @@ -1083,13 +1147,13 @@ func TestBidResponseCurrency(t *testing.T) { }, }, } - emptySeatBid := []openrtb.SeatBid{} + emptySeatBid := []openrtb2.SeatBid{} // Test cases type aTest struct { description string adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid - expectedBidResponse *openrtb.BidResponse + expectedBidResponse *openrtb2.BidResponse } testCases := []aTest{ { @@ -1100,7 +1164,7 @@ func TestBidResponseCurrency(t *testing.T) { currency: "USD", }, }, - expectedBidResponse: &openrtb.BidResponse{ + expectedBidResponse: &openrtb2.BidResponse{ ID: "some-request-id", SeatBid: sampleSeatBid, Cur: "USD", @@ -1116,7 +1180,7 @@ func TestBidResponseCurrency(t *testing.T) { currency: "USD", }, }, - expectedBidResponse: &openrtb.BidResponse{ + expectedBidResponse: &openrtb2.BidResponse{ ID: "some-request-id", SeatBid: emptySeatBid, Cur: "", @@ -1132,7 +1196,7 @@ func TestBidResponseCurrency(t *testing.T) { currency: "", }, }, - expectedBidResponse: &openrtb.BidResponse{ + expectedBidResponse: &openrtb2.BidResponse{ ID: "some-request-id", SeatBid: sampleSeatBid, Cur: "", @@ -1148,7 +1212,7 @@ func TestBidResponseCurrency(t *testing.T) { currency: "", }, }, - expectedBidResponse: &openrtb.BidResponse{ + expectedBidResponse: &openrtb2.BidResponse{ ID: "some-request-id", SeatBid: emptySeatBid, Cur: "", @@ -1203,7 +1267,11 @@ func TestRaceIntegration(t *testing.T) { ExtraAdapterInfo: "{\"video_endpoint\":\"" + server.URL + "\"}", } - biddersInfo := adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.CoreBidderNames()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + t.Fatal(err) + } + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) @@ -1219,7 +1287,7 @@ func TestRaceIntegration(t *testing.T) { debugLog := DebugLog{} ex := NewExchange(adapters, &wellBehavedCache{}, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, &nilCategoryFetcher{}).(*exchange) - _, err := ex.HoldAuction(context.Background(), auctionRequest, &debugLog) + _, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) } @@ -1239,39 +1307,39 @@ func newCategoryFetcher(directory string) (stored_requests.CategoryFetcher, erro // newRaceCheckingRequest builds a BidRequest from all the params in the // adapters/{bidder}/{bidder}test/params/race/*.json files -func newRaceCheckingRequest(t *testing.T) *openrtb.BidRequest { +func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest { dnt := int8(1) - return &openrtb.BidRequest{ - Site: &openrtb.Site{ + return &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "www.some.domain.com", Domain: "domain.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "some-publisher-id", }, }, - Device: &openrtb.Device{ + Device: &openrtb2.Device{ 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", IP: "132.173.230.74", DNT: &dnt, Language: "EN", }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ COPPA: 1, Ext: json.RawMessage(`{"gdpr":1}`), }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }, { @@ -1281,7 +1349,7 @@ func newRaceCheckingRequest(t *testing.T) *openrtb.BidRequest { }, Ext: buildImpExt(t, "banner"), }, { - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: []string{"video/mp4"}, MinDuration: 1, MaxDuration: 300, @@ -1301,7 +1369,11 @@ func TestPanicRecovery(t *testing.T) { Adapters: blankAdapterConfig(openrtb_ext.CoreBidderNames()), } - biddersInfo := adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.CoreBidderNames()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + t.Fatal(err) + } + adapters, adaptersErr := BuildAdapters(&http.Client{}, cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) @@ -1329,14 +1401,14 @@ func TestPanicRecovery(t *testing.T) { BidderName: "bidder1", BidderCoreName: "appnexus", BidderLabels: apnLabels, - BidRequest: &openrtb.BidRequest{ + BidRequest: &openrtb2.BidRequest{ ID: "b-1", }, }, { BidderName: "bidder2", BidderCoreName: "bidder2", - BidRequest: &openrtb.BidRequest{ + BidRequest: &openrtb2.BidRequest{ ID: "b-2", }, }, @@ -1387,7 +1459,11 @@ func TestPanicRecoveryHighLevel(t *testing.T) { } cfg.Adapters["audiencenetwork"] = config.Adapter{Disabled: true} - biddersInfo := adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.CoreBidderNames()) + biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info", cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + t.Fatal(err) + } + adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.DummyMetricsEngine{}) if adaptersErr != nil { t.Fatalf("Error intializing adapters: %v", adaptersErr) @@ -1405,23 +1481,23 @@ func TestPanicRecoveryHighLevel(t *testing.T) { e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{} e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{} - request := &openrtb.BidRequest{ - Site: &openrtb.Site{ + request := &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "www.some.domain.com", Domain: "domain.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "some-publisher-id", }, }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }, { @@ -1439,7 +1515,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { UserSyncs: &emptyUsersync{}, } debugLog := DebugLog{} - _, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog) + _, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog) if err != nil { t.Errorf("HoldAuction returned unexpected error: %v", err) } @@ -1551,8 +1627,11 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { EEACountriesMap: eeac, }, } - - ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig) + bidIdGenerator := &mockBidIDGenerator{} + if spec.BidIDGenerator != nil { + *bidIdGenerator = *spec.BidIDGenerator + } + ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator) biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest) debugLog := &DebugLog{} if spec.DebugLog != nil { @@ -1608,11 +1687,10 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { if spec.IncomingRequest.OrtbRequest.Test == 1 { //compare debug info diffJson(t, "Debug info modified", bid.Ext, spec.Response.Ext) - } } -func findBiddersInAuction(t *testing.T, context string, req *openrtb.BidRequest) []string { +func findBiddersInAuction(t *testing.T, context string, req *openrtb2.BidRequest) []string { if splitImps, err := splitImps(req.Imp); err != nil { t.Errorf("%s: Failed to parse Bidders from request: %v", context, err) return nil @@ -1628,7 +1706,7 @@ func findBiddersInAuction(t *testing.T, context string, req *openrtb.BidRequest) // extractResponseTimes validates the format of bid.ext.responsetimemillis, and then removes it. // This is done because the response time will change from run to run, so it's impossible to hardcode a value // into the JSON. The best we can do is make sure that the property exists. -func extractResponseTimes(t *testing.T, context string, bid *openrtb.BidResponse) map[string]int { +func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidResponse) map[string]int { if data, dataType, _, err := jsonparser.Get(bid.Ext, "responsetimemillis"); err != nil || dataType != jsonparser.Object { t.Errorf("%s: Exchange did not return ext.responsetimemillis object: %v", context, err) return nil @@ -1649,9 +1727,9 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb.BidResponse } } -func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy) Exchange { +func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator) Exchange { bidderAdapters := make(map[openrtb_ext.BidderName]adaptedBidder, len(expectations)) - bidderInfos := make(adapters.BidderInfos, len(expectations)) + bidderInfos := make(config.BidderInfos, len(expectations)) for _, bidderName := range openrtb_ext.CoreBidderNames() { if spec, ok := expectations[string(bidderName)]; ok { bidderAdapters[bidderName] = &validatingBidder{ @@ -1661,7 +1739,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] expectations: map[string]*bidderRequest{string(bidderName): spec.ExpectedRequest}, mockResponses: map[string]bidderResponse{string(bidderName): spec.MockResponse}, } - bidderInfos[string(bidderName)] = adapters.BidderInfo{ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed} + bidderInfos[string(bidderName)] = config.BidderInfo{ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed} } } @@ -1699,7 +1777,27 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] categoriesFetcher: categoriesFetcher, bidderInfo: bidderInfos, externalURL: "http://localhost", + bidIDGenerator: bidIDGenerator, + } +} + +type mockBidIDGenerator struct { + GenerateBidID bool `json:"generateBidID"` + ReturnError bool `json:"returnError"` +} + +func (big *mockBidIDGenerator) Enabled() bool { + return big.GenerateBidID +} + +func (big *mockBidIDGenerator) New() (string, error) { + + if big.ReturnError { + err := errors.New("Test error generating bid.ext.prebid.bidid") + return "", err } + return "mock_uuid", nil + } func newExtRequest() openrtb_ext.ExtRequest { @@ -1764,7 +1862,6 @@ func TestCategoryMapping(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequest() targData := &targetData{ @@ -1780,15 +1877,15 @@ func TestCategoryMapping(t *testing.T) { cats2 := []string{"IAB1-4"} cats3 := []string{"IAB1-1000"} cats4 := []string{"IAB1-2000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} + 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: 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} + bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1797,12 +1894,12 @@ func TestCategoryMapping(t *testing.T) { &bid1_4, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -1821,7 +1918,6 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequestNoBrandCat() targData := &targetData{ @@ -1836,15 +1932,15 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { cats2 := []string{"IAB1-4"} cats3 := []string{"IAB1-1000"} cats4 := []string{"IAB1-2000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} - bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} + 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: 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} + bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1} - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1853,12 +1949,12 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { &bid1_4, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -1877,7 +1973,6 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequestTranslateCategories(nil) targData := &targetData{ @@ -1892,13 +1987,13 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { cats1 := []string{"IAB1-3"} cats2 := []string{"IAB1-4"} cats3 := []string{"IAB1-1000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} + 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: 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 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1906,12 +2001,12 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { &bid1_3, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -1960,7 +2055,6 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { } translateCategories := false - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequestTranslateCategories(&translateCategories) targData := &targetData{ @@ -1975,13 +2069,13 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { cats1 := []string{"IAB1-3"} cats2 := []string{"IAB1-4"} cats3 := []string{"IAB1-1000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1} + 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: 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 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids := []*pbsOrtbBid{ &bid1_1, @@ -1989,12 +2083,12 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { &bid1_3, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2012,7 +2106,6 @@ func TestCategoryDedupe(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequest() targData := &targetData{ @@ -2026,17 +2119,17 @@ func TestCategoryDedupe(t *testing.T) { cats2 := []string{"IAB1-4"} // bid3 will be same price, category, and duration as bid1 so one of them should get removed cats4 := []string{"IAB1-2000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} - bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} - bid5 := openrtb.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1} - - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + 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: 15.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} + 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: 20.0000, Cat: cats1, W: 1, H: 1} + + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -2060,12 +2153,12 @@ func TestCategoryDedupe(t *testing.T) { &bid1_5, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages") @@ -2093,7 +2186,6 @@ func TestNoCategoryDedupe(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequestNoBrandCat() targData := &targetData{ @@ -2106,17 +2198,17 @@ func TestNoCategoryDedupe(t *testing.T) { cats1 := []string{"IAB1-3"} cats2 := []string{"IAB1-4"} cats4 := []string{"IAB1-2000"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 14.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 14.0000, Cat: cats2, W: 1, H: 1} - bid3 := openrtb.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} - bid4 := openrtb.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1} - bid5 := openrtb.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1} - - bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 14.0000, Cat: cats1, W: 1, H: 1} + bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 14.0000, Cat: cats2, W: 1, H: 1} + bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1} + 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 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_3 := pbsOrtbBid{&bid3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_4 := pbsOrtbBid{&bid4, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_5 := pbsOrtbBid{&bid5, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} selectedBids := make(map[string]int) expectedCategories := map[string]string{ @@ -2141,12 +2233,12 @@ func TestNoCategoryDedupe(t *testing.T) { &bid1_5, } - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} bidderName1 := openrtb_ext.BidderName("appnexus") adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") @@ -2175,7 +2267,6 @@ func TestCategoryMappingBidderName(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequest() requestExt.Prebid.Targeting.AppendBidderNames = true @@ -2190,11 +2281,11 @@ func TestCategoryMappingBidderName(t *testing.T) { cats1 := []string{"IAB1-1"} cats2 := []string{"IAB1-2"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1} + 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 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids1 := []*pbsOrtbBid{ &bid1_1, @@ -2203,16 +2294,16 @@ func TestCategoryMappingBidderName(t *testing.T) { &bid1_2, } - seatBid1 := pbsOrtbSeatBid{innerBids1, "USD", nil, nil} + seatBid1 := pbsOrtbSeatBid{bids: innerBids1, currency: "USD"} bidderName1 := openrtb_ext.BidderName("bidder1") - seatBid2 := pbsOrtbSeatBid{innerBids2, "USD", nil, nil} + seatBid2 := pbsOrtbSeatBid{bids: innerBids2, currency: "USD"} bidderName2 := openrtb_ext.BidderName("bidder2") adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2230,7 +2321,6 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequestNoBrandCat() requestExt.Prebid.Targeting.AppendBidderNames = true @@ -2245,11 +2335,11 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { cats1 := []string{"IAB1-1"} cats2 := []string{"IAB1-2"} - bid1 := openrtb.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bid2 := openrtb.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1} + 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 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_1 := pbsOrtbBid{&bid1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_2 := pbsOrtbBid{&bid2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBids1 := []*pbsOrtbBid{ &bid1_1, @@ -2258,16 +2348,16 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { &bid1_2, } - seatBid1 := pbsOrtbSeatBid{innerBids1, "USD", nil, nil} + seatBid1 := pbsOrtbSeatBid{bids: innerBids1, currency: "USD"} bidderName1 := openrtb_ext.BidderName("bidder1") - seatBid2 := pbsOrtbSeatBid{innerBids2, "USD", nil, nil} + seatBid2 := pbsOrtbSeatBid{bids: innerBids2, currency: "USD"} bidderName2 := openrtb_ext.BidderName("bidder2") adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2303,7 +2393,7 @@ func TestBidRejectionErrors(t *testing.T) { testCases := []struct { description string reqExt openrtb_ext.ExtRequest - bids []*openrtb.Bid + bids []*openrtb2.Bid duration int expectedRejections []string expectedCatDur string @@ -2311,7 +2401,7 @@ func TestBidRejectionErrors(t *testing.T) { { description: "Bid should be rejected due to not containing a category", reqExt: requestExt, - bids: []*openrtb.Bid{ + bids: []*openrtb2.Bid{ {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{}, W: 1, H: 1}, }, duration: 30, @@ -2322,7 +2412,7 @@ func TestBidRejectionErrors(t *testing.T) { { description: "Bid should be rejected due to missing category mapping file", reqExt: invalidReqExt, - bids: []*openrtb.Bid{ + bids: []*openrtb2.Bid{ {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, }, duration: 30, @@ -2333,7 +2423,7 @@ func TestBidRejectionErrors(t *testing.T) { { description: "Bid should be rejected due to duration exceeding maximum", reqExt: requestExt, - bids: []*openrtb.Bid{ + bids: []*openrtb2.Bid{ {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, }, duration: 70, @@ -2344,7 +2434,7 @@ func TestBidRejectionErrors(t *testing.T) { { description: "Bid should be rejected due to duplicate bid", reqExt: requestExt, - bids: []*openrtb.Bid{ + bids: []*openrtb2.Bid{ {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, {ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1}, }, @@ -2360,17 +2450,15 @@ func TestBidRejectionErrors(t *testing.T) { innerBids := []*pbsOrtbBid{} for _, bid := range test.bids { currentBid := pbsOrtbBid{ - bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, - } + bid, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, 0, false, ""} innerBids = append(innerBids, ¤tBid) } - bidRequest := &openrtb.BidRequest{} - seatBid := pbsOrtbSeatBid{innerBids, "USD", nil, nil} + seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} adapterBids[bidderName] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &test.reqExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &test.reqExt, adapterBids, categoriesFetcher, targData) if len(test.expectedCatDur) > 0 { // Bid deduplication case @@ -2394,7 +2482,6 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } - bidRequest := &openrtb.BidRequest{} requestExt := newExtRequestTranslateCategories(nil) targData := &targetData{ @@ -2408,11 +2495,11 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { cats1 := []string{"IAB1-3"} cats2 := []string{"IAB1-4"} - bidApn1 := openrtb.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1} - bidApn2 := openrtb.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1} + 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 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} - bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false} + bid1_Apn1 := pbsOrtbBid{&bidApn1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn2 := pbsOrtbBid{&bidApn2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} innerBidsApn1 := []*pbsOrtbBid{ &bid1_Apn1, @@ -2425,16 +2512,16 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { for i := 1; i < 10; i++ { adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) - seatBidApn1 := pbsOrtbSeatBid{innerBidsApn1, "USD", nil, nil} + seatBidApn1 := pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"} bidderNameApn1 := openrtb_ext.BidderName("appnexus1") - seatBidApn2 := pbsOrtbSeatBid{innerBidsApn2, "USD", nil, nil} + seatBidApn2 := pbsOrtbSeatBid{bids: innerBidsApn2, currency: "USD"} bidderNameApn2 := openrtb_ext.BidderName("appnexus2") adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) assert.NoError(t, err, "Category mapping error should be empty") assert.Len(t, rejections, 1, "There should be 1 bid rejection message") @@ -2529,9 +2616,9 @@ func TestApplyDealSupport(t *testing.T) { bidderName := openrtb_ext.BidderName("appnexus") for _, test := range testCases { - bidRequest := &openrtb.BidRequest{ + bidRequest := &openrtb2.BidRequest{ ID: "some-request-id", - Imp: []openrtb.Imp{ + Imp: []openrtb2.Imp{ { ID: "imp_id1", Ext: test.impExt, @@ -2539,7 +2626,7 @@ func TestApplyDealSupport(t *testing.T) { }, } - bid := pbsOrtbBid{&openrtb.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false} + bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""} bidCategory := map[string]string{ bid.bid.ID: test.targ["hb_pb_cat_dur"], } @@ -2565,20 +2652,20 @@ func TestApplyDealSupport(t *testing.T) { func TestGetDealTiers(t *testing.T) { testCases := []struct { description string - request openrtb.BidRequest + request openrtb2.BidRequest expected map[string]openrtb_ext.DealTierBidderMap }{ { description: "None", - request: openrtb.BidRequest{ - Imp: []openrtb.Imp{}, + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{}, }, expected: map[string]openrtb_ext.DealTierBidderMap{}, }, { description: "One", - request: openrtb.BidRequest{ - Imp: []openrtb.Imp{ + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}}}`)}, }, }, @@ -2588,8 +2675,8 @@ func TestGetDealTiers(t *testing.T) { }, { description: "Many", - request: openrtb.BidRequest{ - Imp: []openrtb.Imp{ + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 8, "prefix": "tier2"}}}`)}, }, @@ -2601,8 +2688,8 @@ func TestGetDealTiers(t *testing.T) { }, { description: "Many - Skips Malformed", - request: openrtb.BidRequest{ - Imp: []openrtb.Imp{ + request: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}`)}, {ID: "imp2", Ext: json.RawMessage(`{"appnexus": {"dealTier": "wrong type"}}`)}, }, @@ -2706,7 +2793,7 @@ func TestUpdateHbPbCatDur(t *testing.T) { } for _, test := range testCases { - bid := pbsOrtbBid{&openrtb.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false} + bid := pbsOrtbBid{&openrtb2.Bid{ID: "123456"}, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, test.dealPriority, false, ""} bidCategory := map[string]string{ bid.bid.ID: test.targ["hb_pb_cat_dur"], } @@ -2718,50 +2805,6 @@ func TestUpdateHbPbCatDur(t *testing.T) { } } -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{}, nil) - - for _, testcase := range testCases { - var adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid - if nil == testcase.bidderCollisions { - break - } - adapterBids = make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) - for bidder, collisions := range *testcase.bidderCollisions { - bids := make([]*pbsOrtbBid, 0) - testBidID := "bid_id_for_bidder_" + bidder - // add bids as per collisions value - bidCount := 0 - for ; bidCount < collisions; bidCount++ { - bids = append(bids, &pbsOrtbBid{ - bid: &openrtb.Bid{ - ID: testBidID, - }, - }) - } - if nil == adapterBids[openrtb_ext.BidderName(bidder)] { - adapterBids[openrtb_ext.BidderName(bidder)] = new(pbsOrtbSeatBid) - } - adapterBids[openrtb_ext.BidderName(bidder)].bids = bids - } - assert.Equal(t, testcase.hasCollision, recordAdaptorDuplicateBidIDs(testEngine, adapterBids)) - } -} - type exchangeSpec struct { GDPREnabled bool `json:"gdpr_enabled"` IncomingRequest exchangeRequest `json:"incomingRequest"` @@ -2773,17 +2816,18 @@ type exchangeSpec struct { DebugLog *DebugLog `json:"debuglog,omitempty"` EventsEnabled bool `json:"events_enabled,omitempty"` StartTime int64 `json:"start_time_ms,omitempty"` + BidIDGenerator *mockBidIDGenerator `json:"bidIDGenerator,omitempty"` } type exchangeRequest struct { - OrtbRequest openrtb.BidRequest `json:"ortbRequest"` - Usersyncs map[string]string `json:"usersyncs"` + OrtbRequest openrtb2.BidRequest `json:"ortbRequest"` + Usersyncs map[string]string `json:"usersyncs"` } type exchangeResponse struct { - Bids *openrtb.BidResponse `json:"bids"` - Error string `json:"error,omitempty"` - Ext json.RawMessage `json:"ext,omitempty"` + Bids *openrtb2.BidResponse `json:"bids"` + Error string `json:"error,omitempty"` + Ext json.RawMessage `json:"ext,omitempty"` } type bidderSpec struct { @@ -2793,8 +2837,8 @@ type bidderSpec struct { } type bidderRequest struct { - OrtbRequest openrtb.BidRequest `json:"ortbRequest"` - BidAdjustment float64 `json:"bidAdjustment"` + OrtbRequest openrtb2.BidRequest `json:"ortbRequest"` + BidAdjustment float64 `json:"bidAdjustment"` } type bidderResponse struct { @@ -2813,8 +2857,8 @@ type bidderSeatBid struct { // bidderBid is basically a subset of pbsOrtbBid from exchange/bidder.go. // See the comment on bidderSeatBid for more info. type bidderBid struct { - Bid *openrtb.Bid `json:"ortbBid,omitempty"` - Type string `json:"bidType,omitempty"` + Bid *openrtb2.Bid `json:"ortbBid,omitempty"` + Type string `json:"bidType,omitempty"` } type mockIdFetcher map[string]string @@ -2838,7 +2882,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(name)]; ok { if expectedRequest != nil { if expectedRequest.BidAdjustment != bidAdjustment { @@ -2881,7 +2925,7 @@ func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb.BidR return } -func diffOrtbRequests(t *testing.T, description string, expected *openrtb.BidRequest, actual *openrtb.BidRequest) { +func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) { t.Helper() actualJSON, err := json.Marshal(actual) if err != nil { @@ -2896,7 +2940,7 @@ func diffOrtbRequests(t *testing.T, description string, expected *openrtb.BidReq diffJson(t, description, actualJSON, expectedJSON) } -func diffOrtbResponses(t *testing.T, description string, expected *openrtb.BidResponse, actual *openrtb.BidResponse) { +func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidResponse, actual *openrtb2.BidResponse) { t.Helper() // The OpenRTB spec is wonky here. Since "bidresponse.seatbid" is an array, order technically matters to any JSON diff or // deep equals method. However, for all intents and purposes it really *doesn't* matter. ...so this nasty logic makes compares @@ -2919,8 +2963,8 @@ func diffOrtbResponses(t *testing.T, description string, expected *openrtb.BidRe diffJson(t, description, actualJSON, expectedJSON) } -func mapifySeatBids(t *testing.T, context string, seatBids []openrtb.SeatBid) map[string]*openrtb.SeatBid { - seatMap := make(map[string]*openrtb.SeatBid, len(seatBids)) +func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) map[string]*openrtb2.SeatBid { + seatMap := make(map[string]*openrtb2.SeatBid, len(seatBids)) for i := 0; i < len(seatBids); i++ { seatName := seatBids[i].Seat if _, ok := seatMap[seatName]; ok { @@ -3017,7 +3061,7 @@ func (e *mockUsersync) LiveSyncCount() int { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } diff --git a/exchange/exchangetest/append-bidder-names.json b/exchange/exchangetest/append-bidder-names.json index dbab99b238b..507aee1a103 100644 --- a/exchange/exchangetest/append-bidder-names.json +++ b/exchange/exchangetest/append-bidder-names.json @@ -216,4 +216,4 @@ } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/bid-ext-prebid-collision.json b/exchange/exchangetest/bid-ext-prebid-collision.json new file mode 100644 index 00000000000..054671ce8d2 --- /dev/null +++ b/exchange/exchangetest/bid-ext-prebid-collision.json @@ -0,0 +1,90 @@ +{ + "description": "Verifies bid.ext values are left alone from the adapter, except for overwriting bid.ext.prebid if the adapter ext includes a collision.", + + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }] + } + }, + "outgoingRequests": { + "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 + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue", + "prebid": { + "willBeOverwritten": "by core logic" + } + } + }, + "bidType": "video" + }] + } + } + } + }, + "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": { + "someField": "someValue", + "prebid": { + "type": "video" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/bid-ext.json b/exchange/exchangetest/bid-ext.json new file mode 100644 index 00000000000..8211ac88eac --- /dev/null +++ b/exchange/exchangetest/bid-ext.json @@ -0,0 +1,87 @@ +{ + "description": "Verifies bid.ext values are left alone from the adapter, except for adding in bid.ext.prebid.", + + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [{ + "id": "my-imp-id", + "video": { + "mimes": ["video/mp4"] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + }] + } + }, + "outgoingRequests": { + "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 + } + } + }] + }, + "bidAdjustment": 1.0 + }, + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [{ + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "ext": { + "someField": "someValue" + } + }, + "bidType": "video" + }] + } + } + } + }, + "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": { + "someField": "someValue", + "prebid": { + "type": "video" + } + } + }] + }] + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/bid-id-invalid.json b/exchange/exchangetest/bid-id-invalid.json new file mode 100644 index 00000000000..17d3a3ec4d8 --- /dev/null +++ b/exchange/exchangetest/bid-id-invalid.json @@ -0,0 +1,161 @@ +{ + "bidIDGenerator": { + "generateBidID": true, + "returnError": true + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "appendbiddernames": true + } + } + } + }, + "usersyncs": { + "appnexus": "123" + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } + } + ] + } + } + } + }, + "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", + "cat": [ + "IAB1-1" + ], + "ext": { + "prebid": { + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.20", + "hb_pb_appnexus": "0.20", + "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } + } + } + } + ] + } + ] + }, + "ext": { + "debug": { + "resolvedrequest": { + "id": "some-request-id", + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "appendbiddernames": true + } + } + } + } + }, + "errors": { + "prebid": [ + { + "code": 999, + "message": "Error generating bid.ext.prebid.bidid" + } + ] + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/bid-id-valid.json b/exchange/exchangetest/bid-id-valid.json new file mode 100644 index 00000000000..8da6410e8d8 --- /dev/null +++ b/exchange/exchangetest/bid-id-valid.json @@ -0,0 +1,154 @@ +{ + "bidIDGenerator": { + "generateBidID": true, + "returnError": false + }, + "incomingRequest": { + "ortbRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "appendbiddernames": true + } + } + } + }, + "usersyncs": { + "appnexus": "123" + } + }, + "outgoingRequests": { + "appnexus": { + "mockResponse": { + "pbsSeatBid": { + "pbsBids": [ + { + "ortbBid": { + "id": "apn-bid", + "impid": "my-imp-id", + "price": 0.3, + "w": 200, + "h": 250, + "crid": "creative-1", + "cat": [ + "IAB1-1" + ] + }, + "bidType": "video", + "bidVideo": { + "duration": 30, + "PrimaryCategory": "" + } + } + ] + } + } + } + }, + "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", + "cat": [ + "IAB1-1" + ], + "ext": { + "prebid": { + "bidid": "mock_uuid", + "type": "video", + "targeting": { + "hb_bidder": "appnexus", + "hb_bidder_appnexus": "appnexus", + "hb_cache_host": "www.pbcserver.com", + "hb_cache_host_appnex": "www.pbcserver.com", + "hb_cache_path": "/pbcache/endpoint", + "hb_cache_path_appnex": "/pbcache/endpoint", + "hb_pb": "0.20", + "hb_pb_appnexus": "0.20", + "hb_pb_cat_dur": "0.20_VideoGames_0s_appnexus", + "hb_pb_cat_dur_appnex": "0.20_VideoGames_0s_appnexus", + "hb_size": "200x250", + "hb_size_appnexus": "200x250" + } + } + } + } + ] + } + ] + }, + "ext": { + "debug": { + "resolvedrequest": { + "id": "some-request-id", + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 1 + } + } + } + ], + "site": { + "page": "test.somepage.com" + }, + "test": 1, + "ext": { + "prebid": { + "targeting": { + "includebrandcategory": { + "primaryadserver": 1, + "publisher": "", + "withcategory": true + }, + "appendbiddernames": true + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/exchange/exchangetest/debuglog_disabled.json b/exchange/exchangetest/debuglog_disabled.json index cc66a1f524d..e95b556b7ec 100644 --- a/exchange/exchangetest/debuglog_disabled.json +++ b/exchange/exchangetest/debuglog_disabled.json @@ -224,4 +224,4 @@ } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index e7b59945e0e..851bda69097 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -224,4 +224,4 @@ } } } -} +} \ No newline at end of file diff --git a/exchange/exchangetest/events-vast-account-off-request-on.json b/exchange/exchangetest/events-vast-account-off-request-on.json index 6e29ccdf749..13afb8409af 100644 --- a/exchange/exchangetest/events-vast-account-off-request-on.json +++ b/exchange/exchangetest/events-vast-account-off-request-on.json @@ -1,6 +1,10 @@ { "events_enabled": false, "start_time_ms": 1234567890, + "bidIDGenerator": { + "generateBidID": true, + "returnError": false + }, "incomingRequest": { "ortbRequest": { "id": "some-request-id", @@ -105,6 +109,7 @@ "crid": "creative-4", "ext": { "prebid": { + "bidid": "mock_uuid", "type": "video" } } @@ -116,7 +121,7 @@ "bid": [ { "id": "winning-bid", - "adm": "prebid.org wrapper", + "adm": "prebid.org wrapper", "nurl": "http://domain.com/winning-bid", "impid": "my-imp-id", "price": 0.71, @@ -125,6 +130,7 @@ "crid": "creative-1", "ext": { "prebid": { + "bidid": "mock_uuid", "type": "video", "targeting": { "hb_bidder": "appnexus", @@ -139,7 +145,7 @@ }, { "id": "losing-bid", - "adm": "prebid.org wrapper", + "adm": "prebid.org wrapper", "nurl": "http://domain.com/losing-bid", "impid": "my-imp-id", "price": 0.21, @@ -148,6 +154,7 @@ "crid": "creative-2", "ext": { "prebid": { + "bidid": "mock_uuid", "type": "video" } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json index 8004c3c2646..df0e4e33ea5 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-bidders.json @@ -25,9 +25,12 @@ "siteId": 2, "zoneId": 3 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -57,9 +60,12 @@ "bidder": { "placementId": 1 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -107,9 +113,12 @@ "siteId": 2, "zoneId": 3 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json similarity index 90% rename from exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json rename to exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json index d62afccf426..ea4a980c63f 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json @@ -29,9 +29,12 @@ } } }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -61,10 +64,12 @@ "bidder": { "placementId": 1 }, - "prebid": {}, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -112,10 +117,12 @@ "siteId": 2, "zoneId": 3 }, - "prebid": {}, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json index 6f0bab9529c..7e2c4b9a16d 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-bidder.json @@ -20,9 +20,12 @@ "appnexus": { "placementId": 1 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -52,9 +55,12 @@ "bidder": { "placementId": 1 }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } diff --git a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json index 1610b9ea47e..f1977426591 100644 --- a/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json +++ b/exchange/exchangetest/firstpartydata-imp-ext-one-prebid-bidder.json @@ -24,9 +24,12 @@ } } }, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } @@ -56,10 +59,12 @@ "bidder": { "placementId": 1 }, - "prebid": {}, + "data": { + "keywords": "prebid server example" + }, "context": { "data": { - "keywords": "prebid server example" + "keywords": "another prebid server example" } } } diff --git a/exchange/gdpr.go b/exchange/gdpr.go index 6b6a073bb23..208ce0fdb0b 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -3,12 +3,12 @@ package exchange import ( "encoding/json" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/gdpr" ) // ExtractGDPR will pull the gdpr flag from an openrtb request -func extractGDPR(bidRequest *openrtb.BidRequest) (gdpr.Signal, error) { +func extractGDPR(bidRequest *openrtb2.BidRequest) (gdpr.Signal, error) { var re regsExt var err error if bidRequest.Regs != nil && bidRequest.Regs.Ext != nil { @@ -21,7 +21,7 @@ func extractGDPR(bidRequest *openrtb.BidRequest) (gdpr.Signal, error) { } // ExtractConsent will pull the consent string from an openrtb request -func extractConsent(bidRequest *openrtb.BidRequest) (consent string, err error) { +func extractConsent(bidRequest *openrtb2.BidRequest) (consent string, err error) { var ue userExt if bidRequest.User != nil && bidRequest.User.Ext != nil { err = json.Unmarshal(bidRequest.User.Ext, &ue) diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index e767c269b3b..e44dc9702fb 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -4,31 +4,31 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/gdpr" "github.com/stretchr/testify/assert" ) func TestExtractGDPR(t *testing.T) { tests := []struct { description string - giveRegs *openrtb.Regs + giveRegs *openrtb2.Regs wantGDPR gdpr.Signal wantError bool }{ { description: "Regs Ext GDPR = 0", - giveRegs: &openrtb.Regs{Ext: json.RawMessage(`{"gdpr": 0}`)}, + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 0}`)}, wantGDPR: gdpr.SignalNo, }, { description: "Regs Ext GDPR = 1", - giveRegs: &openrtb.Regs{Ext: json.RawMessage(`{"gdpr": 1}`)}, + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": 1}`)}, wantGDPR: gdpr.SignalYes, }, { description: "Regs Ext GDPR = null", - giveRegs: &openrtb.Regs{Ext: json.RawMessage(`{"gdpr": null}`)}, + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"gdpr": null}`)}, wantGDPR: gdpr.SignalAmbiguous, }, { @@ -38,19 +38,19 @@ func TestExtractGDPR(t *testing.T) { }, { description: "Regs Ext is nil", - giveRegs: &openrtb.Regs{Ext: nil}, + giveRegs: &openrtb2.Regs{Ext: nil}, wantGDPR: gdpr.SignalAmbiguous, }, { description: "JSON unmarshal error", - giveRegs: &openrtb.Regs{Ext: json.RawMessage(`{"`)}, + giveRegs: &openrtb2.Regs{Ext: json.RawMessage(`{"`)}, wantGDPR: gdpr.SignalAmbiguous, wantError: true, }, } for _, tt := range tests { - bidReq := openrtb.BidRequest{ + bidReq := openrtb2.BidRequest{ Regs: tt.giveRegs, } @@ -68,23 +68,23 @@ func TestExtractGDPR(t *testing.T) { func TestExtractConsent(t *testing.T) { tests := []struct { description string - giveUser *openrtb.User + giveUser *openrtb2.User wantConsent string wantError bool }{ { description: "User Ext Consent is not empty", - giveUser: &openrtb.User{Ext: json.RawMessage(`{"consent": "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}`)}, + giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}`)}, wantConsent: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA", }, { description: "User Ext Consent is empty", - giveUser: &openrtb.User{Ext: json.RawMessage(`{"consent": ""}`)}, + giveUser: &openrtb2.User{Ext: json.RawMessage(`{"consent": ""}`)}, wantConsent: "", }, { description: "User Ext is nil", - giveUser: &openrtb.User{Ext: nil}, + giveUser: &openrtb2.User{Ext: nil}, wantConsent: "", }, { @@ -94,14 +94,14 @@ func TestExtractConsent(t *testing.T) { }, { description: "JSON unmarshal error", - giveUser: &openrtb.User{Ext: json.RawMessage(`{`)}, + giveUser: &openrtb2.User{Ext: json.RawMessage(`{`)}, wantConsent: "", wantError: true, }, } for _, tt := range tests { - bidReq := openrtb.BidRequest{ + bidReq := openrtb2.BidRequest{ User: tt.giveUser, } diff --git a/exchange/legacy.go b/exchange/legacy.go index dac7c7b59f6..0e7d1590686 100644 --- a/exchange/legacy.go +++ b/exchange/legacy.go @@ -6,13 +6,13 @@ import ( "errors" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/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/pbs" + "github.com/prebid/prebid-server/usersync" ) // AdaptLegacyAdapter turns a bidder.Adapter into an adaptedBidder. @@ -34,7 +34,7 @@ type adaptedAdapter struct { // // This is not ideal. OpenRTB provides a superset of the legacy data structures. // For requests which use those features, the best we can do is respond with "no bid". -func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { legacyRequest, legacyBidder, errs := bidder.toLegacyAdapterInputs(request, name) if legacyRequest == nil || legacyBidder == nil { return nil, errs @@ -59,7 +59,7 @@ func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb.B // toLegacyAdapterInputs is a best-effort transformation of an OpenRTB BidRequest into the args needed to run a legacy Adapter. // If the OpenRTB request is too complex, it fails with an error. // If the error is nil, then the PBSRequest and PBSBidder are valid. -func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSRequest, *pbs.PBSBidder, []error) { +func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSRequest, *pbs.PBSBidder, []error) { legacyReq, err := bidder.toLegacyRequest(req) if err != nil { return nil, nil, []error{err} @@ -73,7 +73,7 @@ func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb.BidRequest, nam return legacyReq, legacyBidder, errs } -func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb.BidRequest) (*pbs.PBSRequest, error) { +func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb2.BidRequest) (*pbs.PBSRequest, error) { acctId, err := toAccountId(req) if err != nil { return nil, err @@ -135,7 +135,7 @@ func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb.BidRequest) (*pbs.PBS App: req.App, Device: req.Device, // PBSUser is excluded because rubicon is the only adapter which reads from it, and they're supporting OpenRTB directly - // SDK is excluded because that information doesn't exist in OpenRTB. + // SDK is excluded because that information doesn't exist in openrtb2. // Bidders is excluded because no legacy adapters read from it User: req.User, Cookie: cookie, @@ -146,7 +146,7 @@ func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb.BidRequest) (*pbs.PBS }, nil } -func toAccountId(req *openrtb.BidRequest) (string, error) { +func toAccountId(req *openrtb2.BidRequest) (string, error) { if req.Site != nil && req.Site.Publisher != nil { return req.Site.Publisher.ID, nil } @@ -156,14 +156,14 @@ func toAccountId(req *openrtb.BidRequest) (string, error) { return "", errors.New("bidrequest.site.publisher.id or bidrequest.app.publisher.id required for legacy bidders.") } -func toTransactionId(req *openrtb.BidRequest) (string, error) { +func toTransactionId(req *openrtb2.BidRequest) (string, error) { if req.Source != nil { return req.Source.TID, nil } return "", errors.New("bidrequest.source.tid required for legacy bidders.") } -func toSecure(req *openrtb.BidRequest) (secure int8, err error) { +func toSecure(req *openrtb2.BidRequest) (secure int8, err error) { secure = -1 for _, imp := range req.Imp { if imp.Secure != nil { @@ -190,7 +190,7 @@ func toSecure(req *openrtb.BidRequest) (secure int8, err error) { return } -func toLegacyBidder(req *openrtb.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSBidder, []error) { +func toLegacyBidder(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSBidder, []error) { adUnits, errs := toPBSAdUnits(req) if len(adUnits) > 0 { return &pbs.PBSBidder{ @@ -211,7 +211,7 @@ func toLegacyBidder(req *openrtb.BidRequest, name openrtb_ext.BidderName) (*pbs. } } -func toPBSAdUnits(req *openrtb.BidRequest) ([]pbs.PBSAdUnit, []error) { +func toPBSAdUnits(req *openrtb2.BidRequest) ([]pbs.PBSAdUnit, []error) { adUnits := make([]pbs.PBSAdUnit, len(req.Imp)) var errs []error = nil nextAdUnit := 0 @@ -226,8 +226,8 @@ func toPBSAdUnits(req *openrtb.BidRequest) ([]pbs.PBSAdUnit, []error) { return adUnits[:nextAdUnit], errs } -func initPBSAdUnit(imp *openrtb.Imp, adUnit *pbs.PBSAdUnit) error { - var sizes []openrtb.Format = nil +func initPBSAdUnit(imp *openrtb2.Imp, adUnit *pbs.PBSAdUnit) error { + var sizes []openrtb2.Format = nil video := pbs.PBSVideo{} if imp.Video != nil { @@ -251,7 +251,7 @@ func initPBSAdUnit(imp *openrtb.Imp, adUnit *pbs.PBSAdUnit) error { } // Fixes #360 if imp.Video.W != 0 && imp.Video.H != 0 { - sizes = append(sizes, openrtb.Format{ + sizes = append(sizes, openrtb2.Format{ W: imp.Video.W, H: imp.Video.H, }) @@ -333,12 +333,12 @@ func transformBid(legacyBid *pbs.PBSBid) (*pbsOrtbBid, error) { }, nil } -func transformBidToOrtb(legacyBid *pbs.PBSBid) *openrtb.Bid { - return &openrtb.Bid{ +func transformBidToOrtb(legacyBid *pbs.PBSBid) *openrtb2.Bid { + return &openrtb2.Bid{ ID: legacyBid.BidID, ImpID: legacyBid.AdUnitCode, CrID: legacyBid.Creative_id, - // legacyBid.CreativeMediaType is handled by transformBid(), because it doesn't exist on the openrtb.Bid + // legacyBid.CreativeMediaType is handled by transformBid(), because it doesn't exist on the openrtb2.Bid // legacyBid.BidderCode is handled by the exchange, which already knows which bidder we are. // legacyBid.BidHash is ignored, because it doesn't get sent in the response anyway Price: legacyBid.Price, diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go index 2474fda50e1..cbb5fda4fcc 100644 --- a/exchange/legacy_test.go +++ b/exchange/legacy_test.go @@ -9,46 +9,46 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/pbs" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" + "github.com/mxmCherry/openrtb/v15/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/pbs" + "github.com/prebid/prebid-server/usersync" ) func TestSiteVideo(t *testing.T) { - ortbRequest := &openrtb.BidRequest{ + ortbRequest := &openrtb2.BidRequest{ ID: "request-id", TMax: 1000, - Site: &openrtb.Site{ + Site: &openrtb2.Site{ Page: "http://www.site.com", Domain: "site.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", }, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "transaction-id", }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "host-id", BuyerUID: "bidder-id", }, Test: 1, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "imp-id", - Video: &openrtb.Video{ + Video: &openrtb2.Video{ MIMEs: []string{"video/mp4"}, MinDuration: 20, MaxDuration: 40, - Protocols: []openrtb.Protocol{openrtb.ProtocolVAST10}, - StartDelay: openrtb.StartDelayGenericMidRoll.Ptr(), + Protocols: []openrtb2.Protocol{openrtb2.ProtocolVAST10}, + StartDelay: openrtb2.StartDelayGenericMidRoll.Ptr(), }, - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }}, @@ -85,7 +85,7 @@ func TestSiteVideo(t *testing.T) { func TestAppBanner(t *testing.T) { ortbRequest := newAppOrtbRequest() ortbRequest.TMax = 1000 - ortbRequest.User = &openrtb.User{ + ortbRequest.User = &openrtb2.User{ ID: "host-id", BuyerUID: "bidder-id", } @@ -187,8 +187,8 @@ func TestBidTransforms(t *testing.T) { func TestInsecureImps(t *testing.T) { insecure := int8(0) - bidReq := &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ + bidReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ Secure: &insecure, }, { Secure: &insecure, @@ -205,8 +205,8 @@ func TestInsecureImps(t *testing.T) { func TestSecureImps(t *testing.T) { secure := int8(1) - bidReq := &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ + bidReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ Secure: &secure, }, { Secure: &secure, @@ -224,8 +224,8 @@ func TestSecureImps(t *testing.T) { func TestMixedSecureImps(t *testing.T) { insecure := int8(0) secure := int8(1) - bidReq := &openrtb.BidRequest{ - Imp: []openrtb.Imp{{ + bidReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ Secure: &insecure, }, { Secure: &secure, @@ -237,21 +237,21 @@ func TestMixedSecureImps(t *testing.T) { } } -func newAppOrtbRequest() *openrtb.BidRequest { - return &openrtb.BidRequest{ +func newAppOrtbRequest() *openrtb2.BidRequest { + return &openrtb2.BidRequest{ ID: "request-id", - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", }, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "transaction-id", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }}, @@ -262,20 +262,20 @@ func newAppOrtbRequest() *openrtb.BidRequest { } func TestErrorResponse(t *testing.T) { - ortbRequest := &openrtb.BidRequest{ + ortbRequest := &openrtb2.BidRequest{ ID: "request-id", - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", }, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "transaction-id", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }}, @@ -300,20 +300,20 @@ func TestErrorResponse(t *testing.T) { } func TestWithTargeting(t *testing.T) { - ortbRequest := &openrtb.BidRequest{ + ortbRequest := &openrtb2.BidRequest{ ID: "request-id", - App: &openrtb.App{ - Publisher: &openrtb.Publisher{ + App: &openrtb2.App{ + Publisher: &openrtb2.Publisher{ ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", }, }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "transaction-id", }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }}, @@ -343,7 +343,7 @@ func TestWithTargeting(t *testing.T) { // assertEquivalentFields compares the OpenRTB request with the legacy request, using the mappings defined here: // https://gist.github.com/dbemiller/68aa3387189fa17d3addfb9818dd4d97 -func assertEquivalentRequests(t *testing.T, req *openrtb.BidRequest, legacy *pbs.PBSRequest) { +func assertEquivalentRequests(t *testing.T, req *openrtb2.BidRequest, legacy *pbs.PBSRequest) { if req.Site != nil { if req.Site.Publisher.ID != legacy.AccountID { t.Errorf("Account ID did not translate. OpenRTB: %s, Legacy: %s.", req.Site.Publisher.ID, legacy.AccountID) @@ -399,7 +399,7 @@ func assertEquivalentRequests(t *testing.T, req *openrtb.BidRequest, legacy *pbs } } -func assertEquivalentBidder(t *testing.T, req *openrtb.BidRequest, legacy *pbs.PBSBidder) { +func assertEquivalentBidder(t *testing.T, req *openrtb2.BidRequest, legacy *pbs.PBSBidder) { if len(req.Imp) != len(legacy.AdUnits) { t.Errorf("Wrong number of Imps. Expected %d, got %d", len(req.Imp), len(legacy.AdUnits)) return @@ -409,7 +409,7 @@ func assertEquivalentBidder(t *testing.T, req *openrtb.BidRequest, legacy *pbs.P } } -func assertEquivalentImp(t *testing.T, index int, imp *openrtb.Imp, legacy *pbs.PBSAdUnit) { +func assertEquivalentImp(t *testing.T, index int, imp *openrtb2.Imp, legacy *pbs.PBSAdUnit) { if imp.ID != legacy.BidID { t.Errorf("imp[%d].id did not translate. OpenRTB %s, legacy %s", index, imp.ID, legacy.BidID) } diff --git a/exchange/price_granularity.go b/exchange/price_granularity.go index ffcce061465..242d420f1fc 100644 --- a/exchange/price_granularity.go +++ b/exchange/price_granularity.go @@ -4,7 +4,7 @@ import ( "math" "strconv" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/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 6dccc677b7b..13840838ba7 100644 --- a/exchange/price_granularity_test.go +++ b/exchange/price_granularity_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/exchange/seatbid.go b/exchange/seatbid.go deleted file mode 100644 index b675127410e..00000000000 --- a/exchange/seatbid.go +++ /dev/null @@ -1,8 +0,0 @@ -package exchange - -import "encoding/json" - -// ExtSeatBid defines the contract for bidresponse.seatbid.ext -type ExtSeatBid struct { - Bidder json.RawMessage `json:"bidder,omitempty"` -} diff --git a/exchange/targeting.go b/exchange/targeting.go index 31db7114f67..c4710f826f0 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -3,8 +3,8 @@ package exchange import ( "strconv" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" ) const MaxKeyLength = 20 @@ -91,9 +91,9 @@ func (targData *targetData) addKeys(keys map[string]string, key openrtb_ext.Targ } } -func makeHbSize(bid *openrtb.Bid) string { +func makeHbSize(bid *openrtb2.Bid) string { if bid.W != 0 && bid.H != 0 { - return strconv.FormatUint(bid.W, 10) + "x" + strconv.FormatUint(bid.H, 10) + return strconv.FormatInt(bid.W, 10) + "x" + strconv.FormatInt(bid.H, 10) } return "" } @@ -104,4 +104,4 @@ func (targData *targetData) addBidderKeys(keys map[string]string, bidderKeys map keys[index] = element } } -} +} \ No newline at end of file diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index e6db921529c..aa07ed0c77b 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -8,22 +8,22 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/currency" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" + "github.com/prebid/prebid-server/gdpr" - metricsConf "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - metricsConfig "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" + metricsConf "github.com/prebid/prebid-server/metrics/config" + metricsConfig "github.com/prebid/prebid-server/metrics/config" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) // Using this set of bids in more than one test -var mockBids = map[openrtb_ext.BidderName][]*openrtb.Bid{ +var mockBids = map[openrtb_ext.BidderName][]*openrtb2.Bid{ openrtb_ext.BidderAppnexus: {{ ID: "losing-bid", ImpID: "some-imp", @@ -67,7 +67,7 @@ func TestTargetingCache(t *testing.T) { } -func assertKeyExists(t *testing.T, bid *openrtb.Bid, key string, expected bool) { +func assertKeyExists(t *testing.T, bid *openrtb2.Bid, key string, expected bool) { t.Helper() targets := parseTargets(t, bid) if _, ok := targets[key]; ok != expected { @@ -77,7 +77,7 @@ func assertKeyExists(t *testing.T, bid *openrtb.Bid, key string, expected bool) // runAuction takes a bunch of mock bids by Bidder and runs an auction. It returns a map of Bids indexed by their ImpID. // If includeCache is true, the auction will be run with cacheing as well, so the cache targeting keys should exist. -func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid, includeCache bool, includeWinners bool, includeBidderKeys bool, isApp bool) map[string]*openrtb.Bid { +func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid, includeCache bool, includeWinners bool, includeBidderKeys bool, isApp bool) map[string]*openrtb2.Bid { server := httptest.NewServer(http.HandlerFunc(mockServer)) defer server.Close() @@ -95,18 +95,19 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), UsersyncIfAmbiguous: false, categoriesFetcher: categoriesFetcher, + bidIDGenerator: &mockBidIDGenerator{false, false}, } imps := buildImps(t, mockBids) - req := &openrtb.BidRequest{ + req := &openrtb2.BidRequest{ Imp: imps, Ext: buildTargetingExt(includeCache, includeWinners, includeBidderKeys), } if isApp { - req.App = &openrtb.App{} + req.App = &openrtb2.App{} } else { - req.Site = &openrtb.Site{} + req.Site = &openrtb2.Site{} } auctionRequest := AuctionRequest{ @@ -128,7 +129,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op return buildBidMap(bidResp.SeatBid, len(mockBids)) } -func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb.Bid) []openrtb_ext.BidderName { +func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb_ext.BidderName { bidders := make([]openrtb_ext.BidderName, 0, len(bids)) for name := range bids { bidders = append(bidders, name) @@ -136,7 +137,7 @@ func buildBidderList(bids map[openrtb_ext.BidderName][]*openrtb.Bid) []openrtb_e return bidders } -func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder { +func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]adaptedBidder { adapterMap := make(map[openrtb_ext.BidderName]adaptedBidder, len(bids)) for bidder, bids := range bids { adapterMap[bidder] = adaptBidder(&mockTargetingBidder{ @@ -166,7 +167,7 @@ func buildTargetingExt(includeCache bool, includeWinners bool, includeBidderKeys return json.RawMessage(`{"prebid":{"targeting":` + targeting + `}}`) } -func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid) json.RawMessage { +func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid) json.RawMessage { params := make(map[string]json.RawMessage) for bidder := range mockBids { params[string(bidder)] = json.RawMessage(`{"whatever":true}`) @@ -178,7 +179,7 @@ func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bi return ext } -func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid) []openrtb.Imp { +func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb2.Imp { impExt := buildParams(t, mockBids) var s struct{} @@ -189,9 +190,9 @@ func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid) } } - imps := make([]openrtb.Imp, 0, len(impIds)) + imps := make([]openrtb2.Imp, 0, len(impIds)) for impId := range impIds { - imps = append(imps, openrtb.Imp{ + imps = append(imps, openrtb2.Imp{ ID: impId, Ext: impExt, }) @@ -199,8 +200,8 @@ func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb.Bid) return imps } -func buildBidMap(seatBids []openrtb.SeatBid, numBids int) map[string]*openrtb.Bid { - bids := make(map[string]*openrtb.Bid, numBids) +func buildBidMap(seatBids []openrtb2.SeatBid, numBids int) map[string]*openrtb2.Bid { + bids := make(map[string]*openrtb2.Bid, numBids) for _, seatBid := range seatBids { for i := 0; i < len(seatBid.Bid); i++ { bid := seatBid.Bid[i] @@ -210,7 +211,7 @@ func buildBidMap(seatBids []openrtb.SeatBid, numBids int) map[string]*openrtb.Bi return bids } -func parseTargets(t *testing.T, bid *openrtb.Bid) map[string]string { +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 { @@ -221,10 +222,10 @@ func parseTargets(t *testing.T, bid *openrtb.Bid) map[string]string { type mockTargetingBidder struct { mockServerURL string - bids []*openrtb.Bid + bids []*openrtb2.Bid } -func (m *mockTargetingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { +func (m *mockTargetingBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { return []*adapters.RequestData{{ Method: "POST", Uri: m.mockServerURL, @@ -233,7 +234,7 @@ func (m *mockTargetingBidder) MakeRequests(request *openrtb.BidRequest, reqInfo }}, nil } -func (m *mockTargetingBidder) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { +func (m *mockTargetingBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { bidResponse := &adapters.BidderResponse{ Bids: make([]*adapters.TypedBid, len(m.bids)), } @@ -259,15 +260,15 @@ type TargetingTestData struct { ExpectedBidTargetsByBidder map[string]map[openrtb_ext.BidderName]map[string]string } -var bid123 *openrtb.Bid = &openrtb.Bid{ +var bid123 *openrtb2.Bid = &openrtb2.Bid{ Price: 1.23, } -var bid111 *openrtb.Bid = &openrtb.Bid{ +var bid111 *openrtb2.Bid = &openrtb2.Bid{ Price: 1.11, DealID: "mydeal", } -var bid084 *openrtb.Bid = &openrtb.Bid{ +var bid084 *openrtb2.Bid = &openrtb2.Bid{ Price: 0.84, } @@ -396,7 +397,7 @@ var TargetingTests []TargetingTestData = []TargetingTestData{ }, }, }, - cacheIds: map[*openrtb.Bid]string{ + cacheIds: map[*openrtb2.Bid]string{ bid123: "55555", bid111: "cacheme", }, diff --git a/exchange/utils.go b/exchange/utils.go index 13de8ab4f5b..38d21751f8f 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -6,17 +6,17 @@ import ( "fmt" "math/rand" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/go-gdpr/vendorconsent" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/privacy" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/lmt" "github.com/buger/jsonparser" + "github.com/prebid/prebid-server/config" + "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" ) var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{ @@ -57,10 +57,12 @@ func cleanOpenRTBRequests(ctx context.Context, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, usersyncIfAmbiguous bool, - privacyConfig config.Privacy) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { + privacyConfig config.Privacy, + account *config.Account) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { - impsByBidder, errs := splitImps(req.BidRequest.Imp) - if len(errs) > 0 { + impsByBidder, err := splitImps(req.BidRequest.Imp) + if err != nil { + errs = []error{err} return } @@ -121,8 +123,17 @@ func cleanOpenRTBRequests(ctx context.Context, // GDPR if gdprEnforced { + weakVendorEnforcement := false + if account != nil { + for _, vendor := range account.GDPR.BasicEnforcementVendors { + if vendor == string(bidderRequest.BidderCoreName) { + weakVendorEnforcement = true + break + } + } + } var publisherID = req.LegacyLabels.PubID - _, geo, id, err := gDPR.PersonalInfoAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent) + _, geo, id, err := gDPR.PersonalInfoAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent, weakVendorEnforcement) if err == nil { privacyEnforcement.GDPRGeo = !geo privacyEnforcement.GDPRID = !id @@ -152,7 +163,7 @@ func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestT return privacyConfig.CCPA.Enforce } -func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) { +func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) { ccpaPolicy, err := ccpa.ReadFromRequest(orig) if err != nil { return privacy.NilPolicyEnforcer{}, err @@ -171,7 +182,7 @@ func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, account return ccpaEnforcer, nil } -func extractLMT(orig *openrtb.BidRequest, privacyConfig config.Privacy) privacy.PolicyEnforcer { +func extractLMT(orig *openrtb2.BidRequest, privacyConfig config.Privacy) privacy.PolicyEnforcer { return privacy.EnabledPolicyEnforcer{ Enabled: privacyConfig.LMT.Enforce, PolicyEnforcer: lmt.ReadFromRequest(orig), @@ -203,7 +214,7 @@ func getBidderExts(reqExt *openrtb_ext.ExtRequest) (map[string]map[string]interf func getAuctionBidderRequests(req AuctionRequest, requestExt *openrtb_ext.ExtRequest, - impsByBidder map[string][]openrtb.Imp, + impsByBidder map[string][]openrtb2.Imp, aliases map[string]string) ([]BidderRequest, []error) { bidderRequests := make([]BidderRequest, 0, len(impsByBidder)) @@ -239,6 +250,8 @@ func getAuctionBidderRequests(req AuctionRequest, reqCopy := *req.BidRequest reqCopy.Imp = imps + reqCopy.Ext = reqExt + prepareSource(&reqCopy, bidder, sChainsByBidder) if len(bidderExt) != 0 { @@ -286,7 +299,7 @@ func getAuctionBidderRequests(req AuctionRequest, return bidderRequests, errs } -func getExtJson(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) { +func getExtJson(req *openrtb2.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (json.RawMessage, error) { if len(req.Ext) == 0 || unpackedExt == nil { return json.RawMessage(``), nil } @@ -296,7 +309,7 @@ func getExtJson(req *openrtb.BidRequest, unpackedExt *openrtb_ext.ExtRequest) (j return json.Marshal(extCopy) } -func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) { +func prepareSource(req *openrtb2.BidRequest, bidder string, sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) { const sChainWildCard = "*" var selectedSChain *openrtb_ext.ExtRequestPrebidSChainSChain @@ -316,7 +329,7 @@ func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[s // set source if req.Source == nil { - req.Source = &openrtb.Source{} + req.Source = &openrtb2.Source{} } schain := openrtb_ext.ExtRequestPrebidSChain{ SChain: *selectedSChain, @@ -329,7 +342,7 @@ func prepareSource(req *openrtb.BidRequest, bidder string, sChainsByBidder map[s // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. // This prevents a Bidder from using these values to figure out who else is involved in the Auction. -func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) { +func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { if user == nil { return nil, nil } @@ -371,106 +384,104 @@ func extractBuyerUIDs(user *openrtb.User) (map[string]string, error) { // The "imp.ext" value of the rubicon Imp will only contain the "prebid" values, and "rubicon" value at the "bidder" key. // // The goal here is so that Bidders only get Imps and Imp.Ext values which are intended for them. -func splitImps(imps []openrtb.Imp) (map[string][]openrtb.Imp, []error) { - impExts, err := parseImpExts(imps) - if err != nil { - return nil, []error{err} - } +func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { + bidderImps := make(map[string][]openrtb2.Imp) - splitImps := make(map[string][]openrtb.Imp, len(imps)) - var errList []error + for i, imp := range imps { + var impExt map[string]json.RawMessage + if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + return nil, fmt.Errorf("invalid json for imp[%d]: %v", i, err) + } - for i := 0; i < len(imps); i++ { - imp := imps[i] - impExt := impExts[i] + 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) + } - var firstPartyDataContext json.RawMessage - if context, exists := impExt[openrtb_ext.FirstPartyDataContextExtKey]; exists { - firstPartyDataContext = context + 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) } - rawPrebidExt, ok := impExt[openrtb_ext.PrebidExtKey] + sanitizedImpExt, err := createSanitizedImpExt(impExt, impExtPrebid) + if err != nil { + return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: %v", i, err) + } - if ok { - var prebidExt openrtb_ext.ExtImpPrebid + for bidder, bidderExt := range extractBidderExts(impExt, impExtPrebidBidder) { + impCopy := imp - if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil && prebidExt.Bidder != nil { - if errs := sanitizedImpCopy(&imp, prebidExt.Bidder, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil { - errList = append(errList, errs...) - } + sanitizedImpExt[openrtb_ext.PrebidExtBidderKey] = bidderExt - continue + impExtJSON, err := json.Marshal(sanitizedImpExt) + if err != nil { + return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: cannot marshal ext: %v", i, err) } - } + impCopy.Ext = impExtJSON - if errs := sanitizedImpCopy(&imp, impExt, rawPrebidExt, firstPartyDataContext, &splitImps); errs != nil { - errList = append(errList, errs...) + bidderImps[bidder] = append(bidderImps[bidder], impCopy) } } - return splitImps, nil + return bidderImps, nil } -// sanitizedImpCopy returns a copy of imp with its ext filtered so that only "prebid", "context", and bidder params exist. -// It will not mutate the input imp. -// This function will write the new imps to the output map passed in -func sanitizedImpCopy(imp *openrtb.Imp, - bidderExts map[string]json.RawMessage, - rawPrebidExt json.RawMessage, - firstPartyDataContext json.RawMessage, - out *map[string][]openrtb.Imp) []error { - - var prebidExt map[string]json.RawMessage - var errs []error - - if err := json.Unmarshal(rawPrebidExt, &prebidExt); err == nil { - // Remove the entire bidder field. We will already have the content we need in bidderExts. We - // don't want to include other demand partners' bidder params in the sanitized imp. - if _, hasBidderField := prebidExt["bidder"]; hasBidderField { - delete(prebidExt, "bidder") +func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map[string]json.RawMessage, error) { + sanitizedImpExt := make(map[string]json.RawMessage, 3) - var err error - if rawPrebidExt, err = json.Marshal(prebidExt); err != nil { - errs = append(errs, err) - } + delete(impExtPrebid, openrtb_ext.PrebidExtBidderKey) + if len(impExtPrebid) > 0 { + if impExtPrebidJSON, err := json.Marshal(impExtPrebid); err == nil { + sanitizedImpExt[openrtb_ext.PrebidExtKey] = impExtPrebidJSON + } else { + return nil, fmt.Errorf("cannot marshal ext.prebid: %v", err) } } - for bidder, ext := range bidderExts { - if bidder == openrtb_ext.PrebidExtKey || bidder == openrtb_ext.FirstPartyDataContextExtKey { - continue - } + if v, exists := impExt[openrtb_ext.FirstPartyDataExtKey]; exists { + sanitizedImpExt[openrtb_ext.FirstPartyDataExtKey] = v + } - impCopy := *imp - newExt := make(map[string]json.RawMessage, 3) + if v, exists := impExt[openrtb_ext.FirstPartyDataContextExtKey]; exists { + sanitizedImpExt[openrtb_ext.FirstPartyDataContextExtKey] = v + } - newExt["bidder"] = ext + if v, exists := impExt[openrtb_ext.SKAdNExtKey]; exists { + sanitizedImpExt[openrtb_ext.SKAdNExtKey] = v + } - if rawPrebidExt != nil { - newExt[openrtb_ext.PrebidExtKey] = rawPrebidExt - } + return sanitizedImpExt, nil +} - if len(firstPartyDataContext) > 0 { - newExt[openrtb_ext.FirstPartyDataContextExtKey] = firstPartyDataContext - } +func extractBidderExts(impExt, impExtPrebidBidders map[string]json.RawMessage) map[string]json.RawMessage { + bidderExts := make(map[string]json.RawMessage) - rawExt, err := json.Marshal(newExt) - if err != nil { - errs = append(errs, err) + // prefer imp.ext.prebid.bidder.BIDDER + for bidder, bidderExt := range impExtPrebidBidders { + bidderExts[bidder] = bidderExt + } + + // fallback to imp.BIDDER + for bidder, bidderExt := range impExt { + if isSpecialField(bidder) { continue } - impCopy.Ext = rawExt - - otherImps, _ := (*out)[bidder] - (*out)[bidder] = append(otherImps, impCopy) + if _, exists := bidderExts[bidder]; !exists { + bidderExts[bidder] = bidderExt + } } - if len(errs) > 0 { - return errs - } + return bidderExts +} - return nil +func isSpecialField(bidder string) bool { + return bidder == openrtb_ext.FirstPartyDataContextExtKey || + bidder == openrtb_ext.FirstPartyDataExtKey || + bidder == openrtb_ext.SKAdNExtKey || + bidder == openrtb_ext.PrebidExtKey } // prepareUser changes req.User so that it's ready for the given bidder. @@ -478,7 +489,7 @@ func sanitizedImpCopy(imp *openrtb.Imp, // // In this function, "givenBidder" may or may not be an alias. "coreBidder" must *not* be an alias. // It returns true if a Cookie User Sync existed, and false otherwise. -func prepareUser(req *openrtb.BidRequest, givenBidder string, coreBidder openrtb_ext.BidderName, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { +func prepareUser(req *openrtb2.BidRequest, givenBidder string, coreBidder openrtb_ext.BidderName, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { cookieId, hadCookie := usersyncs.GetId(coreBidder) if id, ok := explicitBuyerUIDs[givenBidder]; ok { @@ -492,9 +503,9 @@ func prepareUser(req *openrtb.BidRequest, givenBidder string, coreBidder openrtb // copyWithBuyerUID either overwrites the BuyerUID property on user with the argument, or returns // a new (empty) User with the BuyerUID already set. -func copyWithBuyerUID(user *openrtb.User, buyerUID string) *openrtb.User { +func copyWithBuyerUID(user *openrtb2.User, buyerUID string) *openrtb2.User { if user == nil { - return &openrtb.User{ + return &openrtb2.User{ BuyerUID: buyerUID, } } @@ -507,7 +518,7 @@ func copyWithBuyerUID(user *openrtb.User, buyerUID string) *openrtb.User { } // removeUnpermissionedEids modifies the request to remove any request.user.ext.eids not permissions for the specific bidder -func removeUnpermissionedEids(request *openrtb.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error { +func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error { // ensure request might have eids (as much as we can check before unmarshalling) if request.User == nil || len(request.User.Ext) == 0 { return nil @@ -594,7 +605,7 @@ func removeUnpermissionedEids(request *openrtb.BidRequest, bidder string, reques return nil } -func setUserExtWithCopy(request *openrtb.BidRequest, userExtJSON json.RawMessage) { +func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessage) { userCopy := *request.User userCopy.Ext = userExtJSON request.User = &userCopy @@ -608,23 +619,8 @@ func resolveBidder(bidder string, aliases map[string]string) openrtb_ext.BidderN return openrtb_ext.BidderName(bidder) } -// parseImpExts does a partial-unmarshal of the imp[].Ext field. -// The keys in the returned map are expected to be "prebid", "context", CoreBidderNames, or Aliases for this request. -func parseImpExts(imps []openrtb.Imp) ([]map[string]json.RawMessage, error) { - exts := make([]map[string]json.RawMessage, len(imps)) - // Loop over every impression in the request - for i := 0; i < len(imps); i++ { - // Unpack each set of extensions found in the Imp array - err := json.Unmarshal(imps[i].Ext, &exts[i]) - if err != nil { - return nil, fmt.Errorf("Error unpacking extensions for Imp[%d]: %s", i, err.Error()) - } - } - return exts, nil -} - // parseAliases parses the aliases from the BidRequest -func parseAliases(orig *openrtb.BidRequest) (map[string]string, []error) { +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 { @@ -657,7 +653,7 @@ func randomizeList(list []openrtb_ext.BidderName) { } } -func extractBidRequestExt(bidRequest *openrtb.BidRequest) (*openrtb_ext.ExtRequest, error) { +func extractBidRequestExt(bidRequest *openrtb2.BidRequest) (*openrtb_ext.ExtRequest, error) { requestExt := &openrtb_ext.ExtRequest{} if bidRequest == nil { @@ -720,7 +716,7 @@ func getExtTargetData(requestExt *openrtb_ext.ExtRequest, cacheInstructions *ext return targData } -func getDebugInfo(bidRequest *openrtb.BidRequest, requestExt *openrtb_ext.ExtRequest) bool { +func getDebugInfo(bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest) bool { return (bidRequest != nil && bidRequest.Test == 1) || (requestExt != nil && requestExt.Prebid.Debug) } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index e222103e44c..55a0950aac6 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -7,12 +7,12 @@ import ( "fmt" "testing" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "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/stretchr/testify/assert" ) @@ -32,7 +32,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string) (bool, bool, bool, error) { +func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowedError } @@ -55,6 +55,381 @@ func assertReq(t *testing.T, bidderRequests []BidderRequest, } } +func TestSplitImps(t *testing.T) { + testCases := []struct { + description string + givenImps []openrtb2.Imp + expectedImps map[string][]openrtb2.Imp + expectedError string + }{ + { + description: "Nil", + givenImps: nil, + expectedImps: map[string][]openrtb2.Imp{}, + expectedError: "", + }, + { + description: "Empty", + givenImps: []openrtb2.Imp{}, + expectedImps: map[string][]openrtb2.Imp{}, + expectedError: "", + }, + { + description: "1 Imp, 1 Bidder", + givenImps: []openrtb2.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"}}}}`)}, + }, + expectedImps: map[string][]openrtb2.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, + }, + }, + expectedError: "", + }, + { + description: "1 Imp, 2 Bidders", + givenImps: []openrtb2.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"},"bidderB":{"imp1ParamB":"imp1ValueB"}}}}`)}, + }, + expectedImps: map[string][]openrtb2.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, + }, + "bidderB": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamB":"imp1ValueB"}}`)}, + }, + }, + expectedError: "", + }, + { + description: "2 Imps, 1 Bidders Each", + givenImps: []openrtb2.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"}}}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2ParamA":"imp2ValueA"}}}}`)}, + }, + expectedImps: map[string][]openrtb2.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2ParamA":"imp2ValueA"}}`)}, + }, + }, + expectedError: "", + }, + { + description: "2 Imps, 2 Bidders Each", + givenImps: []openrtb2.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}}}`)}, + }, + expectedImps: map[string][]openrtb2.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"}}`)}, + }, + "bidderB": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"}}`)}, + }, + }, + expectedError: "", + }, + { + // This is a "happy path" integration test. Functionality is covered in detail by TestCreateSanitizedImpExt. + description: "Other Fields - 2 Imps, 2 Bidders Each", + givenImps: []openrtb2.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}},"skadn":"imp1SkAdN"}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}},"skadn":"imp2SkAdN"}`)}, + }, + expectedImps: map[string][]openrtb2.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"},"skadn":"imp1SkAdN"}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"},"skadn":"imp2SkAdN"}`)}, + }, + "bidderB": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"},"skadn":"imp1SkAdN"}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"},"skadn":"imp2SkAdN"}`)}, + }, + }, + expectedError: "", + }, + { + // This is a "happy path" integration test. Functionality is covered in detail by TestExtractBidderExts. + description: "Legacy imp.ext.BIDDER - 2 Imps, 2 Bidders Each", + givenImps: []openrtb2.Imp{ + {ID: "imp1", Ext: json.RawMessage(`{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}`)}, + }, + expectedImps: map[string][]openrtb2.Imp{ + "bidderA": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"}}`)}, + }, + "bidderB": { + {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"}}`)}, + {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"}}`)}, + }, + }, + expectedError: "", + }, + { + description: "Malformed imp.ext", + givenImps: []openrtb2.Imp{ + {ID: "imp1", Ext: json.RawMessage(`malformed`)}, + }, + expectedError: "invalid json for imp[0]: invalid character 'm' looking for beginning of value", + }, + { + 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", + }, + { + 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", + }, + } + + for _, test := range testCases { + imps, err := splitImps(test.givenImps) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + + assert.Equal(t, test.expectedImps, imps, test.description+":imps") + } +} + +func TestCreateSanitizedImpExt(t *testing.T) { + testCases := []struct { + description string + givenImpExt map[string]json.RawMessage + givenImpExtPrebid map[string]json.RawMessage + expected map[string]json.RawMessage + expectedError string + }{ + { + description: "Nil", + givenImpExt: nil, + givenImpExtPrebid: nil, + expected: map[string]json.RawMessage{}, + expectedError: "", + }, + { + description: "Empty", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebid: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{}, + expectedError: "", + }, + { + description: "imp.ext.prebid - Bidders Only", + givenImpExt: map[string]json.RawMessage{ + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + }, + expected: map[string]json.RawMessage{ + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "imp.ext.prebid - Bidders + Other Values", + givenImpExt: map[string]json.RawMessage{ + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + "someOther": json.RawMessage(`"value"`), + }, + expected: map[string]json.RawMessage{ + "prebid": json.RawMessage(`{"someOther":"value"}`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "imp.ext", + givenImpExt: map[string]json.RawMessage{ + "anyBidder": json.RawMessage(`"anyBidderValues"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{ + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "imp.ext + imp.ext.prebid - Prebid Bidders Only", + givenImpExt: map[string]json.RawMessage{ + "anyBidder": json.RawMessage(`"anyBidderValues"`), + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + }, + expected: map[string]json.RawMessage{ + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + expectedError: "", + }, + { + description: "imp.ext + imp.ext.prebid - Prebid Bidders + Other Values", + givenImpExt: map[string]json.RawMessage{ + "anyBidder": json.RawMessage(`"anyBidderValues"`), + "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "bidder": json.RawMessage(`"anyBidder"`), + "someOther": json.RawMessage(`"value"`), + }, + expected: map[string]json.RawMessage{ + "prebid": json.RawMessage(`{"someOther":"value"}`), + "data": json.RawMessage(`"anyData"`), + "context": json.RawMessage(`"anyContext"`), + "skadn": json.RawMessage(`"anySKAdNetwork"`), + }, + 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"`), + }, + givenImpExtPrebid: map[string]json.RawMessage{ + "malformed": json.RawMessage(`json`), // String value without quotes. + }, + expected: nil, + expectedError: "cannot marshal ext.prebid: json: error calling MarshalJSON for type json.RawMessage: invalid character 'j' looking for beginning of value", + }, + } + + for _, test := range testCases { + result, err := createSanitizedImpExt(test.givenImpExt, test.givenImpExtPrebid) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestExtractBidderExts(t *testing.T) { + bidderAJSON := json.RawMessage(`{"paramA":"valueA"}}`) + bidderBJSON := json.RawMessage(`{"paramB":"valueB"}}`) + + testCases := []struct { + description string + givenImpExt map[string]json.RawMessage + givenImpExtPrebidBidders map[string]json.RawMessage + expected map[string]json.RawMessage + }{ + { + description: "Nil", + givenImpExt: nil, + givenImpExtPrebidBidders: nil, + expected: map[string]json.RawMessage{}, + }, + { + description: "Empty", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebidBidders: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{}, + }, + { + description: "One - imp.ext.BIDDER", + givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON}, + givenImpExtPrebidBidders: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, + }, + { + description: "Many - imp.ext.BIDDER", + givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + givenImpExtPrebidBidders: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + }, + { + description: "Special Names Ignored - imp.ext.BIDDER", + givenImpExt: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`)}, + givenImpExtPrebidBidders: map[string]json.RawMessage{}, + expected: map[string]json.RawMessage{}, + }, + { + description: "One - imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, + }, + { + description: "Many - imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + }, + { + description: "Special Names Not Treated Differently - imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`)}, + expected: map[string]json.RawMessage{"prebid": json.RawMessage(`{"prebid":"value1"}}`), "context": json.RawMessage(`{"firstPartyData":"value2"}}`), "skadn": json.RawMessage(`{"skAdNetwork":"value3"}}`)}, + }, + { + description: "Mixed - Both - imp.ext.BIDDER + imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{"bidderA": bidderAJSON}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderB": bidderBJSON}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON, "bidderB": bidderBJSON}, + }, + { + description: "Mixed - Overwrites - imp.ext.BIDDER + imp.ext.prebid.bidder.BIDDER", + givenImpExt: map[string]json.RawMessage{"bidderA": json.RawMessage(`{"shouldBe":"Ignored"}}`)}, + givenImpExtPrebidBidders: map[string]json.RawMessage{"bidderA": bidderAJSON}, + expected: map[string]json.RawMessage{"bidderA": bidderAJSON}, + }, + } + + for _, test := range testCases { + result := extractBidderExts(test.givenImpExt, test.givenImpExtPrebidBidders) + assert.Equal(t, test.expected, result, test.description) + } +} + func TestCleanOpenRTBRequests(t *testing.T) { testCases := []struct { req AuctionRequest @@ -90,7 +465,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -219,7 +594,7 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) req.Ext = test.reqExt - req.Regs = &openrtb.Regs{ + req.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`), } @@ -247,7 +622,8 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { nil, &permissionsMock{personalInfoAllowed: true}, true, - privacyConfig) + privacyConfig, + nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -273,7 +649,10 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { description: "Invalid Consent", reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), reqRegsExt: json.RawMessage(`{"us_privacy":"malformed"}`), - expectError: &errortypes.InvalidPrivacyConsent{"request.regs.ext.us_privacy must contain 4 characters"}, + expectError: &errortypes.Warning{ + Message: "request.regs.ext.us_privacy must contain 4 characters", + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + }, }, { description: "Invalid No Sale Bidders", @@ -286,7 +665,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) req.Ext = test.reqExt - req.Regs = &openrtb.Regs{Ext: test.reqRegsExt} + req.Regs = &openrtb2.Regs{Ext: test.reqRegsExt} var reqExtStruct openrtb_ext.ExtRequest err := json.Unmarshal(req.Ext, &reqExtStruct) @@ -302,7 +681,7 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { Enforce: true, }, } - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -335,14 +714,14 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) - req.Regs = &openrtb.Regs{COPPA: test.coppa} + req.Regs = &openrtb2.Regs{COPPA: test.coppa} auctionReq := AuctionRequest{ BidRequest: req, UserSyncs: &emptyUsersync{}, } - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -449,7 +828,7 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { UserSyncs: &emptyUsersync{}, } - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissionsMock{}, true, config.Privacy{}) + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissionsMock{}, true, config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -469,13 +848,13 @@ func TestExtractBidRequestExt(t *testing.T) { testCases := []struct { desc string - inBidRequest *openrtb.BidRequest + inBidRequest *openrtb2.BidRequest outRequestExt *openrtb_ext.ExtRequest outError error }{ { desc: "Valid vastxml.returnCreative set to false", - inBidRequest: &openrtb.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true,"cache":{"vastxml":{"returnCreative":false}}}}`)}, + inBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true,"cache":{"vastxml":{"returnCreative":false}}}}`)}, outRequestExt: &openrtb_ext.ExtRequest{ Prebid: openrtb_ext.ExtRequestPrebid{ Debug: true, @@ -490,7 +869,7 @@ func TestExtractBidRequestExt(t *testing.T) { }, { desc: "Valid vastxml.returnCreative set to true", - inBidRequest: &openrtb.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true,"cache":{"vastxml":{"returnCreative":true}}}}`)}, + inBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"debug":true,"cache":{"vastxml":{"returnCreative":true}}}}`)}, outRequestExt: &openrtb_ext.ExtRequest{ Prebid: openrtb_ext.ExtRequestPrebid{ Debug: true, @@ -511,13 +890,13 @@ func TestExtractBidRequestExt(t *testing.T) { }, { desc: "Non-nil bidRequest with empty Ext, we expect a blank requestExt", - inBidRequest: &openrtb.BidRequest{}, + inBidRequest: &openrtb2.BidRequest{}, outRequestExt: &openrtb_ext.ExtRequest{}, outError: nil, }, { desc: "Non-nil bidRequest with non-empty, invalid Ext, we expect unmarshaling error", - inBidRequest: &openrtb.BidRequest{Ext: json.RawMessage(`invalid`)}, + inBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`invalid`)}, outRequestExt: &openrtb_ext.ExtRequest{}, outError: fmt.Errorf("Error decoding Request.ext : invalid character 'i' looking for beginning of value"), }, @@ -885,7 +1264,7 @@ func TestGetExtTargetData(t *testing.T) { func TestGetDebugInfo(t *testing.T) { type inTest struct { - bidRequest *openrtb.BidRequest + bidRequest *openrtb2.BidRequest requestExt *openrtb_ext.ExtRequest } testCases := []struct { @@ -900,7 +1279,7 @@ func TestGetDebugInfo(t *testing.T) { }, { desc: "bid request test == 0, nil requestExt", - in: inTest{&openrtb.BidRequest{Test: 0}, nil}, + in: inTest{&openrtb2.BidRequest{Test: 0}, nil}, out: false, }, { @@ -910,22 +1289,22 @@ func TestGetDebugInfo(t *testing.T) { }, { desc: "bid request test == 0, requestExt debug flag false", - in: inTest{&openrtb.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, + in: inTest{&openrtb2.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, out: false, }, { desc: "bid request test == 1, requestExt debug flag false", - in: inTest{&openrtb.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, + in: inTest{&openrtb2.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: false}}}, out: true, }, { desc: "bid request test == 0, requestExt debug flag true", - in: inTest{&openrtb.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, + in: inTest{&openrtb2.BidRequest{Test: 0}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, out: true, }, { desc: "bid request test == 1, requestExt debug flag true", - in: inTest{&openrtb.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, + in: inTest{&openrtb2.BidRequest{Test: 1}, &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Debug: true}}}, out: true, }, } @@ -1030,7 +1409,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig) + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1215,7 +1594,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { for _, test := range testCases { req := newBidRequest(t) req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`) - req.Regs = &openrtb.Regs{ + req.Regs = &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":` + test.gdpr + `}`), } @@ -1247,7 +1626,8 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { nil, &permissionsMock{personalInfoAllowed: !test.gdprScrub, personalInfoAllowedError: test.permissionsError}, test.userSyncIfAmbiguous, - privacyConfig) + privacyConfig, + nil) result := results[0] if test.expectError { @@ -1268,17 +1648,17 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { } // newAdapterAliasBidRequest builds a BidRequest with aliases -func newAdapterAliasBidRequest(t *testing.T) *openrtb.BidRequest { +func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { dnt := int8(1) - return &openrtb.BidRequest{ - Site: &openrtb.Site{ + return &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "www.some.domain.com", Domain: "domain.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "some-publisher-id", }, }, - Device: &openrtb.Device{ + 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", @@ -1286,21 +1666,21 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb.BidRequest { DNT: &dnt, Language: "EN", }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Regs: &openrtb.Regs{ + Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1}`), }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }, { @@ -1314,35 +1694,35 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb.BidRequest { } } -func newBidRequest(t *testing.T) *openrtb.BidRequest { - return &openrtb.BidRequest{ - Site: &openrtb.Site{ +func newBidRequest(t *testing.T) *openrtb2.BidRequest { + return &openrtb2.BidRequest{ + Site: &openrtb2.Site{ Page: "www.some.domain.com", Domain: "domain.com", - Publisher: &openrtb.Publisher{ + Publisher: &openrtb2.Publisher{ ID: "some-publisher-id", }, }, - Device: &openrtb.Device{ + 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", IP: "132.173.230.74", Language: "EN", }, - Source: &openrtb.Source{ + Source: &openrtb2.Source{ TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87", }, - User: &openrtb.User{ + User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", Yob: 1982, Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), }, - Imp: []openrtb.Imp{{ + Imp: []openrtb2.Imp{{ ID: "some-imp-id", - Banner: &openrtb.Banner{ - Format: []openrtb.Format{{ + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{{ W: 300, H: 250, }, { @@ -1591,8 +1971,8 @@ func TestRemoveUnpermissionedEids(t *testing.T) { } for _, test := range testCases { - request := &openrtb.BidRequest{ - User: &openrtb.User{Ext: test.userExt}, + request := &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: test.userExt}, } requestExt := &openrtb_ext.ExtRequest{ @@ -1603,8 +1983,8 @@ func TestRemoveUnpermissionedEids(t *testing.T) { }, } - expectedRequest := &openrtb.BidRequest{ - User: &openrtb.User{Ext: test.expectedUserExt}, + expectedRequest := &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: test.expectedUserExt}, } resultErr := removeUnpermissionedEids(request, bidder, requestExt) @@ -1637,8 +2017,8 @@ func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { } for _, test := range testCases { - request := &openrtb.BidRequest{ - User: &openrtb.User{Ext: test.userExt}, + request := &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: test.userExt}, } requestExt := &openrtb_ext.ExtRequest{ @@ -1659,12 +2039,12 @@ func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { testCases := []struct { description string - request *openrtb.BidRequest + request *openrtb2.BidRequest requestExt *openrtb_ext.ExtRequest }{ { description: "Nil User", - request: &openrtb.BidRequest{ + request: &openrtb2.BidRequest{ User: nil, }, requestExt: &openrtb_ext.ExtRequest{ @@ -1679,8 +2059,8 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { }, { description: "Empty User", - request: &openrtb.BidRequest{ - User: &openrtb.User{}, + request: &openrtb2.BidRequest{ + User: &openrtb2.User{}, }, requestExt: &openrtb_ext.ExtRequest{ Prebid: openrtb_ext.ExtRequestPrebid{ @@ -1694,15 +2074,15 @@ func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { }, { description: "Nil Ext", - request: &openrtb.BidRequest{ - User: &openrtb.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, + request: &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, }, requestExt: nil, }, { description: "Nil Prebid Data", - request: &openrtb.BidRequest{ - User: &openrtb.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, + request: &openrtb2.BidRequest{ + User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, }, requestExt: &openrtb_ext.ExtRequest{ Prebid: openrtb_ext.ExtRequestPrebid{ diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 272bc1f21f0..616d0f0ae07 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -5,10 +5,10 @@ import ( "net/http" "strconv" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/prebid/go-gdpr/vendorlist" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" ) type Permissions interface { @@ -25,7 +25,7 @@ type Permissions interface { // Determines whether or not to send PI information to a bidder, or mask it out. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string) (bool, bool, bool, error) + PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) } // Versions of the GDPR TCF technical specification. @@ -44,8 +44,8 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ cfg: cfg, vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tcf1SpecVersion), - tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker, tcf2SpecVersion)}, + tcf1SpecVersion: newVendorListFetcherTCF1(cfg), + tcf2SpecVersion: newVendorListFetcherTCF2(ctx, cfg, client, vendorListURLMaker)}, } if cfg.HostVendorID == 0 { diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go index 81d3c6156f7..5048cf118f5 100644 --- a/gdpr/gdpr_test.go +++ b/gdpr/gdpr_test.go @@ -5,8 +5,8 @@ import ( "net/http" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/gdpr/impl.go b/gdpr/impl.go index 8d9a357393d..55d1cd4aeb0 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -4,18 +4,18 @@ import ( "context" "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/prebid/go-gdpr/api" tcf1constants "github.com/prebid/go-gdpr/consentconstants" consentconstants "github.com/prebid/go-gdpr/consentconstants/tcf2" "github.com/prebid/go-gdpr/vendorconsent" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/go-gdpr/vendorlist" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) // This file implements GDPR permissions for the app. -// For more info, see https://github.com/PubMatic-OpenWrap/prebid-server/issues/501 +// For more info, see https://github.com/prebid/prebid-server/issues/501 // // Nothing in this file is exported. Public APIs can be found in gdpr.go @@ -58,7 +58,12 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string) (allowPI bool, allowGeo bool, allowID bool, err error) { +func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, + bidder openrtb_ext.BidderName, + PublisherID string, + gdprSignal Signal, + consent string, + weakVendorEnforcement bool) (allowPI bool, allowGeo bool, allowID bool, err error) { if _, ok := p.cfg.NonStandardPublisherMap[PublisherID]; ok { return true, true, true, nil } @@ -74,7 +79,7 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt } if id, ok := p.vendorIDs[bidder]; ok { - return p.allowPI(ctx, id, consent) + return p.allowPI(ctx, id, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() @@ -122,7 +127,7 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen err := fmt.Errorf("Unable to access TCF2 parsed consent") return false, err } - return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess), nil + return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, false), nil } if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) { return true, nil @@ -130,7 +135,7 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } -func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string) (bool, bool, bool, error) { +func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) if err != nil { return false, false, false, err @@ -142,9 +147,9 @@ func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent if parsedConsent.Version() == 2 { if p.cfg.TCF2.Enabled { - return p.allowPITCF2(parsedConsent, vendor, vendorID) + return p.allowPITCF2(parsedConsent, vendor, vendorID, weakVendorEnforcement) } - if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile)) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && parsedConsent.VendorConsent(vendorID) { + if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && (parsedConsent.VendorConsent(vendorID) || weakVendorEnforcement) { return true, true, true, nil } } else { @@ -155,7 +160,7 @@ func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent return false, false, false, nil } -func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16) (allowPI bool, allowGeo bool, allowID bool, err error) { +func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowPI bool, allowGeo bool, allowID bool, err error) { consent, ok := parsedConsent.(tcf2.ConsentMetadata) err = nil allowPI = false @@ -166,12 +171,12 @@ func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor a return } if p.cfg.TCF2.SpecialPurpose1.Enabled { - allowGeo = consent.SpecialFeatureOptIn(1) && vendor.SpecialPurpose(1) + allowGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) } else { allowGeo = true } for i := 2; i <= 10; i++ { - if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i)) { + if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i), weakVendorEnforcement) { allowID = true break } @@ -179,13 +184,13 @@ func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor a // Set to true so any purpose check can flip it to false allowPI = true if p.cfg.TCF2.Purpose1.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess) + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, weakVendorEnforcement) } if p.cfg.TCF2.Purpose2.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.BasicAdserving) + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.BasicAdserving, weakVendorEnforcement) } if p.cfg.TCF2.Purpose7.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.AdPerformance) + allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.AdPerformance, weakVendorEnforcement) } return } @@ -194,22 +199,24 @@ const pubRestrictNotAllowed = 0 const pubRestrictRequireConsent = 1 const pubRestrictRequireLegitInterest = 2 -func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose) bool { +func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose, weakVendorEnforcement bool) bool { if purpose == consentconstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed } if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) { return false } + + purposeAllowed := consent.PurposeAllowed(purpose) && (weakVendorEnforcement || (vendor.Purpose(purpose) && consent.VendorConsent(vendorID))) + legitInterest := consent.PurposeLITransparency(purpose) && (weakVendorEnforcement || (vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID))) + if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) { - return vendor.PurposeStrict(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID) + return purposeAllowed } if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireLegitInterest, vendorID) { // Need LITransparency here - return vendor.LegitimateInterestStrict(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID) + return legitInterest } - purposeAllowed := vendor.Purpose(purpose) && consent.PurposeAllowed(purpose) && consent.VendorConsent(vendorID) - legitInterest := vendor.LegitimateInterest(purpose) && consent.PurposeLITransparency(purpose) && consent.VendorLegitInterest(vendorID) return purposeAllowed || legitInterest } @@ -258,7 +265,7 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B return true, nil } -func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string) (bool, bool, bool, error) { +func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return true, true, true, nil } @@ -273,6 +280,6 @@ func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi return false, nil } -func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string) (bool, bool, bool, error) { +func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { return false, false, false, nil } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 1977c4cf62e..b13d469a955 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -6,8 +6,8 @@ import ( "fmt" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" @@ -175,13 +175,14 @@ func TestAllowPersonalInfo(t *testing.T) { consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" tests := []struct { - description string - bidderName openrtb_ext.BidderName - publisherID string - userSyncIfAmbiguous bool - gdpr Signal - consent string - allowPI bool + description string + bidderName openrtb_ext.BidderName + publisherID string + userSyncIfAmbiguous bool + gdpr Signal + consent string + allowPI bool + weakVendorEnforcement bool }{ { description: "Allow PI - Non standard publisher", @@ -285,7 +286,7 @@ func TestAllowPersonalInfo(t *testing.T) { for _, tt := range tests { perms.cfg.UsersyncIfAmbiguous = tt.userSyncIfAmbiguous - allowPI, _, _, err := perms.PersonalInfoAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent) + allowPI, _, _, err := perms.PersonalInfoAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) assert.Nil(t, err, tt.description) assert.Equal(t, tt.allowPI, allowPI, tt.description) @@ -317,6 +318,12 @@ func buildTCF2VendorList34() tcf2VendorList { Purposes: []int{2, 4, 7}, SpecialPurposes: []int{1}, }, + "20": { + ID: 20, + Purposes: []int{1}, + LegIntPurposes: []int{2, 7}, + FlexiblePurposes: []int{2, 7}, + }, "32": { ID: 32, Purposes: []int{1, 2, 4, 7}, @@ -337,12 +344,13 @@ var tcf2Config = config.GDPR{ } type tcf2TestDef struct { - description string - bidder openrtb_ext.BidderName - consent string - allowPI bool - allowGeo bool - allowID bool + description string + bidder openrtb_ext.BidderName + consent string + allowPI bool + allowGeo bool + allowID bool + weakVendorEnforcement bool } func TestAllowPersonalInfoTCF2(t *testing.T) { @@ -353,11 +361,13 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { openrtb_ext.BidderAppnexus: 2, openrtb_ext.BidderPubmatic: 6, openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ tcf1SpecVersion: nil, tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ 34: parseVendorListDataV2(t, vendorListData), + 74: parseVendorListDataV2(t, vendorListData), }), }, } @@ -373,6 +383,15 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { allowGeo: false, allowID: false, }, + { + description: "Appnexus vendor test, insufficient purposes claimed, basic enforcement", + bidder: openrtb_ext.BidderAppnexus, + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + allowPI: true, + allowGeo: true, + allowID: true, + weakVendorEnforcement: true, + }, { description: "Pubmatic vendor test, flex purposes claimed", bidder: openrtb_ext.BidderPubmatic, @@ -389,10 +408,21 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { allowGeo: false, allowID: true, }, + { + // This requires publisher restrictions on any claimed purposes, 2-10. Vendor must declare all claimed purposes + // as flex with legit interest as primary. + // Using vendor 20 for this. + description: "OpenX vendor test, Specific purposes/LIs claimed, no geo claimed, Publisher restrictions apply", + bidder: openrtb_ext.BidderOpenx, + consent: "CPAavcCPAavcCAGABCFRBKCsAP_AAH_AAAqIHFNf_X_fb3_j-_59_9t0eY1f9_7_v-0zjgeds-8Nyd_X_L8X5mM7vB36pq4KuR4Eu3LBAQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XT_ZKY79_____7__-_____7_f__-__3_vp9V---wOJAIMBAUAgAEMAAQIFCIQAAQhiQAAAABBCIBQJIAEqgAWVwEdoIEACAxAQgQAgBBQgwCAAQAAJKAgBACwQCAAiAQAAgAEAIAAEIAILACQEAAAEAJCAAiACECAgiAAg5DAgIgCCAFABAAAuJDACAMooASBAPGQGAAKAAqACGAEwALgAjgBlgDUAHZAPsA_ACMAFLAK2AbwBMQCbAFogLYAYEAw8BkQDOQGeAM-EQHwAVABWAC4AIYAZAAywBqADZAHYAPwAgABGAClgFPANYAdUA-QCGwEOgIvASIAmwBOwCkQFyAMCAYSAw8Bk4DOQGfCQAYADgBzgN_CQTgAEAALgAoACoAGQAOAAeABAACIAFQAMIAaABqADyAIYAigBMgCqAKwAWAAuABvADmAHoAQ0AiACJgEsAS4AmgBSgC3AGGAMgAZcA1ADVAGyAO8AewA-IB9gH6AQAAjABQQClgFPAL8AYoA1gBtADcAG8AOIAegA-QCGwEOgIqAReAkQBMQCZQE2AJ2AUOApEBYoC2AFyALvAYEAwYBhIDDQGHgMiAZIAycBlwDOQGfANIAadA1gDWQoAEAYQaBIACoAKwAXABDADIAGWANQAbIA7AB-AEAAIKARgApYBT4C0ALSAawA3gB1QD5AIbAQ6Ai8BIgCbAE7AKRAXIAwIBhIDDwGMAMnAZyAzwBnwcAEAA4Bv4qA2ABQAFQAQwAmABcAEcAMsAagA7AB-AEYAKXAWgBaQDeAJBATEAmwBTYC2AFyAMCAYeAyIBnIDPAGfANyHQWQAFwAUABUADIAHAAQAAiABdADAAMYAaABqADwAH0AQwBFACZAFUAVgAsABcADEAGYAN4AcwA9ACGAERAJYAmABNACjAFKALEAW4AwwBkADKAGiANQAbIA3wB3gD2gH2AfoBGACVAFBAKeAWKAtAC0gFzALyAX4AxQBuADiQHTAdQA9ACGwEOgIiAReAkEBIgCbAE7AKHAU0AqwBYsC2ALZAXAAuQBdoC7wGEgMNAYeAxIBjADHgGSAMnAZUAywBlwDOQGfANEgaQBpIDSwGnANYAbGPABAIqAb-QgZgALAAoABkAEQALgAYgBDACYAFUALgAYgAzABvAD0AI4AWIAygBqADfAHfAPsA_ACMAFBAKGAU-AtAC0gF-AMUAdQA9ACQQEiAJsAU0AsUBaMC2ALaAXAAuQBdoDDwGJAMiAZOAzkBngDPgGiANJAaWA4AlAyAAQAAsACgAGQAOAAigBgAGIAPAAiABMACqAFwAMQAZgA2gCGgEQARIAowBSgC3AGEAMoAaoA2QB3gD8AIwAU-AtAC0gGKANwAcQA6gCHQEXgJEATYAsUBbAC7QGHgMiAZOAywBnIDPAGfANIAawA4AmACARUA38pBBAAXABQAFQAMgAcABAACKAGAAYwA0ADUAHkAQwBFACYAFIAKoAWAAuABiADMAHMAQwAiABRgClAFiALcAZQA0QBqgDZAHfAPsA_ACMAFBAKGAVsAuYBeQDaAG4APQAh0BF4CRAE2AJ2AUOApoBWwCxQFsALgAXIAu0BhoDDwGMAMiAZIAycBlwDOQGeAM-gaQBpMDWANZAbGVABAA-Ab-A.YAAAAAAAAAAA", + allowPI: true, + allowGeo: false, + allowID: true, + }, } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) @@ -418,7 +448,7 @@ func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { } // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array perms.cfg.NonStandardPublisherMap = map[string]struct{}{"appNexusAppID": {}} - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed") assert.EqualValuesf(t, true, allowPI, "AllowPI failure") assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure") @@ -472,7 +502,7 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) @@ -528,7 +558,7 @@ func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) @@ -585,7 +615,7 @@ func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent) + allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index 61fa166d212..bc7eab40647 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -10,11 +10,11 @@ import ( "sync/atomic" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" "github.com/golang/glog" "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" + "github.com/prebid/prebid-server/config" "golang.org/x/net/context/ctxhttp" ) @@ -22,34 +22,44 @@ type saveVendors func(uint16, api.VendorList) // This file provides the vendorlist-fetching function for Prebid Server. // -// For more info, see https://github.com/PubMatic-OpenWrap/prebid-server/issues/504 +// For more info, see https://github.com/prebid/prebid-server/issues/504 // // 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, uint8) string, tcfSpecVersion uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { - var fallback api.VendorList - - if tcfSpecVersion == tcf1SpecVersion && len(cfg.TCF1.FallbackGVLPath) == 0 { - return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { +func newVendorListFetcherTCF1(cfg config.GDPR) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { + if len(cfg.TCF1.FallbackGVLPath) == 0 { + return func(_ context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { return nil, makeVendorListNotFoundError(vendorListVersion) } } - if tcfSpecVersion == tcf1SpecVersion { - fallback = loadFallbackGVL(cfg.TCF1.FallbackGVLPath) + fallback := loadFallbackGVLForTCF1(cfg.TCF1.FallbackGVLPath) + return func(_ context.Context, _ uint16) (vendorlist.VendorList, error) { + return fallback, nil + } +} - return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { - return fallback, nil - } +func loadFallbackGVLForTCF1(fallbackGVLPath string) vendorlist.VendorList { + fallbackContents, err := ioutil.ReadFile(fallbackGVLPath) + if err != nil { + glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err) + } + + fallback, err := vendorlist.ParseEagerly(fallbackContents) + if err != nil { + glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err) } + return fallback +} - cacheSave, cacheLoad := newVendorListCache(fallback) +func newVendorListFetcherTCF2(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { + cacheSave, cacheLoad := newVendorListCache() preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) defer cancel() - preloadCache(preloadContext, client, urlMaker, cacheSave, tcfSpecVersion) + preloadCache(preloadContext, client, urlMaker, cacheSave) - saveOneRateLimited := newOccasionalSaver(cfg.Timeouts.ActiveTimeout(), tcfSpecVersion) + saveOneRateLimited := newOccasionalSaver(cfg.Timeouts.ActiveTimeout()) return func(ctx context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { // Attempt To Load From Cache if list := cacheLoad(vendorListVersion); list != nil { @@ -58,7 +68,7 @@ func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http // Attempt To Download // - May not add to cache immediately. - saveOneRateLimited(ctx, client, urlMaker(vendorListVersion, tcfSpecVersion), cacheSave) + saveOneRateLimited(ctx, client, urlMaker(vendorListVersion), cacheSave) // Attempt To Load From Cache Again // - May have been added by the call to saveOneRateLimited. @@ -66,11 +76,6 @@ func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http return list, nil } - // Attempt To Use Hardcoded Fallback - if fallback != nil { - return fallback, nil - } - // Give Up return nil, makeVendorListNotFoundError(vendorListVersion) } @@ -81,27 +86,24 @@ func makeVendorListNotFoundError(vendorListVersion uint16) error { } // preloadCache saves all the known versions of the vendor list for future use. -func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16, uint8) string, saver saveVendors, tcfSpecVersion uint8) { - latestVersion := saveOne(ctx, client, urlMaker(0, tcfSpecVersion), saver, tcfSpecVersion) +func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16) string, saver saveVendors) { + latestVersion := saveOne(ctx, client, urlMaker(0), saver) - for i := uint16(1); i < latestVersion; i++ { - saveOne(ctx, client, urlMaker(i, tcfSpecVersion), saver, tcfSpecVersion) + // The GVL for TCF2 has no vendors defined in its first version. It's very unlikely to be used, so don't preload it. + firstVersionToLoad := uint16(2) + + for i := firstVersionToLoad; i < latestVersion; i++ { + saveOne(ctx, client, urlMaker(i), saver) } } // Make a URL which can be used to fetch a given version of the Global Vendor List. If the version is 0, // this will fetch the latest version. -func vendorListURLMaker(vendorListVersion uint16, tcfSpecVersion uint8) string { - if tcfSpecVersion == tcf2SpecVersion { - if vendorListVersion == 0 { - return "https://vendor-list.consensu.org/v2/vendor-list.json" - } - return "https://vendor-list.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json" - } +func vendorListURLMaker(vendorListVersion uint16) string { if vendorListVersion == 0 { - return "https://vendor-list.consensu.org/vendorlist.json" + return "https://vendor-list.consensu.org/v2/vendor-list.json" } - return "https://vendor-list.consensu.org/v-" + strconv.Itoa(int(vendorListVersion)) + "/vendorlist.json" + return "https://vendor-list.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json" } // newOccasionalSaver returns a wrapped version of saveOne() which only activates every few minutes. @@ -109,7 +111,7 @@ func vendorListURLMaker(vendorListVersion uint16, tcfSpecVersion uint8) string { // The goal here is to update quickly when new versions of the VendorList are released, but not wreck // server performance if a bad CMP starts sending us malformed consent strings that advertize a version // that doesn't exist yet. -func newOccasionalSaver(timeout time.Duration, tcfSpecVersion uint8) func(ctx context.Context, client *http.Client, url string, saver saveVendors) { +func newOccasionalSaver(timeout time.Duration) func(ctx context.Context, client *http.Client, url string, saver saveVendors) { lastSaved := &atomic.Value{} lastSaved.Store(time.Time{}) @@ -120,13 +122,13 @@ func newOccasionalSaver(timeout time.Duration, tcfSpecVersion uint8) func(ctx co if timeSinceLastSave.Minutes() > 10 { withTimeout, cancel := context.WithTimeout(ctx, timeout) defer cancel() - saveOne(withTimeout, client, url, saver, tcfSpecVersion) + saveOne(withTimeout, client, url, saver) lastSaved.Store(now) } } } -func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors, tcfSpecVersion uint8) uint16 { +func saveOne(ctx context.Context, client *http.Client, url string, saver saveVendors) uint16 { req, err := http.NewRequest("GET", url, nil) if err != nil { glog.Errorf("Failed to build GET %s request. Cookie syncs may be affected: %v", url, err) @@ -150,11 +152,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen return 0 } var newList api.VendorList - if tcfSpecVersion == tcf2SpecVersion { - newList, err = vendorlist2.ParseEagerly(respBody) - } else { - newList, err = vendorlist.ParseEagerly(respBody) - } + newList, err = vendorlist2.ParseEagerly(respBody) if err != nil { glog.Errorf("GET %s returned malformed JSON. Cookie syncs may be affected. Error was %v. Body was %s", url, err, string(respBody)) return 0 @@ -164,7 +162,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen return newList.Version() } -func newVendorListCache(fallbackVL api.VendorList) (save func(vendorListVersion uint16, list api.VendorList), load func(vendorListVersion uint16) api.VendorList) { +func newVendorListCache() (save func(vendorListVersion uint16, list api.VendorList), load func(vendorListVersion uint16) api.VendorList) { cache := &sync.Map{} save = func(vendorListVersion uint16, list api.VendorList) { @@ -180,16 +178,3 @@ func newVendorListCache(fallbackVL api.VendorList) (save func(vendorListVersion } return } - -func loadFallbackGVL(fallbackGVLPath string) vendorlist.VendorList { - fallbackContents, err := ioutil.ReadFile(fallbackGVLPath) - if err != nil { - glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err) - } - - fallback, err := vendorlist.ParseEagerly(fallbackContents) - if err != nil { - glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err) - } - return fallback -} diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index cd97b300883..27f1bc3b996 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -10,8 +10,8 @@ import ( "github.com/stretchr/testify/assert" - "github.com/PubMatic-OpenWrap/prebid-server/config" "github.com/prebid/go-gdpr/consentconstants" + "github.com/prebid/prebid-server/config" ) func TestTCF1FetcherInitialLoad(t *testing.T) { @@ -66,67 +66,13 @@ func TestTCF1FetcherInitialLoad(t *testing.T) { } for _, test := range testCases { - runTest(t, test, tcf1SpecVersion, server) - } -} - -func TestTCF2FetcherInitialLoad(t *testing.T) { - // Loads two vendor lists during initialization by setting the latest vendor list version to 2. - // Ensures TCF1 fetch settings have no effect on TCF2. - - server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ - vendorListLatestVersion: 2, - vendorLists: map[int]string{ - 1: tcf2VendorList1, - 2: tcf2VendorList2, - }, - }))) - defer server.Close() - - testCases := []test{ - { - description: "Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 1, - }, - expected: vendorList1Expected, - }, - { - description: "Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "No Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 1, - }, - expected: vendorList1Expected, - }, - { - description: "No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - } - - for _, test := range testCases { - runTest(t, test, tcf2SpecVersion, server) + runTestTCF1(t, test, server) } } func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor lists will be dynamically loaded. - // Ensures TCF1 fetch settings have no effect on TCF2. server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, @@ -137,34 +83,20 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { }))) defer server.Close() - testCases := []test{ - { - description: "Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorList2Expected, - }, - { - description: "No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: vendorList2Expected, + test := test{ + description: "Dynamic Load - List Exists", + setup: testSetup{ + vendorListVersion: 2, }, + expected: vendorList2Expected, } - for _, test := range testCases { - runTest(t, test, tcf2SpecVersion, server) - } + runTestTCF2(t, test, server) } func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor list load attempts will be done dynamically. - // Ensures TCF1 fetch settings have no effect on TCF2. server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, @@ -174,32 +106,17 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { }))) defer server.Close() - testCases := []test{ - { - description: "Fallback - Vendor Doesn't Exist", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, + test := test{ + description: "No Fallback - Vendor Doesn't Exist", + setup: testSetup{ + vendorListVersion: 2, }, - { - description: "No Fallback - Vendor Doesn't Exist", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, + expected: testExpected{ + errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", }, } - for _, test := range testCases { - runTest(t, test, tcf2SpecVersion, server) - } + runTestTCF2(t, test, server) } func TestTCF2FetcherThrottling(t *testing.T) { @@ -222,7 +139,7 @@ func TestTCF2FetcherThrottling(t *testing.T) { }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion) + fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) // Dynamically Load List 2 Successfully _, errList1 := fetcher(context.Background(), 2) @@ -243,7 +160,7 @@ func TestTCF2MalformedVendorlist(t *testing.T) { }))) defer server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion) + fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) _, err := fetcher(context.Background(), 1) // Fetching should fail since vendor list could not be unmarshalled. @@ -254,9 +171,9 @@ func TestTCF2ServerUrlInvalid(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() - invalidURLGenerator := func(uint16, uint8) string { return " http://invalid-url-has-leading-whitespace" } + invalidURLGenerator := func(uint16) string { return " http://invalid-url-has-leading-whitespace" } - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator, tcf2SpecVersion) + fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), invalidURLGenerator) _, err := fetcher(context.Background(), 1) assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") @@ -266,7 +183,7 @@ func TestTCF2ServerUnavailable(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() - fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server), tcf2SpecVersion) + fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) _, err := fetcher(context.Background(), 1) assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") @@ -275,38 +192,23 @@ func TestTCF2ServerUnavailable(t *testing.T) { func TestVendorListURLMaker(t *testing.T) { testCases := []struct { description string - tcfSpecVersion uint8 vendorListVersion uint16 expectedURL string }{ { - description: "TCF1 - Latest", - tcfSpecVersion: 1, - vendorListVersion: 0, // Forces latest version. - expectedURL: "https://vendor-list.consensu.org/vendorlist.json", - }, - { - description: "TCF1 - Specific", - tcfSpecVersion: 1, - vendorListVersion: 42, - expectedURL: "https://vendor-list.consensu.org/v-42/vendorlist.json", - }, - { - description: "TCF2 - Latest", - tcfSpecVersion: 2, - vendorListVersion: 0, // Forces latest version. + description: "Latest", + vendorListVersion: 0, expectedURL: "https://vendor-list.consensu.org/v2/vendor-list.json", }, { - description: "TCF2 - Specific", - tcfSpecVersion: 2, + description: "Specific", vendorListVersion: 42, expectedURL: "https://vendor-list.consensu.org/v2/archives/vendor-list-v42.json", }, } for _, test := range testCases { - result := vendorListURLMaker(test.vendorListVersion, test.tcfSpecVersion) + result := vendorListURLMaker(test.vendorListVersion) assert.Equal(t, test.expectedURL, result) } } @@ -321,12 +223,6 @@ var tcf2VendorList1 = tcf2MarshalVendorList(tcf2VendorList{ Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2}}}, }) -var vendorList1Expected = testExpected{ - vendorListVersion: 1, - vendorID: 12, - vendorPurposes: map[int]bool{1: false, 2: true, 3: false}, -} - var tcf1VendorList2 = tcf1MarshalVendorList(tcf1VendorList{ VendorListVersion: 2, Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2, 3}}}, @@ -438,13 +334,31 @@ type testExpected struct { vendorPurposes map[int]bool } -func runTest(t *testing.T, test test, tcfSpecVersion uint8, server *httptest.Server) { +func runTestTCF1(t *testing.T, test test, server *httptest.Server) { config := testConfig() if test.setup.enableTCF1Fallback { config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" } - fetcher := newVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server), tcfSpecVersion) + fetcher := newVendorListFetcherTCF1(config) + vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) + + if test.expected.errorMessage != "" { + assert.EqualError(t, err, test.expected.errorMessage, test.description+":error") + } else { + assert.NoError(t, err, test.description+":vendorlist") + assert.Equal(t, test.expected.vendorListVersion, vendorList.Version(), test.description+":vendorlistid") + vendor := vendorList.Vendor(test.expected.vendorID) + for id, expected := range test.expected.vendorPurposes { + result := vendor.Purpose(consentconstants.Purpose(id)) + assert.Equalf(t, expected, result, "%s:vendor-%d:purpose-%d", test.description, vendorList.Version(), id) + } + } +} + +func runTestTCF2(t *testing.T, test test, server *httptest.Server) { + config := testConfig() + fetcher := newVendorListFetcherTCF2(context.Background(), config, server.Client(), testURLMaker(server)) vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) if test.expected.errorMessage != "" { @@ -460,9 +374,9 @@ func runTest(t *testing.T, test test, tcfSpecVersion uint8, server *httptest.Ser } } -func testURLMaker(server *httptest.Server) func(uint16, uint8) string { +func testURLMaker(server *httptest.Server) func(uint16) string { url := server.URL - return func(vendorListVersion uint16, tcfSpecVersion uint8) string { + return func(vendorListVersion uint16) string { return url + "?version=" + strconv.Itoa(int(vendorListVersion)) } } diff --git a/go.mod b/go.mod index b64f13db2a1..ac5447dcfce 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/PubMatic-OpenWrap/prebid-server +module github.com/prebid/prebid-server go 1.14 @@ -7,8 +7,7 @@ require ( github.com/DATA-DOG/go-sqlmock v1.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/PubMatic-OpenWrap/etree v1.0.2-0.20210129100623-8f30cfecf9f4 - github.com/PubMatic-OpenWrap/openrtb v11.0.1-0.20200228131822-5216ebe65c0c+incompatible + github.com/PubMatic-OpenWrap/etree v1.0.1 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/blang/semver v3.5.1+incompatible @@ -30,8 +29,7 @@ require ( github.com/mattn/go-colorable v0.1.2 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.0.0 // indirect - github.com/onsi/ginkgo v1.10.1 // indirect - github.com/onsi/gomega v1.7.0 // indirect + github.com/mxmCherry/openrtb/v15 v15.0.0 github.com/pelletier/go-toml v1.2.0 // indirect github.com/prebid/go-gdpr v0.8.3 github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed @@ -56,9 +54,9 @@ require ( github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect - golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - golang.org/x/sys v0.0.0-20190422165155-953cdadca894 // indirect - golang.org/x/text v0.3.0 - gopkg.in/yaml.v2 v2.2.2 + golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb + golang.org/x/text v0.3.3 + gopkg.in/yaml.v2 v2.4.0 ) + +replace github.com/mxmCherry/openrtb/v15 v15.0.0 => github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425134848-adbedb6f42c6 diff --git a/go.sum b/go.sum index 0b055532881..510e0ee0648 100644 --- a/go.sum +++ b/go.sum @@ -8,16 +8,10 @@ github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/PubMatic-OpenWrap/etree v1.0.1 h1:Q8sZ99MuXKmAx2v4XThKjwlstgadZffiRbNwUG0Ey1U= github.com/PubMatic-OpenWrap/etree v1.0.1/go.mod h1:5Y8qgcuDoy3XXG907UXkGnVTwihF16rXyJa4zRT7hOE= -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/openrtb v11.0.1-0.20200228131822-5216ebe65c0c+incompatible h1:BGwndVLu0ncwweHnofXzLo+SnRMe04Bq3KFfELLzif4= -github.com/PubMatic-OpenWrap/openrtb v11.0.1-0.20200228131822-5216ebe65c0c+incompatible/go.mod h1:Ply/+GFe6FLkPMLV8Yh8xW0MpqclQyVf7m4PRsnaLDY= +github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425134848-adbedb6f42c6 h1:HYFXG8R1mtbDYpwWPxtBXuQ8pfgndMlQd7opo+wSAbk= +github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425134848-adbedb6f42c6/go.mod h1:XMFHmDvfVyIhz5JGzvNXVt0afDLN2mrdYviUOKIYqAo= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -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/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c h1:uYq6BD31fkfeNKQmfLj7ODcEfkb5JLsKrXVSqgnfGg8= -github.com/beevik/etree v1.1.1-0.20200718192613-4a2f8b9d084c/go.mod h1:0yGO2rna3S9DkITDWHY1bMtcY4IJ4w+4S+EooZUR0bE= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= @@ -40,17 +34,29 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DP github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd h1:biTJQdqouE5by89AAffXG8++TY+9Fsdrg5rinbt3tHk= github.com/evanphx/json-patch v0.0.0-20180720181644-f195058310bd/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/influxdata/influxdb v1.6.1 h1:OseoBlzI5ftNI/bczyxSWq6PKRCNEeiXvyWP/wS5fB0= github.com/influxdata/influxdb v1.6.1/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= @@ -70,11 +76,17 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= +github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= +github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -110,7 +122,6 @@ github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -128,30 +139,57 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3Ifn 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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/main.go b/main.go index 376fe31f254..294cc141169 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,17 @@ package prebidServer import ( + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/usersync" "math/rand" "net/http" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - pbc "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/router" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" - "github.com/PubMatic-OpenWrap/prebid-server/util/task" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/currency" + pbc "github.com/prebid/prebid-server/prebid_cache_client" + "github.com/prebid/prebid-server/router" + "github.com/prebid/prebid-server/util/task" "github.com/golang/glog" "github.com/spf13/viper" @@ -20,7 +20,7 @@ import ( // Rev holds binary revision string // Set manually at build time using: // go build -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -// Populated automatically at build / release time via .travis.yml +// Populated automatically at build / releases // `gox -os="linux" -arch="386" -output="{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.Rev=`git rev-parse --short HEAD`" -verbose ./...;` // See issue #559 var Rev string diff --git a/main_test.go b/main_test.go index f3b6748ba48..1d2ec332164 100644 --- a/main_test.go +++ b/main_test.go @@ -4,7 +4,7 @@ import ( "os" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" + "github.com/prebid/prebid-server/config" "github.com/stretchr/testify/assert" "github.com/spf13/viper" diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 513fbd0ff53..70738f2fd1a 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -3,10 +3,10 @@ package config import ( "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - prometheusmetrics "github.com/PubMatic-OpenWrap/prebid-server/metrics/prometheus" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "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" 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 5465061d5a0..5b70b53bb1a 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -4,9 +4,9 @@ import ( "testing" "time" - mainConfig "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + mainConfig "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" gometrics "github.com/rcrowley/go-metrics" ) diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index a84a3ea8b74..dc4bc1f8217 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -5,9 +5,9 @@ import ( "sync" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/golang/glog" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" metrics "github.com/rcrowley/go-metrics" ) diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 712e8d5254c..2d0b9097b11 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" metrics "github.com/rcrowley/go-metrics" "github.com/stretchr/testify/assert" ) diff --git a/metrics/metrics.go b/metrics/metrics.go index 1908bca497b..4e6a6ea7275 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/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 51fb2b902fc..54b448bbe19 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -3,7 +3,7 @@ package metrics import ( "time" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/mock" ) diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index 0f2ada29e3a..f4dfe43469d 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -1,7 +1,7 @@ package prometheusmetrics import ( - "github.com/PubMatic-OpenWrap/prebid-server/metrics" + "github.com/prebid/prebid-server/metrics" "github.com/prometheus/client_golang/prometheus" ) diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 015ec033d81..33b36fbb61c 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -4,9 +4,9 @@ import ( "strconv" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prometheus/client_golang/prometheus" ) @@ -415,6 +415,7 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "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}) + preloadLabelValues(&metrics) return &metrics diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index c136b57c02b..9b4dc2aa09e 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" diff --git a/metrics/prometheus/type_conversion.go b/metrics/prometheus/type_conversion.go index eff379e8b2c..0e5c80636db 100644 --- a/metrics/prometheus/type_conversion.go +++ b/metrics/prometheus/type_conversion.go @@ -3,8 +3,8 @@ package prometheusmetrics import ( "strconv" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/openrtb_ext" ) func actionsAsString() []string { diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index 8f4d7a094bb..a568392beba 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -23,6 +23,7 @@ type ExtBidPrebid struct { Type BidType `json:"type"` Video *ExtBidPrebidVideo `json:"video,omitempty"` Events *ExtBidPrebidEvents `json:"events,omitempty"` + BidId string `json:"bidid,omitempty"` } // ExtBidPrebidCache defines the contract for bidresponse.seatbid.bid[i].ext.prebid.cache diff --git a/openrtb_ext/bid_request_video.go b/openrtb_ext/bid_request_video.go index 13ec8eb4538..29e62da3d35 100644 --- a/openrtb_ext/bid_request_video.go +++ b/openrtb_ext/bid_request_video.go @@ -1,8 +1,6 @@ package openrtb_ext -import ( - "github.com/PubMatic-OpenWrap/openrtb" -) +import "github.com/mxmCherry/openrtb/v15/openrtb2" type BidRequestVideo struct { // Attribute: @@ -27,7 +25,7 @@ type BidRequestVideo struct { // object; App or Site required // Description: // Application where the impression will be shown - App *openrtb.App `json:"app"` + App *openrtb2.App `json:"app"` // Attribute: // site @@ -35,7 +33,7 @@ type BidRequestVideo struct { // object; App or Site required // Description: // Site where the impression will be shown - Site *openrtb.Site `json:"site"` + Site *openrtb2.Site `json:"site"` // Attribute: // user @@ -43,7 +41,7 @@ type BidRequestVideo struct { // object; optional // Description: // Container object for the user of of the actual device - User *openrtb.User `json:"user,omitempty"` + User *openrtb2.User `json:"user,omitempty"` // Attribute: // device @@ -51,7 +49,7 @@ type BidRequestVideo struct { // object; optional // Description: // Device specific data - Device openrtb.Device `json:"device,omitempty"` + Device openrtb2.Device `json:"device,omitempty"` // Attribute: // includebrandcategory @@ -67,7 +65,7 @@ type BidRequestVideo struct { // object; required // Description: // Player container object - Video *openrtb.Video `json:"video,omitempty"` + Video *openrtb2.Video `json:"video,omitempty"` // Attribute: // content @@ -75,7 +73,7 @@ type BidRequestVideo struct { // object; optional // Description: // Misc content meta data that can be used for targeting the adPod(s) - Content openrtb.Content `json:"content,omitempty"` + Content openrtb2.Content `json:"content,omitempty"` // Attribute: // cacheconfig @@ -135,7 +133,7 @@ type BidRequestVideo struct { // object; optional // Description: // Contains the OpenRTB Regs object to be passed to OpenRTB request - Regs *openrtb.Regs `json:"regs,omitempty"` + Regs *openrtb2.Regs `json:"regs,omitempty"` // Attribute: // supportdeals diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 2cd2405ebfe..ef114914cd6 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -17,12 +17,6 @@ const schemaDirectory = "static/bidder-params" // BidderName refers to a core bidder id or an alias id. type BidderName string -// BidderNameGeneral is reserved for non-bidder specific messages when using a map keyed on the bidder name. -const BidderNameGeneral = BidderName("general") - -// BidderNameContext is reserved for first party data. -const BidderNameContext = BidderName("context") - func (name BidderName) MarshalJSON() ([]byte, error) { return []byte(name), nil } @@ -34,6 +28,45 @@ func (name *BidderName) String() string { return string(*name) } +// Names of reserved bidders. These names may not be used by a core bidder or alias. +const ( + BidderReservedAll BidderName = "all" // Reserved for the /info/bidders/all endpoint. + BidderReservedContext BidderName = "context" // Reserved for first party data. + BidderReservedData BidderName = "data" // Reserved for first party data. + BidderReservedGeneral BidderName = "general" // Reserved for non-bidder specific messages when using a map keyed on the bidder name. + BidderReservedPrebid BidderName = "prebid" // Reserved for Prebid Server configuration. + BidderReservedSKAdN BidderName = "skadn" // Reserved for Apple's SKAdNetwork OpenRTB extension. +) + +// IsBidderNameReserved returns true if the specified name is a case insensitive match for a reserved bidder name. +func IsBidderNameReserved(name string) bool { + if strings.EqualFold(name, string(BidderReservedAll)) { + return true + } + + if strings.EqualFold(name, string(BidderReservedContext)) { + return true + } + + if strings.EqualFold(name, string(BidderReservedData)) { + return true + } + + if strings.EqualFold(name, string(BidderReservedGeneral)) { + return true + } + + if strings.EqualFold(name, string(BidderReservedSKAdN)) { + return true + } + + if strings.EqualFold(name, string(BidderReservedPrebid)) { + return true + } + + return false +} + // Names of core bidders. These names *must* match the bidder code in Prebid.js if an adapter also exists in that // project. You may *not* use the name 'general' as that is reserved for general error messages nor 'context' as // that is reserved for first party data. @@ -57,6 +90,8 @@ const ( BidderAdtarget BidderName = "adtarget" BidderAdtelligent BidderName = "adtelligent" BidderAdvangelists BidderName = "advangelists" + BidderAdxcg BidderName = "adxcg" + BidderAdyoulike BidderName = "adyoulike" BidderAJA BidderName = "aja" BidderAMX BidderName = "amx" BidderApplogy BidderName = "applogy" @@ -66,12 +101,14 @@ const ( BidderBeachfront BidderName = "beachfront" BidderBeintoo BidderName = "beintoo" BidderBetween BidderName = "between" + BidderBidmachine BidderName = "bidmachine" BidderBrightroll BidderName = "brightroll" BidderColossus BidderName = "colossus" BidderConnectAd BidderName = "connectad" BidderConsumable BidderName = "consumable" BidderConversant BidderName = "conversant" BidderCpmstar BidderName = "cpmstar" + BidderCriteo BidderName = "criteo" BidderDatablocks BidderName = "datablocks" BidderDmx BidderName = "dmx" BidderDecenterAds BidderName = "decenterads" @@ -79,6 +116,7 @@ const ( BidderEmxDigital BidderName = "emx_digital" BidderEngageBDR BidderName = "engagebdr" BidderEPlanning BidderName = "eplanning" + BidderEpom BidderName = "epom" BidderGamma BidderName = "gamma" BidderGamoshi BidderName = "gamoshi" BidderGrid BidderName = "grid" @@ -87,6 +125,7 @@ const ( BidderInMobi BidderName = "inmobi" BidderInvibes BidderName = "invibes" BidderIx BidderName = "ix" + BidderJixie BidderName = "jixie" BidderKidoz BidderName = "kidoz" BidderKrushmedia BidderName = "krushmedia" BidderKubient BidderName = "kubient" @@ -102,8 +141,11 @@ const ( BidderNanoInteractive BidderName = "nanointeractive" BidderNinthDecimal BidderName = "ninthdecimal" BidderNoBid BidderName = "nobid" + BidderOneTag BidderName = "onetag" BidderOpenx BidderName = "openx" BidderOrbidder BidderName = "orbidder" + BidderOutbrain BidderName = "outbrain" + BidderPangle BidderName = "pangle" BidderPubmatic BidderName = "pubmatic" BidderPubnative BidderName = "pubnative" BidderPulsepoint BidderName = "pulsepoint" @@ -126,7 +168,9 @@ const ( BidderTelaria BidderName = "telaria" BidderTriplelift BidderName = "triplelift" BidderTripleliftNative BidderName = "triplelift_native" + BidderTrustX BidderName = "trustx" BidderUcfunnel BidderName = "ucfunnel" + BidderUnicorn BidderName = "unicorn" BidderUnruly BidderName = "unruly" BidderValueImpression BidderName = "valueimpression" BidderVerizonMedia BidderName = "verizonmedia" @@ -159,6 +203,8 @@ func CoreBidderNames() []BidderName { BidderAdtarget, BidderAdtelligent, BidderAdvangelists, + BidderAdxcg, + BidderAdyoulike, BidderAJA, BidderAMX, BidderApplogy, @@ -168,12 +214,14 @@ func CoreBidderNames() []BidderName { BidderBeachfront, BidderBeintoo, BidderBetween, + BidderBidmachine, BidderBrightroll, BidderColossus, BidderConnectAd, BidderConsumable, BidderConversant, BidderCpmstar, + BidderCriteo, BidderDatablocks, BidderDecenterAds, BidderDeepintent, @@ -181,6 +229,7 @@ func CoreBidderNames() []BidderName { BidderEmxDigital, BidderEngageBDR, BidderEPlanning, + BidderEpom, BidderGamma, BidderGamoshi, BidderGrid, @@ -189,6 +238,7 @@ func CoreBidderNames() []BidderName { BidderInMobi, BidderInvibes, BidderIx, + BidderJixie, BidderKidoz, BidderKrushmedia, BidderKubient, @@ -204,8 +254,11 @@ func CoreBidderNames() []BidderName { BidderNanoInteractive, BidderNinthDecimal, BidderNoBid, + BidderOneTag, BidderOpenx, BidderOrbidder, + BidderOutbrain, + BidderPangle, BidderPubmatic, BidderPubnative, BidderPulsepoint, @@ -228,7 +281,9 @@ func CoreBidderNames() []BidderName { BidderTelaria, BidderTriplelift, BidderTripleliftNative, + BidderTrustX, BidderUcfunnel, + BidderUnicorn, BidderUnruly, BidderValueImpression, BidderVerizonMedia, @@ -252,6 +307,16 @@ func BuildBidderMap() map[string]BidderName { return lookup } +// BuildBidderStringSlice builds a slioce of strings for each BidderName. +func BuildBidderStringSlice() []string { + coreBidders := CoreBidderNames() + slice := make([]string, len(coreBidders)) + for i, name := range CoreBidderNames() { + slice[i] = string(name) + } + return slice +} + func BuildBidderNameHashSet() map[string]struct{} { hashSet := make(map[string]struct{}) for _, name := range CoreBidderNames() { diff --git a/openrtb_ext/bidders_test.go b/openrtb_ext/bidders_test.go index 9c4889c9779..26ebf7dc74b 100644 --- a/openrtb_ext/bidders_test.go +++ b/openrtb_ext/bidders_test.go @@ -2,114 +2,119 @@ package openrtb_ext import ( "encoding/json" - "os" "testing" "github.com/stretchr/testify/assert" "github.com/xeipuuv/gojsonschema" ) -// TestMain does the expensive setup so we don't keep re-reading the files in static/bidder-params for each test. -func TestMain(m *testing.M) { - bidderParams, err := NewBidderParamsValidator("../static/bidder-params") - if err != nil { - os.Exit(1) +func TestBidderParamValidatorValidate(t *testing.T) { + testSchemaLoader := gojsonschema.NewStringLoader(`{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Test Params", + "description": "Test Description", + "type": "object", + "properties": { + "placementId": { + "type": "integer", + "description": "An ID which identifies this placement of the impression." + }, + "optionalText": { + "type": "string", + "description": "Optional text for testing." + } + }, + "required": ["placementId"] + }`) + testSchema, err := gojsonschema.NewSchema(testSchemaLoader) + if !assert.NoError(t, err) { + t.FailNow() } - validator = bidderParams - os.Exit(m.Run()) -} - -var validator BidderParamValidator - -// TestBidderParamSchemas makes sure that the validator.Schema() function -// returns valid JSON for all known CoreBidderNames. -func TestBidderParamSchemas(t *testing.T) { - for _, bidderName := range CoreBidderNames() { - schema := validator.Schema(bidderName) - if schema == "" { - t.Errorf("No schema exists for bidder %s. Does static/bidder-params/%s.json exist?", bidderName, bidderName) - } - - if _, err := gojsonschema.NewBytesLoader([]byte(schema)).LoadJSON(); err != nil { - t.Errorf("static/bidder-params/%s.json does not have a valid json-schema. %v", bidderName, err) - } + testBidderName := BidderName("foo") + testValidator := bidderParamValidator{ + parsedSchemas: map[BidderName]*gojsonschema.Schema{ + testBidderName: testSchema, + }, } -} -// TestValidParams and TestInvalidParams overlap with adapters/appnexus/params_test... but those tests -// from the other packages don't show up in code coverage. -func TestValidParams(t *testing.T) { - if err := validator.Validate(BidderAppnexus, json.RawMessage(`{"placementId":123}`)); err != nil { - t.Errorf("These params should be valid. Error was: %v", err) + testCases := []struct { + description string + ext json.RawMessage + expectedError string + }{ + { + description: "Valid", + ext: json.RawMessage(`{"placementId":123}`), + expectedError: "", + }, + { + description: "Invalid - Wrong Type", + ext: json.RawMessage(`{"placementId":"stringInsteadOfInt"}`), + expectedError: "placementId: Invalid type. Expected: integer, given: string", + }, + { + description: "Invalid - Empty Object", + ext: json.RawMessage(`{}`), + expectedError: "placementId: placementId is required", + }, + { + description: "Malformed", + ext: json.RawMessage(`malformedJSON`), + expectedError: "invalid character 'm' looking for beginning of value", + }, } -} -func TestInvalidParams(t *testing.T) { - if err := validator.Validate(BidderAppnexus, json.RawMessage(`{}`)); err == nil { - t.Error("These params should be invalid.") + for _, test := range testCases { + err := testValidator.Validate(testBidderName, test.ext) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } } } -func TestBidderListDoesNotDefineGeneral(t *testing.T) { - assert.NotContains(t, CoreBidderNames(), BidderNameGeneral) -} - -func TestBidderListDoesNotDefineContext(t *testing.T) { - assert.NotContains(t, CoreBidderNames(), BidderNameContext) -} - -// TestBidderUniquenessGatekeeping acts as a gatekeeper of bidder name uniqueness. If this test fails -// when you're building a new adapter, please consider choosing a different bidder name to maintain the -// current uniqueness threshold, or else start a discussion in the PR. -func TestBidderUniquenessGatekeeping(t *testing.T) { - // Get List Of Bidders - // - 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 != BidderTripleliftNative && bidder != BidderAdkernelAdn { - bidders = append(bidders, string(bidder)) - } +func TestBidderParamValidatorSchema(t *testing.T) { + testValidator := bidderParamValidator{ + schemaContents: map[BidderName]string{ + BidderName("foo"): "foo content", + BidderName("bar"): "bar content", + }, } - currentThreshold := 6 - measuredThreshold := minUniquePrefixLength(bidders) - - assert.NotZero(t, measuredThreshold, "BidderMap contains duplicate bidder name values.") - assert.LessOrEqual(t, measuredThreshold, currentThreshold) -} + result := testValidator.Schema(BidderName("bar")) -// minUniquePrefixLength measures the minimun amount of characters needed to uniquely identify -// one of the strings, or returns 0 if there are duplicates. -func minUniquePrefixLength(b []string) int { - targetingKeyMaxLength := 20 - for prefixLength := 1; prefixLength <= targetingKeyMaxLength; prefixLength++ { - if uniqueForPrefixLength(b, prefixLength) { - return prefixLength - } - } - return 0 + assert.Equal(t, "bar content", result) } -func uniqueForPrefixLength(b []string, prefixLength int) bool { - m := make(map[string]struct{}) - - if prefixLength <= 0 { - return false +func TestIsBidderNameReserved(t *testing.T) { + testCases := []struct { + bidder string + expected bool + }{ + {"all", true}, + {"aLl", true}, + {"ALL", true}, + {"context", true}, + {"CONTEXT", true}, + {"conTExt", true}, + {"data", true}, + {"DATA", true}, + {"DaTa", true}, + {"general", true}, + {"gEnErAl", true}, + {"GENERAL", true}, + {"skadn", true}, + {"skADN", true}, + {"SKADN", true}, + {"prebid", true}, + {"PREbid", true}, + {"PREBID", true}, + {"notreserved", false}, } - for i, n := range b { - ns := string(n) - - if len(ns) > prefixLength { - ns = ns[0:prefixLength] - } - - m[ns] = struct{}{} - - if len(m) != i+1 { - return false - } + for _, test := range testCases { + result := IsBidderNameReserved(test.bidder) + assert.Equal(t, test.expected, result, test.bidder) } - - return true } diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go new file mode 100644 index 00000000000..2ee3dd7d806 --- /dev/null +++ b/openrtb_ext/bidders_validate_test.go @@ -0,0 +1,99 @@ +package openrtb_ext + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/xeipuuv/gojsonschema" +) + +// TestMain does the expensive setup so we don't keep re-reading the files in static/bidder-params for each test. +func TestMain(m *testing.M) { + bidderParams, err := NewBidderParamsValidator("../static/bidder-params") + if err != nil { + os.Exit(1) + } + validator = bidderParams + os.Exit(m.Run()) +} + +var validator BidderParamValidator + +// TestBidderParamSchemas makes sure that the validator.Schema() function +// returns valid JSON for all known CoreBidderNames. +func TestBidderParamSchemas(t *testing.T) { + for _, bidderName := range CoreBidderNames() { + schema := validator.Schema(bidderName) + if schema == "" { + t.Errorf("No schema exists for bidder %s. Does static/bidder-params/%s.json exist?", bidderName, bidderName) + } + + if _, err := gojsonschema.NewBytesLoader([]byte(schema)).LoadJSON(); err != nil { + t.Errorf("static/bidder-params/%s.json does not have a valid json-schema. %v", bidderName, err) + } + } +} + +func TestBidderNamesValid(t *testing.T) { + for _, bidder := range CoreBidderNames() { + isReserved := IsBidderNameReserved(string(bidder)) + assert.False(t, isReserved, "bidder %v conflicts with a reserved name", bidder) + } +} + +// TestBidderUniquenessGatekeeping acts as a gatekeeper of bidder name uniqueness. If this test fails +// when you're building a new adapter, please consider choosing a different bidder name to maintain the +// current uniqueness threshold, or else start a discussion in the PR. +func TestBidderUniquenessGatekeeping(t *testing.T) { + // Get List Of Bidders + // - 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 != BidderTripleliftNative && bidder != BidderAdkernelAdn { + bidders = append(bidders, string(bidder)) + } + } + + currentThreshold := 6 + measuredThreshold := minUniquePrefixLength(bidders) + + assert.NotZero(t, measuredThreshold, "BidderMap contains duplicate bidder name values.") + assert.LessOrEqual(t, measuredThreshold, currentThreshold) +} + +// minUniquePrefixLength measures the minimun amount of characters needed to uniquely identify +// one of the strings, or returns 0 if there are duplicates. +func minUniquePrefixLength(b []string) int { + targetingKeyMaxLength := 20 + for prefixLength := 1; prefixLength <= targetingKeyMaxLength; prefixLength++ { + if uniqueForPrefixLength(b, prefixLength) { + return prefixLength + } + } + return 0 +} + +func uniqueForPrefixLength(b []string, prefixLength int) bool { + m := make(map[string]struct{}) + + if prefixLength <= 0 { + return false + } + + for i, n := range b { + ns := string(n) + + if len(ns) > prefixLength { + ns = ns[0:prefixLength] + } + + m[ns] = struct{}{} + + if len(m) != i+1 { + return false + } + } + + return true +} diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go index e882235d01e..8aeedb81a5e 100644 --- a/openrtb_ext/deal_tier.go +++ b/openrtb_ext/deal_tier.go @@ -3,7 +3,7 @@ package openrtb_ext import ( "encoding/json" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) // DealTier defines the configuration of a deal tier. @@ -20,7 +20,7 @@ type DealTier struct { type DealTierBidderMap map[BidderName]DealTier // ReadDealTiersFromImp returns a map of bidder deal tiers read from the impression of an original request (not split / cleaned). -func ReadDealTiersFromImp(imp openrtb.Imp) (DealTierBidderMap, error) { +func ReadDealTiersFromImp(imp openrtb2.Imp) (DealTierBidderMap, error) { dealTiers := make(DealTierBidderMap) if len(imp.Ext) == 0 { diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go index 717e0703466..29d58d6c071 100644 --- a/openrtb_ext/deal_tier_test.go +++ b/openrtb_ext/deal_tier_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -83,7 +83,7 @@ func TestReadDealTiersFromImp(t *testing.T) { } for _, test := range testCases { - imp := openrtb.Imp{Ext: test.impExt} + imp := openrtb2.Imp{Ext: test.impExt} result, err := ReadDealTiersFromImp(imp) diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index afbea276988..cc06f3806cf 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -1,25 +1,74 @@ package openrtb_ext import ( + "encoding/json" + "errors" "strconv" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" "github.com/buger/jsonparser" + "github.com/prebid/prebid-server/errortypes" ) // PrebidExtKey represents the prebid extension key used in requests const PrebidExtKey = "prebid" +// PrebidExtBidderKey represents the field name within request.imp.ext.prebid reserved for bidder params. +const PrebidExtBidderKey = "bidder" + // ExtDevice defines the contract for bidrequest.device.ext type ExtDevice struct { + // Attribute: + // atts + // Type: + // integer; optional - iOS Only + // Description: + // iOS app tracking authorization status. + // Extension Spec: + // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/extensions/community_extensions/skadnetwork.md + ATTS *IOSAppTrackingStatus `json:"atts"` + + // Attribute: + // prebid + // Type: + // object; optional + // Description: + // Prebid extensions for the Device object. Prebid ExtDevicePrebid `json:"prebid"` } -// Pointer to interstitial so we do not force it to exist +// IOSAppTrackingStatus describes the values for iOS app tracking authorization status. +type IOSAppTrackingStatus int + +// Values of the IOSAppTrackingStatus enumeration. +const ( + IOSAppTrackingStatusNotDetermined IOSAppTrackingStatus = 0 + IOSAppTrackingStatusRestricted IOSAppTrackingStatus = 1 + IOSAppTrackingStatusDenied IOSAppTrackingStatus = 2 + IOSAppTrackingStatusAuthorized IOSAppTrackingStatus = 3 +) + +// IsKnownIOSAppTrackingStatus returns true if the value is a known iOS app tracking authorization status. +func IsKnownIOSAppTrackingStatus(v int64) bool { + switch IOSAppTrackingStatus(v) { + case IOSAppTrackingStatusNotDetermined: + return true + case IOSAppTrackingStatusRestricted: + return true + case IOSAppTrackingStatusDenied: + return true + case IOSAppTrackingStatusAuthorized: + return true + default: + return false + } +} + +// ExtDevicePrebid defines the contract for bidrequest.device.ext.prebid type ExtDevicePrebid struct { Interstitial *ExtDeviceInt `json:"interstitial"` } +// ExtDeviceInt defines the contract for bidrequest.device.ext.prebid.interstitial type ExtDeviceInt struct { MinWidthPerc uint64 `json:"minwidtheperc"` MinHeightPerc uint64 `json:"minheightperc"` @@ -49,3 +98,26 @@ func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error { } return nil } + +// ParseDeviceExtATTS parses the ATTS value from the request.device.ext OpenRTB field. +func ParseDeviceExtATTS(deviceExt json.RawMessage) (*IOSAppTrackingStatus, error) { + v, err := jsonparser.GetInt(deviceExt, "atts") + + // node not found error + if err == jsonparser.KeyPathNotFoundError { + return nil, nil + } + + // unexpected parse error + if err != nil { + return nil, err + } + + // invalid value error + if !IsKnownIOSAppTrackingStatus(v) { + return nil, errors.New("invalid status") + } + + status := IOSAppTrackingStatus(v) + return &status, nil +} diff --git a/openrtb_ext/device_test.go b/openrtb_ext/device_test.go index b4c85bcc0b0..1a3dbe8e2f4 100644 --- a/openrtb_ext/device_test.go +++ b/openrtb_ext/device_test.go @@ -1,15 +1,14 @@ -package openrtb_ext_test +package openrtb_ext import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) func TestInvalidDeviceExt(t *testing.T) { - var s openrtb_ext.ExtDevice + 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") @@ -23,7 +22,7 @@ func TestInvalidDeviceExt(t *testing.T) { } func TestValidDeviceExt(t *testing.T) { - var s openrtb_ext.ExtDevice + var s ExtDevice assert.NoError(t, json.Unmarshal([]byte(`{"prebid":{}}`), &s)) assert.Nil(t, s.Prebid.Interstitial) assert.NoError(t, json.Unmarshal([]byte(`{}`), &s)) @@ -32,3 +31,79 @@ func TestValidDeviceExt(t *testing.T) { assert.EqualValues(t, 75, s.Prebid.Interstitial.MinWidthPerc) assert.EqualValues(t, 60, s.Prebid.Interstitial.MinHeightPerc) } + +func TestIsKnownIOSAppTrackingStatus(t *testing.T) { + valid := []int64{0, 1, 2, 3} + invalid := []int64{-1, 4} + + for _, v := range valid { + assert.True(t, IsKnownIOSAppTrackingStatus(v)) + } + + for _, v := range invalid { + assert.False(t, IsKnownIOSAppTrackingStatus(v)) + } +} + +func TestParseDeviceExtATTS(t *testing.T) { + authorized := IOSAppTrackingStatusAuthorized + + tests := []struct { + description string + givenExt json.RawMessage + expectedStatus *IOSAppTrackingStatus + expectedError string + }{ + { + description: "Nil", + givenExt: nil, + expectedStatus: nil, + }, + { + description: "Empty", + givenExt: json.RawMessage(``), + expectedStatus: nil, + }, + { + description: "Empty Object", + givenExt: json.RawMessage(`{}`), + expectedStatus: nil, + }, + { + description: "Valid", + givenExt: json.RawMessage(`{"atts":3}`), + expectedStatus: &authorized, + }, + { + description: "Invalid Value", + givenExt: json.RawMessage(`{"atts":5}`), + expectedStatus: nil, + expectedError: "invalid status", + }, + { + // This test case produces an error with the standard Go library, but jsonparser doesn't + // return an error for malformed JSON. It treats this case the same as not being found. + description: "Malformed - Standard Test Case", + givenExt: json.RawMessage(`malformed`), + expectedStatus: nil, + }, + { + description: "Malformed - Wrong Type", + givenExt: json.RawMessage(`{"atts":"1"}`), + expectedStatus: nil, + expectedError: "Value is not a number: 1", + }, + } + + for _, test := range tests { + status, err := ParseDeviceExtATTS(test.givenExt) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + + assert.Equal(t, test.expectedStatus, status, test.description+":status") + } +} diff --git a/openrtb_ext/imp_adyoulike.go b/openrtb_ext/imp_adyoulike.go new file mode 100644 index 00000000000..67a94123734 --- /dev/null +++ b/openrtb_ext/imp_adyoulike.go @@ -0,0 +1,18 @@ +package openrtb_ext + +// ExtImpAdyoulike defines the contract for bidrequest.imp[i].ext.adyoulike +type ExtImpAdyoulike struct { + // placementId, only mandatory field + PlacementId string `json:"placement"` + + // Id of the forced campaign + Campaign string `json:"campaign"` + // Id of the forced track + Track string `json:"track"` + // Id of the forced creative + Creative string `json:"creative"` + // Context of the campaign values [SSP|AdServer] + Source string `json:"source"` + // Abitrary Id used for debug purpose + Debug string `json:"debug"` +} diff --git a/openrtb_ext/imp_bidmachine.go b/openrtb_ext/imp_bidmachine.go new file mode 100644 index 00000000000..20491d25aca --- /dev/null +++ b/openrtb_ext/imp_bidmachine.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ExtImpBidmachine struct { + Host string `json:"host"` + Path string `json:"path"` + SellerID string `json:"seller_id"` +} diff --git a/openrtb_ext/imp_criteo.go b/openrtb_ext/imp_criteo.go new file mode 100644 index 00000000000..e200aace496 --- /dev/null +++ b/openrtb_ext/imp_criteo.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpCriteo defines the contract for bidrequest.imp[i].ext.criteo +type ExtImpCriteo struct { + ZoneID int64 `json:"zoneId"` + NetworkID int64 `json:"networkId"` +} diff --git a/openrtb_ext/imp_epom.go b/openrtb_ext/imp_epom.go new file mode 100644 index 00000000000..a99f60ef368 --- /dev/null +++ b/openrtb_ext/imp_epom.go @@ -0,0 +1,4 @@ +package openrtb_ext + +type ImpExtEpom struct { +} diff --git a/openrtb_ext/imp_gumgum.go b/openrtb_ext/imp_gumgum.go index 7959e1cccc3..56b000a6acf 100644 --- a/openrtb_ext/imp_gumgum.go +++ b/openrtb_ext/imp_gumgum.go @@ -1,5 +1,14 @@ package openrtb_ext +// ExtImpGumGum defines the contract for bidrequest.imp[i].ext.gumgum +// Either Zone or PubId must be present, others are optional parameters type ExtImpGumGum struct { - Zone string `json:"zone"` + Zone string `json:"zone,omitempty"` + PubID float64 `json:"pubId,omitempty"` + IrisID string `json:"irisid,omitempty"` +} + +// ExtImpGumGumVideo defines the contract for bidresponse.seatbid.bid[i].ext.gumgum.video +type ExtImpGumGumVideo struct { + IrisID string `json:"irisid,omitempty"` } diff --git a/openrtb_ext/imp_jixie.go b/openrtb_ext/imp_jixie.go new file mode 100644 index 00000000000..8fa0570c56b --- /dev/null +++ b/openrtb_ext/imp_jixie.go @@ -0,0 +1,8 @@ +package openrtb_ext + +type ExtImpJixie struct { + Unit string `json:"unit"` + AccountId string `json:"accountid,omitempty"` + JxProp1 string `json:"jxprop1,omitempty"` + JxProp2 string `json:"jxprop2,omitempty"` +} diff --git a/openrtb_ext/imp_onetag.go b/openrtb_ext/imp_onetag.go new file mode 100644 index 00000000000..47d06ed4873 --- /dev/null +++ b/openrtb_ext/imp_onetag.go @@ -0,0 +1,10 @@ +package openrtb_ext + +import ( + "encoding/json" +) + +type ExtImpOnetag struct { + PubId string `json:"pubId"` + Ext json.RawMessage `json:"ext"` +} diff --git a/openrtb_ext/imp_outbrain.go b/openrtb_ext/imp_outbrain.go new file mode 100644 index 00000000000..634f29481c1 --- /dev/null +++ b/openrtb_ext/imp_outbrain.go @@ -0,0 +1,15 @@ +package openrtb_ext + +// ExtImpOutbrain defines the contract for bidrequest.imp[i].ext.outbrain +type ExtImpOutbrain struct { + Publisher ExtImpOutbrainPublisher `json:"publisher"` + TagId string `json:"tagid"` + BCat []string `json:"bcat"` + BAdv []string `json:"badv"` +} + +type ExtImpOutbrainPublisher struct { + Id string `json:"id"` + Name string `json:"name"` + Domain string `json:"domain"` +} diff --git a/openrtb_ext/imp_pangle.go b/openrtb_ext/imp_pangle.go new file mode 100644 index 00000000000..cc32dcf1d01 --- /dev/null +++ b/openrtb_ext/imp_pangle.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtPangle struct { + Token string `json:"token"` +} diff --git a/openrtb_ext/imp_unicorn.go b/openrtb_ext/imp_unicorn.go new file mode 100644 index 00000000000..ad75414caa5 --- /dev/null +++ b/openrtb_ext/imp_unicorn.go @@ -0,0 +1,9 @@ +package openrtb_ext + +// ExtImpUnicorn defines the contract for bidrequest.imp[i].ext.unicorn +type ExtImpUnicorn struct { + PlacementID string `json:"placementId,omitempty"` + PublisherID int `json:"publisherId,omitempty"` + MediaID string `json:"mediaId"` + AccountID int `json:"accountId"` +} diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 57bef59d711..87807075d8e 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -5,9 +5,15 @@ import ( "errors" ) -// FirstPartyDataContextExtKey defines the field name within bidrequest.ext reserved -// for first party data support. -const FirstPartyDataContextExtKey string = "context" +// FirstPartyDataExtKey defines a field name within request.ext and request.imp.ext reserved for first party data. +const FirstPartyDataExtKey = "data" + +// FirstPartyDataContextExtKey defines a field name within request.ext and request.imp.ext reserved for first party data. +const FirstPartyDataContextExtKey = "context" + +// SKAdNExtKey defines the field name within request.ext reserved for Apple's SKAdNetwork. +const SKAdNExtKey = "skadn" + const MaxDecimalFigures int = 15 // ExtRequest defines the contract for bidrequest.ext @@ -33,10 +39,6 @@ type ExtRequestPrebid struct { // passing of personally identifiable information doesn't constitute a sale per CCPA law. // The array may contain a single sstar ('*') entry to represent all bidders. NoSale []string `json:"nosale,omitempty"` - - // Macros specifies list of custom macros along with the values. This is used while forming - // the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding - Macros map[string]string `json:"macros,omitempty"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains diff --git a/openrtb_ext/response.go b/openrtb_ext/response.go index f2119f2c871..1c7177daf49 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -1,14 +1,13 @@ package openrtb_ext -import ( - "github.com/PubMatic-OpenWrap/openrtb" -) +import "github.com/mxmCherry/openrtb/v15/openrtb2" // ExtBidResponse defines the contract for bidresponse.ext type ExtBidResponse struct { Debug *ExtResponseDebug `json:"debug,omitempty"` // Errors defines the contract for bidresponse.ext.errors - Errors map[BidderName][]ExtBidderError `json:"errors,omitempty"` + Errors map[BidderName][]ExtBidderMessage `json:"errors,omitempty"` + Warnings map[BidderName][]ExtBidderMessage `json:"warnings,omitempty"` // ResponseTimeMillis defines the contract for bidresponse.ext.responsetimemillis ResponseTimeMillis map[BidderName]int `json:"responsetimemillis,omitempty"` // RequestTimeoutMillis returns the timeout used in the auction. @@ -26,7 +25,7 @@ type ExtResponseDebug struct { // HttpCalls defines the contract for bidresponse.ext.debug.httpcalls HttpCalls map[BidderName][]*ExtHttpCall `json:"httpcalls,omitempty"` // Request after resolution of stored requests and debug overrides - ResolvedRequest *openrtb.BidRequest `json:"resolvedrequest,omitempty"` + ResolvedRequest *openrtb2.BidRequest `json:"resolvedrequest,omitempty"` } // ExtResponseSyncData defines the contract for bidresponse.ext.usersync.{bidder} @@ -47,8 +46,8 @@ type ExtUserSync struct { Type UserSyncType `json:"type"` } -// ExtBidderError defines an error object to be returned, consiting of a machine readable error code, and a human readable error message string. -type ExtBidderError struct { +// 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"` } diff --git a/openrtb_ext/site_test.go b/openrtb_ext/site_test.go index 0d41e0c02ce..67ec6cc4f99 100644 --- a/openrtb_ext/site_test.go +++ b/openrtb_ext/site_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go index bd07a6c558b..52840d95d9c 100644 --- a/pbs/pbsrequest.go +++ b/pbs/pbsrequest.go @@ -10,14 +10,14 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/cache" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" - "github.com/PubMatic-OpenWrap/prebid-server/util/httputil" - "github.com/PubMatic-OpenWrap/prebid-server/util/iputil" - - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/cache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/usersync" + "github.com/prebid/prebid-server/util/httputil" + "github.com/prebid/prebid-server/util/iputil" + "github.com/blang/semver" "github.com/buger/jsonparser" "github.com/golang/glog" @@ -84,18 +84,18 @@ type PBSVideo struct { } type AdUnit struct { - Code string `json:"code"` - TopFrame int8 `json:"is_top_frame"` - Sizes []openrtb.Format `json:"sizes"` - Bids []Bids `json:"bids"` - ConfigID string `json:"config_id"` - MediaTypes []string `json:"media_types"` - Instl int8 `json:"instl"` - Video PBSVideo `json:"video"` + Code string `json:"code"` + TopFrame int8 `json:"is_top_frame"` + Sizes []openrtb2.Format `json:"sizes"` + Bids []Bids `json:"bids"` + ConfigID string `json:"config_id"` + MediaTypes []string `json:"media_types"` + Instl int8 `json:"instl"` + Video PBSVideo `json:"video"` } type PBSAdUnit struct { - Sizes []openrtb.Format + Sizes []openrtb2.Format TopFrame int8 Code string BidID string @@ -153,27 +153,27 @@ func (bidder *PBSBidder) LookupAdUnit(Code string) (unit *PBSAdUnit) { } type PBSRequest struct { - AccountID string `json:"account_id"` - Tid string `json:"tid"` - CacheMarkup int8 `json:"cache_markup"` - SortBids int8 `json:"sort_bids"` - MaxKeyLength int8 `json:"max_key_length"` - Secure int8 `json:"secure"` - TimeoutMillis int64 `json:"timeout_millis"` - AdUnits []AdUnit `json:"ad_units"` - IsDebug bool `json:"is_debug"` - App *openrtb.App `json:"app"` - Device *openrtb.Device `json:"device"` - PBSUser json.RawMessage `json:"user"` - SDK *SDK `json:"sdk"` + AccountID string `json:"account_id"` + Tid string `json:"tid"` + CacheMarkup int8 `json:"cache_markup"` + SortBids int8 `json:"sort_bids"` + MaxKeyLength int8 `json:"max_key_length"` + Secure int8 `json:"secure"` + TimeoutMillis int64 `json:"timeout_millis"` + AdUnits []AdUnit `json:"ad_units"` + IsDebug bool `json:"is_debug"` + App *openrtb2.App `json:"app"` + Device *openrtb2.Device `json:"device"` + PBSUser json.RawMessage `json:"user"` + SDK *SDK `json:"sdk"` // internal Bidders []*PBSBidder `json:"-"` - User *openrtb.User `json:"-"` + User *openrtb2.User `json:"-"` Cookie *usersync.PBSCookie `json:"-"` Url string `json:"-"` Domain string `json:"-"` - Regs *openrtb.Regs `json:"regs"` + Regs *openrtb2.Regs `json:"-"` Start time.Time } @@ -236,7 +236,7 @@ func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.C pbsReq.TimeoutMillis = int64(cfg.LimitAuctionTimeout(time.Duration(pbsReq.TimeoutMillis)*time.Millisecond) / time.Millisecond) if pbsReq.Device == nil { - pbsReq.Device = &openrtb.Device{} + pbsReq.Device = &openrtb2.Device{} } if ip, _ := httputil.FindIP(r, ipv4Validator); ip != nil { pbsReq.Device.IP = ip.String() @@ -259,7 +259,7 @@ func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.C } if pbsReq.User == nil { - pbsReq.User = &openrtb.User{} + pbsReq.User = &openrtb2.User{} } // use client-side data for web requests diff --git a/pbs/pbsrequest_test.go b/pbs/pbsrequest_test.go index 566057473b8..52cd6153323 100644 --- a/pbs/pbsrequest_test.go +++ b/pbs/pbsrequest_test.go @@ -8,9 +8,9 @@ import ( "strings" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/config" "github.com/magiconair/properties/assert" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/config" ) const mimeVideoMp4 = "video/mp4" diff --git a/pbs/pbsresponse.go b/pbs/pbsresponse.go index 7c6cea31261..b8cf2c19ff7 100644 --- a/pbs/pbsresponse.go +++ b/pbs/pbsresponse.go @@ -32,9 +32,9 @@ type PBSBid struct { // If NURL and Adm are both defined, then Adm takes precedence. Adm string `json:"adm,omitempty"` // Width is the intended width which Adm should be shown, in pixels. - Width uint64 `json:"width,omitempty"` + Width int64 `json:"width,omitempty"` // Height is the intended width which Adm should be shown, in pixels. - Height uint64 `json:"height,omitempty"` + Height int64 `json:"height,omitempty"` // DealId is not used by prebid-server, but may be used by buyers and sellers who make special // deals with each other. We simply pass this information along with the bid. DealId string `json:"deal_id,omitempty"` diff --git a/pbs/usersync.go b/pbs/usersync.go index f50933b2434..4cac3544804 100644 --- a/pbs/usersync.go +++ b/pbs/usersync.go @@ -9,13 +9,13 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/server/ssl" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/golang/glog" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/server/ssl" + "github.com/prebid/prebid-server/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 8d363e147bd..730d54b0acb 100644 --- a/prebid_cache_client/client.go +++ b/prebid_cache_client/client.go @@ -12,8 +12,8 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" "github.com/buger/jsonparser" "github.com/golang/glog" @@ -22,7 +22,7 @@ import ( // Client stores values in Prebid Cache. For more info, see https://github.com/prebid/prebid-cache type Client interface { - // PutJson stores JSON values for the given openrtb.Bids in the cache. Null values will be + // PutJson stores JSON values for the given openrtb2.Bids in the cache. Null values will be // // The returned string slice will always have the same number of elements as the values argument. If a // value could not be saved, the element will be an empty string. Implementations are responsible for diff --git a/prebid_cache_client/client_test.go b/prebid_cache_client/client_test.go index c4eeca82b8f..1ba30a6faab 100644 --- a/prebid_cache_client/client_test.go +++ b/prebid_cache_client/client_test.go @@ -10,9 +10,9 @@ import ( "strconv" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - metricsConf "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" + metricsConf "github.com/prebid/prebid-server/metrics/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/prebid_cache_client/prebid_cache.go b/prebid_cache_client/prebid_cache.go index d19e8f1c9a6..842eb5b19d4 100644 --- a/prebid_cache_client/prebid_cache.go +++ b/prebid_cache_client/prebid_cache.go @@ -22,8 +22,8 @@ type CacheObject struct { type BidCache struct { Adm string `json:"adm,omitempty"` NURL string `json:"nurl,omitempty"` - Width uint64 `json:"width,omitempty"` - Height uint64 `json:"height,omitempty"` + Width int64 `json:"width,omitempty"` + Height int64 `json:"height,omitempty"` } // internal protocol objects diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 4ef412fd3ef..41f1c39447b 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -1,8 +1,6 @@ package ccpa -import ( - "github.com/PubMatic-OpenWrap/openrtb" -) +import "github.com/mxmCherry/openrtb/v15/openrtb2" // ConsentWriter implements the PolicyWriter interface for CCPA. type ConsentWriter struct { @@ -10,7 +8,7 @@ type ConsentWriter struct { } // Write mutates an OpenRTB bid request with the CCPA consent string. -func (c ConsentWriter) Write(req *openrtb.BidRequest) error { +func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index 1e491d9d167..d59428626b8 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -12,8 +12,8 @@ func TestConsentWriter(t *testing.T) { consent := "anyConsent" testCases := []struct { description string - request *openrtb.BidRequest - expected *openrtb.BidRequest + request *openrtb2.BidRequest + expected *openrtb2.BidRequest expectedError bool }{ { @@ -23,19 +23,19 @@ func TestConsentWriter(t *testing.T) { }, { description: "Success", - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, }, { description: "Error With Regs.Ext - Does Not Mutate", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, expectedError: true, - expected: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, }, } diff --git a/privacy/ccpa/parsedpolicy.go b/privacy/ccpa/parsedpolicy.go index 52977104716..7b9c2d1fa7c 100644 --- a/privacy/ccpa/parsedpolicy.go +++ b/privacy/ccpa/parsedpolicy.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" + "github.com/prebid/prebid-server/errortypes" ) const ( @@ -43,7 +43,10 @@ func (p Policy) Parse(validBidders map[string]struct{}) (ParsedPolicy, error) { consentOptOut, err := parseConsent(p.Consent) if err != nil { msg := fmt.Sprintf("request.regs.ext.us_privacy %s", err.Error()) - return ParsedPolicy{}, &errortypes.InvalidPrivacyConsent{Message: msg} + return ParsedPolicy{}, &errortypes.Warning{ + Message: msg, + WarningCode: errortypes.InvalidPrivacyConsentWarningCode, + } } noSaleForAllBidders, noSaleSpecificBidders, err := parseNoSaleBidders(p.NoSaleBidders, validBidders) diff --git a/privacy/ccpa/parsedpolicy_test.go b/privacy/ccpa/parsedpolicy_test.go index 4fa9f92684d..33563b50567 100644 --- a/privacy/ccpa/parsedpolicy_test.go +++ b/privacy/ccpa/parsedpolicy_test.go @@ -3,7 +3,7 @@ package ccpa import ( "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -385,7 +385,7 @@ type mockPolicWriter struct { mock.Mock } -func (m *mockPolicWriter) Write(req *openrtb.BidRequest) error { +func (m *mockPolicWriter) Write(req *openrtb2.BidRequest) error { args := m.Called(req) return args.Error(0) } diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index 3f5dd25c6bc..d57ba8deaa4 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" - "github.com/PubMatic-OpenWrap/openrtb" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" ) // Policy represents the CCPA regulatory information from an OpenRTB bid request. @@ -16,7 +16,7 @@ type Policy struct { } // ReadFromRequest extracts the CCPA regulatory information from an OpenRTB bid request. -func ReadFromRequest(req *openrtb.BidRequest) (Policy, error) { +func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { var consent string var noSaleBidders []string @@ -46,7 +46,7 @@ func ReadFromRequest(req *openrtb.BidRequest) (Policy, error) { } // Write mutates an OpenRTB bid request with the CCPA regulatory information. -func (p Policy) Write(req *openrtb.BidRequest) error { +func (p Policy) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } @@ -65,14 +65,14 @@ func (p Policy) Write(req *openrtb.BidRequest) error { return nil } -func buildRegs(consent string, regs *openrtb.Regs) (*openrtb.Regs, error) { +func buildRegs(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) { if consent == "" { return buildRegsClear(regs) } return buildRegsWrite(consent, regs) } -func buildRegsClear(regs *openrtb.Regs) (*openrtb.Regs, error) { +func buildRegsClear(regs *openrtb2.Regs) (*openrtb2.Regs, error) { if regs == nil || len(regs.Ext) == 0 { return regs, nil } @@ -92,7 +92,7 @@ func buildRegsClear(regs *openrtb.Regs) (*openrtb.Regs, error) { } // Marshal ext if there are still other fields - var regsResult openrtb.Regs + var regsResult openrtb2.Regs ext, err := json.Marshal(extMap) if err == nil { regsResult = *regs @@ -101,9 +101,9 @@ func buildRegsClear(regs *openrtb.Regs) (*openrtb.Regs, error) { return ®sResult, err } -func buildRegsWrite(consent string, regs *openrtb.Regs) (*openrtb.Regs, error) { +func buildRegsWrite(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) { if regs == nil { - return marshalRegsExt(openrtb.Regs{}, openrtb_ext.ExtRegs{USPrivacy: consent}) + return marshalRegsExt(openrtb2.Regs{}, openrtb_ext.ExtRegs{USPrivacy: consent}) } if regs.Ext == nil { @@ -119,7 +119,7 @@ func buildRegsWrite(consent string, regs *openrtb.Regs) (*openrtb.Regs, error) { return marshalRegsExt(*regs, extMap) } -func marshalRegsExt(regs openrtb.Regs, ext interface{}) (*openrtb.Regs, error) { +func marshalRegsExt(regs openrtb2.Regs, ext interface{}) (*openrtb2.Regs, error) { extJSON, err := json.Marshal(ext) if err == nil { regs.Ext = extJSON diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index c1fdd9cd903..416ebffa31a 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -4,21 +4,21 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) func TestReadFromRequest(t *testing.T) { testCases := []struct { description string - request *openrtb.BidRequest + request *openrtb2.BidRequest expectedPolicy Policy expectedError bool }{ { description: "Success", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -36,7 +36,7 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Nil Regs", - request: &openrtb.BidRequest{ + request: &openrtb2.BidRequest{ Regs: nil, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, @@ -47,8 +47,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Nil Regs.Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -58,8 +58,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Empty Regs.Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -69,8 +69,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Missing Regs.Ext USPrivacy Value", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"anythingElse":"42"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"anythingElse":"42"}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedPolicy: Policy{ @@ -80,24 +80,24 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Malformed Regs.Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedError: true, }, { description: "Invalid Regs.Ext Type", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a", "b"]}}`), }, expectedError: true, }, { description: "Nil Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: nil, }, expectedPolicy: Policy{ @@ -107,8 +107,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Empty Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`{}`), }, expectedPolicy: Policy{ @@ -118,8 +118,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Missing Ext.Prebid No Sale Value", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`{"anythingElse":"42"}`), }, expectedPolicy: Policy{ @@ -129,24 +129,24 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Malformed Ext", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`malformed`), }, expectedError: true, }, { description: "Invalid Ext.Prebid.NoSale Type", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":"wrongtype"}}`), }, expectedError: true, }, { description: "Injection Attack", - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`)}, }, expectedPolicy: Policy{ Consent: "1YYY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", @@ -165,8 +165,8 @@ func TestWrite(t *testing.T) { testCases := []struct { description string policy Policy - request *openrtb.BidRequest - expected *openrtb.BidRequest + request *openrtb2.BidRequest + expected *openrtb2.BidRequest expectedError bool }{ { @@ -178,31 +178,31 @@ func TestWrite(t *testing.T) { { description: "Success", policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, Ext: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), }, }, { description: "Error Regs.Ext - No Partial Update To Request", policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, - request: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, expectedError: true, - expected: &openrtb.BidRequest{ - Regs: &openrtb.Regs{Ext: json.RawMessage(`malformed}`)}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, }, }, { description: "Error Ext - No Partial Update To Request", policy: Policy{Consent: "anyConsent", NoSaleBidders: []string{"a", "b"}}, - request: &openrtb.BidRequest{ + request: &openrtb2.BidRequest{ Ext: json.RawMessage(`malformed}`), }, expectedError: true, - expected: &openrtb.BidRequest{ + expected: &openrtb2.BidRequest{ Ext: json.RawMessage(`malformed}`), }, }, @@ -219,22 +219,22 @@ func TestBuildRegs(t *testing.T) { testCases := []struct { description string consent string - regs *openrtb.Regs - expected *openrtb.Regs + regs *openrtb2.Regs + expected *openrtb2.Regs expectedError bool }{ { description: "Clear", consent: "", - regs: &openrtb.Regs{ + regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy":"ABC"}`), }, - expected: &openrtb.Regs{}, + expected: &openrtb2.Regs{}, }, { description: "Clear - Error", consent: "", - regs: &openrtb.Regs{ + regs: &openrtb2.Regs{ Ext: json.RawMessage(`malformed`), }, expectedError: true, @@ -243,14 +243,14 @@ func TestBuildRegs(t *testing.T) { description: "Write", consent: "anyConsent", regs: nil, - expected: &openrtb.Regs{ + expected: &openrtb2.Regs{ Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`), }, }, { description: "Write - Error", consent: "anyConsent", - regs: &openrtb.Regs{ + regs: &openrtb2.Regs{ Ext: json.RawMessage(`malformed`), }, expectedError: true, @@ -267,8 +267,8 @@ func TestBuildRegs(t *testing.T) { func TestBuildRegsClear(t *testing.T) { testCases := []struct { description string - regs *openrtb.Regs - expected *openrtb.Regs + regs *openrtb2.Regs + expected *openrtb2.Regs expectedError bool }{ { @@ -278,32 +278,32 @@ func TestBuildRegsClear(t *testing.T) { }, { description: "Nil Regs.Ext", - regs: &openrtb.Regs{Ext: nil}, - expected: &openrtb.Regs{Ext: nil}, + regs: &openrtb2.Regs{Ext: nil}, + expected: &openrtb2.Regs{Ext: nil}, }, { description: "Empty Regs.Ext", - regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, - expected: &openrtb.Regs{}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{}`)}, + expected: &openrtb2.Regs{}, }, { description: "Removes Regs.Ext Entirely", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, - expected: &openrtb.Regs{}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + expected: &openrtb2.Regs{}, }, { description: "Leaves Other Regs.Ext Values", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC", "other":"any"}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC", "other":"any"}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, }, { description: "Invalid Regs.Ext Type - Still Cleared", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, - expected: &openrtb.Regs{}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb2.Regs{}, }, { description: "Malformed Regs.Ext", - regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, expectedError: true, }, } @@ -319,50 +319,50 @@ func TestBuildRegsWrite(t *testing.T) { testCases := []struct { description string consent string - regs *openrtb.Regs - expected *openrtb.Regs + regs *openrtb2.Regs + expected *openrtb2.Regs expectedError bool }{ { description: "Nil Regs", consent: "anyConsent", regs: nil, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Nil Regs.Ext", consent: "anyConsent", - regs: &openrtb.Regs{Ext: nil}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: nil}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Empty Regs.Ext", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`{}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Overwrites Existing", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"ABC"}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Leaves Other Ext Values", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"other":"any","us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any","us_privacy":"anyConsent"}`)}, }, { description: "Invalid Regs.Ext Type - Still Overwrites", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, - expected: &openrtb.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, }, { description: "Malformed Regs.Ext", consent: "anyConsent", - regs: &openrtb.Regs{Ext: json.RawMessage(`malformed`)}, + regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, expectedError: true, }, } diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 47c1f0d03dd..ab2f64a691b 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -1,8 +1,6 @@ package privacy -import ( - "github.com/PubMatic-OpenWrap/openrtb" -) +import "github.com/mxmCherry/openrtb/v15/openrtb2" // Enforcement represents the privacy policies to enforce for an OpenRTB bid request. type Enforcement struct { @@ -19,11 +17,11 @@ func (e Enforcement) Any() bool { } // Apply cleans personally identifiable information from an OpenRTB bid request. -func (e Enforcement) Apply(bidRequest *openrtb.BidRequest) { +func (e Enforcement) Apply(bidRequest *openrtb2.BidRequest) { e.apply(bidRequest, NewScrubber()) } -func (e Enforcement) apply(bidRequest *openrtb.BidRequest, scrubber Scrubber) { +func (e Enforcement) apply(bidRequest *openrtb2.BidRequest, scrubber Scrubber) { if bidRequest != nil && e.Any() { bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy()) bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy()) diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index a5e41c83198..61899e4d60e 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -3,7 +3,7 @@ package privacy import ( "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -197,12 +197,12 @@ func TestApply(t *testing.T) { } for _, test := range testCases { - req := &openrtb.BidRequest{ - Device: &openrtb.Device{}, - User: &openrtb.User{}, + req := &openrtb2.BidRequest{ + Device: &openrtb2.Device{}, + User: &openrtb2.User{}, } - replacedDevice := &openrtb.Device{} - replacedUser := &openrtb.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() @@ -217,7 +217,7 @@ func TestApply(t *testing.T) { } func TestApplyNoneApplicable(t *testing.T) { - req := &openrtb.BidRequest{} + req := &openrtb2.BidRequest{} m := &mockScrubber{} @@ -248,12 +248,12 @@ type mockScrubber struct { mock.Mock } -func (m *mockScrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { +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).(*openrtb.Device) + return args.Get(0).(*openrtb2.Device) } -func (m *mockScrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User { +func (m *mockScrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { args := m.Called(user, strategy, geo) - return args.Get(0).(*openrtb.User) + return args.Get(0).(*openrtb2.User) } diff --git a/privacy/gdpr/consentwriter.go b/privacy/gdpr/consentwriter.go index f1cc2ce12f7..ca784b7a5c1 100644 --- a/privacy/gdpr/consentwriter.go +++ b/privacy/gdpr/consentwriter.go @@ -3,9 +3,8 @@ package gdpr import ( "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" ) // ConsentWriter implements the PolicyWriter interface for GDPR TCF. @@ -14,13 +13,13 @@ type ConsentWriter struct { } // Write mutates an OpenRTB bid request with the GDPR TCF consent. -func (c ConsentWriter) Write(req *openrtb.BidRequest) error { +func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if c.Consent == "" { return nil } if req.User == nil { - req.User = &openrtb.User{} + req.User = &openrtb2.User{} } if req.User.Ext == nil { diff --git a/privacy/gdpr/consentwriter_test.go b/privacy/gdpr/consentwriter_test.go index 65df8051d02..5753442fa01 100644 --- a/privacy/gdpr/consentwriter_test.go +++ b/privacy/gdpr/consentwriter_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -12,76 +12,76 @@ func TestConsentWriter(t *testing.T) { testCases := []struct { description string consent string - request *openrtb.BidRequest - expected *openrtb.BidRequest + request *openrtb2.BidRequest + expected *openrtb2.BidRequest expectedError bool }{ { description: "Empty", consent: "", - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{}, + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{}, }, { description: "Enabled With Nil Request User Object", consent: "anyConsent", - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, }, { description: "Enabled With Nil Request User Ext Object", consent: "anyConsent", - request: &openrtb.BidRequest{User: &openrtb.User{}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{}}, + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"anyConsent"}`)}}, }, { description: "Enabled With Existing Request User Ext Object - Doesn't Overwrite", consent: "anyConsent", - request: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"existing":"any"}`)}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, }, { description: "Enabled With Existing Request User Ext Object - Overwrites", consent: "anyConsent", - request: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"existing":"any","consent":"toBeOverwritten"}`)}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"anyConsent","existing":"any"}`)}}, }, { description: "Enabled With Existing Malformed Request User Ext Object", consent: "anyConsent", - request: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`malformed`)}}, expectedError: true, }, { description: "Injection Attack With Nil Request User Object", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", - request: &openrtb.BidRequest{}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), }}, }, { description: "Injection Attack With Nil Request User Ext Object", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", - request: &openrtb.BidRequest{User: &openrtb.User{}}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{}}, + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\""}`), }}, }, { description: "Injection Attack With Existing Request User Ext Object", consent: "BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"", - request: &openrtb.BidRequest{User: &openrtb.User{ + request: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"existing":"any"}`), }}, - expected: &openrtb.BidRequest{User: &openrtb.User{ + expected: &openrtb2.BidRequest{User: &openrtb2.User{ Ext: json.RawMessage(`{"consent":"BONV8oqONXwgmADACHENAO7pqzAAppY\"},\"oops\":\"malicious\",\"p\":{\"p\":\"","existing":"any"}`), }}, }, diff --git a/privacy/lmt/ios.go b/privacy/lmt/ios.go new file mode 100644 index 00000000000..55e1764c8c2 --- /dev/null +++ b/privacy/lmt/ios.go @@ -0,0 +1,67 @@ +package lmt + +import ( + "strings" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/iosutil" +) + +var ( + int8Zero int8 = 0 + int8One int8 = 1 +) + +// ModifyForIOS modifies the request's LMT flag based on iOS version and identity. +func ModifyForIOS(req *openrtb2.BidRequest) { + modifiers := map[iosutil.VersionClassification]modifier{ + iosutil.Version140: modifyForIOS14X, + iosutil.Version141: modifyForIOS14X, + iosutil.Version142OrGreater: modifyForIOS142OrGreater, + } + modifyForIOS(req, modifiers) +} + +func modifyForIOS(req *openrtb2.BidRequest, modifiers map[iosutil.VersionClassification]modifier) { + if !isRequestForIOS(req) { + return + } + + versionClassification := iosutil.DetectVersionClassification(req.Device.OSV) + if modifier, ok := modifiers[versionClassification]; ok { + modifier(req) + } +} + +func isRequestForIOS(req *openrtb2.BidRequest) bool { + return req != nil && req.App != nil && req.Device != nil && strings.EqualFold(req.Device.OS, "ios") +} + +type modifier func(req *openrtb2.BidRequest) + +func modifyForIOS14X(req *openrtb2.BidRequest) { + if req.Device.IFA == "" || req.Device.IFA == "00000000-0000-0000-0000-000000000000" { + req.Device.Lmt = &int8One + } else { + req.Device.Lmt = &int8Zero + } +} + +func modifyForIOS142OrGreater(req *openrtb2.BidRequest) { + atts, err := openrtb_ext.ParseDeviceExtATTS(req.Device.Ext) + if err != nil || atts == nil { + return + } + + switch *atts { + case openrtb_ext.IOSAppTrackingStatusNotDetermined: + req.Device.Lmt = &int8Zero + case openrtb_ext.IOSAppTrackingStatusRestricted: + req.Device.Lmt = &int8One + case openrtb_ext.IOSAppTrackingStatusDenied: + req.Device.Lmt = &int8One + case openrtb_ext.IOSAppTrackingStatusAuthorized: + req.Device.Lmt = &int8Zero + } +} diff --git a/privacy/lmt/ios_test.go b/privacy/lmt/ios_test.go new file mode 100644 index 00000000000..2caec1be64c --- /dev/null +++ b/privacy/lmt/ios_test.go @@ -0,0 +1,276 @@ +package lmt + +import ( + "encoding/json" + "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/util/iosutil" + "github.com/stretchr/testify/assert" +) + +// TestModifyForIOS is a simple spot check end-to-end test for the integration of all functional components. +func TestModifyForIOS(t *testing.T) { + testCases := []struct { + description string + givenRequest *openrtb2.BidRequest + expectedLMT *int8 + }{ + { + description: "13.0", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "13.0", IFA: "", Lmt: nil}, + }, + expectedLMT: nil, + }, + { + description: "14.0", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "14.0", IFA: "", Lmt: nil}, + }, + expectedLMT: openrtb2.Int8Ptr(1), + }, + } + + for _, test := range testCases { + ModifyForIOS(test.givenRequest) + assert.Equal(t, test.expectedLMT, test.givenRequest.Device.Lmt, test.description) + } +} + +func TestModifyForIOSHelper(t *testing.T) { + testCases := []struct { + description string + givenRequest *openrtb2.BidRequest + expectedModifier140Called bool + expectedModifier142Called bool + }{ + { + description: "Valid 14.0", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "14.0"}, + }, + expectedModifier140Called: true, + expectedModifier142Called: false, + }, + { + description: "Valid 14.2 Or Greater", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "14.2"}, + }, + expectedModifier140Called: false, + expectedModifier142Called: true, + }, + { + description: "Invalid Version", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS", OSV: "invalid"}, + }, + expectedModifier140Called: false, + expectedModifier142Called: false, + }, + { + description: "Invalid OS", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "invalid", OSV: "14.0"}, + }, + expectedModifier140Called: false, + expectedModifier142Called: false, + }, + { + description: "Invalid Nil Device", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: nil, + }, + expectedModifier140Called: false, + expectedModifier142Called: false, + }, + } + + for _, test := range testCases { + modifierIOS140Called := false + modifierIOS140 := func(req *openrtb2.BidRequest) { modifierIOS140Called = true } + + modifierIOS142Called := false + modifierIOS142 := func(req *openrtb2.BidRequest) { modifierIOS142Called = true } + + modifiers := map[iosutil.VersionClassification]modifier{ + iosutil.Version140: modifierIOS140, + iosutil.Version142OrGreater: modifierIOS142, + } + + modifyForIOS(test.givenRequest, modifiers) + + assert.Equal(t, test.expectedModifier140Called, modifierIOS140Called, test.description) + assert.Equal(t, test.expectedModifier142Called, modifierIOS142Called, test.description) + } +} + +func TestIsRequestForIOS(t *testing.T) { + testCases := []struct { + description string + givenRequest *openrtb2.BidRequest + expected bool + }{ + { + description: "Valid", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "iOS"}, + }, + expected: true, + }, + { + description: "Valid - OS Case Insensitive", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "IOS"}, + }, + expected: true, + }, + { + description: "Invalid - Nil Request", + givenRequest: nil, + expected: false, + }, + { + description: "Invalid - Nil App", + givenRequest: &openrtb2.BidRequest{ + App: nil, + Device: &openrtb2.Device{OS: "iOS"}, + }, + expected: false, + }, + { + description: "Invalid - Nil Device", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: nil, + }, + expected: false, + }, + { + description: "Invalid - Empty OS", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: ""}, + }, + expected: false, + }, + { + description: "Invalid - Wrong OS", + givenRequest: &openrtb2.BidRequest{ + App: &openrtb2.App{}, + Device: &openrtb2.Device{OS: "Android"}, + }, + expected: false, + }, + } + + for _, test := range testCases { + result := isRequestForIOS(test.givenRequest) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestModifyForIOS14X(t *testing.T) { + testCases := []struct { + description string + givenDevice openrtb2.Device + expectedLMT *int8 + }{ + { + description: "IFA Empty", + givenDevice: openrtb2.Device{IFA: "", Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "IFA Zero UUID", + givenDevice: openrtb2.Device{IFA: "00000000-0000-0000-0000-000000000000", Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "IFA Populated", + givenDevice: openrtb2.Device{IFA: "any-real-value", Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(0), + }, + { + description: "Overwrites Existing", + givenDevice: openrtb2.Device{IFA: "", Lmt: openrtb2.Int8Ptr(0)}, + expectedLMT: openrtb2.Int8Ptr(1), + }, + } + + for _, test := range testCases { + request := &openrtb2.BidRequest{Device: &test.givenDevice} + modifyForIOS14X(request) + assert.Equal(t, test.expectedLMT, request.Device.Lmt, test.description) + } +} + +func TestModifyForIOS142OrGreater(t *testing.T) { + testCases := []struct { + description string + givenDevice openrtb2.Device + expectedLMT *int8 + }{ + { + description: "Not Determined", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":0}`), Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(0), + }, + { + description: "Restricted", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":1}`), Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "Denied", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":2}`), Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "Authorized", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":3}`), Lmt: nil}, + expectedLMT: openrtb2.Int8Ptr(0), + }, + { + description: "Overwrites Existing", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":3}`), Lmt: openrtb2.Int8Ptr(1)}, + expectedLMT: openrtb2.Int8Ptr(0), + }, + { + description: "Invalid Value - Unknown", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":4}`), Lmt: nil}, + expectedLMT: nil, + }, + { + description: "Invalid Value - Unknown - Does Not Overwrite Existing", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":4}`), Lmt: openrtb2.Int8Ptr(1)}, + expectedLMT: openrtb2.Int8Ptr(1), + }, + { + description: "Invalid Value - Missing", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{}`), Lmt: nil}, + expectedLMT: nil, + }, + { + description: "Invalid Value - Wrong Type", + givenDevice: openrtb2.Device{Ext: json.RawMessage(`{"atts":"wrong type"}`), Lmt: nil}, + expectedLMT: nil, + }, + } + + for _, test := range testCases { + request := &openrtb2.BidRequest{Device: &test.givenDevice} + modifyForIOS142OrGreater(request) + assert.Equal(t, test.expectedLMT, request.Device.Lmt, test.description) + } +} diff --git a/privacy/lmt/policy.go b/privacy/lmt/policy.go index 5f23b9a3eef..0f2829254a2 100644 --- a/privacy/lmt/policy.go +++ b/privacy/lmt/policy.go @@ -1,8 +1,6 @@ package lmt -import ( - "github.com/PubMatic-OpenWrap/openrtb" -) +import "github.com/mxmCherry/openrtb/v15/openrtb2" const ( trackingUnrestricted = 0 @@ -16,7 +14,7 @@ type Policy struct { } // ReadFromRequest extracts the LMT (Limit Ad Tracking) policy from an OpenRTB bid request. -func ReadFromRequest(req *openrtb.BidRequest) (policy Policy) { +func ReadFromRequest(req *openrtb2.BidRequest) (policy Policy) { if req != nil && req.Device != nil && req.Device.Lmt != nil { policy.Signal = int(*req.Device.Lmt) policy.SignalProvided = true diff --git a/privacy/lmt/policy_test.go b/privacy/lmt/policy_test.go index 9d0e3b6aa9a..f475d2fb702 100644 --- a/privacy/lmt/policy_test.go +++ b/privacy/lmt/policy_test.go @@ -3,7 +3,7 @@ package lmt import ( "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) @@ -12,7 +12,7 @@ func TestReadFromRequest(t *testing.T) { testCases := []struct { description string - request *openrtb.BidRequest + request *openrtb2.BidRequest expectedPolicy Policy }{ { @@ -25,7 +25,7 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Nil Device", - request: &openrtb.BidRequest{ + request: &openrtb2.BidRequest{ Device: nil, }, expectedPolicy: Policy{ @@ -35,8 +35,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Nil Device.Lmt", - request: &openrtb.BidRequest{ - Device: &openrtb.Device{ + request: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ Lmt: nil, }, }, @@ -47,8 +47,8 @@ func TestReadFromRequest(t *testing.T) { }, { description: "Enabled", - request: &openrtb.BidRequest{ - Device: &openrtb.Device{ + request: &openrtb2.BidRequest{ + Device: &openrtb2.Device{ Lmt: &one, }, }, diff --git a/privacy/policies.go b/privacy/policies.go index a1c3fca49be..bc844a4e463 100644 --- a/privacy/policies.go +++ b/privacy/policies.go @@ -1,9 +1,9 @@ package privacy import ( - "github.com/PubMatic-OpenWrap/prebid-server/privacy/ccpa" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/gdpr" - "github.com/PubMatic-OpenWrap/prebid-server/privacy/lmt" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/prebid/prebid-server/privacy/lmt" ) // Policies represents the privacy regulations for an OpenRTB bid request. diff --git a/privacy/scrubber.go b/privacy/scrubber.go index aea5c9008f4..edaa5bb07c6 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strings" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) // ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address. @@ -73,8 +73,8 @@ const ( // Scrubber removes PII from parts of an OpenRTB request. type Scrubber interface { - ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device - ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User + 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 scrubber struct{} @@ -84,7 +84,7 @@ func NewScrubber() Scrubber { return scrubber{} } -func (scrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device { +func (scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { if device == nil { return nil } @@ -124,7 +124,7 @@ func (scrubber) ScrubDevice(device *openrtb.Device, id ScrubStrategyDeviceID, ip return &deviceCopy } -func (scrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User { +func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User { if user == nil { return nil } @@ -194,15 +194,15 @@ func removeLowestIPV6Segment(ip string) string { return ip[0:i] } -func scrubGeoFull(geo *openrtb.Geo) *openrtb.Geo { +func scrubGeoFull(geo *openrtb2.Geo) *openrtb2.Geo { if geo == nil { return nil } - return &openrtb.Geo{} + return &openrtb2.Geo{} } -func scrubGeoPrecision(geo *openrtb.Geo) *openrtb.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 4d989e1c5a1..9207315f593 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -4,12 +4,12 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) func TestScrubDevice(t *testing.T) { - device := &openrtb.Device{ + device := &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -19,7 +19,7 @@ func TestScrubDevice(t *testing.T) { IFA: "anyIFA", IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -30,7 +30,7 @@ func TestScrubDevice(t *testing.T) { testCases := []struct { description string - expected *openrtb.Device + expected *openrtb2.Device id ScrubStrategyDeviceID ipv4 ScrubStrategyIPV4 ipv6 ScrubStrategyIPV6 @@ -46,7 +46,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "All Strageties - Strictest", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "", DIDSHA1: "", DPIDMD5: "", @@ -56,7 +56,7 @@ func TestScrubDevice(t *testing.T) { IFA: "", IP: "1.2.3.0", IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, id: ScrubStrategyDeviceIDAll, ipv4: ScrubStrategyIPV4Lowest8, @@ -65,7 +65,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - ID - All", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "", DIDSHA1: "", DPIDMD5: "", @@ -84,7 +84,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - IPv4 - Lowest 8", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -103,7 +103,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - IPv6 - Lowest 16", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -122,7 +122,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - IPv6 - Lowest 32", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -141,7 +141,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - Geo - Reduced Precision", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -151,7 +151,7 @@ func TestScrubDevice(t *testing.T) { IFA: "anyIFA", IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -166,7 +166,7 @@ func TestScrubDevice(t *testing.T) { }, { description: "Isolated - Geo - Full", - expected: &openrtb.Device{ + expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", DPIDMD5: "anyDPIDMD5", @@ -176,7 +176,7 @@ func TestScrubDevice(t *testing.T) { IFA: "anyIFA", IP: "1.2.3.4", IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, id: ScrubStrategyDeviceIDNone, ipv4: ScrubStrategyIPV4None, @@ -197,13 +197,13 @@ func TestScrubDeviceNil(t *testing.T) { } func TestScrubUser(t *testing.T) { - user := &openrtb.User{ + user := &openrtb2.User{ ID: "anyID", BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -214,32 +214,32 @@ func TestScrubUser(t *testing.T) { testCases := []struct { description string - expected *openrtb.User + expected *openrtb2.User scrubUser ScrubStrategyUser scrubGeo ScrubStrategyGeo }{ { description: "User ID And Demographic & Geo Full", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 0, Gender: "", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserIDAndDemographic, scrubGeo: ScrubStrategyGeoFull, }, { description: "User ID And Demographic & Geo Reduced", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 0, Gender: "", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -252,13 +252,13 @@ func TestScrubUser(t *testing.T) { }, { description: "User ID And Demographic & Geo None", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 0, Gender: "", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -271,26 +271,26 @@ func TestScrubUser(t *testing.T) { }, { description: "User ID & Geo Full", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserID, scrubGeo: ScrubStrategyGeoFull, }, { description: "User ID & Geo Reduced", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -303,13 +303,13 @@ func TestScrubUser(t *testing.T) { }, { description: "User ID & Geo None", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "", BuyerUID: "", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -322,26 +322,26 @@ func TestScrubUser(t *testing.T) { }, { description: "User None & Geo Full", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "anyID", BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - Geo: &openrtb.Geo{}, + Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserNone, scrubGeo: ScrubStrategyGeoFull, }, { description: "User None & Geo Reduced", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "anyID", BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -354,13 +354,13 @@ func TestScrubUser(t *testing.T) { }, { description: "User None & Geo None", - expected: &openrtb.User{ + expected: &openrtb2.User{ ID: "anyID", BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - Geo: &openrtb.Geo{ + Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", @@ -503,14 +503,14 @@ func TestScrubIPV6Lowest32Bits(t *testing.T) { } func TestScrubGeoFull(t *testing.T) { - geo := &openrtb.Geo{ + geo := &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", City: "some city", ZIP: "some zip", } - geoExpected := &openrtb.Geo{ + geoExpected := &openrtb2.Geo{ Lat: 0, Lon: 0, Metro: "", @@ -529,14 +529,14 @@ func TestScrubGeoFullWhenNil(t *testing.T) { } func TestScrubGeoPrecision(t *testing.T) { - geo := &openrtb.Geo{ + geo := &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, Metro: "some metro", City: "some city", ZIP: "some zip", } - geoExpected := &openrtb.Geo{ + geoExpected := &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", diff --git a/privacy/writer.go b/privacy/writer.go index a68a158ced8..0f04a52f292 100644 --- a/privacy/writer.go +++ b/privacy/writer.go @@ -1,18 +1,16 @@ package privacy -import ( - "github.com/PubMatic-OpenWrap/openrtb" -) +import "github.com/mxmCherry/openrtb/v15/openrtb2" // PolicyWriter mutates an OpenRTB bid request with a policy's regulatory information. type PolicyWriter interface { - Write(req *openrtb.BidRequest) error + Write(req *openrtb2.BidRequest) error } // NilPolicyWriter implements the PolicyWriter interface but performs no action. type NilPolicyWriter struct{} // Write is hardcoded to perform no action with the OpenRTB bid request. -func (NilPolicyWriter) Write(req *openrtb.BidRequest) error { +func (NilPolicyWriter) Write(req *openrtb2.BidRequest) error { return nil } diff --git a/privacy/writer_test.go b/privacy/writer_test.go index 754e6ffe2c9..f5b02387124 100644 --- a/privacy/writer_test.go +++ b/privacy/writer_test.go @@ -4,16 +4,16 @@ import ( "encoding/json" "testing" - "github.com/PubMatic-OpenWrap/openrtb" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/stretchr/testify/assert" ) func TestNilWriter(t *testing.T) { - request := &openrtb.BidRequest{ + request := &openrtb2.BidRequest{ ID: "anyID", Ext: json.RawMessage(`{"anyJson":"anyValue"}`), } - expectedRequest := &openrtb.BidRequest{ + expectedRequest := &openrtb2.BidRequest{ ID: "anyID", Ext: json.RawMessage(`{"anyJson":"anyValue"}`), } diff --git a/router/admin.go b/router/admin.go index ba4f25f585b..b66bf55a5d6 100644 --- a/router/admin.go +++ b/router/admin.go @@ -5,8 +5,8 @@ import ( "net/http/pprof" "time" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints" + "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/endpoints" ) func Admin(revision string, 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 9c51b9b8570..39a4341f995 100644 --- a/router/aspects/request_timeout_handler.go +++ b/router/aspects/request_timeout_handler.go @@ -5,9 +5,9 @@ import ( "strconv" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/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 3d6fa34e5bd..26e546dcd40 100644 --- a/router/aspects/request_timeout_handler_test.go +++ b/router/aspects/request_timeout_handler_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" "github.com/stretchr/testify/assert" ) diff --git a/router/router.go b/router/router.go index eb69f5fafd1..e79e9782f89 100644 --- a/router/router.go +++ b/router/router.go @@ -6,47 +6,47 @@ import ( "database/sql" "encoding/json" "fmt" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/usersync" + "github.com/prometheus/client_golang/prometheus" "io/ioutil" "net/http" "path/filepath" "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/analytics" - "github.com/PubMatic-OpenWrap/prebid-server/currency" - "github.com/PubMatic-OpenWrap/prebid-server/errortypes" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" - "github.com/prometheus/client_golang/prometheus" - - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - - "github.com/PubMatic-OpenWrap/prebid-server/adapters" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adform" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/appnexus" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/conversant" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/ix" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lifestreet" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/pubmatic" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/pulsepoint" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/rubicon" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/sovrn" - analyticsConf "github.com/PubMatic-OpenWrap/prebid-server/analytics/config" - "github.com/PubMatic-OpenWrap/prebid-server/cache" - "github.com/PubMatic-OpenWrap/prebid-server/cache/dummycache" - "github.com/PubMatic-OpenWrap/prebid-server/cache/filecache" - "github.com/PubMatic-OpenWrap/prebid-server/cache/postgrescache" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints" - "github.com/PubMatic-OpenWrap/prebid-server/endpoints/openrtb2" - "github.com/PubMatic-OpenWrap/prebid-server/exchange" - "github.com/PubMatic-OpenWrap/prebid-server/gdpr" - metricsConf "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - pbc "github.com/PubMatic-OpenWrap/prebid-server/prebid_cache_client" - "github.com/PubMatic-OpenWrap/prebid-server/server/ssl" - storedRequestsConf "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/config" - "github.com/PubMatic-OpenWrap/prebid-server/usersync/usersyncers" + "github.com/prebid/prebid-server/currency" + "github.com/prebid/prebid-server/errortypes" + + "github.com/prebid/prebid-server/metrics" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/adform" + "github.com/prebid/prebid-server/adapters/appnexus" + "github.com/prebid/prebid-server/adapters/conversant" + "github.com/prebid/prebid-server/adapters/ix" + "github.com/prebid/prebid-server/adapters/lifestreet" + "github.com/prebid/prebid-server/adapters/pubmatic" + "github.com/prebid/prebid-server/adapters/pulsepoint" + "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sovrn" + analyticsConf "github.com/prebid/prebid-server/analytics/config" + "github.com/prebid/prebid-server/cache" + "github.com/prebid/prebid-server/cache/dummycache" + "github.com/prebid/prebid-server/cache/filecache" + "github.com/prebid/prebid-server/cache/postgrescache" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/endpoints" + "github.com/prebid/prebid-server/endpoints/openrtb2" + "github.com/prebid/prebid-server/exchange" + "github.com/prebid/prebid-server/gdpr" + metricsConf "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/server/ssl" + storedRequestsConf "github.com/prebid/prebid-server/stored_requests/config" + "github.com/prebid/prebid-server/usersync/usersyncers" "github.com/golang/glog" "github.com/julienschmidt/httprouter" @@ -192,7 +192,6 @@ type Router struct { } func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *Router, err error) { - const schemaDirectory = "/home/http/GO_SERVER/dmhbserver/static/bidder-params" const infoDirectory = "/home/http/GO_SERVER/dmhbserver/static/bidder-info" @@ -252,84 +251,91 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R } p, _ := filepath.Abs(infoDirectory) - bidderInfos := adapters.ParseBidderInfos(cfg.Adapters, p, openrtb_ext.CoreBidderNames()) + bidderInfos, err := config.LoadBidderInfoFromDisk(p, cfg.Adapters, openrtb_ext.BuildBidderStringSlice()) + if err != nil { + glog.Fatal(err) + } g_activeBidders = exchange.GetActiveBidders(bidderInfos) g_disabledBidders = exchange.GetDisabledBiddersErrorMessages(bidderInfos) - _, g_defReqJSON = readDefaultRequest(cfg.DefReqConfig) + defaultAliases, defReqJSON := readDefaultRequest(cfg.DefReqConfig) + if err := validateDefaultAliases(defaultAliases); err != nil { + glog.Fatal(err) + } + + g_defReqJSON = defReqJSON g_syncers = usersyncers.NewSyncerMap(cfg) - g_gdprPerms = gdpr.NewPermissions(context.Background(), cfg.GDPR, adapters.GDPRAwareSyncerIDs(g_syncers), generalHttpClient) + gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() + g_gdprPerms = gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient) exchanges = newExchangeMap(cfg) g_cacheClient = pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, g_metrics) adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, r.MetricsEngine) if len(adaptersErrs) > 0 { - errs := errortypes.NewAggregateErrors("Failed to initialize adapters", adaptersErrs) + errs := errortypes.NewAggregateError("Failed to initialize adapters", adaptersErrs) glog.Fatalf("%v", errs) } g_ex = exchange.NewExchange(adapters, g_cacheClient, cfg, g_metrics, bidderInfos, g_gdprPerms, rateConvertor, g_categoriesFetcher) - /* - openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) - if err != nil { - glog.Fatalf("Failed to create the openrtb endpoint handler. %v", err) - } + /*openrtbEndpoint, err := openrtb2.NewEndpoint(theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) + if err != nil { + glog.Fatalf("Failed to create the openrtb2 endpoint handler. %v", err) + } - ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) + ampEndpoint, err := openrtb2.NewAmpEndpoint(theExchange, paramsValidator, ampFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders) + if err != nil { + glog.Fatalf("Failed to create the amp endpoint handler. %v", err) + } - if err != nil { - glog.Fatalf("Failed to create the amp endpoint handler. %v", err) - } + videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, cacheClient) + if err != nil { + glog.Fatalf("Failed to create the video endpoint handler. %v", err) + } - videoEndpoint, err := openrtb2.NewVideoEndpoint(theExchange, paramsValidator, fetcher, videoFetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, cacheClient) - if err != nil { - glog.Fatalf("Failed to create the video endpoint handler. %v", err) - } + requestTimeoutHeaders := config.RequestTimeoutHeaders{} + if cfg.RequestTimeoutHeaders != requestTimeoutHeaders { + videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, metrics.ReqTypeVideo) + } - requestTimeoutHeaders := config.RequestTimeoutHeaders{} - if cfg.RequestTimeoutHeaders != requestTimeoutHeaders { - videoEndpoint = aspects.QueuedRequestTimeout(videoEndpoint, cfg.RequestTimeoutHeaders, r.MetricsEngine, metrics.ReqTypeVideo) - } + r.POST("/auction", endpoints.Auction(cfg, syncers, gdprPerms, r.MetricsEngine, dataCache, exchanges)) + r.POST("/openrtb2/auction", openrtbEndpoint) + r.POST("/openrtb2/video", videoEndpoint) + r.GET("/openrtb2/amp", ampEndpoint) + r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(bidderInfos, defaultAliases)) + r.GET("/info/bidders/:bidderName", infoEndpoints.NewBiddersDetailEndpoint(bidderInfos, cfg.Adapters, defaultAliases)) + r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases)) + r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncers, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, activeBidders)) + r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse)) + r.GET("/", serveIndex) + r.ServeFiles("/static/*filepath", http.Dir("static")) + + // vtrack endpoint + if cfg.VTrack.Enabled { + vtrackEndpoint := events.NewVTrackEndpoint(cfg, accounts, cacheClient, bidderInfos) + r.POST("/vtrack", vtrackEndpoint) + } - r.POST("/auction", endpoints.Auction(cfg, syncers, gdprPerms, r.MetricsEngine, dataCache, exchanges)) - r.POST("/openrtb2/auction", openrtbEndpoint) - r.POST("/openrtb2/video", videoEndpoint) - r.GET("/openrtb2/amp", ampEndpoint) - r.GET("/info/bidders", infoEndpoints.NewBiddersEndpoint(defaultAliases)) - r.GET("/info/bidders/:bidderName", infoEndpoints.NewBidderDetailsEndpoint(bidderInfos, defaultAliases)) - r.GET("/bidders/params", NewJsonDirectoryServer(schemaDirectory, paramsValidator, defaultAliases)) - r.POST("/cookie_sync", endpoints.NewCookieSyncEndpoint(syncers, cfg, gdprPerms, r.MetricsEngine, pbsAnalytics, activeBidders)) - r.GET("/status", endpoints.NewStatusEndpoint(cfg.StatusResponse)) - r.GET("/", serveIndex) - r.ServeFiles("/static/*filepath", http.Dir("static")) - - // vtrack endpoint - if cfg.VTrack.Enabled { - vtrackEndpoint := events.NewVTrackEndpoint(cfg, accounts, cacheClient, bidderInfos) - r.POST("/vtrack", vtrackEndpoint) - } + // event endpoint + eventEndpoint := events.NewEventEndpoint(cfg, accounts, pbsAnalytics) + r.GET("/event", eventEndpoint) - // event endpoint - eventEndpoint := events.NewEventEndpoint(cfg, accounts, pbsAnalytics) - r.GET("/event", eventEndpoint) + userSyncDeps := &pbs.UserSyncDeps{ + HostCookieConfig: &(cfg.HostCookie), + ExternalUrl: cfg.ExternalURL, + RecaptchaSecret: cfg.RecaptchaSecret, + MetricsEngine: r.MetricsEngine, + PBSAnalytics: pbsAnalytics, + } - userSyncDeps := &pbs.UserSyncDeps{ - HostCookieConfig: &(cfg.HostCookie), - ExternalUrl: cfg.ExternalURL, - RecaptchaSecret: cfg.RecaptchaSecret, - MetricsEngine: r.MetricsEngine, - PBSAnalytics: pbsAnalytics, - } + r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncers, gdprPerms, pbsAnalytics, r.MetricsEngine)) + r.GET("/getuids", endpoints.NewGetUIDsEndpoint(cfg.HostCookie)) + r.POST("/optout", userSyncDeps.OptOut) + r.GET("/optout", userSyncDeps.OptOut)*/ - r.GET("/setuid", endpoints.NewSetUIDEndpoint(cfg.HostCookie, syncers, gdprPerms, pbsAnalytics, r.MetricsEngine)) - r.GET("/getuids", endpoints.NewGetUIDsEndpoint(cfg.HostCookie)) - r.POST("/optout", userSyncDeps.OptOut) - r.GET("/optout", userSyncDeps.OptOut) - */ return r, nil } @@ -453,3 +459,19 @@ func GetPrometheusRegistry() *prometheus.Registry { return mEngine.PrometheusMetrics.Registry } + +func validateDefaultAliases(aliases map[string]string) error { + var errs []error + + for alias := range aliases { + if openrtb_ext.IsBidderNameReserved(alias) { + errs = append(errs, fmt.Errorf("alias %s is a reserved bidder name and cannot be used", alias)) + } + } + + if len(errs) > 0 { + return errortypes.NewAggregateError("default request alias errors", errs) + } + + return nil +} diff --git a/router/router_test.go b/router/router_test.go index 09b07a0a7ce..65fe299e309 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -8,8 +8,8 @@ import ( "os" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -180,5 +180,43 @@ func TestLoadDefaultAliasesNoInfo(t *testing.T) { assert.JSONEq(t, string(expectedJSON), string(aliasJSON)) assert.Equal(t, expectedAliases, defAliases) +} + +func TestValidateDefaultAliases(t *testing.T) { + var testCases = []struct { + description string + givenAliases map[string]string + expectedError string + }{ + { + description: "None", + givenAliases: map[string]string{}, + expectedError: "", + }, + { + description: "Valid", + givenAliases: map[string]string{"aAlias": "a"}, + expectedError: "", + }, + { + description: "Invalid", + givenAliases: map[string]string{"all": "a"}, + expectedError: "default request alias errors (1 error):\n 1: alias all is a reserved bidder name and cannot be used\n", + }, + { + description: "Mixed", + givenAliases: map[string]string{"aAlias": "a", "all": "a"}, + expectedError: "default request alias errors (1 error):\n 1: alias all is a reserved bidder name and cannot be used\n", + }, + } + + for _, test := range testCases { + err := validateDefaultAliases(test.givenAliases) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } } diff --git a/server/listener.go b/server/listener.go index f172ad7d961..c1f57723da3 100644 --- a/server/listener.go +++ b/server/listener.go @@ -5,8 +5,8 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" "github.com/golang/glog" + "github.com/prebid/prebid-server/metrics" ) // monitorableListener tracks any opened connections in the metrics. diff --git a/server/listener_test.go b/server/listener_test.go index 58f3d42d95a..f91dbddbc54 100644 --- a/server/listener_test.go +++ b/server/listener_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" gometrics "github.com/rcrowley/go-metrics" ) diff --git a/server/prometheus.go b/server/prometheus.go index 8001234faa2..4b9f7037d0a 100644 --- a/server/prometheus.go +++ b/server/prometheus.go @@ -7,9 +7,9 @@ import ( "github.com/golang/glog" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/PubMatic-OpenWrap/prebid-server/config" - metricsconfig "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" - prometheusMetrics "github.com/PubMatic-OpenWrap/prebid-server/metrics/prometheus" + "github.com/prebid/prebid-server/config" + metricsconfig "github.com/prebid/prebid-server/metrics/config" + prometheusMetrics "github.com/prebid/prebid-server/metrics/prometheus" ) func newPrometheusServer(cfg *config.Configuration, metrics *metricsconfig.DetailedMetricsEngine) *http.Server { diff --git a/server/server.go b/server/server.go index 0b1aa76b63b..46b7e5ae610 100644 --- a/server/server.go +++ b/server/server.go @@ -12,10 +12,10 @@ import ( "time" "github.com/NYTimes/gziphandler" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - metricsconfig "github.com/PubMatic-OpenWrap/prebid-server/metrics/config" "github.com/golang/glog" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" + metricsconfig "github.com/prebid/prebid-server/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 3d6d5684e96..e7ef593a4b5 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -5,7 +5,7 @@ import ( "os" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" + "github.com/prebid/prebid-server/config" ) func TestNewAdminServer(t *testing.T) { diff --git a/static/bidder-info/33across.yaml b/static/bidder-info/33across.yaml index e3eea765556..044cfe140b2 100644 --- a/static/bidder-info/33across.yaml +++ b/static/bidder-info/33across.yaml @@ -1,5 +1,6 @@ maintainer: email: "headerbidding@33across.com" +gvlVendorID: 58 capabilities: site: mediaTypes: diff --git a/static/bidder-info/acuityads.yaml b/static/bidder-info/acuityads.yaml index 9da1446d918..9539e36b91e 100644 --- a/static/bidder-info/acuityads.yaml +++ b/static/bidder-info/acuityads.yaml @@ -1,5 +1,6 @@ maintainer: email: "integrations@acuityads.com" +gvlVendorID: 231 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adform.yaml b/static/bidder-info/adform.yaml index 4dce10b9af8..461714ac44d 100644 --- a/static/bidder-info/adform.yaml +++ b/static/bidder-info/adform.yaml @@ -1,5 +1,6 @@ maintainer: email: "scope.sspp@adform.com" +gvlVendorID: 50 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adkernel.yaml b/static/bidder-info/adkernel.yaml index 26f8c322ddc..a78b3cde498 100644 --- a/static/bidder-info/adkernel.yaml +++ b/static/bidder-info/adkernel.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid-dev@adkernel.com" +gvlVendorID: 14 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adkernelAdn.yaml b/static/bidder-info/adkernelAdn.yaml index fd47367db7c..1f54f8e5a8f 100644 --- a/static/bidder-info/adkernelAdn.yaml +++ b/static/bidder-info/adkernelAdn.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid-dev@adkernel.com" +gvlVendorID: 14 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adman.yaml b/static/bidder-info/adman.yaml index 932ef2e4242..2db7c07584c 100644 --- a/static/bidder-info/adman.yaml +++ b/static/bidder-info/adman.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@admanmedia.com" +gvlVendorID: 149 capabilities: app: mediaTypes: diff --git a/static/bidder-info/admixer.yaml b/static/bidder-info/admixer.yaml index 64ad2024058..9faf0eb3a3e 100644 --- a/static/bidder-info/admixer.yaml +++ b/static/bidder-info/admixer.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@admixer.net" +gvlVendorID: 511 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml index 2f31fe92eaf..b3a23718a5a 100644 --- a/static/bidder-info/adocean.yaml +++ b/static/bidder-info/adocean.yaml @@ -1,5 +1,6 @@ maintainer: email: "aoteam@gemius.com" +gvlVendorID: 328 capabilities: site: mediaTypes: diff --git a/static/bidder-info/adpone.yaml b/static/bidder-info/adpone.yaml index 969983a12f6..7c5b473770b 100644 --- a/static/bidder-info/adpone.yaml +++ b/static/bidder-info/adpone.yaml @@ -1,5 +1,6 @@ maintainer: email: "tech@adpone.com" +gvlVendorID: 799 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adtelligent.yaml b/static/bidder-info/adtelligent.yaml index 7a20d52b266..30f55ea912e 100644 --- a/static/bidder-info/adtelligent.yaml +++ b/static/bidder-info/adtelligent.yaml @@ -1,5 +1,6 @@ maintainer: email: "hb@adtelligent.com" +gvlVendorID: 410 capabilities: app: mediaTypes: diff --git a/static/bidder-info/adxcg.yaml b/static/bidder-info/adxcg.yaml new file mode 100644 index 00000000000..e5535ae7258 --- /dev/null +++ b/static/bidder-info/adxcg.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "info@adxcg.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/adyoulike.yaml b/static/bidder-info/adyoulike.yaml new file mode 100644 index 00000000000..d9b23d6c1a1 --- /dev/null +++ b/static/bidder-info/adyoulike.yaml @@ -0,0 +1,10 @@ +maintainer: + email: "core@adyoulike.com" +gvlVendorID: 259 +modifyingVastXmlAllowed: true +capabilities: + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/amx.yaml b/static/bidder-info/amx.yaml index 3e20d2095f6..f9fdfbb4a41 100644 --- a/static/bidder-info/amx.yaml +++ b/static/bidder-info/amx.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@amxrtb.com" +gvlVendorID: 737 capabilities: site: mediaTypes: diff --git a/static/bidder-info/appnexus.yaml b/static/bidder-info/appnexus.yaml index f1e7ca23cfb..f2f4a1266df 100644 --- a/static/bidder-info/appnexus.yaml +++ b/static/bidder-info/appnexus.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid-server@xandr.com" +gvlVendorID: 32 capabilities: app: mediaTypes: diff --git a/static/bidder-info/avocet.yaml b/static/bidder-info/avocet.yaml index ea98982d69c..42147c96ebf 100644 --- a/static/bidder-info/avocet.yaml +++ b/static/bidder-info/avocet.yaml @@ -1,5 +1,6 @@ maintainer: email: "developers@avocet.io" +gvlVendorID: 63 capabilities: app: mediaTypes: diff --git a/static/bidder-info/beachfront.yaml b/static/bidder-info/beachfront.yaml index 335545ff1ee..06991698090 100644 --- a/static/bidder-info/beachfront.yaml +++ b/static/bidder-info/beachfront.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@beachfront.com" +gvlVendorID: 335 capabilities: app: mediaTypes: diff --git a/static/bidder-info/beintoo.yaml b/static/bidder-info/beintoo.yaml index fcca29220cf..905d89a44c4 100644 --- a/static/bidder-info/beintoo.yaml +++ b/static/bidder-info/beintoo.yaml @@ -1,5 +1,6 @@ maintainer: email: "adops@beintoo.com" +gvlVendorID: 618 capabilities: site: mediaTypes: diff --git a/static/bidder-info/between.yaml b/static/bidder-info/between.yaml index d317d275c59..71bd8ba6256 100644 --- a/static/bidder-info/between.yaml +++ b/static/bidder-info/between.yaml @@ -1,5 +1,6 @@ maintainer: email: "buying@betweenx.com" +gvlVendorID: 724 capabilities: site: mediaTypes: diff --git a/static/bidder-info/bidmachine.yaml b/static/bidder-info/bidmachine.yaml new file mode 100644 index 00000000000..6868125b6e6 --- /dev/null +++ b/static/bidder-info/bidmachine.yaml @@ -0,0 +1,8 @@ +maintainer: + email: hi@bidmachine.io +gvlVendorID: 736 +capabilities: + app: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-info/brightroll.yaml b/static/bidder-info/brightroll.yaml index f913be6da8c..a2ea0c74b77 100644 --- a/static/bidder-info/brightroll.yaml +++ b/static/bidder-info/brightroll.yaml @@ -1,5 +1,6 @@ maintainer: email: "dsp-supply-prebid@verizonmedia.com" +gvlVendorID: 25 capabilities: app: mediaTypes: diff --git a/static/bidder-info/connectad.yaml b/static/bidder-info/connectad.yaml index 1b3e593d78d..fa0ae4520fc 100644 --- a/static/bidder-info/connectad.yaml +++ b/static/bidder-info/connectad.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@connectad.io" +gvlVendorID: 138 capabilities: app: mediaTypes: diff --git a/static/bidder-info/consumable.yaml b/static/bidder-info/consumable.yaml index f12ab2b4016..e9b5f72623c 100644 --- a/static/bidder-info/consumable.yaml +++ b/static/bidder-info/consumable.yaml @@ -1,5 +1,6 @@ maintainer: email: "naffis@consumable.com" +gvlVendorID: 591 capabilities: app: mediaTypes: diff --git a/static/bidder-info/conversant.yaml b/static/bidder-info/conversant.yaml index 017f0e0c57e..aa3d3822802 100644 --- a/static/bidder-info/conversant.yaml +++ b/static/bidder-info/conversant.yaml @@ -1,5 +1,6 @@ maintainer: email: "CNVR_PublisherIntegration@conversantmedia.com" +gvlVendorID: 24 capabilities: app: mediaTypes: diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml new file mode 100644 index 00000000000..b27e1fae369 --- /dev/null +++ b/static/bidder-info/criteo.yaml @@ -0,0 +1,10 @@ +maintainer: + email: "pi-direct@criteo.com" +gvlVendorID: 91 +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner \ No newline at end of file diff --git a/static/bidder-info/deepintent.yaml b/static/bidder-info/deepintent.yaml index 490e161c8d2..a8f17a55d6f 100644 --- a/static/bidder-info/deepintent.yaml +++ b/static/bidder-info/deepintent.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebiddev@deepintent.com" +gvlVendorID: 541 capabilities: app: mediaTypes: diff --git a/static/bidder-info/dmx.yaml b/static/bidder-info/dmx.yaml index 3ecb452f7f6..d29d699daeb 100644 --- a/static/bidder-info/dmx.yaml +++ b/static/bidder-info/dmx.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@districtm.net" +gvlVendorID: 144 capabilities: site: mediaTypes: diff --git a/static/bidder-info/emx_digital.yaml b/static/bidder-info/emx_digital.yaml index 49a068093eb..ec0d090fb4c 100644 --- a/static/bidder-info/emx_digital.yaml +++ b/static/bidder-info/emx_digital.yaml @@ -1,5 +1,6 @@ maintainer: email: "adops@emxdigital.com" +gvlVendorID: 183 capabilities: site: mediaTypes: diff --git a/static/bidder-info/engagebdr.yaml b/static/bidder-info/engagebdr.yaml index 57c359e451d..fd08367acc7 100644 --- a/static/bidder-info/engagebdr.yaml +++ b/static/bidder-info/engagebdr.yaml @@ -1,5 +1,6 @@ maintainer: email: "tech@engagebdr.com" +gvlVendorID: 62 capabilities: app: mediaTypes: diff --git a/static/bidder-info/eplanning.yaml b/static/bidder-info/eplanning.yaml index 907523d3eb3..ab0b7609dbb 100644 --- a/static/bidder-info/eplanning.yaml +++ b/static/bidder-info/eplanning.yaml @@ -1,5 +1,6 @@ maintainer: email: "producto@e-planning.net" +gvlVendorID: 90 capabilities: app: mediaTypes: diff --git a/static/bidder-info/epom.yaml b/static/bidder-info/epom.yaml new file mode 100644 index 00000000000..32afa346c9e --- /dev/null +++ b/static/bidder-info/epom.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "support@epom.com" +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/gamoshi.yaml b/static/bidder-info/gamoshi.yaml index c3ed3ff10e4..0cfd495762f 100644 --- a/static/bidder-info/gamoshi.yaml +++ b/static/bidder-info/gamoshi.yaml @@ -1,5 +1,6 @@ maintainer: email: "dev@gamoshi.com" +gvlVendorID: 644 capabilities: app: mediaTypes: diff --git a/static/bidder-info/grid.yaml b/static/bidder-info/grid.yaml index 325421a2c05..31c7b7320e3 100644 --- a/static/bidder-info/grid.yaml +++ b/static/bidder-info/grid.yaml @@ -1,5 +1,6 @@ maintainer: email: "grid-tech@themediagrid.com" +gvlVendorID: 686 capabilities: app: mediaTypes: diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml index 6ba563b4c1c..bfefe63ab40 100644 --- a/static/bidder-info/gumgum.yaml +++ b/static/bidder-info/gumgum.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@gumgum.com" +gvlVendorID: 61 capabilities: site: mediaTypes: diff --git a/static/bidder-info/improvedigital.yaml b/static/bidder-info/improvedigital.yaml index 7983cbc1a61..f7fea4a8402 100644 --- a/static/bidder-info/improvedigital.yaml +++ b/static/bidder-info/improvedigital.yaml @@ -1,11 +1,14 @@ maintainer: email: "hb@azerion.com" +gvlVendorID: 253 capabilities: app: mediaTypes: - banner - video + - native site: mediaTypes: - banner - video + - native diff --git a/static/bidder-info/invibes.yaml b/static/bidder-info/invibes.yaml index 1432529787e..45184fb9f65 100644 --- a/static/bidder-info/invibes.yaml +++ b/static/bidder-info/invibes.yaml @@ -1,5 +1,6 @@ maintainer: email: "system_operations@invibes.com" +gvlVendorID: 436 capabilities: site: mediaTypes: diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml index 2f00ceb952f..1e89c72e5bb 100644 --- a/static/bidder-info/ix.yaml +++ b/static/bidder-info/ix.yaml @@ -1,5 +1,6 @@ maintainer: email: "pdu-supply-prebid@indexexchange.com" +gvlVendorID: 10 capabilities: app: mediaTypes: diff --git a/static/bidder-info/jixie.yaml b/static/bidder-info/jixie.yaml new file mode 100644 index 00000000000..ac38f313da1 --- /dev/null +++ b/static/bidder-info/jixie.yaml @@ -0,0 +1,8 @@ +maintainer: + email: contact@jixie.io +modifyingVastXmlAllowed: true +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/lifestreet.yaml b/static/bidder-info/lifestreet.yaml index 139d55990b9..34dc4eca2d9 100644 --- a/static/bidder-info/lifestreet.yaml +++ b/static/bidder-info/lifestreet.yaml @@ -1,5 +1,6 @@ maintainer: email: "mobile.tech@lifestreet.com" +gvlVendorID: 67 capabilities: app: mediaTypes: diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml index 112f67fe556..98611402905 100644 --- a/static/bidder-info/mediafuse.yaml +++ b/static/bidder-info/mediafuse.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@mediafuse.com" +gvlVendorID: 411 capabilities: app: mediaTypes: diff --git a/static/bidder-info/mgid.yaml b/static/bidder-info/mgid.yaml index f8ba6db60b1..bddb8b8598e 100644 --- a/static/bidder-info/mgid.yaml +++ b/static/bidder-info/mgid.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@mgid.com" +gvlVendorID: 358 capabilities: app: mediaTypes: diff --git a/static/bidder-info/nanointeractive.yaml b/static/bidder-info/nanointeractive.yaml index 244e7602950..d199e9e8ff5 100644 --- a/static/bidder-info/nanointeractive.yaml +++ b/static/bidder-info/nanointeractive.yaml @@ -1,5 +1,6 @@ maintainer: email: "development@nanointeractive.com" +gvlVendorID: 72 capabilities: app: mediaTypes: diff --git a/static/bidder-info/nobid.yaml b/static/bidder-info/nobid.yaml index 51a55de46bc..89f2a28abcd 100644 --- a/static/bidder-info/nobid.yaml +++ b/static/bidder-info/nobid.yaml @@ -1,5 +1,6 @@ maintainer: email: "developers@nobid.io" +gvlVendorID: 816 capabilities: site: mediaTypes: diff --git a/static/bidder-info/onetag.yaml b/static/bidder-info/onetag.yaml new file mode 100644 index 00000000000..697ef04d5a1 --- /dev/null +++ b/static/bidder-info/onetag.yaml @@ -0,0 +1,14 @@ +maintainer: + email: devops@onetag.com +gvlVendorID: 241 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/openx.yaml b/static/bidder-info/openx.yaml index 96ef8616c96..709f3db0147 100644 --- a/static/bidder-info/openx.yaml +++ b/static/bidder-info/openx.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@openx.com" +gvlVendorID: 69 capabilities: app: mediaTypes: diff --git a/static/bidder-info/orbidder.yaml b/static/bidder-info/orbidder.yaml index c683087d197..467093c1256 100644 --- a/static/bidder-info/orbidder.yaml +++ b/static/bidder-info/orbidder.yaml @@ -1,9 +1,7 @@ maintainer: email: "realtime-siggi@otto.de" +gvlVendorID: 559 capabilities: app: mediaTypes: - banner - site: - mediaTypes: - - banner \ No newline at end of file diff --git a/static/bidder-info/outbrain.yaml b/static/bidder-info/outbrain.yaml new file mode 100644 index 00000000000..e38ec915f49 --- /dev/null +++ b/static/bidder-info/outbrain.yaml @@ -0,0 +1,12 @@ +maintainer: + email: prog-ops-team@outbrain.com +gvlVendorID: 164 +capabilities: + app: + mediaTypes: + - banner + - native + site: + mediaTypes: + - banner + - native diff --git a/static/bidder-info/pangle.yaml b/static/bidder-info/pangle.yaml new file mode 100644 index 00000000000..3ac3a0ced0f --- /dev/null +++ b/static/bidder-info/pangle.yaml @@ -0,0 +1,9 @@ +maintainer: + email: pangle_dsp@bytedance.com +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/pubmatic.yaml b/static/bidder-info/pubmatic.yaml index 4009d439352..45c0418af8a 100644 --- a/static/bidder-info/pubmatic.yaml +++ b/static/bidder-info/pubmatic.yaml @@ -1,5 +1,6 @@ maintainer: email: "header-bidding@pubmatic.com" +gvlVendorID: 76 capabilities: app: mediaTypes: diff --git a/static/bidder-info/pulsepoint.yaml b/static/bidder-info/pulsepoint.yaml index 056a0bf3d7c..bda03efd99c 100644 --- a/static/bidder-info/pulsepoint.yaml +++ b/static/bidder-info/pulsepoint.yaml @@ -1,5 +1,6 @@ maintainer: email: "ExchangeTeam@pulsepoint.com" +gvlVendorID: 81 capabilities: app: mediaTypes: diff --git a/static/bidder-info/rhythmone.yaml b/static/bidder-info/rhythmone.yaml index 89da6cfea35..852344db3e3 100644 --- a/static/bidder-info/rhythmone.yaml +++ b/static/bidder-info/rhythmone.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@rhythmone.com" +gvlVendorID: 36 capabilities: app: mediaTypes: diff --git a/static/bidder-info/rtbhouse.yaml b/static/bidder-info/rtbhouse.yaml index f15af6ca2e1..12744fdc75e 100644 --- a/static/bidder-info/rtbhouse.yaml +++ b/static/bidder-info/rtbhouse.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@rtbhouse.com" +gvlVendorID: 16 capabilities: site: mediaTypes: diff --git a/static/bidder-info/rubicon.yaml b/static/bidder-info/rubicon.yaml index 40848e3ec25..0f19ddb9627 100644 --- a/static/bidder-info/rubicon.yaml +++ b/static/bidder-info/rubicon.yaml @@ -1,5 +1,6 @@ maintainer: email: "header-bidding@rubiconproject.com" +gvlVendorID: 52 capabilities: app: mediaTypes: diff --git a/static/bidder-info/sharethrough.yaml b/static/bidder-info/sharethrough.yaml index 09530be508c..45dceb94d22 100644 --- a/static/bidder-info/sharethrough.yaml +++ b/static/bidder-info/sharethrough.yaml @@ -1,5 +1,6 @@ maintainer: email: "pubgrowth.engineering@sharethrough.com" +gvlVendorID: 80 capabilities: app: mediaTypes: diff --git a/static/bidder-info/smartadserver.yaml b/static/bidder-info/smartadserver.yaml index 626b7dac00d..f22c7149ff7 100644 --- a/static/bidder-info/smartadserver.yaml +++ b/static/bidder-info/smartadserver.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@smartadserver.com" +gvlVendorID: 45 capabilities: app: mediaTypes: diff --git a/static/bidder-info/somoaudience.yaml b/static/bidder-info/somoaudience.yaml index 17156c18039..83b64e5c58e 100644 --- a/static/bidder-info/somoaudience.yaml +++ b/static/bidder-info/somoaudience.yaml @@ -1,5 +1,6 @@ maintainer: email: "publishers@somoaudience.com" +gvlVendorID: 341 capabilities: app: mediaTypes: diff --git a/static/bidder-info/sonobi.yaml b/static/bidder-info/sonobi.yaml index 6d39319a9f5..22a0a158306 100644 --- a/static/bidder-info/sonobi.yaml +++ b/static/bidder-info/sonobi.yaml @@ -1,5 +1,6 @@ maintainer: email: "apex.prebid@sonobi.com" +gvlVendorID: 104 capabilities: site: mediaTypes: diff --git a/static/bidder-info/sovrn.yaml b/static/bidder-info/sovrn.yaml index c62d61df8d8..4c6251bdf57 100644 --- a/static/bidder-info/sovrn.yaml +++ b/static/bidder-info/sovrn.yaml @@ -1,5 +1,6 @@ maintainer: email: "sovrnoss@sovrn.com" +gvlVendorID: 13 capabilities: app: mediaTypes: diff --git a/static/bidder-info/tappx.yaml b/static/bidder-info/tappx.yaml index 4c8d1560f27..eb655aa6a0c 100644 --- a/static/bidder-info/tappx.yaml +++ b/static/bidder-info/tappx.yaml @@ -1,7 +1,12 @@ maintainer: email: "tappx@tappx.com" +gvlVendorID: 628 capabilities: app: mediaTypes: - banner - video + site: + mediaTypes: + - banner + - video \ No newline at end of file diff --git a/static/bidder-info/telaria.yaml b/static/bidder-info/telaria.yaml index 43e8707a17b..736fb9720b3 100644 --- a/static/bidder-info/telaria.yaml +++ b/static/bidder-info/telaria.yaml @@ -1,5 +1,6 @@ maintainer: email: "github@telaria.com" +gvlVendorID: 202 capabilities: app: mediaTypes: diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml index 2b9ff8d5675..fe0ad8b2203 100644 --- a/static/bidder-info/triplelift.yaml +++ b/static/bidder-info/triplelift.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@triplelift.com" +gvlVendorID: 28 capabilities: app: mediaTypes: diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml index abe91659935..40d9be8f294 100644 --- a/static/bidder-info/triplelift_native.yaml +++ b/static/bidder-info/triplelift_native.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@triplelift.com" +gvlVendorID: 28 capabilities: app: mediaTypes: diff --git a/static/bidder-info/trustx.yaml b/static/bidder-info/trustx.yaml new file mode 100644 index 00000000000..6cfdd9fa465 --- /dev/null +++ b/static/bidder-info/trustx.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "grid-tech@themediagrid.com" +gvlVendorID: 686 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/ucfunnel.yaml b/static/bidder-info/ucfunnel.yaml index 288b0b3f1b8..e6be68a0261 100644 --- a/static/bidder-info/ucfunnel.yaml +++ b/static/bidder-info/ucfunnel.yaml @@ -1,5 +1,6 @@ maintainer: email: "support@ucfunnel.com" +gvlVendorID: 607 capabilities: app: mediaTypes: diff --git a/static/bidder-info/unicorn.yaml b/static/bidder-info/unicorn.yaml new file mode 100644 index 00000000000..f1b5a4e7f3e --- /dev/null +++ b/static/bidder-info/unicorn.yaml @@ -0,0 +1,6 @@ +maintainer: + email: prebid@unicorn.inc +capabilities: + app: + mediaTypes: + - banner \ No newline at end of file diff --git a/static/bidder-info/unruly.yaml b/static/bidder-info/unruly.yaml index 01ed3774118..e31ea600b3e 100644 --- a/static/bidder-info/unruly.yaml +++ b/static/bidder-info/unruly.yaml @@ -1,5 +1,6 @@ maintainer: email: "adspaces@unrulygroup.com" +gvlVendorID: 162 capabilities: site: mediaTypes: diff --git a/static/bidder-info/verizonmedia.yaml b/static/bidder-info/verizonmedia.yaml index c00f2158d4b..0b4642fcb6a 100644 --- a/static/bidder-info/verizonmedia.yaml +++ b/static/bidder-info/verizonmedia.yaml @@ -1,5 +1,6 @@ maintainer: email: "dsp-supply-prebid@verizonmedia.com" +gvlVendorID: 25 capabilities: app: mediaTypes: diff --git a/static/bidder-info/visx.yaml b/static/bidder-info/visx.yaml index b6a16e4c2d0..789ce478bea 100644 --- a/static/bidder-info/visx.yaml +++ b/static/bidder-info/visx.yaml @@ -1,5 +1,6 @@ maintainer: email: "supply.partners@yoc.com" +gvlVendorID: 154 capabilities: site: mediaTypes: diff --git a/static/bidder-info/yieldlab.yaml b/static/bidder-info/yieldlab.yaml index 654e6c749cb..3030d8a1d42 100644 --- a/static/bidder-info/yieldlab.yaml +++ b/static/bidder-info/yieldlab.yaml @@ -1,5 +1,6 @@ maintainer: email: "solutions@yieldlab.de" +gvlVendorID: 70 capabilities: site: mediaTypes: diff --git a/static/bidder-info/yieldmo.yaml b/static/bidder-info/yieldmo.yaml index 64cda519acd..b1385acbebc 100644 --- a/static/bidder-info/yieldmo.yaml +++ b/static/bidder-info/yieldmo.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@yieldmo.com" +gvlVendorID: 173 capabilities: app: mediaTypes: diff --git a/static/bidder-params/adform.json b/static/bidder-params/adform.json index f0b8c7a6be0..6c8a67732d5 100644 --- a/static/bidder-params/adform.json +++ b/static/bidder-params/adform.json @@ -25,7 +25,7 @@ }, "cdims": { "type": "string", - "description": "Comma-separated creative dimentions.", + "description": "Comma-separated creative dimensions.", "pattern": "(^\\d+x\\d+)(,\\d+x\\d+)*$" }, "minp": { diff --git a/static/bidder-params/adxcg.json b/static/bidder-params/adxcg.json new file mode 100644 index 00000000000..3f89234359d --- /dev/null +++ b/static/bidder-params/adxcg.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adxcg Adapter Params", + "description": "A schema which validates params accepted by the Adxcg adapter", + "type": "object", + "properties": { + "adzoneid": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the placement selling the impression" + } + }, + "required": ["adzoneid"] +} diff --git a/static/bidder-params/adyoulike.json b/static/bidder-params/adyoulike.json new file mode 100644 index 00000000000..448de344739 --- /dev/null +++ b/static/bidder-params/adyoulike.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AdYouLike Adapter Params", + "description": "A schema which validates params accepted by the AdYouLike adapter", + "type": "object", + "properties": { + "placement": { + "type": "string", + "description": "Placement Id" + }, + "campaign": { + "type": "string", + "description": "Id of a forced campaign" + }, + "track": { + "type": "string", + "description": "Id of a forced Track" + }, + "creative": { + "type": "string", + "description": "Id of a forced creative" + }, + "source": { + "type": "string", + "description": "context of the campaign" + }, + "debug": { + "type": "string", + "description": "Arbitrary id used for debug purpose" + } + }, + "required": ["placement"] +} diff --git a/static/bidder-params/bidmachine.json b/static/bidder-params/bidmachine.json new file mode 100644 index 00000000000..819dc9b7e2d --- /dev/null +++ b/static/bidder-params/bidmachine.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Bidmachine Adapter Params", + "description": "A schema which validates params accepted by the Kidoz adapter", + "type": "object", + "properties": { + "host": { + "type": "string", + "minLength": 1, + "description": "Host" + }, + "path": { + "type": "string", + "minLength": 1, + "description": "URL path" + }, + "seller_id": { + "type": "string", + "minLength": 1, + "description": "Seller Identifier" + } + }, + "required": [ + "host", + "path", + "seller_id" + ] +} diff --git a/static/bidder-params/connectad.json b/static/bidder-params/connectad.json index 961b3b71202..15f4ab66bf3 100644 --- a/static/bidder-params/connectad.json +++ b/static/bidder-params/connectad.json @@ -16,9 +16,9 @@ }, "bidfloor": { "type": "number", - "description": "Requestes Floorprice" + "description": "Requests Floorprice" } }, "required": ["networkId", "siteId"] } - \ No newline at end of file + diff --git a/static/bidder-params/criteo.json b/static/bidder-params/criteo.json new file mode 100644 index 00000000000..9d348a7eded --- /dev/null +++ b/static/bidder-params/criteo.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Criteo adapter params", + "description": "The schema to validate Criteo specific params accepted by Criteo adapter", + "type": "object", + "properties": { + "zoneid": { + "type": "number", + "description": "Impression's zone ID.", + "minimum": 0 + }, + "networkid": { + "type": "number", + "description": "Impression's network ID.", + "minimum": 0 + } + }, + "anyOf": [ + { + "required": [ + "zoneid" + ] + }, + { + "required": [ + "networkid" + ] + } + ] +} \ No newline at end of file diff --git a/static/bidder-params/dmx.json b/static/bidder-params/dmx.json index fa2d447b5d2..3c76e507b92 100644 --- a/static/bidder-params/dmx.json +++ b/static/bidder-params/dmx.json @@ -31,4 +31,4 @@ }, "required": ["memberid"] -} +} \ No newline at end of file diff --git a/static/bidder-params/epom.json b/static/bidder-params/epom.json new file mode 100644 index 00000000000..ee8c14e4f7e --- /dev/null +++ b/static/bidder-params/epom.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Epom Adapter Params", + "description": "A schema which validates params accepted by the Epom adapter", + "type": "object", + + "properties": {} +} diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json index 5ec2b8e0cbf..8c8677d9407 100644 --- a/static/bidder-params/gumgum.json +++ b/static/bidder-params/gumgum.json @@ -8,7 +8,26 @@ "type": "string", "description": "A tracking id used to identify GumGum zone.", "minLength": 8 + }, + "pubId": { + "type": "integer", + "description": "A tracking id used to identify GumGum publisher" + }, + "irisid": { + "type": "string", + "description": "A hashed IRIS.TV Content ID" } }, - "required": ["zone"] + "anyOf": [ + { + "required": [ + "zone" + ] + }, + { + "required": [ + "pubId" + ] + } + ] } diff --git a/static/bidder-params/invibes.json b/static/bidder-params/invibes.json index 11d276f8d3e..5545b17409d 100644 --- a/static/bidder-params/invibes.json +++ b/static/bidder-params/invibes.json @@ -23,7 +23,7 @@ "type": "boolean" } }, - "description": "Parameters used for debugging porposes" + "description": "Parameters used for debugging purposes" } }, "required": ["placementId"] diff --git a/static/bidder-params/jixie.json b/static/bidder-params/jixie.json new file mode 100644 index 00000000000..d3d294fe481 --- /dev/null +++ b/static/bidder-params/jixie.json @@ -0,0 +1,27 @@ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Jixie Adapter Params", + "description": "A schema which validates params accepted by the Jixie adapter", + "type": "object", + "properties": { + "unit" : { + "type": "string", + "description": "The unit code of an inventory target", + "minLength": 18 + }, + "accountid" : { + "type": "string", + "description": "The accountid of an inventory target" + }, + "jxprop1" : { + "type": "string", + "description": "jxprop1 of an inventory target" + }, + "jxprop2" : { + "type": "string", + "description": "jxprop2 of an inventory target" + } + }, + "required": ["unit"] + } + diff --git a/static/bidder-params/kidoz.json b/static/bidder-params/kidoz.json index 79e2edc2fd2..16511a15ca7 100644 --- a/static/bidder-params/kidoz.json +++ b/static/bidder-params/kidoz.json @@ -5,22 +5,18 @@ "type": "object", "properties": { "access_token": { - "$ref": "#/definitions/non-empty-string", + "type": "string", + "minLength": 1, "description": "Kidoz access_token" }, "publisher_id": { - "$ref": "#/definitions/non-empty-string", - "description": "Kidoz publisher_id" - } - }, - "definitions": { - "non-empty-string": { "type": "string", - "minLength": 1 + "minLength": 1, + "description": "Kidoz publisher_id" } }, "required": [ "access_token", "publisher_id" ] -} \ No newline at end of file +} diff --git a/static/bidder-params/mobfoxpb.json b/static/bidder-params/mobfoxpb.json index 0cc7a16c026..40ce83abc8e 100644 --- a/static/bidder-params/mobfoxpb.json +++ b/static/bidder-params/mobfoxpb.json @@ -1,14 +1,30 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Mobfox Adapter Params", - "description": "A schema which validates params accepted by the Mobfox adapter", - "type": "object", - "properties": { - "TagID": { - "type": "string", - "minLength": 1, - "description": "An ID which identifies the mobfox ad tag" - } - }, - "required" : [ "TagID" ] -} + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Mobfox Adapter Params", + "description": "A schema which validates params accepted by the Mobfox adapter", + "type": "object", + "properties": { + "TagID": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the mobfox ad tag" + }, + "key": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the mobfox adexchange partner" + } + }, + "oneOf": [ + { + "required": [ + "TagID" + ] + }, + { + "required": [ + "key" + ] + } + ] +} \ No newline at end of file diff --git a/static/bidder-params/nanointeractive.json b/static/bidder-params/nanointeractive.json index 707dff2fa50..aacd3154a92 100644 --- a/static/bidder-params/nanointeractive.json +++ b/static/bidder-params/nanointeractive.json @@ -6,7 +6,7 @@ "properties": { "pid": { "type": "string", - "description": "Placement idd" + "description": "Placement id" }, "nq": { "type": "array", @@ -29,4 +29,4 @@ } }, "required": ["pid"] -} \ No newline at end of file +} diff --git a/static/bidder-params/nobid.json b/static/bidder-params/nobid.json index 576dbfecb5c..6293c40c9fd 100644 --- a/static/bidder-params/nobid.json +++ b/static/bidder-params/nobid.json @@ -7,12 +7,12 @@ "properties": { "siteId": { "type": "integer", - "description": "A Required ID which identifies the NoBid site. The siteId paramerter is provided by your NoBid account manager." + "description": "A Required ID which identifies the NoBid site. The siteId parameter is provided by your NoBid account manager." }, "placementId": { "type": "integer", - "description": "An oprional ID which identifies an adunit in a site. The placementId paramerter is provided by your NoBid account manager." + "description": "An optional ID which identifies an adunit in a site. The placementId parameter is provided by your NoBid account manager." } }, "required": ["siteId"] } - \ No newline at end of file + diff --git a/static/bidder-params/onetag.json b/static/bidder-params/onetag.json new file mode 100644 index 00000000000..ca2911ba4d1 --- /dev/null +++ b/static/bidder-params/onetag.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Onetag Adapter Params", + "description": "A schema which validates params accepted by the Onetag adapter", + "type": "object", + + "properties": { + "pubId": { + "type": "string", + "minLength": 1, + "description": "The publisher’s ID provided by OneTag" + }, + "ext": { + "type": "object", + "description": "A set of custom key-value pairs" + } + }, + + "required": ["pubId"] +} \ No newline at end of file diff --git a/static/bidder-params/outbrain.json b/static/bidder-params/outbrain.json new file mode 100644 index 00000000000..8cfb8bbdf28 --- /dev/null +++ b/static/bidder-params/outbrain.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Outbrain Adapter Params", + "description": "A schema which validates params accepted by the Outbrain adapter", + + "type": "object", + "properties": { + "publisher": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "domain": { + "type": "string" + } + }, + "required": ["id"] + }, + "tagid": { + "type": "string" + }, + "bcat": { + "type": "array", + "items": { + "type": "string" + } + }, + "badv": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["publisher"] +} diff --git a/static/bidder-params/pangle.json b/static/bidder-params/pangle.json new file mode 100644 index 00000000000..74085cb5e65 --- /dev/null +++ b/static/bidder-params/pangle.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Pangle Adapter Params", + "description": "A schema which validates params accepted by the Pangle adapter", + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "Access Token", + "pattern": ".+" + } + }, + "required": [ + "token" + ] +} diff --git a/static/bidder-params/rtbhouse.json b/static/bidder-params/rtbhouse.json index e8f6bd03cff..00732bedd2f 100644 --- a/static/bidder-params/rtbhouse.json +++ b/static/bidder-params/rtbhouse.json @@ -1,8 +1,13 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "RTB House Adapter Params", - "description": "A schema which validates params accepted by the RTB House adapter", - "type": "object", - "properties": {}, - "required": [] + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "RTB House Adapter Params", + "description": "A schema which validates params accepted by the RTB House adapter", + "type": "object", + "properties": { + "publisherId": { + "type": "string", + "description": "The publisher’s ID provided by RTB House" + } + }, + "required": ["publisherId"] } diff --git a/static/bidder-params/trustx.json b/static/bidder-params/trustx.json new file mode 100644 index 00000000000..efedf9de537 --- /dev/null +++ b/static/bidder-params/trustx.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "TrustX Adapter Params", + "description": "A schema which validates params accepted by TrustX adapter", + "type": "object", + "properties": { + "uid": { + "type": "integer", + "description": "An ID which identifies this placement of the impression" + } + }, + "required": [] +} diff --git a/static/bidder-params/unicorn.json b/static/bidder-params/unicorn.json new file mode 100644 index 00000000000..f9c4e1677b6 --- /dev/null +++ b/static/bidder-params/unicorn.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "UNICORN Adapter Params", + "description": "A schema which validates params accepted by the UNICORN adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "description": "In Application, if placementId is empty, prebid server configuration id will be used as placementId." + }, + "publisherId": { + "type": "integer", + "description": "Account specific publisher id" + }, + "mediaId": { + "type": "string", + "description": "Publisher specific media id" + }, + "accountId": { + "type": "integer", + "description": "Account ID for charge request" + } + }, + "required" : ["mediaId", "accountId"] +} \ No newline at end of file diff --git a/static/category-mapping/freewheel/freewheel.json b/static/category-mapping/freewheel/freewheel.json index 1b849b5392d..11529206087 100644 --- a/static/category-mapping/freewheel/freewheel.json +++ b/static/category-mapping/freewheel/freewheel.json @@ -1255,4 +1255,4 @@ "id": "403", "name": "Retail Stores/Chains" } -} +} \ No newline at end of file diff --git a/stored_requests/backends/db_fetcher/fetcher.go b/stored_requests/backends/db_fetcher/fetcher.go index c3b71a3be67..d8cf132d25b 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/PubMatic-OpenWrap/prebid-server/stored_requests" "github.com/golang/glog" + "github.com/prebid/prebid-server/stored_requests" ) func NewFetcher(db *sql.DB, queryMaker func(int, int) string) stored_requests.AllFetcher { diff --git a/stored_requests/backends/empty_fetcher/fetcher.go b/stored_requests/backends/empty_fetcher/fetcher.go index 6edf3cc4d00..ee6b98b3b2e 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/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/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 bff94b21e79..2d3b00657b9 100644 --- a/stored_requests/backends/file_fetcher/fetcher.go +++ b/stored_requests/backends/file_fetcher/fetcher.go @@ -7,7 +7,7 @@ import ( "io/ioutil" "strings" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests" ) // NewFileFetcher _immediately_ loads stored request data from local files. diff --git a/stored_requests/backends/file_fetcher/fetcher_test.go b/stored_requests/backends/file_fetcher/fetcher_test.go index f0900002c8c..a145a3b43a2 100644 --- a/stored_requests/backends/file_fetcher/fetcher_test.go +++ b/stored_requests/backends/file_fetcher/fetcher_test.go @@ -6,7 +6,7 @@ import ( "fmt" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests" "github.com/stretchr/testify/assert" ) diff --git a/stored_requests/backends/http_fetcher/fetcher.go b/stored_requests/backends/http_fetcher/fetcher.go index 5a7d8fa2878..bc12caecb98 100644 --- a/stored_requests/backends/http_fetcher/fetcher.go +++ b/stored_requests/backends/http_fetcher/fetcher.go @@ -10,7 +10,7 @@ import ( "net/url" "strings" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests" "github.com/golang/glog" "golang.org/x/net/context/ctxhttp" diff --git a/stored_requests/caches/cachestest/reliable.go b/stored_requests/caches/cachestest/reliable.go index a0ab07df431..7fbaf7238af 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/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests" ) const ( diff --git a/stored_requests/caches/memory/cache.go b/stored_requests/caches/memory/cache.go index 288e6c26b71..5939c26ddec 100644 --- a/stored_requests/caches/memory/cache.go +++ b/stored_requests/caches/memory/cache.go @@ -5,9 +5,9 @@ import ( "encoding/json" "sync" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" "github.com/coocood/freecache" "github.com/golang/glog" + "github.com/prebid/prebid-server/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 20ec1239cd2..b89bd5af26f 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/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/caches/cachestest" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests/caches/cachestest" ) func TestLRURobustness(t *testing.T) { diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index ed51ed6f5de..7f92f2521cd 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -6,23 +6,23 @@ import ( "net/http" "time" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/db_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/file_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/http_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/caches/memory" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/caches/nil_cache" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events" - apiEvents "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events/api" - httpEvents "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events/http" - postgresEvents "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events/postgres" - "github.com/PubMatic-OpenWrap/prebid-server/util/task" + "github.com/prebid/prebid-server/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/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" + httpEvents "github.com/prebid/prebid-server/stored_requests/events/http" + postgresEvents "github.com/prebid/prebid-server/stored_requests/events/postgres" + "github.com/prebid/prebid-server/util/task" ) // This gets set to the connection string used when a database connection is made. We only support a single diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index fa1bbfd8764..6c8cd612299 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -12,14 +12,14 @@ import ( "github.com/stretchr/testify/assert" sqlmock "github.com/DATA-DOG/go-sqlmock" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/empty_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/backends/http_fetcher" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events" - httpEvents "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events/http" "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/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/stretchr/testify/mock" ) diff --git a/stored_requests/events/api/api.go b/stored_requests/events/api/api.go index 8fb6f6be9eb..6dce4ebaad6 100644 --- a/stored_requests/events/api/api.go +++ b/stored_requests/events/api/api.go @@ -5,8 +5,8 @@ import ( "io/ioutil" "net/http" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events" "github.com/julienschmidt/httprouter" + "github.com/prebid/prebid-server/stored_requests/events" ) type eventsAPI struct { diff --git a/stored_requests/events/api/api_test.go b/stored_requests/events/api/api_test.go index 74e02e69e4d..cd3af77bd83 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/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/caches/memory" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests/caches/memory" + "github.com/prebid/prebid-server/stored_requests/events" ) func TestGoodRequests(t *testing.T) { diff --git a/stored_requests/events/events.go b/stored_requests/events/events.go index 60909a0d426..5b89943572f 100644 --- a/stored_requests/events/events.go +++ b/stored_requests/events/events.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" + "github.com/prebid/prebid-server/stored_requests" ) // Save represents a bulk save diff --git a/stored_requests/events/events_test.go b/stored_requests/events/events_test.go index 0a48b4cc365..f3483705e86 100644 --- a/stored_requests/events/events_test.go +++ b/stored_requests/events/events_test.go @@ -7,8 +7,8 @@ import ( "reflect" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/caches/memory" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/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 790c247e368..4615183f693 100644 --- a/stored_requests/events/http/http.go +++ b/stored_requests/events/http/http.go @@ -11,8 +11,8 @@ import ( "golang.org/x/net/context/ctxhttp" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events" "github.com/buger/jsonparser" + "github.com/prebid/prebid-server/stored_requests/events" "github.com/golang/glog" ) diff --git a/stored_requests/events/postgres/database.go b/stored_requests/events/postgres/database.go index 89fb30b88c8..e769a55585c 100644 --- a/stored_requests/events/postgres/database.go +++ b/stored_requests/events/postgres/database.go @@ -8,11 +8,11 @@ import ( "net" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events" - "github.com/PubMatic-OpenWrap/prebid-server/util/timeutil" "github.com/golang/glog" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/util/timeutil" ) func bytesNull() []byte { diff --git a/stored_requests/events/postgres/database_test.go b/stored_requests/events/postgres/database_test.go index 63625061dd3..15d0fbffbc3 100644 --- a/stored_requests/events/postgres/database_test.go +++ b/stored_requests/events/postgres/database_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/events" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/stored_requests/events" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/stored_requests/fetcher.go b/stored_requests/fetcher.go index 597b660cb61..865231ee757 100644 --- a/stored_requests/fetcher.go +++ b/stored_requests/fetcher.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" + "github.com/prebid/prebid-server/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 07631e79bbd..e77bc75c310 100644 --- a/stored_requests/fetcher_test.go +++ b/stored_requests/fetcher_test.go @@ -6,8 +6,8 @@ import ( "errors" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/metrics" - "github.com/PubMatic-OpenWrap/prebid-server/stored_requests/caches/nil_cache" + "github.com/prebid/prebid-server/metrics" + "github.com/prebid/prebid-server/stored_requests/caches/nil_cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" diff --git a/usersync/cookie.go b/usersync/cookie.go index c3a045aca77..f5083a83bbe 100644 --- a/usersync/cookie.go +++ b/usersync/cookie.go @@ -8,8 +8,8 @@ import ( "net/http" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) const ( @@ -177,9 +177,8 @@ func (cookie *PBSCookie) GetId(bidderName openrtb_ext.BidderName) (id string, ex // SetCookieOnResponse is a shortcut for "ToHTTPCookie(); cookie.setDomain(domain); setCookie(w, cookie)" func (cookie *PBSCookie) SetCookieOnResponse(w http.ResponseWriter, setSiteCookie bool, cfg *config.HostCookie, ttl time.Duration) { httpCookie := cookie.ToHTTPCookie(ttl) - httpCookie.Secure = true - var domain string = cfg.Domain + httpCookie.Secure = true if domain != "" { httpCookie.Domain = domain @@ -210,7 +209,6 @@ func (cookie *PBSCookie) SetCookieOnResponse(w http.ResponseWriter, setSiteCooki //httpCookie.Secure = true uidsCookieStr = httpCookie.String() uidsCookieStr += SameSiteAttribute - sameSiteCookie = &http.Cookie{ Name: SameSiteCookieName, Value: SameSiteCookieValue, @@ -218,7 +216,6 @@ func (cookie *PBSCookie) SetCookieOnResponse(w http.ResponseWriter, setSiteCooki Path: "/", Secure: true, } - sameSiteCookieStr := sameSiteCookie.String() sameSiteCookieStr += SameSiteAttribute w.Header().Add("Set-Cookie", sameSiteCookieStr) diff --git a/usersync/cookie_test.go b/usersync/cookie_test.go index c05eadd4a98..ef2e9911e46 100644 --- a/usersync/cookie_test.go +++ b/usersync/cookie_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) diff --git a/usersync/usersync.go b/usersync/usersync.go index 7730febcd90..dffc1832f01 100644 --- a/usersync/usersync.go +++ b/usersync/usersync.go @@ -1,6 +1,6 @@ package usersync -import "github.com/PubMatic-OpenWrap/prebid-server/privacy" +import "github.com/prebid/prebid-server/privacy" type Usersyncer interface { // GetUsersyncInfo returns basic info the browser needs in order to run a user sync. @@ -14,16 +14,6 @@ type Usersyncer interface { // TODO #362: when the appnexus usersyncer is consistent, delete this and use the key // of NewSyncerMap() here instead. FamilyName() string - - // GDPRVendorID returns the ID in the IAB Global Vendor List which refers to this Bidder. - // - // The Global Vendor list can be found here: https://vendor-list.consensu.org/vendorlist.json - // Bidders can register for the list here: https://register.consensu.org/ - // - // If you're not on the list, this should return 0. If cookie sync requests have GDPR consent info, - // or the Prebid Server host company configures its deploy to be "cautious" when no GDPR info exists - // in the request, it will _not_ sync user IDs with you. - GDPRVendorID() uint16 } type UsersyncInfo struct { diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go old mode 100755 new mode 100644 index 04ec05430e5..50362ad04ec --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -4,87 +4,95 @@ import ( "strings" "text/template" - ttx "github.com/PubMatic-OpenWrap/prebid-server/adapters/33across" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/acuityads" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adform" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adkernel" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adkernelAdn" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adman" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/admixer" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adocean" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adpone" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adtarget" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/adtelligent" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/advangelists" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/aja" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/amx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/appnexus" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/audienceNetwork" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/avocet" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/beachfront" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/beintoo" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/between" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/brightroll" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/colossus" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/connectad" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/consumable" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/conversant" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/cpmstar" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/datablocks" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/deepintent" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/dmx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/emx_digital" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/engagebdr" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/eplanning" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/gamma" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/gamoshi" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/grid" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/gumgum" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/improvedigital" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/invibes" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/ix" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/krushmedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lifestreet" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lockerdome" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/logicad" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/lunamedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/marsmedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/mediafuse" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/mgid" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/nanointeractive" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/ninthdecimal" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/nobid" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/openx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/pubmatic" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/pulsepoint" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/rhythmone" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/rtbhouse" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/rubicon" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/sharethrough" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/smartadserver" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/smartrtb" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/smartyads" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/somoaudience" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/sonobi" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/sovrn" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/synacormedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/telaria" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/triplelift" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/triplelift_native" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/ucfunnel" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/unruly" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/valueimpression" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/verizonmedia" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/visx" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/vrtcal" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/yieldlab" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/yieldmo" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/yieldone" - "github.com/PubMatic-OpenWrap/prebid-server/adapters/zeroclickfraud" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" - "github.com/PubMatic-OpenWrap/prebid-server/usersync" "github.com/golang/glog" + ttx "github.com/prebid/prebid-server/adapters/33across" + "github.com/prebid/prebid-server/adapters/acuityads" + "github.com/prebid/prebid-server/adapters/adform" + "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/adocean" + "github.com/prebid/prebid-server/adapters/adpone" + "github.com/prebid/prebid-server/adapters/adtarget" + "github.com/prebid/prebid-server/adapters/adtelligent" + "github.com/prebid/prebid-server/adapters/advangelists" + "github.com/prebid/prebid-server/adapters/adxcg" + "github.com/prebid/prebid-server/adapters/adyoulike" + "github.com/prebid/prebid-server/adapters/aja" + "github.com/prebid/prebid-server/adapters/amx" + "github.com/prebid/prebid-server/adapters/appnexus" + "github.com/prebid/prebid-server/adapters/audienceNetwork" + "github.com/prebid/prebid-server/adapters/avocet" + "github.com/prebid/prebid-server/adapters/beachfront" + "github.com/prebid/prebid-server/adapters/beintoo" + "github.com/prebid/prebid-server/adapters/between" + "github.com/prebid/prebid-server/adapters/brightroll" + "github.com/prebid/prebid-server/adapters/colossus" + "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/datablocks" + "github.com/prebid/prebid-server/adapters/deepintent" + "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/emx_digital" + "github.com/prebid/prebid-server/adapters/engagebdr" + "github.com/prebid/prebid-server/adapters/eplanning" + "github.com/prebid/prebid-server/adapters/gamma" + "github.com/prebid/prebid-server/adapters/gamoshi" + "github.com/prebid/prebid-server/adapters/grid" + "github.com/prebid/prebid-server/adapters/gumgum" + "github.com/prebid/prebid-server/adapters/improvedigital" + "github.com/prebid/prebid-server/adapters/invibes" + "github.com/prebid/prebid-server/adapters/ix" + "github.com/prebid/prebid-server/adapters/jixie" + "github.com/prebid/prebid-server/adapters/krushmedia" + "github.com/prebid/prebid-server/adapters/lifestreet" + "github.com/prebid/prebid-server/adapters/lockerdome" + "github.com/prebid/prebid-server/adapters/logicad" + "github.com/prebid/prebid-server/adapters/lunamedia" + "github.com/prebid/prebid-server/adapters/marsmedia" + "github.com/prebid/prebid-server/adapters/mediafuse" + "github.com/prebid/prebid-server/adapters/mgid" + "github.com/prebid/prebid-server/adapters/nanointeractive" + "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/openx" + "github.com/prebid/prebid-server/adapters/outbrain" + "github.com/prebid/prebid-server/adapters/pubmatic" + "github.com/prebid/prebid-server/adapters/pulsepoint" + "github.com/prebid/prebid-server/adapters/rhythmone" + "github.com/prebid/prebid-server/adapters/rtbhouse" + "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sharethrough" + "github.com/prebid/prebid-server/adapters/smartadserver" + "github.com/prebid/prebid-server/adapters/smartrtb" + "github.com/prebid/prebid-server/adapters/smartyads" + "github.com/prebid/prebid-server/adapters/somoaudience" + "github.com/prebid/prebid-server/adapters/sonobi" + "github.com/prebid/prebid-server/adapters/sovrn" + "github.com/prebid/prebid-server/adapters/synacormedia" + "github.com/prebid/prebid-server/adapters/tappx" + "github.com/prebid/prebid-server/adapters/telaria" + "github.com/prebid/prebid-server/adapters/triplelift" + "github.com/prebid/prebid-server/adapters/triplelift_native" + "github.com/prebid/prebid-server/adapters/trustx" + "github.com/prebid/prebid-server/adapters/ucfunnel" + "github.com/prebid/prebid-server/adapters/unruly" + "github.com/prebid/prebid-server/adapters/valueimpression" + "github.com/prebid/prebid-server/adapters/verizonmedia" + "github.com/prebid/prebid-server/adapters/visx" + "github.com/prebid/prebid-server/adapters/vrtcal" + "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/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/usersync" ) // NewSyncerMap returns a map of all the usersyncer objects. @@ -105,6 +113,8 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtarget, adtarget.NewAdtargetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdtelligent, adtelligent.NewAdtelligentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdvangelists, advangelists.NewAdvangelistsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdxcg, adxcg.NewAdxcgSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdyoulike, adyoulike.NewAdyoulikeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAJA, aja.NewAJASyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAMX, amx.NewAMXSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAppnexus, appnexus.NewAppnexusSyncer) @@ -116,6 +126,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderConnectAd, connectad.NewConnectAdSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConversant, conversant.NewConversantSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderCriteo, criteo.NewCriteoSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderCpmstar, cpmstar.NewCpmstarSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDatablocks, datablocks.NewDatablocksSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDeepintent, deepintent.NewDeepintentSyncer) @@ -131,6 +142,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderImprovedigital, improvedigital.NewImprovedigitalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderInvibes, invibes.NewInvibesSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderJixie, jixie.NewJixieSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderKrushmedia, krushmedia.NewKrushmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) @@ -142,6 +154,8 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderNanoInteractive, nanointeractive.NewNanoInteractiveSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderNinthDecimal, ninthdecimal.NewNinthDecimalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderNoBid, nobid.NewNoBidSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderOneTag, onetag.NewSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderOutbrain, outbrain.NewOutbrainSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer) @@ -156,9 +170,11 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderTappx, tappx.NewTappxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTriplelift, triplelift.NewTripleliftSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTripleliftNative, triplelift_native.NewTripleliftSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderTrustX, trustx.NewTrustXSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUcfunnel, ucfunnel.NewUcfunnelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderUnruly, unruly.NewUnrulySyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderValueImpression, valueimpression.NewValueImpressionSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 08a31322553..7e10c41cd76 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -4,8 +4,8 @@ import ( "strings" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/config" - "github.com/PubMatic-OpenWrap/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" ) func TestNewSyncerMap(t *testing.T) { @@ -26,6 +26,8 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderAdtarget): syncConfig, string(openrtb_ext.BidderAdtelligent): syncConfig, string(openrtb_ext.BidderAdvangelists): syncConfig, + string(openrtb_ext.BidderAdxcg): syncConfig, + string(openrtb_ext.BidderAdyoulike): syncConfig, string(openrtb_ext.BidderAJA): syncConfig, string(openrtb_ext.BidderAMX): syncConfig, string(openrtb_ext.BidderAppnexus): syncConfig, @@ -40,6 +42,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderConsumable): syncConfig, string(openrtb_ext.BidderConversant): syncConfig, string(openrtb_ext.BidderCpmstar): syncConfig, + string(openrtb_ext.BidderCriteo): syncConfig, string(openrtb_ext.BidderDatablocks): syncConfig, string(openrtb_ext.BidderDmx): syncConfig, string(openrtb_ext.BidderDeepintent): syncConfig, @@ -53,6 +56,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderImprovedigital): syncConfig, string(openrtb_ext.BidderInvibes): syncConfig, string(openrtb_ext.BidderIx): syncConfig, + string(openrtb_ext.BidderJixie): syncConfig, string(openrtb_ext.BidderKrushmedia): syncConfig, string(openrtb_ext.BidderLifestreet): syncConfig, string(openrtb_ext.BidderLockerDome): syncConfig, @@ -64,7 +68,9 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderNanoInteractive): syncConfig, string(openrtb_ext.BidderNinthDecimal): syncConfig, string(openrtb_ext.BidderNoBid): syncConfig, + string(openrtb_ext.BidderOneTag): syncConfig, string(openrtb_ext.BidderOpenx): syncConfig, + string(openrtb_ext.BidderOutbrain): syncConfig, string(openrtb_ext.BidderPubmatic): syncConfig, string(openrtb_ext.BidderPulsepoint): syncConfig, string(openrtb_ext.BidderRhythmone): syncConfig, @@ -78,9 +84,11 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, string(openrtb_ext.BidderSynacormedia): syncConfig, + string(openrtb_ext.BidderTappx): syncConfig, string(openrtb_ext.BidderTelaria): syncConfig, string(openrtb_ext.BidderTriplelift): syncConfig, string(openrtb_ext.BidderTripleliftNative): syncConfig, + string(openrtb_ext.BidderTrustX): syncConfig, string(openrtb_ext.BidderUcfunnel): syncConfig, string(openrtb_ext.BidderUnruly): syncConfig, string(openrtb_ext.BidderValueImpression): syncConfig, @@ -101,6 +109,8 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderAdot: true, openrtb_ext.BidderAdprime: true, openrtb_ext.BidderApplogy: true, + openrtb_ext.BidderBidmachine: true, + openrtb_ext.BidderEpom: true, openrtb_ext.BidderDecenterAds: true, openrtb_ext.BidderInMobi: true, openrtb_ext.BidderKidoz: true, @@ -108,12 +118,13 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderMobfoxpb: true, openrtb_ext.BidderMobileFuse: true, openrtb_ext.BidderOrbidder: true, + openrtb_ext.BidderPangle: true, openrtb_ext.BidderPubnative: true, openrtb_ext.BidderRevcontent: true, openrtb_ext.BidderSilverMob: true, openrtb_ext.BidderSmaato: true, openrtb_ext.BidderSpotX: true, - openrtb_ext.BidderTappx: true, + openrtb_ext.BidderUnicorn: true, openrtb_ext.BidderYeahmobi: true, } @@ -130,31 +141,3 @@ func TestNewSyncerMap(t *testing.T) { } } } - -// Bidders may have an ID on the IAB-maintained global vendor list. -// This makes sure that we don't have conflicting IDs among Bidders in our project, -// since that's almost certainly a bug. -func TestVendorIDUniqueness(t *testing.T) { - cfg := &config.Configuration{} - syncers := NewSyncerMap(cfg) - - idMap := make(map[uint16]openrtb_ext.BidderName, len(syncers)) - for name, syncer := range syncers { - id := syncer.GDPRVendorID() - if id == 0 { - continue - } - - if oldName, ok := idMap[id]; ok { - t.Errorf("GDPR VendorList ID %d used by both %s and %s. These must be unique.", id, oldName, name) - } - idMap[id] = name - } -} - -func assertStringsMatch(t *testing.T, expected string, actual string) { - t.Helper() - if expected != actual { - t.Errorf("Expected %s, got %s", expected, actual) - } -} diff --git a/util/httputil/httputil.go b/util/httputil/httputil.go index 93bcca2a8c5..461512771b3 100644 --- a/util/httputil/httputil.go +++ b/util/httputil/httputil.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - "github.com/PubMatic-OpenWrap/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/iputil" ) var ( diff --git a/util/httputil/httputil_test.go b/util/httputil/httputil_test.go index 7b6a9a504f1..f7166740fe5 100644 --- a/util/httputil/httputil_test.go +++ b/util/httputil/httputil_test.go @@ -6,7 +6,7 @@ import ( "net/http" "testing" - "github.com/PubMatic-OpenWrap/prebid-server/util/iputil" + "github.com/prebid/prebid-server/util/iputil" "github.com/stretchr/testify/assert" ) diff --git a/util/iosutil/iosutil.go b/util/iosutil/iosutil.go new file mode 100644 index 00000000000..19d7c53d99a --- /dev/null +++ b/util/iosutil/iosutil.go @@ -0,0 +1,78 @@ +package iosutil + +import ( + "errors" + "strconv" + "strings" +) + +// Version specifies the version of an iOS device. +type Version struct { + Major int + Minor int +} + +// ParseVersion parses the major.minor version for an iOS device. +func ParseVersion(v string) (Version, error) { + parts := strings.Split(v, ".") + + if len(parts) != 2 { + return Version{}, errors.New("expected major.minor format") + } + + major, err := strconv.Atoi(parts[0]) + if err != nil { + return Version{}, errors.New("major version is not an integer") + } + + minor, err := strconv.Atoi(parts[1]) + if err != nil { + return Version{}, errors.New("minor version is not an integer") + } + + version := Version{ + Major: major, + Minor: minor, + } + return version, nil +} + +// EqualOrGreater returns true if the iOS device version is equal or greater to the desired version, using semantic versioning. +func (v Version) EqualOrGreater(major, minor int) bool { + if v.Major == major { + return v.Minor >= minor + } + + return v.Major > major +} + +// VersionClassification describes iOS version classifications which are important to Prebid Server. +type VersionClassification int + +// Values of VersionClassification. +const ( + VersionUnknown VersionClassification = iota + Version140 + Version141 + Version142OrGreater +) + +// DetectVersionClassification detects the iOS version classification. +func DetectVersionClassification(v string) VersionClassification { + // exact comparisons first. no parsing required. + if v == "14.0" { + return Version140 + } + if v == "14.1" { + return Version141 + } + + // semantic versioning comparison second. parsing required. + if iosVersion, err := ParseVersion(v); err == nil { + if iosVersion.EqualOrGreater(14, 2) { + return Version142OrGreater + } + } + + return VersionUnknown +} diff --git a/util/iosutil/iosutil_test.go b/util/iosutil/iosutil_test.go new file mode 100644 index 00000000000..2103760c1dc --- /dev/null +++ b/util/iosutil/iosutil_test.go @@ -0,0 +1,149 @@ +package iosutil + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseVersion(t *testing.T) { + tests := []struct { + description string + given string + expectedVersion Version + expectedError string + }{ + { + description: "Valid", + given: "14.2", + expectedVersion: Version{Major: 14, Minor: 2}, + }, + { + description: "Invalid Parts - Empty", + given: "", + expectedError: "expected major.minor format", + }, + { + description: "Invalid Parts - Too Few", + given: "14", + expectedError: "expected major.minor format", + }, + { + description: "Invalid Parts - Too Many", + given: "14.2.1", + expectedError: "expected major.minor format", + }, + { + description: "Invalid Major", + given: "xxx.2", + expectedError: "major version is not an integer", + }, + { + description: "Invalid Minor", + given: "14.xxx", + expectedError: "minor version is not an integer", + }, + } + + for _, test := range tests { + version, err := ParseVersion(test.given) + + if test.expectedError == "" { + assert.NoError(t, err, test.description+":err") + } else { + assert.EqualError(t, err, test.expectedError, test.description+":err") + } + + assert.Equal(t, test.expectedVersion, version, test.description+":version") + } +} + +func TestEqualOrGreater(t *testing.T) { + givenMajor := 14 + givenMinor := 2 + + tests := []struct { + description string + givenVersion Version + expected bool + }{ + { + description: "Less Than By Major + Minor", + givenVersion: Version{Major: 13, Minor: 1}, + expected: false, + }, + { + description: "Less Than By Major", + givenVersion: Version{Major: 13, Minor: 2}, + expected: false, + }, + { + description: "Less Than By Minor", + givenVersion: Version{Major: 14, Minor: 1}, + expected: false, + }, + { + description: "Equal", + givenVersion: Version{Major: 14, Minor: 2}, + expected: true, + }, + { + description: "Greater By Major + Minor", + givenVersion: Version{Major: 15, Minor: 3}, + expected: true, + }, + { + description: "Greater By Major", + givenVersion: Version{Major: 15, Minor: 2}, + expected: true, + }, + { + description: "Greater By Minor", + givenVersion: Version{Major: 14, Minor: 3}, + expected: true, + }, + } + + for _, test := range tests { + result := test.givenVersion.EqualOrGreater(givenMajor, givenMinor) + assert.Equal(t, test.expected, result, test.description) + } +} + +func TestDetectVersionClassification(t *testing.T) { + + tests := []struct { + given string + expected VersionClassification + }{ + { + given: "13.0", + expected: VersionUnknown, + }, + { + given: "14.0", + expected: Version140, + }, + { + given: "14.1", + expected: Version141, + }, + { + given: "14.2", + expected: Version142OrGreater, + }, + { + given: "14.3", + expected: Version142OrGreater, + }, + { + given: "15.0", + expected: Version142OrGreater, + }, + } + + for _, test := range tests { + result := DetectVersionClassification(test.given) + assert.Equal(t, test.expected, result, test.given) + } +} diff --git a/util/task/ticker_task_test.go b/util/task/ticker_task_test.go index 92cf6835ea6..27551c9a2c2 100644 --- a/util/task/ticker_task_test.go +++ b/util/task/ticker_task_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/PubMatic-OpenWrap/prebid-server/util/task" + "github.com/prebid/prebid-server/util/task" "github.com/stretchr/testify/assert" ) From fa6d8195ebd74dc401ef60bca1f8481ce3ec76f5 Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Fri, 14 May 2021 15:56:07 +0530 Subject: [PATCH 05/31] UOE-6319: Upgraded prebid-server to 0.157.0 (#156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Default TCF1 GVL in anticipation of IAB no longer hosting the v1 GVL (#1433) * update to the latest go-gdpr release (#1436) * Video endpoint bid selection enhancements (#1419) Co-authored-by: Veronika Solovei * [WIP] Bid deduplication enhancement (#1430) Co-authored-by: Veronika Solovei * Refactor rate converter separating scheduler from converter logic to improve testability (#1394) * Fix TCF1 Fetcher Fallback (#1438) * Eplanning adapter: Get domain from page (#1434) * Fix no bid debug log (#1375) * Update the fallback GVL to last version (#1440) * Enable geo activation of GDPR flag (#1427) * Validate External Cache Host (#1422) * first draft * Little tweaks * Scott's review part 1 * Scott's review corrections part 2 * Scotts refactor * correction in config_test.go * Correction and refactor * Multiple return statements * Test case refactor Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * Fixes bug (#1448) * Fixes bug * shortens list * Added adpod_id to request extension (#1444) * Added adpod_id to request -> ext -> appnexus and modified requests splitting based on pod * Unit test fix * Unit test fix * Minor unit test fixes * Code refactoring * Minor code and unit tests refactoring * Unit tests refactoring Co-authored-by: Veronika Solovei * Adform adapter: additional targeting params added (#1424) * Fix minor error message spelling mistake "vastml" -> "vastxml" (#1455) * Fixing comment for usage of deal priority field (#1451) * moving docs to website repo (#1443) * Fix bid dedup (#1456) Co-authored-by: Veronika Solovei * consumable: Correct width and height reported in response. (#1459) Prebid Server now responds with the width and height specified in the Bid Response from Consumable. Previously it would reuse the width and height specified in the Bid Request. That older behaviour was ported from an older version of the prebid.js adapter but is no longer valid. * Panics happen when left with zero length []Imp (#1462) * Add Scheme Option To External Cache URL (#1460) * Update gamma adapter (#1447) * Gamma SSP Adapter * Add Gamma SSP server adapter * increase coverage * Fix conflict with base master * Add check MediaType for Imp * Implement Multi Imps request * Changes requested * remove bad-request * increase coverage * Remove duplicate test file * Update gamma.go * Update gamma.go * Update gamma.go * Update config.go Remove Gamma User Sync Url from config * Gamma SSP Adapter * Add Gamma SSP server adapter * increase coverage * Fix conflict with base master * Add check MediaType for Imp * Implement Multi Imps request * Changes requested * remove bad-request * increase coverage * Remove duplicate test file * Update gamma.go * Update gamma.go * update gamma adapter * return nil when have No-Bid Signaling * add missing-adm.json * discard the bid that's missing adm * discard the bid that's missing adm * escape vast instead of encoded it * expand test coverage Co-authored-by: Easy Life * fix: avoid unexpected EOF on gz writer (#1449) * Smaato adapter: support for video mediaType (#1463) Co-authored-by: vikram * Rubicon liveramp param (#1466) Add liveramp mapping to user.ext should translate the "liveramp.com" id from the "user.ext.eids" array to "user.ext.liveramp_idl" as follows: ``` { "user": { "ext": { "eids": [{ "source": 'liveramp.com', "uids": [{ "id": "T7JiRRvsRAmh88" }] }] } } } ``` to XAPI: ``` { "user": { "ext": { "liveramp_idl": "T7JiRRvsRAmh88" } } } ``` * Consolidate StoredRequest configs, add validation for all data types (#1453) * Fix Test TestEventChannel_OutputFormat (#1468) * Add ability to randomly generate source.TID if empty and set publisher.ID to resolved account ID (#1439) * Add support for Account configuration (PBID-727, #1395) (#1426) * Minor changes to accounts test coverage (#1475) * Brightroll adapter - adding config support (#1461) * Refactor TCF 1/2 Vendor List Fetcher Tests (#1441) * Add validation checker for PRs and merges with github actions (#1476) * Cache refactor (#1431) Reason: Cache has Fetcher-like functionality to handle both requests and imps at a time. Internally, it still uses two caches configured and searched separately, causing some code repetition. Reusing this code to cache other objects like accounts is not easy. Keeping the req/imp repetition in fetcher and out of cache allows for a reusable simpler cache, preserving existing fetcher functionality. Changes in this set: Cache is now a simple generic id->RawMessage store fetcherWithCache handles the separate req and imp caches ComposedCache handles single caches - but it does not appear to be used Removed cache overlap tests since they do not apply now Slightly less code * Pass Through First Party Context Data (#1479) * Added new size 640x360 (Id: 198) (#1490) * Refactor: move getAccount to accounts package (from openrtb2) (#1483) * Fixed TCF2 Geo Only Enforcement (#1492) * New colossus adapter [Clean branch] (#1495) Co-authored-by: Aiholkin * New: InMobi Prebid Server Adapter (#1489) * Adding InMobi adapter * code review feedback, also explicitly working with Imp[0], as we don't support multiple impressions * less tolerant bidder params due to sneaky 1.13 -> 1.14+ change * Revert "Added new size 640x360 (Id: 198) (#1490)" (#1501) This reverts commit fa23f5c226df99a9a4ef318100fdb7d84d3e40fa. * CCPA Publisher No Sale Relationships (#1465) * Fix Merge Conflict (#1502) * Update conversant adapter for new prebid-server interface (#1484) * Implement returnCreative (#1493) * Working solution * clean-up * Test copy/paste error Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * ConnectAd S2S Adapter (#1505) * between adapter (#1437) Co-authored-by: Alexey Elymanov * Invibes adapter (#1469) Co-authored-by: aurel.vasile * Refactor postgres event producer so it will run either the full or de… (#1485) * Refactor postgres event producer so it will run either the full or delta query periodically * Minor cleanup, follow golang conventions, declare const slice, add test cases * Remove comments * Bidder Uniqueness Gatekeeping Test (#1506) * ucfunnel adapter update end point (#1511) * Refactor EEAC map to be more in line with the nonstandard publisher map (#1514) * Added bunch of new sizes (#1516) * New krushmedia bid adapter (#1504) * Invibes: Generic domainId parameter (#1512) * Smarty ads adapter (#1500) Co-authored-by: Kushneryk Pavlo Co-authored-by: user * Add vscode remote container development files (#1481) * First commit (#1510) Co-authored-by: Gus Carreon * Vtrack and event endpoints (#1467) * Rework pubstack module tests to remove race conditions (#1522) * Rework pubstack module tests to remove race conditions * PR feedback * Remove event count and add helper methods to assert events received on channel * Updating smartadserver endpoint configuration. (#1531) Co-authored-by: tadam * Add new size 500x1000 (ID: 548) (#1536) * Fix missing Request parameter for Adgeneration Adapter (#1525) * Fix endpoint url for TheMediaGrid Bid Adapter (#1541) * Add Account cache (#1519) * Add bidder name key support (#1496) * Simplifying exchange module: bidResponseExt gets built anyway (#1518) * first draft * Scott's feedback * stepping out real quick * add cache errors to bidResponseExt before marshalling * Removed vim's swp file Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * Correct GetCpmStringValue's second return value (#1520) * Add metrics to capture stored data fetch all/delta durations with fetch status (#1515) * Adds preferDeals support (#1528) * Emxd 3336 add app video ctv (#1529) * Adapter changes for app and video support * adding ctv devicetype test case * Adding whitespace * Updates based on feedback from Prebid team * protocol bug fix and testing * Modifying test cases to accomodate new imp.ext field * bidtype bug fix and additonal testcase for storeUrl Co-authored-by: Rakesh Balakrishnan Co-authored-by: Dan Bogdan * Add http api for fetching accounts (#1545) * Add missing postgres cache init config validation * Acuity ads adapter (#1537) Co-authored-by: Kushneryk Pavlo * Yieldmo app support in yaml file (#1542) Co-authored-by: Winston * Add metrics for account cache (#1543) * [Invibes] remove user sync for invibes (#1550) * [invibes] new bidder stub * [invibes] make request * [invibes] bid request parameters * [invibes] fix errors, add tests * [invibes] new version of MakeBids * cleaning code * [invibes] production urls, isamp flag * [invibes] fix parameters * [invibes] new test parameter * [invibes] change maintainer email * [invibes] PR fixes * [invibes] fix parameters test * [invibes] refactor endpoint template and bidVersion * [Invibes] fix tests * [invibes] resolve PR * [invibes] fix test * [invibes] fix test * [invibes] generic domainId parameter * [invibes] remove invibes cookie sync * [Invibes] comment missing Usersync Co-authored-by: aurel.vasile * Add Support For imp.ext.prebid For DealTiers (#1539) * Add Support For imp.ext.prebid For DealTiers * Remove Normalization * Add Accounts to http cache events (#1553) * Fix JSON tests ignore expected message field (#1450) * NoBid version 1.0. Initial commit. (#1547) Co-authored-by: Reda Guermas * Added dealTierSatisfied parameters in exchange.pbsOrtbBid and openrtb_ext.ExtBidPrebid and dealPriority in openrtb_ext.ExtBidPrebid (#1558) Co-authored-by: Shriprasad * Add client/AccountID support into Adoppler adapter. (#1535) * Optionally read IFA value and add it the the request url (Adhese) (#1563) * Add AMX RTB adapter (#1549) * update Datablocks usersync.go (#1572) * 33Across: Add video support in adapter (#1557) * SilverMob adapter (#1561) * SilverMob adapter * Fixes andchanges according to notes in PR * Remaining fixes: multibids, expectedMakeRequestsErrors * removed log * removed log * Multi-bid test * Removed unnesesary block Co-authored-by: Anton Nikityuk * Updated ePlanning GVL ID (#1574) * update adpone google vendor id (#1577) * ADtelligent gvlid (#1581) * Add account/ host GDPR enabled flags & account per request type GDPR enabled flags (#1564) * Add account level request type specific and general GDPR enabled flags * Clean up test TestAccountLevelGDPREnabled * Add host-level GDPR enabled flag * Move account GDPR enable check as receiver method on accountGDPR * Remove mapstructure annotations on account structs * Minor test updates * Re-add mapstructure annotations on account structs * Change RequestType to IntegrationType and struct annotation formatting * Update comment * Update account IntegrationType comments * Remove extra space in config/accounts.go via gofmt * DMX Bidfloor fix (#1579) * adform bidder video bid response support (#1573) * Fix Beachfront JSON tests (#1578) * Add account CCPA enabled and per-request-type enabled flags (#1566) * Add account level request-type-specific and general CCPA enabled flags * Remove mapstructure annotations on CCPA account structs and clean up CCPA tests * Adjust account/host CCPA enabled flag logic to incorporate feedback on similar GDPR feature * Add shared privacy policy account integration data structure * Refactor EnabledForIntegrationType methods on account privacy objects * Minor test refactor * Simplify logic in EnabledForIntegrationType methods * Refactored HoldAuction Arguments (#1570) * Fix bug in request.imp.ext Validation (#1575) * First draft * Brian's reivew * Removed leftover comments Co-authored-by: Gus Carreon * Bugfix: default admin port is 6060 (#1595) * Add timestamp to analytics and response.ext.prebid.auctiontimestamp l… (#1584) * Added app capabilities to VerizonMedia adapter (#1596) Co-authored-by: oath-jac * New Adapter Initialization Framework (#1532) * Fix 33Across App Handling (#1602) * Fix adapter JSON tests to have the right test structure (#1589) * Fix JSON EMX Digital * Fix JSON Brightroll * Fix JSON Beintoo * Fix JSON Gamoshi * Fix JSON Kubient * Fix JSON Marsmedia * Fix JSON Nanointeractive * Fix JSON Telaria * Fix JSON valueimpression * Fix JSON smartyads * Fix JSON rhythmone * Fix JSON krushmedia * Fix JSON cpmstar * Fix JSON acuityads * Fix JSON avocet * Rename wrongly named acuity ads test file * Fix JSON gamma * Add expected no bid responses * Fixed indentation and asesome-markup * Added usersync support to Between SSP adapter; Major fixes and refactor (#1587) Co-authored-by: Egor Skorokhodov * Removed Safari Metric (#1571) * Deepintent adapter (#1524) Co-authored-by: Sourabh Gandhe * update mobilefuse endpoint (#1606) Co-authored-by: Dan Barnett * Fix Missing Consumable Clock (#1610) * Remove Hook Scripts (#1614) * Add config gdpr.amp_exception deprecation warning (#1612) * Refactor Adapter Config To Its Own File (#1608) * RP adapter: use video placement parameter to set size ID (#1607) * Add a BidderRequest struct to hold bidder specific request info (#1611) * Add warning that gdpr checks will be skipped when gdpr.host_vendor_id… (#1615) * Add TLS Handshake connection metrics (#1613) * Improve GitHub Actions Validation (#1590) * Move SSL to Server directory (#1625) * Rename currencies to currency (#1626) * Deepintent: Params normalization (#1617) Co-authored-by: Sourabh Gandhe * Set Kubient email to prebid@kubient.com (#1629) * Rename pbsmetrics to metrics (#1624) * 33Across: Add support for multi-imp requests (#1609) * changed usersync endpoint (#1631) Co-authored-by: Sourabh Gandhe * Remove legacy GDPR AMP config flag used to prevent buyer ID scrub on AMP requests (#1565) * Updating contact info for adprime (#1640) * ucfunnel adapter update end point (#1639) * New Adapter: Mobfox (#1585) Co-authored-by: mobfox * IX: Implement Bidder interface, update endpoint. (#1569) Co-authored-by: Index Exchange Prebid Team * Fix GDPR consent assumption when gdpr req signal is unambiguous and s… (#1591) * Fix GDPR consent assumption when gdpr req signal is unambiguous and set to 1 and consent string is blank * Refactor TestAllowPersonalInfo to make it table-based and add empty consent string cases * Update PersonalInfoAllowed to allow PI if the request indicates GDPR does not apply * Update test descriptions * Update default vendor permissions to only allow PI based on UserSyncIfAmbiguous if request GDPR signal is ambiguous * Change GDPR request signal type name and other PR feedback code style changes * Rename GDPR package signal constants and consolidate gdprEnforced and gdprEnabled variables * Hoist GDPR signal/empty consent checks before vendor list check * Rename gdpr to gdprSignal in Permissions interface and implementations * Fix merge mistakes * Update gdpr logic to set the gdpr signal when ambiguous according to the config flag * Add userSyncIfAmbiguous to all test cases in TestAllowPersonalInfo * Simplify TestAllowPersonalInfo * Fix appnexus adapter not setting currency in the bid response (#1642) Co-authored-by: Gus * Add Adot adapter (#1605) Co-authored-by: Aurélien Giudici * Refactor AMP Param Parsing (#1627) * Refactor AMP Param Parsing * Added Tests * Enforce GDPR privacy if there's an error parsing consent (#1593) * Enforce GDPR privacy if there's an error parsing consent * Update test with consent string variables to improve readability * Fix test typo * Update test variable names to follow go conventions * MediaFuse adapter (#1635) * MediaFuse alias * Syncer and tests * gvlid * gvlid * new mail * New Adapter: Revcontent (#1622) * Fix Unruly Bidder Parmaters (#1616) * Implement EID Permissions (#1633) * Implement EID Permissions * Idsync removal (#1644) Co-authored-by: steve-a-districtm Co-authored-by: Steve Alliance * Audit beachfront tests and change some videoResponseType details (#1638) * Adding Events support in bid responses (#1597) * Fix Shared Memory Corruption In EMX_Digital (#1646) * Add gdpr.tcf1.fetch_gvl deprecation warning and update GVL subdomain (#1660) * Bubble up GDPR signal/consent errors while applying privacy policies (#1651) * Always sync when GDPR globally enabled and allow host cookie sync … (#1656) * Eplanning: new prioritization metric for adunit sizes (#1648) * Eplanning: new prioritization metric for adunit sizes * removing IX's usersync default URL (#1670) Co-authored-by: Index Exchange Prebid Team * AMX Bid Adapter: Loop Variable Bug (#1675) * requestheaders: new parameter inside debug.httpcalls. to log request header details (#1659) * Added support for logging requestheaders inside httpCalls.requestheaders * Reverterd test case change * Modified outgoing mock request for appnexus, to send some request header information. Modified sample mock response such that ext.debug.httpcalls.appnexus.requestheaders will return the information of passed request headers * Addressed code review comments given by SyntaxNode. Also Moved RequestHeaders next to RequestBidy in openrtb_ext.ExtHttpCall Co-authored-by: Shriprasad * Updating pulsepoint adapter (#1663) * Debug disable feature implementation: (#1677) Co-authored-by: Veronika Solovei * Always use fallback GVL for TCF1 (#1657) * Update TCF2 GVL subdomain and always use fallback GVL for TCF1 * Add config test coverage for invalid TCF1 FetchGVL and AMP Exception * Delete obselete test * Adform adapter: digitrust cleanup (#1690) * adform secure endpoint as default setting * digitrust cleanup * New Adapter: DecenterAds (#1669) Co-authored-by: vlad * Handle empty consent string during cookie sync and setuid (#1671) * Handle empty consent string during cookie sync and setuid * Remove todo comment * Make auction test table driven and convert GDPR impl normalize method to pass by value * Moved legacy auction endpoint signal parsing into its own method and removed unnecessary test cases * Fix SignalParse method to return nil for error when raw signal is empty and other PR feedback * Tappx User Syncer + Site Update (#1674) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * Beachfront Additional tests (#1679) * added place holder files for all but a couple of the intended new tests. I need to grok what those couple mean before being able to name a file. * This covers most of the suggested cases and a couple more that occured to me. I'll look at the couple that I held off on next. * added the unmarshal tests and found a couple problems to address in the process. * removed my __debug_bin. should be in gitignore. * A bit of clean up and commenting. Bumped version number. Added __debug_bin to .gitignore. This is the debugging binary created by Visual Studio Code, or at least version 1.52.1. * missed a bunch of version strings * removed IP faker * If IP is not included in an AdM request, an error is now thrown for the AdM imp instead of faking it. The AdM endpoint is the only one that requires an IP. Also, added several "no-ip" test cases. * Whent back to the fake IP solution instead of the error. Removed most of the "no-ip" test cases, leaving one. * changed ip in adm-video.json to not match the faker ip * removed a debugging comment * New Adapter: Onetag (#1695) * Pubmatic: Trimming publisher ID before passing (#1685) * Trimming publisher ID before passing * Fix typos in nobid.json (#1704) * Fix Typo In Adform Bidder Params (#1705) * Don't Load GVL v1 for TCF2 (+ TCF1 Cleanup) (#1693) * Typo fix for connectad bidder params (#1706) * Typo fix for invibes bidder params (#1707) * Typo fix nanointeractive bidder params (#1708) * Isolate /info/bidders Data Model + Add Uses HTTPS Flag (#1692) * Initial Commit * Merge Conflict Fixes * Removed Unncessary JSON Attributes * Removed Dev Notes * Add Missing validateDefaultAliases Test * Improved Reversed Test * Remove Var Scope Confusion * Proper Tests For Bidder Param Validator * Removed Unused Test Setup * New Adapter: Epom (#1680) Co-authored-by: Vasyl Zarva * New Adapter: Pangle (#1697) Co-authored-by: hcai * Fix Merge Conflict (#1714) * GumGum: adds pubId and irisid properties/parameters (#1664) * adds pubId and irisid properties * updates per naming convention & makes a video copy * updates when to copy banner, adds Publisher fallback and multiformat request * adds more json tests * rename the json file to remove whitespaces * Accommodate Apple iOS LMT bug (#1718) * New Adapter: jixie (#1698) * initial commit * added notes file for temp use * jixie adapter development work * jixie adaptor development work: mainly the test json files but also the jixie usersync code * added a test case with accountid. and cosmetic line changes in the banner*json test file * updated the jixie user sync: the endpoint and some params stuf * tks and fixing per comments on pull request 1698 * responding to guscarreon's comments: -more checking in makerequest of the bidder params (added 2 more test jsons) -removed blank lines, lines commented out -test_params: a case with unit alone -BadInput error * responding to review. put condition on jixie unit string in the bidder-params/jixie.json file. removed checking in jixie.go that has become unnecssary. removed unnec test cases. updated params-test * added one failed params test * removed a function that I no longer call! * renamed JixieAdapter to adapter * removed bidfloor from jixie explicit ext params * Fix Regs Nil Condition (#1723) * Mobfox: Add rout to adexcange (#1702) Co-authored-by: mobfox * New Adapter: TrustX (#1726) * New Adapter: UNICORN (#1719) * add bidder-info, bidder-params for UNICORN * Add adapter * Fixes GDPR bug about being overly strict on publisher restrictions (#1730) * 33Across: Updated exchange endpoint (#1738) * New Adapter: Adyoulike (#1700) Co-authored-by: Damien Dumas * Hoist GVL ID To Bidder Info (#1721) * Improve Digital adapter: add support for native ads (#1746) * Add Support For SkAdN + Refactor Split Imps (#1741) * No Longer Move bid.ext To bid.ext.bidder (#1742) * No Longer Move bid.ext To bid.ext.bidder * Remove Similar Behavior From seatbid.ext * Avoid Second Bid Copy * Removed Unused seatbid.ext * Typo fix: adyoulike bidder param debug description (#1755) * Aliases: Better Error Message For Disabled Bidder (#1751) * beachfront: Changes to support real 204 (#1737) * Fix race condition in 33across.go (#1757) Co-authored-by: Gus Carreon * Revert "Fix race condition in 33across.go (#1757)" (#1763) This reverts commit bdf1e7b3e13bdf87d3282bf74472fc66504537d5. * Replace TravisCI With GitHub Actions (#1754) * Initial Commit * Finished Configuration * Remove TravisCI * Remove TravisCI * Fix Go Version Badge * Correct Fix For Go Version Badge * Removed Custom Config File Name * Debug warnings (#1724) Co-authored-by: Veronika Solovei * Rubicon: Support sending segments to XAPI (#1752) Co-authored-by: Serhii Nahornyi * validateNativeContextTypes function test cases (#1743) * Applogy: Fix Shared Memory Overwriting (#1758) * Pubmatic: Fix Shared Memory Overwriting (#1759) * Beachfront: Fix Shared Memory Overwriting (#1762) * Fix race condition in Beachfront adapter * Removed nil check and simplified * FPD: Allow imp.ext.data To Passthrough To Adapters (#1765) * Smaato: Add support for app (#1767) Co-authored-by: Bernhard Pickenbrock * Update sync types (#1770) * 33across: Fix Shared Memory Overwriting (#1764) This reverts commit f7df258f061788ef7e72529115aa5fd554fa9f16. * Fix race condition in Yeahmobi adapter (#1761) Co-authored-by: Gus Carreon * Pubnative: Fix Shared Memory Overwriting (#1760) * Add request for registration (#1780) * Update OpenRTB Library (#1733) * Tappx changes - Backward compatible change of version (#1777) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * DMX: Enforcing w and h in imp (#1778) Co-authored-by: steve-a-districtm * Remove Authorization Headers From Debug Response (#1779) * Hide Authorization Headers In Debug Response * MakeExtHeaders Tests * Add Empty Test * Use http.Header Methods * Updates From Code Review * Fix Merge Conflict * New Adapter: Bidmachine (#1769) * New Adapter: Criteo (#1775) * Fix shared memory issue when stripping authorization header from bid requests (#1790) * RTB House: update parameters (#1785) * update parameters required by RTB House adapter * tabs to spaces Co-authored-by: Przemyslaw Iwanczak * Generate seatbid[].bid[].ext.prebid.bidid (#1772) * Basic GDPR enforcement for specific publisher-vendors. (#1782) * New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior * Zemanta: Rename Adapter To Outbrain (#1797) * Update openrtb library to v15 (#1802) * IX: Set bidVideo when category and duration is available (#1794) * Update IX defaults (#1799) Co-authored-by: Mike Burns * Update Adyoulike endpoint to hit production servers (#1805) * Openx: use bidfloor if set - prebid.js adapter behavior (#1795) * [ORBIDDER] add gvlVendorID and set bid response currency (#1798) * New Adapter: ADXCG (#1803) * Update kidoz properties to type string (#1808) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * Update bidmachine properties to type string (#1809) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * transform native eventtrackers to imptrackers and jstracker (#1811) * TheMediaGrid: Added processing of imp[].ext.data (#1807) * Renaming package github.com/PubMatic-OpenWrap/openrtb to github.com/mxmCherry/openrtb * Rename package github.com/PubMatic-OpenWrap/prebid-server to github.com/prebid/prebid-server * UOE-6196: OpenWrap S2S: Remove adpod_id from AppNexus adapter * Refactored code and fixed indentation * Fixed indentation for json files * Fixed indentation for json files * Fixed import in adapters/gumgum/gumgum.go * Reverted unwanted changes in test json files * Fixed unwanted git merge changes * Added missing field SkipDedup in ExtIncludeBrandCategory * Added missing Bidder field in ExtBid type * Exposing CookieSyncRequest for header-bidding * Temporary path change for static folder * Fixed static folder paths * Fixed default value in config for usersync_if_ambiguous * Fixed config after upgrade * Updated router.go to uncomment defaultRequest validation * Fixed path for accounts.filesystem.directorypath * Fixed diff with OW * Added DMX default usersync URL * Adding changes missed for UOE-5114 during prebid-server upgrade * Merged master Co-authored-by: hhhjort <31041505+hhhjort@users.noreply.github.com> Co-authored-by: Veronika Solovei Co-authored-by: Veronika Solovei Co-authored-by: Brian Sardo <1168933+bsardo@users.noreply.github.com> Co-authored-by: Scott Kay Co-authored-by: chino117 Co-authored-by: Cameron Rice <37162584+camrice@users.noreply.github.com> Co-authored-by: guscarreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Jurij Sinickij Co-authored-by: Rob Hazan Co-authored-by: bretg Co-authored-by: Daniel Cassidy Co-authored-by: GammaSSP <35954362+gammassp@users.noreply.github.com> Co-authored-by: Easy Life Co-authored-by: gpolaert Co-authored-by: Stephan Brosinski Co-authored-by: vikram Co-authored-by: Dmitriy Co-authored-by: Laurentiu Badea Co-authored-by: Mansi Nahar Co-authored-by: smithaammassamveettil <39389834+smithaammassamveettil@users.noreply.github.com> Co-authored-by: hdeodhar <35999856+hdeodhar@users.noreply.github.com> Co-authored-by: Bill Newman Co-authored-by: Aiholkin Co-authored-by: Daniel Lawrence Co-authored-by: johnwier <49074029+johnwier@users.noreply.github.com> Co-authored-by: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Co-authored-by: Alexey Elymanov Co-authored-by: Alexey Elymanov Co-authored-by: invibes <51820283+invibes@users.noreply.github.com> Co-authored-by: aurel.vasile Co-authored-by: ucfunnel <39581136+ucfunnel@users.noreply.github.com> Co-authored-by: Krushmedia <71434282+Krushmedia@users.noreply.github.com> Co-authored-by: Kushneryk Pavel Co-authored-by: Kushneryk Pavlo Co-authored-by: user Co-authored-by: Gus Carreon Co-authored-by: Daniel Barrigas Co-authored-by: tadam75 Co-authored-by: tadam Co-authored-by: Andrea Cannuni <57228257+ACannuniRP@users.noreply.github.com> Co-authored-by: Ad Generation Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: Gus Carreon Co-authored-by: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: Rakesh Balakrishnan Co-authored-by: Dan Bogdan Co-authored-by: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Co-authored-by: Winston-Yieldmo <46379634+Winston-Yieldmo@users.noreply.github.com> Co-authored-by: Winston Co-authored-by: redaguermas Co-authored-by: Reda Guermas Co-authored-by: ShriprasadM Co-authored-by: Shriprasad Co-authored-by: Viacheslav Chimishuk Co-authored-by: Sander Co-authored-by: Nick Jacob Co-authored-by: htang555 Co-authored-by: Aparna Rao Co-authored-by: silvermob <73727464+silvermob@users.noreply.github.com> Co-authored-by: Anton Nikityuk Co-authored-by: Seba Perez Co-authored-by: Sergio Co-authored-by: Gena Co-authored-by: Steve Alliance Co-authored-by: Peter Fröhlich Co-authored-by: oath-jac <45564796+oath-jac@users.noreply.github.com> Co-authored-by: oath-jac Co-authored-by: egsk Co-authored-by: Egor Skorokhodov Co-authored-by: Sourabh Gandhe Co-authored-by: Sourabh Gandhe Co-authored-by: dtbarne <7635750+dtbarne@users.noreply.github.com> Co-authored-by: Dan Barnett Co-authored-by: Serhii Nahornyi Co-authored-by: Marsel Co-authored-by: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> Co-authored-by: Index Exchange 3 Prebid Team Co-authored-by: Index Exchange Prebid Team Co-authored-by: Giudici-a <34242194+Giudici-a@users.noreply.github.com> Co-authored-by: Aurélien Giudici Co-authored-by: jcamp-revc <68560678+jcamp-revc@users.noreply.github.com> Co-authored-by: steve-a-districtm Co-authored-by: Steve Alliance Co-authored-by: Jim Naumann Co-authored-by: Anand Venkatraman Co-authored-by: Vladyslav Laktionov Co-authored-by: vlad Co-authored-by: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Co-authored-by: ubuntu Co-authored-by: Albert Grandes Co-authored-by: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Co-authored-by: agilfix Co-authored-by: epomrnd Co-authored-by: Vasyl Zarva Co-authored-by: Hengsheng Cai Co-authored-by: hcai Co-authored-by: susyt Co-authored-by: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Co-authored-by: faithnh Co-authored-by: guiann Co-authored-by: Damien Dumas Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Gus Carreon Co-authored-by: Serhii Nahornyi Co-authored-by: el-chuck Co-authored-by: Bernhard Pickenbrock Co-authored-by: Pavel Dunyashev Co-authored-by: Benjamin Co-authored-by: Przemysław Iwańczak <36727380+piwanczak@users.noreply.github.com> Co-authored-by: Przemyslaw Iwanczak Co-authored-by: Rok Sušnik Co-authored-by: Rok Sušnik Co-authored-by: ixjohnny <75964135+ixjohnny@users.noreply.github.com> Co-authored-by: Michael Burns Co-authored-by: Mike Burns Co-authored-by: Arne Schulz Co-authored-by: adxcgcom <31470944+adxcgcom@users.noreply.github.com> --- config/config.go | 3 +- endpoints/events/vtrack.go | 233 ++++++++ endpoints/events/vtrack_test.go | 560 ++++++++++++++++++ .../ctv/response/adpod_generator_test.go | 310 ++++++++++ endpoints/openrtb2/ctv_auction.go | 48 +- endpoints/openrtb2/ctv_auction_test.go | 93 +++ exchange/auction.go | 19 +- exchange/auction_test.go | 14 + exchange/events.go | 23 +- exchange/events_test.go | 107 ++++ exchange/exchange.go | 55 +- exchange/exchange_test.go | 30 +- go.mod | 4 +- go.sum | 4 + openrtb_ext/request.go | 4 + 15 files changed, 1475 insertions(+), 32 deletions(-) diff --git a/config/config.go b/config/config.go index 7a7deaa43ad..dcf9d445571 100755 --- a/config/config.go +++ b/config/config.go @@ -81,7 +81,8 @@ type Configuration struct { // When true, PBS will assign a randomly generated UUID to req.Source.TID if it is empty AutoGenSourceTID bool `mapstructure:"auto_gen_source_tid"` //When true, new bid id will be generated in seatbid[].bid[].ext.prebid.bidid and used in event urls instead - GenerateBidID bool `mapstructure:"generate_bid_id"` + GenerateBidID bool `mapstructure:"generate_bid_id"` + TrackerURL string `mapstructure:"tracker_url"` } const MIN_COOKIE_SIZE_BYTES = 500 diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index da845162de2..27a1ceea746 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -3,10 +3,15 @@ package events import ( "context" "encoding/json" + "errors" "fmt" + "github.com/PubMatic-OpenWrap/etree" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "io" "io/ioutil" "net/http" + "net/url" "strings" "time" @@ -45,6 +50,40 @@ type CacheObject struct { UUID string `json:"uuid"` } +// 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-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]" +) + +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", +} + func NewVTrackEndpoint(cfg *config.Configuration, accounts stored_requests.AccountFetcher, cache prebid_cache_client.Client, bidderInfos config.BidderInfos) httprouter.Handle { vte := &vtrackEndpoint{ Cfg: cfg, @@ -301,3 +340,197 @@ func ModifyVastXmlJSON(externalUrl string, data json.RawMessage, bidid, bidder, } return json.RawMessage(vast) } + +//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, bidder, 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, bidder, 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 openrtb2.VideoLinearityLinearInStream: + creative.AddChild(doc.CreateElement("Linear")) + case openrtb2.VideoLinearityNonLinearOverlay: + 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, bidder 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, req.Site.Domain) + eventURL = replaceMacro(eventURL, VASTPageURLMacro, req.Site.Page) + if nil != req.Site.Publisher { + eventURL = replaceMacro(eventURL, PBSAccountMacro, req.Site.Publisher.ID) + } + } + + if len(bid.ADomain) > 0 { + //eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, strings.Join(bid.ADomain, ",")) + domain, err := extractDomain(bid.ADomain[0]) + if nil == err { + eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, domain) + } else { + glog.Warningf("Unable to extract domain from '%s'. [%s]", bid.ADomain[0], err.Error()) + } + } + + eventURL = replaceMacro(eventURL, PBSBidderMacro, bidder) + eventURL = replaceMacro(eventURL, PBSBidIDMacro, 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) + } + eventURLMap[event] = eventURL + } + return eventURLMap +} + +func replaceMacro(trackerURL, macro, value string) string { + macro = strings.TrimSpace(macro) + if strings.HasPrefix(macro, "[") && strings.HasSuffix(macro, "]") && len(strings.TrimSpace(value)) > 0 { + trackerURL = strings.ReplaceAll(trackerURL, macro, url.QueryEscape(value)) + } 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 +} diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 1766f2e2e0d..4897fe71034 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -5,8 +5,11 @@ import ( "context" "encoding/json" "fmt" + "github.com/PubMatic-OpenWrap/etree" + "github.com/mxmCherry/openrtb/v15/openrtb2" "io/ioutil" "net/http/httptest" + "net/url" "strings" "testing" @@ -690,3 +693,560 @@ func getVTrackRequestData(wi bool, wic bool) (db []byte, e error) { return data.Bytes(), e } + +func TestInjectVideoEventTrackers(t *testing.T) { + type args struct { + externalURL 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) + biddername := "test_bidder" + injectedVast, injected, ierr := InjectVideoEventTrackers(tc.args.externalURL, vast, tc.args.bid, biddername, 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 + bidder 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=[DOMAIN]", + "midpoint": "http://company.tracker.com?eventId=3&appbundle=[DOMAIN]", + "thirdQuartile": "http://company.tracker.com?eventId=5&appbundle=[DOMAIN]", + "start": "http://company.tracker.com?eventId=2&appbundle=[DOMAIN]", + "complete": "http://company.tracker.com?eventId=6&appbundle=[DOMAIN]"}, + }, + }, + { + 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: "all_macros", // 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]", + 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"}, + bidder: "test_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_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", + "midpoint": "https://company.tracker.com?operId=8&e=3&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", + "thirdQuartile": "https://company.tracker.com?operId=8&e=5&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", + "complete": "https://company.tracker.com?operId=8&e=6&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", + "start": "https://company.tracker.com?operId=8&e=2&p=5890&pid=100&v=2&ts=1234567890&pn=test_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"}, + }, + }, + } + 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.bidder, 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=[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/openrtb2/ctv/response/adpod_generator_test.go b/endpoints/openrtb2/ctv/response/adpod_generator_test.go index f387d5bbe24..ec8d63244ed 100644 --- a/endpoints/openrtb2/ctv/response/adpod_generator_test.go +++ b/endpoints/openrtb2/ctv/response/adpod_generator_test.go @@ -1,6 +1,8 @@ package response import ( + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/constant" + "sort" "testing" "github.com/stretchr/testify/assert" @@ -85,3 +87,311 @@ func Test_findUniqueCombinations(t *testing.T) { }) } } + +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`}}, reasonCode: constant.CTVRCCategoryExclusion}, + `bid-2`: {bid: &types.Bid{Bid: &openrtb2.Bid{ID: `bid-2`}}, reasonCode: constant.CTVRCCategoryExclusion}, + `bid-3`: {bid: &types.Bid{Bid: &openrtb2.Bid{ID: `bid-3`}}, reasonCode: constant.CTVRCCategoryExclusion}, + }, + }, + }, + }, + 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`}}, reasonCode: constant.CTVRCCategoryExclusion}, + }, + }, + }, + }, + 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_auction.go b/endpoints/openrtb2/ctv_auction.go index c28904591c7..c92ed4d0d17 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -5,8 +5,10 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/endpoints/events" "math" "net/http" + "net/url" "sort" "strconv" "strings" @@ -58,7 +60,7 @@ func NewCTVEndpoint( requestsByID stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, accounts stored_requests.AccountFetcher, - //categories stored_requests.CategoryFetcher, +//categories stored_requests.CategoryFetcher, cfg *config.Configuration, met metrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, @@ -828,6 +830,15 @@ func getAdPodBidCreative(video *openrtb2.Video, adpod *types.AdPodBid) *string { continue } + // 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 @@ -907,3 +918,38 @@ func addTargetingKey(bid *openrtb2.Bid, key openrtb_ext.TargetingKey, value stri } 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()) + } + } + } + } +} diff --git a/endpoints/openrtb2/ctv_auction_test.go b/endpoints/openrtb2/ctv_auction_test.go index 7284f856ea3..f03312ead9c 100644 --- a/endpoints/openrtb2/ctv_auction_test.go +++ b/endpoints/openrtb2/ctv_auction_test.go @@ -2,6 +2,10 @@ package openrtb2 import ( "encoding/json" + "fmt" + "github.com/PubMatic-OpenWrap/etree" + "net/url" + "strings" "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -56,3 +60,92 @@ func TestAddTargetingKeys(t *testing.T) { } assert.Equal(t, "Invalid bid", addTargetingKey(nil, openrtb_ext.HbCategoryDurationKey, "some value").Error()) } + +func TestAdjustBidIDInVideoEventTrackers(t *testing.T) { + type args struct { + modifiedBid *openrtb2.Bid + } + type want struct { + eventURLMap map[string]string + } + + tests := []struct { + name string + args args + want want + }{ + { + name: "replace_with_custom_ctv_bid_id", + want: want{ + eventURLMap: map[string]string{ + "thirdQuartile": "https://thirdQuartile.com?operId=8&key1=value1&bidid=1-bid_123", + "complete": "https://complete.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", + "firstQuartile": "https://firstQuartile.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", + "midpoint": "https://midpoint.com?operId=8&key1=value1&bidid=1-bid_123&key2=value2", + "someevent": "https://othermacros?bidid=bid_123&abc=pqr", + }, + }, + args: args{ + modifiedBid: &openrtb2.Bid{ + ID: "1-bid_123", + AdM: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `, + }, + }, + }, + } + for _, test := range tests { + doc := etree.NewDocument() + doc.ReadFromString(test.args.modifiedBid.AdM) + adjustBidIDInVideoEventTrackers(doc, test.args.modifiedBid) + events := doc.FindElements("VAST/Ad/Wrapper/Creatives/Creative/Linear/TrackingEvents/Tracking") + for _, event := range events { + evntName := event.SelectAttr("event").Value + expectedURL, _ := url.Parse(test.want.eventURLMap[evntName]) + expectedValues := expectedURL.Query() + actualURL, _ := url.Parse(event.Text()) + actualValues := actualURL.Query() + 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' [Event = %v]. but found %v", ev[i], k, evntName, av[i])) + } + } + + // check if operId=8 is first param + if evntName != "someevent" { + assert.True(t, strings.HasPrefix(actualURL.RawQuery, "operId=8"), "operId=8 must be first query param") + } + } + } +} diff --git a/exchange/auction.go b/exchange/auction.go index 3d733daaff8..f2c37f7a8bd 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -283,12 +283,19 @@ 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 { - if bid.AdM == "" { - return `` + - `prebid.org wrapper` + - `` + - `` + - `` + wrapperVASTTemplate := `` + + `prebid.org wrapper` + + `` + + `` + + `` + adm := bid.AdM + + if adm == "" { + return fmt.Sprintf(wrapperVASTTemplate, bid.NURL) // set nurl as VASTAdTagURI + } + + if strings.HasPrefix(adm, "http") { // check if it contains URL + return fmt.Sprintf(wrapperVASTTemplate, adm) // set adm as VASTAdTagURI } return bid.AdM } diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 54f67eb8177..1730309287c 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -42,6 +42,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/events.go b/exchange/events.go index bedafddf5a0..45ed458841d 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -2,6 +2,7 @@ package exchange import ( "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" "time" "github.com/evanphx/json-patch" @@ -37,13 +38,13 @@ func getEventTracking(requestExtPrebid *openrtb_ext.ExtRequestPrebid, ts time.Ti } // modifyBidsForEvents adds bidEvents and modifies VAST AdM if necessary. -func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid) map[openrtb_ext.BidderName]*pbsOrtbSeatBid { +func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, req *openrtb2.BidRequest, trackerURL string) map[openrtb_ext.BidderName]*pbsOrtbSeatBid { for bidderName, seatBid := range seatBids { - modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String()) + // modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String()) for _, pbsBid := range seatBid.bids { - if modifyingVastXMLAllowed { - ev.modifyBidVAST(pbsBid, bidderName) - } + // if modifyingVastXMLAllowed { + ev.modifyBidVAST(pbsBid, bidderName, req, trackerURL) + // } pbsBid.bidEvents = ev.makeBidExtEvents(pbsBid, bidderName) } } @@ -56,7 +57,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 *pbsOrtbBid, bidderName openrtb_ext.BidderName) { +func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName 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 @@ -66,8 +67,14 @@ func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ex if len(pbsBid.generatedBidID) > 0 { bidID = pbsBid.generatedBidID } - if newVastXML, ok := events.ModifyVastXmlString(ev.externalURL, vastXML, bidID, bidderName.String(), ev.accountID, ev.auctionTimestampMs); 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); ok { + bid.AdM = newVastXML + } + } + // always inject event trackers without checkign isModifyingVASTXMLAllowed + if newVastXML, injected, _ := events.InjectVideoEventTrackers(trackerURL, vastXML, bid, bidderName.String(), ev.accountID, ev.auctionTimestampMs, req); injected { + bid.AdM = string(newVastXML) } } diff --git a/exchange/events_test.go b/exchange/events_test.go index 887122a687e..07e9e3500a5 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -1,6 +1,8 @@ package exchange import ( + "github.com/prebid/prebid-server/config" + "strings" "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -152,3 +154,108 @@ func Test_eventsData_modifyBidJSON(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(&pbsOrtbBid{ + bid: tc.args.bid, + bidType: openrtb_ext.BidTypeVideo, + }, "somebidder", 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 eaff759f619..9233ea57ec5 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -63,6 +63,7 @@ type exchange struct { privacyConfig config.Privacy categoriesFetcher stored_requests.CategoryFetcher bidIDGenerator BidIDGenerator + trakerURL string } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -117,6 +118,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid LMT: cfg.LMT, }, bidIDGenerator: &bidIDGenerator{cfg.GenerateBidID}, + trakerURL: cfg.TrackerURL, } } @@ -204,7 +206,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * //If includebrandcategory is present in ext then CE feature is on. if requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil { var rejections []string - bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, requestExt, adapterBids, e.categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r.BidRequest, requestExt, adapterBids, e.categoriesFetcher, targData) if err != nil { return nil, fmt.Errorf("Error in category mapping : %s", err.Error()) } @@ -225,7 +227,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } evTracking := getEventTracking(&requestExt.Prebid, r.StartTime, &r.Account, e.bidderInfo, e.externalURL) - adapterBids = evTracking.modifyBidsForEvents(adapterBids) + adapterBids = evTracking.modifyBidsForEvents(adapterBids, r.BidRequest, e.trakerURL) if targData != nil { // A non-nil auction is only needed if targeting is active. (It is used below this block to extract cache keys) @@ -410,6 +412,7 @@ func (e *exchange) getAllBids( adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, len(bidderRequests)) chBids := make(chan *bidResponseWrapper, len(bidderRequests)) bidsFound := false + bidIDsCollision := false for _, bidder := range bidderRequests { // Here we actually call the adapters and collect the bids. @@ -458,6 +461,9 @@ func (e *exchange) getAllBids( 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) + } } } chBids <- brw @@ -477,9 +483,14 @@ func (e *exchange) getAllBids( if !bidsFound && adapterBids[brw.bidder] != nil && len(adapterBids[brw.bidder].bids) > 0 { bidsFound = true + bidIDsCollision = recordAdaptorDuplicateBidIDs(e.me, adapterBids) } - } + } + if bidIDsCollision { + // record this request count this request if bid collision is detected + e.me.RecordRequestHavingDuplicateBidID() + } return adapterBids, adapterExtra, bidsFound } @@ -609,7 +620,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e return buffer.Bytes(), err } -func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { +func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { res := make(map[string]string) type bidDedupe struct { @@ -621,6 +632,8 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques dedupe := make(map[string]bidDedupe) + impMap := make(map[string]*openrtb2.Imp) + // applyCategoryMapping doesn't get called unless // requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil brandCatExt := requestExt.Prebid.Targeting.IncludeBrandCategory @@ -635,6 +648,11 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques 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 @@ -711,6 +729,12 @@ func applyCategoryMapping(ctx context.Context, requestExt *openrtb_ext.ExtReques break } } + } else if 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 @@ -983,3 +1007,26 @@ func listBiddersWithRequests(bidderRequests []BidderRequest) []openrtb_ext.Bidde return liveAdapters } + +// 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]*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 +} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index c18f4533966..24fa2338e51 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1862,6 +1862,7 @@ func TestCategoryMapping(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + bidRequest := openrtb2.BidRequest{} requestExt := newExtRequest() targData := &targetData{ @@ -1899,7 +1900,7 @@ func TestCategoryMapping(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -1918,6 +1919,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + bidRequest := openrtb2.BidRequest{} requestExt := newExtRequestNoBrandCat() targData := &targetData{ @@ -1954,7 +1956,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -1973,6 +1975,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + bidRequest := openrtb2.BidRequest{} requestExt := newExtRequestTranslateCategories(nil) targData := &targetData{ @@ -2006,7 +2009,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -2055,6 +2058,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { } translateCategories := false + bidRequest := openrtb2.BidRequest{} requestExt := newExtRequestTranslateCategories(&translateCategories) targData := &targetData{ @@ -2088,7 +2092,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2106,6 +2110,7 @@ func TestCategoryDedupe(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + bidRequest := openrtb2.BidRequest{} requestExt := newExtRequest() targData := &targetData{ @@ -2158,7 +2163,7 @@ func TestCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages") @@ -2186,6 +2191,7 @@ func TestNoCategoryDedupe(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + bidRequest := openrtb2.BidRequest{} requestExt := newExtRequestNoBrandCat() targData := &targetData{ @@ -2238,7 +2244,7 @@ func TestNoCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") @@ -2275,6 +2281,7 @@ func TestCategoryMappingBidderName(t *testing.T) { includeWinners: true, } + bidRequest := openrtb2.BidRequest{} requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30} adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) @@ -2303,7 +2310,7 @@ func TestCategoryMappingBidderName(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2329,6 +2336,7 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { includeWinners: true, } + bidRequest := openrtb2.BidRequest{} requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30} adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) @@ -2357,7 +2365,7 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2458,7 +2466,8 @@ func TestBidRejectionErrors(t *testing.T) { adapterBids[bidderName] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &test.reqExt, adapterBids, categoriesFetcher, targData) + bidRequest := openrtb2.BidRequest{} + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &test.reqExt, adapterBids, categoriesFetcher, targData) if len(test.expectedCatDur) > 0 { // Bid deduplication case @@ -2482,6 +2491,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { t.Errorf("Failed to create a category Fetcher: %v", error) } + bidRequest := openrtb2.BidRequest{} requestExt := newExtRequestTranslateCategories(nil) targData := &targetData{ @@ -2521,7 +2531,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) assert.NoError(t, err, "Category mapping error should be empty") assert.Len(t, rejections, 1, "There should be 1 bid rejection message") diff --git a/go.mod b/go.mod index ac5447dcfce..13cd3748779 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/DATA-DOG/go-sqlmock v1.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/PubMatic-OpenWrap/etree v1.0.1 + github.com/PubMatic-OpenWrap/etree v1.0.2-0.20210129100623-8f30cfecf9f4 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/blang/semver v3.5.1+incompatible @@ -59,4 +59,4 @@ require ( gopkg.in/yaml.v2 v2.4.0 ) -replace github.com/mxmCherry/openrtb/v15 v15.0.0 => github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425134848-adbedb6f42c6 +replace github.com/mxmCherry/openrtb/v15 v15.0.0 => github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425063110-b01110089669 diff --git a/go.sum b/go.sum index 510e0ee0648..ce383174fb8 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,10 @@ github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/PubMatic-OpenWrap/etree v1.0.1 h1:Q8sZ99MuXKmAx2v4XThKjwlstgadZffiRbNwUG0Ey1U= github.com/PubMatic-OpenWrap/etree v1.0.1/go.mod h1:5Y8qgcuDoy3XXG907UXkGnVTwihF16rXyJa4zRT7hOE= +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/openrtb/v15 v15.0.0-20210425063110-b01110089669 h1:oEMhzdbL9IwS7wJCQfx9qpRZSHryTy8mv9Gx/8BY8eA= +github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425063110-b01110089669/go.mod h1:TVgncsz6MOzbL7lhun1lNuUBzVBlVDbxf9Fyy1TyhZA= github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425134848-adbedb6f42c6 h1:HYFXG8R1mtbDYpwWPxtBXuQ8pfgndMlQd7opo+wSAbk= github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425134848-adbedb6f42c6/go.mod h1:XMFHmDvfVyIhz5JGzvNXVt0afDLN2mrdYviUOKIYqAo= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 87807075d8e..8d6e6d13546 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -39,6 +39,10 @@ type ExtRequestPrebid struct { // passing of personally identifiable information doesn't constitute a sale per CCPA law. // The array may contain a single sstar ('*') entry to represent all bidders. NoSale []string `json:"nosale,omitempty"` + + // Macros specifies list of custom macros along with the values. This is used while forming + // the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding + Macros map[string]string `json:"macros,omitempty"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains From 2037771112115750ee2dee5fa389c73007c8fe84 Mon Sep 17 00:00:00 2001 From: Pubmatic-Dhruv-Sonone <83747371+Pubmatic-Dhruv-Sonone@users.noreply.github.com> Date: Tue, 25 May 2021 17:43:00 +0530 Subject: [PATCH 06/31] OTT-172: Set Default min Ads value to 1 from 2 (#161) * OTT-172: Set Default min Ads value to 1 from 2 --- endpoints/events/vtrack_test.go | 2 +- .../ctv/impressions/impression_generator.go | 2 +- .../impressions/maximize_for_duration_test.go | 25 ++++++++++++++++ .../ctv/impressions/min_max_algorithm_test.go | 29 ++++++++++++++++++- .../ctv/impressions/testdata/input.go | 4 +++ .../ctv/impressions/testdata/output.go | 16 ++++++++++ endpoints/openrtb2/ctv_auction.go | 2 +- exchange/exchange.go | 2 +- exchange/targeting.go | 2 +- openrtb_ext/adpod.go | 2 +- router/router.go | 2 +- 11 files changed, 80 insertions(+), 8 deletions(-) diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 4897fe71034..0f3b5028576 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -936,7 +936,7 @@ func TestGetVideoEventTracking(t *testing.T) { name: "valid_scenario", args: args{ trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", - bid: &openrtb2.Bid{ + bid: &openrtb2.Bid{ // AdM: vastXMLWith2Creatives, }, req: &openrtb2.BidRequest{ diff --git a/endpoints/openrtb2/ctv/impressions/impression_generator.go b/endpoints/openrtb2/ctv/impressions/impression_generator.go index 5488a8dd6a6..4f9edef5886 100644 --- a/endpoints/openrtb2/ctv/impressions/impression_generator.go +++ b/endpoints/openrtb2/ctv/impressions/impression_generator.go @@ -147,7 +147,7 @@ func computeTimeForEachAdSlot(cfg generator, totalAds int64) int64 { // 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.slotMinDuration = %v. Hence, not computing multiples of %v value.", cfg.requested.slotMaxDuration, multipleOf) + util.Logf("requested.slotMinDuration = requested.slotMaxDuration = %v. Hence, not computing multiples of %v value.", cfg.requested.slotMaxDuration, multipleOf) return timeForEachSlot } diff --git a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go index 2bb7323b0c1..c252573cf68 100644 --- a/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go +++ b/endpoints/openrtb2/ctv/impressions/maximize_for_duration_test.go @@ -384,6 +384,31 @@ var impressionsTests = []struct { 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, }}, } diff --git a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go index 332e5d78e4c..a1af101626f 100644 --- a/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go +++ b/endpoints/openrtb2/ctv/impressions/min_max_algorithm_test.go @@ -423,7 +423,34 @@ var impressionsTestsA2 = []struct { 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{}, diff --git a/endpoints/openrtb2/ctv/impressions/testdata/input.go b/endpoints/openrtb2/ctv/impressions/testdata/input.go index 8c7ae520f8c..3ee64544b95 100644 --- a/endpoints/openrtb2/ctv/impressions/testdata/input.go +++ b/endpoints/openrtb2/ctv/impressions/testdata/input.go @@ -54,4 +54,8 @@ var Input = map[string][]int{ "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 index 7b97c56f2bc..d7e854fc575 100644 --- a/endpoints/openrtb2/ctv/impressions/testdata/output.go +++ b/endpoints/openrtb2/ctv/impressions/testdata/output.go @@ -217,4 +217,20 @@ var Scenario = map[string]eout{ 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_auction.go b/endpoints/openrtb2/ctv_auction.go index c92ed4d0d17..c8f5a32e307 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -60,7 +60,7 @@ func NewCTVEndpoint( requestsByID stored_requests.Fetcher, videoFetcher stored_requests.Fetcher, accounts stored_requests.AccountFetcher, -//categories stored_requests.CategoryFetcher, + //categories stored_requests.CategoryFetcher, cfg *config.Configuration, met metrics.MetricsEngine, pbsAnalytics analytics.PBSAnalyticsModule, diff --git a/exchange/exchange.go b/exchange/exchange.go index 9233ea57ec5..41d13e3549e 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -118,7 +118,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid LMT: cfg.LMT, }, bidIDGenerator: &bidIDGenerator{cfg.GenerateBidID}, - trakerURL: cfg.TrackerURL, + trakerURL: cfg.TrackerURL, } } diff --git a/exchange/targeting.go b/exchange/targeting.go index c4710f826f0..24c77935bac 100644 --- a/exchange/targeting.go +++ b/exchange/targeting.go @@ -104,4 +104,4 @@ func (targData *targetData) addBidderKeys(keys map[string]string, bidderKeys map keys[index] = element } } -} \ No newline at end of file +} diff --git a/openrtb_ext/adpod.go b/openrtb_ext/adpod.go index 03b973b6b5f..ac815cda224 100644 --- a/openrtb_ext/adpod.go +++ b/openrtb_ext/adpod.go @@ -184,7 +184,7 @@ func (ext *ExtVideoAdPod) Validate() (err []error) { func (pod *VideoAdPod) SetDefaultValue() { //pod.MinAds setting default value if nil == pod.MinAds { - pod.MinAds = getIntPtr(2) + pod.MinAds = getIntPtr(1) } //pod.MaxAds setting default value diff --git a/router/router.go b/router/router.go index e79e9782f89..022331effea 100644 --- a/router/router.go +++ b/router/router.go @@ -263,7 +263,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R if err := validateDefaultAliases(defaultAliases); err != nil { glog.Fatal(err) } - + g_defReqJSON = defReqJSON g_syncers = usersyncers.NewSyncerMap(cfg) From 80d3d4090db385e19fc4f57c1cb902fb5102a79b Mon Sep 17 00:00:00 2001 From: PubMatic-OpenWrap Date: Tue, 25 May 2021 18:12:31 +0530 Subject: [PATCH 07/31] UOE-6444:updating unruly URLs (#159) Co-authored-by: shalmali-patil --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index dcf9d445571..29489ad2b90 100755 --- a/config/config.go +++ b/config/config.go @@ -645,7 +645,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderTrustX, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dtrustx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") // openrtb_ext.BidderUnicorn doesn't have a good default. - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://usermatch.targeting.unrulymedia.com/pbsync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://sync.1rx.io/usersync2/rmpssp?sub=openwrap&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderVrtcal doesn't have a good default. From a8aed1b60be70bdca053f50000168c105186e5ac Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Tue, 25 May 2021 22:20:42 +0530 Subject: [PATCH 08/31] UOE-6240: Openwrap S2S: Send gpt slot name in extension field (#162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Default TCF1 GVL in anticipation of IAB no longer hosting the v1 GVL (#1433) * update to the latest go-gdpr release (#1436) * Video endpoint bid selection enhancements (#1419) Co-authored-by: Veronika Solovei * [WIP] Bid deduplication enhancement (#1430) Co-authored-by: Veronika Solovei * Refactor rate converter separating scheduler from converter logic to improve testability (#1394) * Fix TCF1 Fetcher Fallback (#1438) * Eplanning adapter: Get domain from page (#1434) * Fix no bid debug log (#1375) * Update the fallback GVL to last version (#1440) * Enable geo activation of GDPR flag (#1427) * Validate External Cache Host (#1422) * first draft * Little tweaks * Scott's review part 1 * Scott's review corrections part 2 * Scotts refactor * correction in config_test.go * Correction and refactor * Multiple return statements * Test case refactor Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * Fixes bug (#1448) * Fixes bug * shortens list * Added adpod_id to request extension (#1444) * Added adpod_id to request -> ext -> appnexus and modified requests splitting based on pod * Unit test fix * Unit test fix * Minor unit test fixes * Code refactoring * Minor code and unit tests refactoring * Unit tests refactoring Co-authored-by: Veronika Solovei * Adform adapter: additional targeting params added (#1424) * Fix minor error message spelling mistake "vastml" -> "vastxml" (#1455) * Fixing comment for usage of deal priority field (#1451) * moving docs to website repo (#1443) * Fix bid dedup (#1456) Co-authored-by: Veronika Solovei * consumable: Correct width and height reported in response. (#1459) Prebid Server now responds with the width and height specified in the Bid Response from Consumable. Previously it would reuse the width and height specified in the Bid Request. That older behaviour was ported from an older version of the prebid.js adapter but is no longer valid. * Panics happen when left with zero length []Imp (#1462) * Add Scheme Option To External Cache URL (#1460) * Update gamma adapter (#1447) * Gamma SSP Adapter * Add Gamma SSP server adapter * increase coverage * Fix conflict with base master * Add check MediaType for Imp * Implement Multi Imps request * Changes requested * remove bad-request * increase coverage * Remove duplicate test file * Update gamma.go * Update gamma.go * Update gamma.go * Update config.go Remove Gamma User Sync Url from config * Gamma SSP Adapter * Add Gamma SSP server adapter * increase coverage * Fix conflict with base master * Add check MediaType for Imp * Implement Multi Imps request * Changes requested * remove bad-request * increase coverage * Remove duplicate test file * Update gamma.go * Update gamma.go * update gamma adapter * return nil when have No-Bid Signaling * add missing-adm.json * discard the bid that's missing adm * discard the bid that's missing adm * escape vast instead of encoded it * expand test coverage Co-authored-by: Easy Life * fix: avoid unexpected EOF on gz writer (#1449) * Smaato adapter: support for video mediaType (#1463) Co-authored-by: vikram * Rubicon liveramp param (#1466) Add liveramp mapping to user.ext should translate the "liveramp.com" id from the "user.ext.eids" array to "user.ext.liveramp_idl" as follows: ``` { "user": { "ext": { "eids": [{ "source": 'liveramp.com', "uids": [{ "id": "T7JiRRvsRAmh88" }] }] } } } ``` to XAPI: ``` { "user": { "ext": { "liveramp_idl": "T7JiRRvsRAmh88" } } } ``` * Consolidate StoredRequest configs, add validation for all data types (#1453) * Fix Test TestEventChannel_OutputFormat (#1468) * Add ability to randomly generate source.TID if empty and set publisher.ID to resolved account ID (#1439) * Add support for Account configuration (PBID-727, #1395) (#1426) * Minor changes to accounts test coverage (#1475) * Brightroll adapter - adding config support (#1461) * Refactor TCF 1/2 Vendor List Fetcher Tests (#1441) * Add validation checker for PRs and merges with github actions (#1476) * Cache refactor (#1431) Reason: Cache has Fetcher-like functionality to handle both requests and imps at a time. Internally, it still uses two caches configured and searched separately, causing some code repetition. Reusing this code to cache other objects like accounts is not easy. Keeping the req/imp repetition in fetcher and out of cache allows for a reusable simpler cache, preserving existing fetcher functionality. Changes in this set: Cache is now a simple generic id->RawMessage store fetcherWithCache handles the separate req and imp caches ComposedCache handles single caches - but it does not appear to be used Removed cache overlap tests since they do not apply now Slightly less code * Pass Through First Party Context Data (#1479) * Added new size 640x360 (Id: 198) (#1490) * Refactor: move getAccount to accounts package (from openrtb2) (#1483) * Fixed TCF2 Geo Only Enforcement (#1492) * New colossus adapter [Clean branch] (#1495) Co-authored-by: Aiholkin * New: InMobi Prebid Server Adapter (#1489) * Adding InMobi adapter * code review feedback, also explicitly working with Imp[0], as we don't support multiple impressions * less tolerant bidder params due to sneaky 1.13 -> 1.14+ change * Revert "Added new size 640x360 (Id: 198) (#1490)" (#1501) This reverts commit fa23f5c226df99a9a4ef318100fdb7d84d3e40fa. * CCPA Publisher No Sale Relationships (#1465) * Fix Merge Conflict (#1502) * Update conversant adapter for new prebid-server interface (#1484) * Implement returnCreative (#1493) * Working solution * clean-up * Test copy/paste error Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * ConnectAd S2S Adapter (#1505) * between adapter (#1437) Co-authored-by: Alexey Elymanov * Invibes adapter (#1469) Co-authored-by: aurel.vasile * Refactor postgres event producer so it will run either the full or de… (#1485) * Refactor postgres event producer so it will run either the full or delta query periodically * Minor cleanup, follow golang conventions, declare const slice, add test cases * Remove comments * Bidder Uniqueness Gatekeeping Test (#1506) * ucfunnel adapter update end point (#1511) * Refactor EEAC map to be more in line with the nonstandard publisher map (#1514) * Added bunch of new sizes (#1516) * New krushmedia bid adapter (#1504) * Invibes: Generic domainId parameter (#1512) * Smarty ads adapter (#1500) Co-authored-by: Kushneryk Pavlo Co-authored-by: user * Add vscode remote container development files (#1481) * First commit (#1510) Co-authored-by: Gus Carreon * Vtrack and event endpoints (#1467) * Rework pubstack module tests to remove race conditions (#1522) * Rework pubstack module tests to remove race conditions * PR feedback * Remove event count and add helper methods to assert events received on channel * Updating smartadserver endpoint configuration. (#1531) Co-authored-by: tadam * Add new size 500x1000 (ID: 548) (#1536) * Fix missing Request parameter for Adgeneration Adapter (#1525) * Fix endpoint url for TheMediaGrid Bid Adapter (#1541) * Add Account cache (#1519) * Add bidder name key support (#1496) * Simplifying exchange module: bidResponseExt gets built anyway (#1518) * first draft * Scott's feedback * stepping out real quick * add cache errors to bidResponseExt before marshalling * Removed vim's swp file Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon * Correct GetCpmStringValue's second return value (#1520) * Add metrics to capture stored data fetch all/delta durations with fetch status (#1515) * Adds preferDeals support (#1528) * Emxd 3336 add app video ctv (#1529) * Adapter changes for app and video support * adding ctv devicetype test case * Adding whitespace * Updates based on feedback from Prebid team * protocol bug fix and testing * Modifying test cases to accomodate new imp.ext field * bidtype bug fix and additonal testcase for storeUrl Co-authored-by: Rakesh Balakrishnan Co-authored-by: Dan Bogdan * Add http api for fetching accounts (#1545) * Add missing postgres cache init config validation * Acuity ads adapter (#1537) Co-authored-by: Kushneryk Pavlo * Yieldmo app support in yaml file (#1542) Co-authored-by: Winston * Add metrics for account cache (#1543) * [Invibes] remove user sync for invibes (#1550) * [invibes] new bidder stub * [invibes] make request * [invibes] bid request parameters * [invibes] fix errors, add tests * [invibes] new version of MakeBids * cleaning code * [invibes] production urls, isamp flag * [invibes] fix parameters * [invibes] new test parameter * [invibes] change maintainer email * [invibes] PR fixes * [invibes] fix parameters test * [invibes] refactor endpoint template and bidVersion * [Invibes] fix tests * [invibes] resolve PR * [invibes] fix test * [invibes] fix test * [invibes] generic domainId parameter * [invibes] remove invibes cookie sync * [Invibes] comment missing Usersync Co-authored-by: aurel.vasile * Add Support For imp.ext.prebid For DealTiers (#1539) * Add Support For imp.ext.prebid For DealTiers * Remove Normalization * Add Accounts to http cache events (#1553) * Fix JSON tests ignore expected message field (#1450) * NoBid version 1.0. Initial commit. (#1547) Co-authored-by: Reda Guermas * Added dealTierSatisfied parameters in exchange.pbsOrtbBid and openrtb_ext.ExtBidPrebid and dealPriority in openrtb_ext.ExtBidPrebid (#1558) Co-authored-by: Shriprasad * Add client/AccountID support into Adoppler adapter. (#1535) * Optionally read IFA value and add it the the request url (Adhese) (#1563) * Add AMX RTB adapter (#1549) * update Datablocks usersync.go (#1572) * 33Across: Add video support in adapter (#1557) * SilverMob adapter (#1561) * SilverMob adapter * Fixes andchanges according to notes in PR * Remaining fixes: multibids, expectedMakeRequestsErrors * removed log * removed log * Multi-bid test * Removed unnesesary block Co-authored-by: Anton Nikityuk * Updated ePlanning GVL ID (#1574) * update adpone google vendor id (#1577) * ADtelligent gvlid (#1581) * Add account/ host GDPR enabled flags & account per request type GDPR enabled flags (#1564) * Add account level request type specific and general GDPR enabled flags * Clean up test TestAccountLevelGDPREnabled * Add host-level GDPR enabled flag * Move account GDPR enable check as receiver method on accountGDPR * Remove mapstructure annotations on account structs * Minor test updates * Re-add mapstructure annotations on account structs * Change RequestType to IntegrationType and struct annotation formatting * Update comment * Update account IntegrationType comments * Remove extra space in config/accounts.go via gofmt * DMX Bidfloor fix (#1579) * adform bidder video bid response support (#1573) * Fix Beachfront JSON tests (#1578) * Add account CCPA enabled and per-request-type enabled flags (#1566) * Add account level request-type-specific and general CCPA enabled flags * Remove mapstructure annotations on CCPA account structs and clean up CCPA tests * Adjust account/host CCPA enabled flag logic to incorporate feedback on similar GDPR feature * Add shared privacy policy account integration data structure * Refactor EnabledForIntegrationType methods on account privacy objects * Minor test refactor * Simplify logic in EnabledForIntegrationType methods * Refactored HoldAuction Arguments (#1570) * Fix bug in request.imp.ext Validation (#1575) * First draft * Brian's reivew * Removed leftover comments Co-authored-by: Gus Carreon * Bugfix: default admin port is 6060 (#1595) * Add timestamp to analytics and response.ext.prebid.auctiontimestamp l… (#1584) * Added app capabilities to VerizonMedia adapter (#1596) Co-authored-by: oath-jac * New Adapter Initialization Framework (#1532) * Fix 33Across App Handling (#1602) * Fix adapter JSON tests to have the right test structure (#1589) * Fix JSON EMX Digital * Fix JSON Brightroll * Fix JSON Beintoo * Fix JSON Gamoshi * Fix JSON Kubient * Fix JSON Marsmedia * Fix JSON Nanointeractive * Fix JSON Telaria * Fix JSON valueimpression * Fix JSON smartyads * Fix JSON rhythmone * Fix JSON krushmedia * Fix JSON cpmstar * Fix JSON acuityads * Fix JSON avocet * Rename wrongly named acuity ads test file * Fix JSON gamma * Add expected no bid responses * Fixed indentation and asesome-markup * Added usersync support to Between SSP adapter; Major fixes and refactor (#1587) Co-authored-by: Egor Skorokhodov * Removed Safari Metric (#1571) * Deepintent adapter (#1524) Co-authored-by: Sourabh Gandhe * update mobilefuse endpoint (#1606) Co-authored-by: Dan Barnett * Fix Missing Consumable Clock (#1610) * Remove Hook Scripts (#1614) * Add config gdpr.amp_exception deprecation warning (#1612) * Refactor Adapter Config To Its Own File (#1608) * RP adapter: use video placement parameter to set size ID (#1607) * Add a BidderRequest struct to hold bidder specific request info (#1611) * Add warning that gdpr checks will be skipped when gdpr.host_vendor_id… (#1615) * Add TLS Handshake connection metrics (#1613) * Improve GitHub Actions Validation (#1590) * Move SSL to Server directory (#1625) * Rename currencies to currency (#1626) * Deepintent: Params normalization (#1617) Co-authored-by: Sourabh Gandhe * Set Kubient email to prebid@kubient.com (#1629) * Rename pbsmetrics to metrics (#1624) * 33Across: Add support for multi-imp requests (#1609) * changed usersync endpoint (#1631) Co-authored-by: Sourabh Gandhe * Remove legacy GDPR AMP config flag used to prevent buyer ID scrub on AMP requests (#1565) * Updating contact info for adprime (#1640) * ucfunnel adapter update end point (#1639) * New Adapter: Mobfox (#1585) Co-authored-by: mobfox * IX: Implement Bidder interface, update endpoint. (#1569) Co-authored-by: Index Exchange Prebid Team * Fix GDPR consent assumption when gdpr req signal is unambiguous and s… (#1591) * Fix GDPR consent assumption when gdpr req signal is unambiguous and set to 1 and consent string is blank * Refactor TestAllowPersonalInfo to make it table-based and add empty consent string cases * Update PersonalInfoAllowed to allow PI if the request indicates GDPR does not apply * Update test descriptions * Update default vendor permissions to only allow PI based on UserSyncIfAmbiguous if request GDPR signal is ambiguous * Change GDPR request signal type name and other PR feedback code style changes * Rename GDPR package signal constants and consolidate gdprEnforced and gdprEnabled variables * Hoist GDPR signal/empty consent checks before vendor list check * Rename gdpr to gdprSignal in Permissions interface and implementations * Fix merge mistakes * Update gdpr logic to set the gdpr signal when ambiguous according to the config flag * Add userSyncIfAmbiguous to all test cases in TestAllowPersonalInfo * Simplify TestAllowPersonalInfo * Fix appnexus adapter not setting currency in the bid response (#1642) Co-authored-by: Gus * Add Adot adapter (#1605) Co-authored-by: Aurélien Giudici * Refactor AMP Param Parsing (#1627) * Refactor AMP Param Parsing * Added Tests * Enforce GDPR privacy if there's an error parsing consent (#1593) * Enforce GDPR privacy if there's an error parsing consent * Update test with consent string variables to improve readability * Fix test typo * Update test variable names to follow go conventions * MediaFuse adapter (#1635) * MediaFuse alias * Syncer and tests * gvlid * gvlid * new mail * New Adapter: Revcontent (#1622) * Fix Unruly Bidder Parmaters (#1616) * Implement EID Permissions (#1633) * Implement EID Permissions * Idsync removal (#1644) Co-authored-by: steve-a-districtm Co-authored-by: Steve Alliance * Audit beachfront tests and change some videoResponseType details (#1638) * Adding Events support in bid responses (#1597) * Fix Shared Memory Corruption In EMX_Digital (#1646) * Add gdpr.tcf1.fetch_gvl deprecation warning and update GVL subdomain (#1660) * Bubble up GDPR signal/consent errors while applying privacy policies (#1651) * Always sync when GDPR globally enabled and allow host cookie sync … (#1656) * Eplanning: new prioritization metric for adunit sizes (#1648) * Eplanning: new prioritization metric for adunit sizes * removing IX's usersync default URL (#1670) Co-authored-by: Index Exchange Prebid Team * AMX Bid Adapter: Loop Variable Bug (#1675) * requestheaders: new parameter inside debug.httpcalls. to log request header details (#1659) * Added support for logging requestheaders inside httpCalls.requestheaders * Reverterd test case change * Modified outgoing mock request for appnexus, to send some request header information. Modified sample mock response such that ext.debug.httpcalls.appnexus.requestheaders will return the information of passed request headers * Addressed code review comments given by SyntaxNode. Also Moved RequestHeaders next to RequestBidy in openrtb_ext.ExtHttpCall Co-authored-by: Shriprasad * Updating pulsepoint adapter (#1663) * Debug disable feature implementation: (#1677) Co-authored-by: Veronika Solovei * Always use fallback GVL for TCF1 (#1657) * Update TCF2 GVL subdomain and always use fallback GVL for TCF1 * Add config test coverage for invalid TCF1 FetchGVL and AMP Exception * Delete obselete test * Adform adapter: digitrust cleanup (#1690) * adform secure endpoint as default setting * digitrust cleanup * New Adapter: DecenterAds (#1669) Co-authored-by: vlad * Handle empty consent string during cookie sync and setuid (#1671) * Handle empty consent string during cookie sync and setuid * Remove todo comment * Make auction test table driven and convert GDPR impl normalize method to pass by value * Moved legacy auction endpoint signal parsing into its own method and removed unnecessary test cases * Fix SignalParse method to return nil for error when raw signal is empty and other PR feedback * Tappx User Syncer + Site Update (#1674) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * Beachfront Additional tests (#1679) * added place holder files for all but a couple of the intended new tests. I need to grok what those couple mean before being able to name a file. * This covers most of the suggested cases and a couple more that occured to me. I'll look at the couple that I held off on next. * added the unmarshal tests and found a couple problems to address in the process. * removed my __debug_bin. should be in gitignore. * A bit of clean up and commenting. Bumped version number. Added __debug_bin to .gitignore. This is the debugging binary created by Visual Studio Code, or at least version 1.52.1. * missed a bunch of version strings * removed IP faker * If IP is not included in an AdM request, an error is now thrown for the AdM imp instead of faking it. The AdM endpoint is the only one that requires an IP. Also, added several "no-ip" test cases. * Whent back to the fake IP solution instead of the error. Removed most of the "no-ip" test cases, leaving one. * changed ip in adm-video.json to not match the faker ip * removed a debugging comment * New Adapter: Onetag (#1695) * Pubmatic: Trimming publisher ID before passing (#1685) * Trimming publisher ID before passing * Fix typos in nobid.json (#1704) * Fix Typo In Adform Bidder Params (#1705) * Don't Load GVL v1 for TCF2 (+ TCF1 Cleanup) (#1693) * Typo fix for connectad bidder params (#1706) * Typo fix for invibes bidder params (#1707) * Typo fix nanointeractive bidder params (#1708) * Isolate /info/bidders Data Model + Add Uses HTTPS Flag (#1692) * Initial Commit * Merge Conflict Fixes * Removed Unncessary JSON Attributes * Removed Dev Notes * Add Missing validateDefaultAliases Test * Improved Reversed Test * Remove Var Scope Confusion * Proper Tests For Bidder Param Validator * Removed Unused Test Setup * New Adapter: Epom (#1680) Co-authored-by: Vasyl Zarva * New Adapter: Pangle (#1697) Co-authored-by: hcai * Fix Merge Conflict (#1714) * GumGum: adds pubId and irisid properties/parameters (#1664) * adds pubId and irisid properties * updates per naming convention & makes a video copy * updates when to copy banner, adds Publisher fallback and multiformat request * adds more json tests * rename the json file to remove whitespaces * Accommodate Apple iOS LMT bug (#1718) * New Adapter: jixie (#1698) * initial commit * added notes file for temp use * jixie adapter development work * jixie adaptor development work: mainly the test json files but also the jixie usersync code * added a test case with accountid. and cosmetic line changes in the banner*json test file * updated the jixie user sync: the endpoint and some params stuf * tks and fixing per comments on pull request 1698 * responding to guscarreon's comments: -more checking in makerequest of the bidder params (added 2 more test jsons) -removed blank lines, lines commented out -test_params: a case with unit alone -BadInput error * responding to review. put condition on jixie unit string in the bidder-params/jixie.json file. removed checking in jixie.go that has become unnecssary. removed unnec test cases. updated params-test * added one failed params test * removed a function that I no longer call! * renamed JixieAdapter to adapter * removed bidfloor from jixie explicit ext params * Fix Regs Nil Condition (#1723) * Mobfox: Add rout to adexcange (#1702) Co-authored-by: mobfox * New Adapter: TrustX (#1726) * New Adapter: UNICORN (#1719) * add bidder-info, bidder-params for UNICORN * Add adapter * Fixes GDPR bug about being overly strict on publisher restrictions (#1730) * 33Across: Updated exchange endpoint (#1738) * New Adapter: Adyoulike (#1700) Co-authored-by: Damien Dumas * Hoist GVL ID To Bidder Info (#1721) * Improve Digital adapter: add support for native ads (#1746) * Add Support For SkAdN + Refactor Split Imps (#1741) * No Longer Move bid.ext To bid.ext.bidder (#1742) * No Longer Move bid.ext To bid.ext.bidder * Remove Similar Behavior From seatbid.ext * Avoid Second Bid Copy * Removed Unused seatbid.ext * Typo fix: adyoulike bidder param debug description (#1755) * Aliases: Better Error Message For Disabled Bidder (#1751) * beachfront: Changes to support real 204 (#1737) * Fix race condition in 33across.go (#1757) Co-authored-by: Gus Carreon * Revert "Fix race condition in 33across.go (#1757)" (#1763) This reverts commit bdf1e7b3e13bdf87d3282bf74472fc66504537d5. * Replace TravisCI With GitHub Actions (#1754) * Initial Commit * Finished Configuration * Remove TravisCI * Remove TravisCI * Fix Go Version Badge * Correct Fix For Go Version Badge * Removed Custom Config File Name * Debug warnings (#1724) Co-authored-by: Veronika Solovei * Rubicon: Support sending segments to XAPI (#1752) Co-authored-by: Serhii Nahornyi * validateNativeContextTypes function test cases (#1743) * Applogy: Fix Shared Memory Overwriting (#1758) * Pubmatic: Fix Shared Memory Overwriting (#1759) * Beachfront: Fix Shared Memory Overwriting (#1762) * Fix race condition in Beachfront adapter * Removed nil check and simplified * FPD: Allow imp.ext.data To Passthrough To Adapters (#1765) * Smaato: Add support for app (#1767) Co-authored-by: Bernhard Pickenbrock * Update sync types (#1770) * 33across: Fix Shared Memory Overwriting (#1764) This reverts commit f7df258f061788ef7e72529115aa5fd554fa9f16. * Fix race condition in Yeahmobi adapter (#1761) Co-authored-by: Gus Carreon * Pubnative: Fix Shared Memory Overwriting (#1760) * Add request for registration (#1780) * Update OpenRTB Library (#1733) * Tappx changes - Backward compatible change of version (#1777) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * DMX: Enforcing w and h in imp (#1778) Co-authored-by: steve-a-districtm * Remove Authorization Headers From Debug Response (#1779) * Hide Authorization Headers In Debug Response * MakeExtHeaders Tests * Add Empty Test * Use http.Header Methods * Updates From Code Review * Fix Merge Conflict * New Adapter: Bidmachine (#1769) * New Adapter: Criteo (#1775) * Fix shared memory issue when stripping authorization header from bid requests (#1790) * RTB House: update parameters (#1785) * update parameters required by RTB House adapter * tabs to spaces Co-authored-by: Przemyslaw Iwanczak * Generate seatbid[].bid[].ext.prebid.bidid (#1772) * Basic GDPR enforcement for specific publisher-vendors. (#1782) * New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior * Zemanta: Rename Adapter To Outbrain (#1797) * Update openrtb library to v15 (#1802) * IX: Set bidVideo when category and duration is available (#1794) * Update IX defaults (#1799) Co-authored-by: Mike Burns * Update Adyoulike endpoint to hit production servers (#1805) * Openx: use bidfloor if set - prebid.js adapter behavior (#1795) * [ORBIDDER] add gvlVendorID and set bid response currency (#1798) * New Adapter: ADXCG (#1803) * Update kidoz properties to type string (#1808) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * Update bidmachine properties to type string (#1809) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * transform native eventtrackers to imptrackers and jstracker (#1811) * TheMediaGrid: Added processing of imp[].ext.data (#1807) * Renaming package github.com/PubMatic-OpenWrap/openrtb to github.com/mxmCherry/openrtb * Rename package github.com/PubMatic-OpenWrap/prebid-server to github.com/prebid/prebid-server * UOE-6196: OpenWrap S2S: Remove adpod_id from AppNexus adapter * Refactored code and fixed indentation * Fixed indentation for json files * Fixed indentation for json files * Fixed import in adapters/gumgum/gumgum.go * Reverted unwanted changes in test json files * Fixed unwanted git merge changes * Added missing field SkipDedup in ExtIncludeBrandCategory * Added missing Bidder field in ExtBid type * Exposing CookieSyncRequest for header-bidding * Temporary path change for static folder * Fixed static folder paths * Fixed default value in config for usersync_if_ambiguous * Fixed config after upgrade * Updated router.go to uncomment defaultRequest validation * Fixed path for accounts.filesystem.directorypath * Fixed diff with OW * Added DMX default usersync URL * Adding changes missed for UOE-5114 during prebid-server upgrade * UOE-6240: Send gpt slot name in extension field * Added newline at the end Co-authored-by: hhhjort <31041505+hhhjort@users.noreply.github.com> Co-authored-by: Veronika Solovei Co-authored-by: Veronika Solovei Co-authored-by: Brian Sardo <1168933+bsardo@users.noreply.github.com> Co-authored-by: Scott Kay Co-authored-by: chino117 Co-authored-by: Cameron Rice <37162584+camrice@users.noreply.github.com> Co-authored-by: guscarreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Gus Carreon Co-authored-by: Jurij Sinickij Co-authored-by: Rob Hazan Co-authored-by: bretg Co-authored-by: Daniel Cassidy Co-authored-by: GammaSSP <35954362+gammassp@users.noreply.github.com> Co-authored-by: Easy Life Co-authored-by: gpolaert Co-authored-by: Stephan Brosinski Co-authored-by: vikram Co-authored-by: Dmitriy Co-authored-by: Laurentiu Badea Co-authored-by: Mansi Nahar Co-authored-by: smithaammassamveettil <39389834+smithaammassamveettil@users.noreply.github.com> Co-authored-by: hdeodhar <35999856+hdeodhar@users.noreply.github.com> Co-authored-by: Bill Newman Co-authored-by: Aiholkin Co-authored-by: Daniel Lawrence Co-authored-by: johnwier <49074029+johnwier@users.noreply.github.com> Co-authored-by: rtuschkany <35923908+rtuschkany@users.noreply.github.com> Co-authored-by: Alexey Elymanov Co-authored-by: Alexey Elymanov Co-authored-by: invibes <51820283+invibes@users.noreply.github.com> Co-authored-by: aurel.vasile Co-authored-by: ucfunnel <39581136+ucfunnel@users.noreply.github.com> Co-authored-by: Krushmedia <71434282+Krushmedia@users.noreply.github.com> Co-authored-by: Kushneryk Pavel Co-authored-by: Kushneryk Pavlo Co-authored-by: user Co-authored-by: Gus Carreon Co-authored-by: Daniel Barrigas Co-authored-by: tadam75 Co-authored-by: tadam Co-authored-by: Andrea Cannuni <57228257+ACannuniRP@users.noreply.github.com> Co-authored-by: Ad Generation Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: Gus Carreon Co-authored-by: Dan Bogdan <43830380+EMXDigital@users.noreply.github.com> Co-authored-by: Rakesh Balakrishnan Co-authored-by: Dan Bogdan Co-authored-by: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Co-authored-by: Winston-Yieldmo <46379634+Winston-Yieldmo@users.noreply.github.com> Co-authored-by: Winston Co-authored-by: redaguermas Co-authored-by: Reda Guermas Co-authored-by: ShriprasadM Co-authored-by: Shriprasad Co-authored-by: Viacheslav Chimishuk Co-authored-by: Sander Co-authored-by: Nick Jacob Co-authored-by: htang555 Co-authored-by: Aparna Rao Co-authored-by: silvermob <73727464+silvermob@users.noreply.github.com> Co-authored-by: Anton Nikityuk Co-authored-by: Seba Perez Co-authored-by: Sergio Co-authored-by: Gena Co-authored-by: Steve Alliance Co-authored-by: Peter Fröhlich Co-authored-by: oath-jac <45564796+oath-jac@users.noreply.github.com> Co-authored-by: oath-jac Co-authored-by: egsk Co-authored-by: Egor Skorokhodov Co-authored-by: Sourabh Gandhe Co-authored-by: Sourabh Gandhe Co-authored-by: dtbarne <7635750+dtbarne@users.noreply.github.com> Co-authored-by: Dan Barnett Co-authored-by: Serhii Nahornyi Co-authored-by: Marsel Co-authored-by: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> Co-authored-by: Index Exchange 3 Prebid Team Co-authored-by: Index Exchange Prebid Team Co-authored-by: Giudici-a <34242194+Giudici-a@users.noreply.github.com> Co-authored-by: Aurélien Giudici Co-authored-by: jcamp-revc <68560678+jcamp-revc@users.noreply.github.com> Co-authored-by: steve-a-districtm Co-authored-by: Steve Alliance Co-authored-by: Jim Naumann Co-authored-by: Anand Venkatraman Co-authored-by: Vladyslav Laktionov Co-authored-by: vlad Co-authored-by: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Co-authored-by: ubuntu Co-authored-by: Albert Grandes Co-authored-by: onetag-dev <38786435+onetag-dev@users.noreply.github.com> Co-authored-by: agilfix Co-authored-by: epomrnd Co-authored-by: Vasyl Zarva Co-authored-by: Hengsheng Cai Co-authored-by: hcai Co-authored-by: susyt Co-authored-by: jxdeveloper1 <71084096+jxdeveloper1@users.noreply.github.com> Co-authored-by: faithnh Co-authored-by: guiann Co-authored-by: Damien Dumas Co-authored-by: Jozef Bartek <31618107+jbartek25@users.noreply.github.com> Co-authored-by: Gus Carreon Co-authored-by: Serhii Nahornyi Co-authored-by: el-chuck Co-authored-by: Bernhard Pickenbrock Co-authored-by: Pavel Dunyashev Co-authored-by: Benjamin Co-authored-by: Przemysław Iwańczak <36727380+piwanczak@users.noreply.github.com> Co-authored-by: Przemyslaw Iwanczak Co-authored-by: Rok Sušnik Co-authored-by: Rok Sušnik Co-authored-by: ixjohnny <75964135+ixjohnny@users.noreply.github.com> Co-authored-by: Michael Burns Co-authored-by: Mike Burns Co-authored-by: Arne Schulz Co-authored-by: adxcgcom <31470944+adxcgcom@users.noreply.github.com> --- adapters/bidder.go | 3 +- adapters/pubmatic/pubmatic.go | 7 + .../supplemental/gptSlotNameInImpExt.json | 174 ++++++++++++++++++ openrtb_ext/imp.go | 9 + 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json diff --git a/adapters/bidder.go b/adapters/bidder.go index 3592134feff..395d4bd2201 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -129,7 +129,8 @@ type ExtImpBidder struct { // // Bidder implementations may safely assume that this JSON has been validated by their // static/bidder-params/{bidder}.json file. - Bidder json.RawMessage `json:"bidder"` + Bidder json.RawMessage `json:"bidder"` + Data *openrtb_ext.ExtData `json:"data,omitempty"` } func (r *RequestData) SetBasicAuth(username string, password string) { diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index c9e3660b4c8..a0ca5f50529 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -28,6 +28,8 @@ const ( buyIdTargetingKey = "hb_buyid_pubmatic" skAdnetworkKey = "skadn" rewardKey = "reward" + ImpExtAdUnitKey = "dfp_ad_unit_code" + AdServerGAM = "gam" ) type PubmaticAdapter struct { @@ -631,6 +633,11 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID } } + if bidderExt.Data != nil && bidderExt.Data.AdServer != nil && + bidderExt.Data.AdServer.Name == AdServerGAM && bidderExt.Data.AdServer.AdSlot != "" { + impExtMap[ImpExtAdUnitKey] = bidderExt.Data.AdServer.AdSlot + } + if len(impExtMap) != 0 { impExtBytes, err := json.Marshal(impExtMap) if err == nil { diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json new file mode 100644 index 00000000000..9336ed63262 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json @@ -0,0 +1,174 @@ +{ + "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" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + }, + "data": { + "adserver": { + "name": "gam", + "adslot": "/1111/home" + } + } + } + } + ], + "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" + } + }, + "ext": { + "prebid": { + "bidderparams": { + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } + } + }, + "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": { + "pmZoneID": "Zone1,Zone2", + "preference": "sports,movies", + "dfp_ad_unit_code": "/1111/home" + } + } + ], + "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": { + "wrapper": { + "profile": 5123, + "version": 1, + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } + } + }, + "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/openrtb_ext/imp.go b/openrtb_ext/imp.go index f83fa63df84..4a2c2bc5c77 100644 --- a/openrtb_ext/imp.go +++ b/openrtb_ext/imp.go @@ -22,3 +22,12 @@ type ExtImpPrebid struct { type ExtStoredRequest struct { ID string `json:"id"` } + +type ExtData struct { + AdServer *ExtAdServer `json:"adserver"` +} + +type ExtAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} From 9860ee06c78e8dfa28e16216a852bbf8587bb36e Mon Sep 17 00:00:00 2001 From: Pubmatic-Dhruv-Sonone <83747371+Pubmatic-Dhruv-Sonone@users.noreply.github.com> Date: Wed, 2 Jun 2021 12:14:26 +0530 Subject: [PATCH 09/31] OTT-192: Ensure sURL (Event Tracker) and orig (OW Logger) parameter values are consistent (#164) --- endpoints/events/vtrack.go | 46 ++++++++++++++++++++++++++------- endpoints/events/vtrack_test.go | 45 ++++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index 27a1ceea746..2eb680f367c 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -5,9 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/PubMatic-OpenWrap/etree" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" "io" "io/ioutil" "net/http" @@ -15,6 +12,10 @@ import ( "strings" "time" + "github.com/PubMatic-OpenWrap/etree" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/golang/glog" "github.com/julienschmidt/httprouter" accountService "github.com/prebid/prebid-server/account" @@ -464,23 +465,25 @@ func GetVideoEventTracking(trackerURL string, bid *openrtb2.Bid, bidder string, } } if nil != req && nil != req.Site { - eventURL = replaceMacro(eventURL, VASTDomainMacro, req.Site.Domain) + 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 nil == err { - eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, domain) - } else { + 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, bidder) eventURL = replaceMacro(eventURL, PBSBidIDMacro, bid.ID) // replace [EVENT_ID] macro with PBS defined event ID @@ -488,7 +491,11 @@ func GetVideoEventTracking(trackerURL string, bid *openrtb2.Bid, bidder string, 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 @@ -496,8 +503,12 @@ func GetVideoEventTracking(trackerURL string, bid *openrtb2.Bid, bidder string, func replaceMacro(trackerURL, macro, value string) string { macro = strings.TrimSpace(macro) - if strings.HasPrefix(macro, "[") && strings.HasSuffix(macro, "]") && len(strings.TrimSpace(value)) > 0 { + 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) } @@ -534,3 +545,20 @@ func extractDomain(rawURL string) (string, error) { // 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_test.go b/endpoints/events/vtrack_test.go index 0f3b5028576..f5189a4ba83 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -5,14 +5,15 @@ import ( "context" "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/etree" - "github.com/mxmCherry/openrtb/v15/openrtb2" "io/ioutil" "net/http/httptest" "net/url" "strings" "testing" + "github.com/PubMatic-OpenWrap/etree" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" @@ -975,11 +976,11 @@ func TestGetVideoEventTracking(t *testing.T) { // "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=[DOMAIN]", - "midpoint": "http://company.tracker.com?eventId=3&appbundle=[DOMAIN]", - "thirdQuartile": "http://company.tracker.com?eventId=5&appbundle=[DOMAIN]", - "start": "http://company.tracker.com?eventId=2&appbundle=[DOMAIN]", - "complete": "http://company.tracker.com?eventId=6&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="}, }, }, { @@ -1117,6 +1118,34 @@ func TestGetVideoEventTracking(t *testing.T) { 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", // expect encoding for WRAPPER_IMPRESSION_ID macro args: args{ @@ -1213,7 +1242,7 @@ func TestReplaceMacro(t *testing.T) { {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=[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"}}, From ca9b117b3435b5ada2e92f336d320676d2e1e7d6 Mon Sep 17 00:00:00 2001 From: Pubmatic-Dhruv-Sonone <83747371+Pubmatic-Dhruv-Sonone@users.noreply.github.com> Date: Tue, 15 Jun 2021 15:46:58 +0530 Subject: [PATCH 10/31] OTT-197: Log Partner bidder code in video event tracker (#168) --- endpoints/events/vtrack.go | 12 ++++++---- endpoints/events/vtrack_test.go | 39 ++++++++++++++++++--------------- exchange/bidder.go | 2 ++ exchange/events.go | 13 ++++++----- exchange/events_test.go | 2 +- exchange/exchange.go | 3 +++ 6 files changed, 43 insertions(+), 28 deletions(-) diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index 2eb680f367c..b58d758d877 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -71,6 +71,8 @@ const ( 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"} @@ -344,7 +346,7 @@ func ModifyVastXmlJSON(externalUrl string, data json.RawMessage, bidid, bidder, //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, bidder, accountID string, timestamp int64, bidRequest *openrtb2.BidRequest) ([]byte, bool, error) { +func InjectVideoEventTrackers(trackerURL, vastXML string, bid *openrtb2.Bid, requestingBidder, bidderCoreName, accountID string, timestamp int64, bidRequest *openrtb2.BidRequest) ([]byte, bool, error) { // parse VAST doc := etree.NewDocument() err := doc.ReadFromString(vastXML) @@ -361,7 +363,7 @@ func InjectVideoEventTrackers(trackerURL, vastXML string, bid *openrtb2.Bid, bid impMap[bidRequest.Imp[i].ID] = &bidRequest.Imp[i] } - eventURLMap := GetVideoEventTracking(trackerURL, bid, bidder, accountID, timestamp, bidRequest, doc, impMap) + eventURLMap := GetVideoEventTracking(trackerURL, bid, requestingBidder, bidderCoreName, accountID, timestamp, bidRequest, doc, impMap) trackersInjected := false // return if if no tracking URL if len(eventURLMap) == 0 { @@ -429,7 +431,7 @@ func InjectVideoEventTrackers(trackerURL, vastXML string, bid *openrtb2.Bid, bid // 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, bidder string, accountId string, timestamp int64, req *openrtb2.BidRequest, doc *etree.Document, impMap map[string]*openrtb2.Imp) map[string]string { +func GetVideoEventTracking(trackerURL string, bid *openrtb2.Bid, 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 @@ -484,7 +486,9 @@ func GetVideoEventTracking(trackerURL string, bid *openrtb2.Bid, bidder string, eventURL = replaceMacro(eventURL, PBSAdvertiserNameMacro, domain) - eventURL = replaceMacro(eventURL, PBSBidderMacro, bidder) + eventURL = replaceMacro(eventURL, PBSBidderMacro, bidderCoreName) + eventURL = replaceMacro(eventURL, PBSBidderCodeMacro, requestingBidder) + eventURL = replaceMacro(eventURL, PBSBidIDMacro, bid.ID) // replace [EVENT_ID] macro with PBS defined event ID eventURL = replaceMacro(eventURL, PBSEventIDMacro, eventIDMap[event]) diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index f5189a4ba83..0980843e650 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -868,8 +868,9 @@ func TestInjectVideoEventTrackers(t *testing.T) { tc.args.bid.ImpID = tc.args.req.Imp[0].ID accountID := "" timestamp := int64(0) - biddername := "test_bidder" - injectedVast, injected, ierr := InjectVideoEventTrackers(tc.args.externalURL, vast, tc.args.bid, biddername, accountID, timestamp, tc.args.req) + requestingBidder := "test_bidder" + bidderCoreName := "test_core_bidder" + injectedVast, injected, ierr := InjectVideoEventTrackers(tc.args.externalURL, vast, tc.args.bid, requestingBidder, bidderCoreName, accountID, timestamp, tc.args.req) if !injected { // expect no change in input vast if tracking events are not injected @@ -917,13 +918,14 @@ func TestInjectVideoEventTrackers(t *testing.T) { func TestGetVideoEventTracking(t *testing.T) { type args struct { - trackerURL string - bid *openrtb2.Bid - bidder string - accountId string - timestamp int64 - req *openrtb2.BidRequest - doc *etree.Document + trackerURL string + bid *openrtb2.Bid + requestingBidder string + bidderCoreName string + accountId string + timestamp int64 + req *openrtb2.BidRequest + doc *etree.Document } type want struct { trackerURLMap map[string]string @@ -1149,7 +1151,7 @@ func TestGetVideoEventTracking(t *testing.T) { { name: "all_macros", // 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]", + 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]&bc=[BIDDER_CODE]", req: &openrtb2.BidRequest{ App: &openrtb2.App{Bundle: "com.someapp.com", Publisher: &openrtb2.Publisher{ID: "5890"}}, Ext: []byte(`{ @@ -1167,16 +1169,17 @@ func TestGetVideoEventTracking(t *testing.T) { {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"}, - bidder: "test_bidder:234", + bid: &openrtb2.Bid{ADomain: []string{"http://a.com/32?k=v", "b.com"}, ImpID: "imp_1", ID: "test_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_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", - "midpoint": "https://company.tracker.com?operId=8&e=3&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", - "thirdQuartile": "https://company.tracker.com?operId=8&e=5&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", - "complete": "https://company.tracker.com?operId=8&e=6&p=5890&pid=100&v=2&ts=1234567890&pn=test_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", - "start": "https://company.tracker.com?operId=8&e=2&p=5890&pid=100&v=2&ts=1234567890&pn=test_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"}, + "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&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&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&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&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&bc=test_bidder%3A234"}, }, }, } @@ -1193,7 +1196,7 @@ func TestGetVideoEventTracking(t *testing.T) { impMap[imp.ID] = &imp } - eventURLMap := GetVideoEventTracking(tc.args.trackerURL, tc.args.bid, tc.args.bidder, tc.args.accountId, tc.args.timestamp, tc.args.req, tc.args.doc, impMap) + eventURLMap := GetVideoEventTracking(tc.args.trackerURL, tc.args.bid, 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 { diff --git a/exchange/bidder.go b/exchange/bidder.go index c8f319b231c..c30d791d221 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -85,6 +85,8 @@ type pbsOrtbSeatBid struct { // httpCalls is the list of debugging info. It should only be populated if the request.test == 1. // This will become response.ext.debug.httpcalls.{bidder} on the final Response. httpCalls []*openrtb_ext.ExtHttpCall + // bidderCoreName represents the core bidder id. + bidderCoreName openrtb_ext.BidderName } // adaptBidder converts an adapters.Bidder into an exchange.adaptedBidder. diff --git a/exchange/events.go b/exchange/events.go index 45ed458841d..35929d8e604 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -2,10 +2,11 @@ package exchange import ( "encoding/json" - "github.com/mxmCherry/openrtb/v15/openrtb2" "time" - "github.com/evanphx/json-patch" + "github.com/mxmCherry/openrtb/v15/openrtb2" + + jsonpatch "github.com/evanphx/json-patch" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/endpoints/events" @@ -43,7 +44,7 @@ func (ev *eventTracking) modifyBidsForEvents(seatBids map[openrtb_ext.BidderName // modifyingVastXMLAllowed := ev.isModifyingVASTXMLAllowed(bidderName.String()) for _, pbsBid := range seatBid.bids { // if modifyingVastXMLAllowed { - ev.modifyBidVAST(pbsBid, bidderName, req, trackerURL) + ev.modifyBidVAST(pbsBid, bidderName, seatBid.bidderCoreName, req, trackerURL) // } pbsBid.bidEvents = ev.makeBidExtEvents(pbsBid, bidderName) } @@ -57,11 +58,12 @@ func (ev *eventTracking) isModifyingVASTXMLAllowed(bidderName string) bool { } // modifyBidVAST injects event Impression url if needed, otherwise returns original VAST string -func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ext.BidderName, req *openrtb2.BidRequest, trackerURL string) { +func (ev *eventTracking) modifyBidVAST(pbsBid *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 } + vastXML := makeVAST(bid) bidID := bid.ID if len(pbsBid.generatedBidID) > 0 { @@ -72,8 +74,9 @@ func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ex bid.AdM = newVastXML } } + // always inject event trackers without checkign isModifyingVASTXMLAllowed - if newVastXML, injected, _ := events.InjectVideoEventTrackers(trackerURL, vastXML, bid, bidderName.String(), ev.accountID, ev.auctionTimestampMs, req); injected { + if newVastXML, injected, _ := events.InjectVideoEventTrackers(trackerURL, vastXML, bid, 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 07e9e3500a5..a4fe03601b7 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -243,7 +243,7 @@ func TestModifyBidVAST(t *testing.T) { ev.modifyBidVAST(&pbsOrtbBid{ bid: tc.args.bid, bidType: openrtb_ext.BidTypeVideo, - }, "somebidder", tc.args.bidReq, "http://company.tracker.com?e=[EVENT_ID]") + }, "somebidder", "coreBidder", tc.args.bidReq, "http://company.tracker.com?e=[EVENT_ID]") validator(t, tc.args.bid, tc.want.tags) }) } diff --git a/exchange/exchange.go b/exchange/exchange.go index 41d13e3549e..4a2ae73802b 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -438,6 +438,9 @@ func (e *exchange) getAllBids( reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed) + // Setting bidderCoreName in SeatBid + bids.bidderCoreName = bidderRequest.BidderCoreName + // Add in time reporting elapsed := time.Since(start) brw.adapterBids = bids From 7c2e2d2d582995b1683045e42ccb7b099daeea37 Mon Sep 17 00:00:00 2001 From: Viral Vala Date: Tue, 6 Jul 2021 16:43:49 +0530 Subject: [PATCH 11/31] OTT-223 Adding Client Configurations --- config/config.go | 16 +++++++--- router/router.go | 81 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/config/config.go b/config/config.go index 29489ad2b90..33855cb1f43 100755 --- a/config/config.go +++ b/config/config.go @@ -88,10 +88,14 @@ type Configuration struct { const MIN_COOKIE_SIZE_BYTES = 500 type HTTPClient struct { - MaxConnsPerHost int `mapstructure:"max_connections_per_host"` - MaxIdleConns int `mapstructure:"max_idle_connections"` - MaxIdleConnsPerHost int `mapstructure:"max_idle_connections_per_host"` - IdleConnTimeout int `mapstructure:"idle_connection_timeout_seconds"` + MaxConnsPerHost int `mapstructure:"max_connections_per_host"` + 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() []error { @@ -709,6 +713,10 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("http_client.max_idle_connections", 400) v.SetDefault("http_client.max_idle_connections_per_host", 10) v.SetDefault("http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("http_client.tls_handshake_timeout", 0) //no timeout + v.SetDefault("http_client.response_header_timeout", 0) //unlimited + v.SetDefault("http_client.dial_timeout", 0) //no timeout + v.SetDefault("http_client.dial_keepalive", 0) //no restriction v.SetDefault("http_client_cache.max_connections_per_host", 0) // unlimited v.SetDefault("http_client_cache.max_idle_connections", 10) v.SetDefault("http_client_cache.max_idle_connections_per_host", 2) diff --git a/router/router.go b/router/router.go index 022331effea..5b8ec014a1c 100644 --- a/router/router.go +++ b/router/router.go @@ -3,19 +3,22 @@ package router import ( "context" "crypto/tls" + "crypto/x509" "database/sql" "encoding/json" "fmt" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prometheus/client_golang/prometheus" "io/ioutil" + "net" "net/http" "path/filepath" "strings" "time" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/usersync" + "github.com/prometheus/client_golang/prometheus" + "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" @@ -72,6 +75,7 @@ var ( g_activeBidders map[string]openrtb_ext.BidderName g_defReqJSON []byte g_cacheClient pbc.Client + g_transport *http.Transport ) // NewJsonDirectoryServer is used to serve .json files from a directory as a single blob. For example, @@ -191,6 +195,39 @@ type Router struct { Shutdown func() } +func getTransport(cfg *config.Configuration, certPool *x509.CertPool) *http.Transport { + transport := &http.Transport{ + MaxConnsPerHost: cfg.Client.MaxConnsPerHost, + IdleConnTimeout: time.Duration(cfg.Client.IdleConnTimeout) * time.Second, + TLSClientConfig: &tls.Config{RootCAs: certPool}, + } + + if cfg.Client.DialTimeout > 0 { + transport.Dial = (&net.Dialer{ + Timeout: time.Duration(cfg.Client.DialTimeout) * time.Millisecond, + KeepAlive: time.Duration(cfg.Client.DialKeepAlive) * time.Second, + }).Dial + } + + if cfg.Client.TLSHandshakeTimeout > 0 { + transport.TLSHandshakeTimeout = time.Duration(cfg.Client.TLSHandshakeTimeout) * time.Second + } + + if cfg.Client.ResponseHeaderTimeout > 0 { + transport.ResponseHeaderTimeout = time.Duration(cfg.Client.ResponseHeaderTimeout) * time.Second + } + + if cfg.Client.MaxIdleConns > 0 { + transport.MaxIdleConns = cfg.Client.MaxIdleConns + } + + if cfg.Client.MaxIdleConnsPerHost > 0 { + transport.MaxIdleConnsPerHost = cfg.Client.MaxIdleConnsPerHost + } + + return transport +} + func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *Router, err error) { const schemaDirectory = "/home/http/GO_SERVER/dmhbserver/static/bidder-params" const infoDirectory = "/home/http/GO_SERVER/dmhbserver/static/bidder-info" @@ -208,16 +245,32 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R glog.Infof("Could not read certificates file: %s \n", readCertErr.Error()) } + g_transport = getTransport(cfg, certPool) generalHttpClient := &http.Client{ - Transport: &http.Transport{ - MaxConnsPerHost: cfg.Client.MaxConnsPerHost, - MaxIdleConns: cfg.Client.MaxIdleConns, - MaxIdleConnsPerHost: cfg.Client.MaxIdleConnsPerHost, - IdleConnTimeout: time.Duration(cfg.Client.IdleConnTimeout) * time.Second, - TLSClientConfig: &tls.Config{RootCAs: certPool}, - }, + Transport: g_transport, } + /* + * Add Dialer: + * Add TLSHandshakeTimeout: + * MaxConnsPerHost: Max value should be QPS + * MaxIdleConnsPerHost: + * ResponseHeaderTimeout: Max Timeout from OW End + * No Need for MaxIdleConns: + * + + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 100 * time.Millisecond, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + MaxIdleConnsPerHost: (maxIdleConnsPerHost / size), // ideal needs to be defined diff? + MaxConnsPerHost: (maxConnPerHost / size), + ResponseHeaderTimeout: responseHdrTimeout, + } + */ cacheHttpClient := &http.Client{ Transport: &http.Transport{ MaxConnsPerHost: cfg.CacheClient.MaxConnsPerHost, @@ -273,7 +326,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R exchanges = newExchangeMap(cfg) g_cacheClient = pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, g_metrics) - adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, r.MetricsEngine) + adapters, adaptersErrs := exchange.BuildAdapters(generalHttpClient, cfg, bidderInfos, g_metrics) if len(adaptersErrs) > 0 { errs := errortypes.NewAggregateError("Failed to initialize adapters", adaptersErrs) glog.Fatalf("%v", errs) @@ -339,6 +392,10 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R return r, nil } +func GetGlobalTransport() *http.Transport { + return g_transport +} + //OrtbAuctionEndpointWrapper Openwrap wrapper method for calling /openrtb2/auction endpoint func OrtbAuctionEndpointWrapper(w http.ResponseWriter, r *http.Request) error { ortbAuctionEndpoint, err := openrtb2.NewEndpoint(g_ex, g_paramsValidator, g_storedReqFetcher, g_accounts, g_cfg, g_metrics, g_analytics, g_disabledBidders, g_defReqJSON, g_activeBidders) From a8fb2e7e081600b8224dcdc46a5c64f47b11cd79 Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Fri, 9 Jul 2021 14:35:57 +0530 Subject: [PATCH 12/31] UOE-6646: Added label adapter_name for tls_handshake_time stat --- exchange/bidder.go | 2 +- exchange/bidder_test.go | 2 +- metrics/config/metrics.go | 6 +-- metrics/go_metrics.go | 44 ++++++++++------ metrics/go_metrics_test.go | 44 ++++++++++++---- metrics/metrics.go | 2 +- metrics/metrics_mock.go | 4 +- metrics/prometheus/prometheus.go | 76 +++++++++++++++------------ metrics/prometheus/prometheus_test.go | 59 ++++++++++++++------- 9 files changed, 153 insertions(+), 86 deletions(-) diff --git a/exchange/bidder.go b/exchange/bidder.go index c30d791d221..262d2d8d3f3 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -516,7 +516,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 6db249ec6ed..a3a0acbe5f0 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -1523,7 +1523,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() // Instantiate the bidder that will send the request. We'll make sure to use an // http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{}) diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 70738f2fd1a..6c9344b325b 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -147,9 +147,9 @@ func (me *MultiMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { } } -func (me *MultiMetricsEngine) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { +func (me *MultiMetricsEngine) RecordTLSHandshakeTime(bidderName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { for _, thisME := range *me { - thisME.RecordTLSHandshakeTime(tlsHandshakeTime) + thisME.RecordTLSHandshakeTime(bidderName, tlsHandshakeTime) } } @@ -338,7 +338,7 @@ func (me *DummyMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { } // RecordTLSHandshakeTime as a noop -func (me *DummyMetricsEngine) RecordTLSHandshakeTime(tlsHandshakeTime time.Duration) { +func (me *DummyMetricsEngine) RecordTLSHandshakeTime(bidderName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { } // RecordAdapterBidReceived as a noop diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index dc4bc1f8217..f21ebc5907c 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -31,7 +31,6 @@ type Metrics struct { StoredImpCacheMeter map[CacheResult]metrics.Meter AccountCacheMeter map[CacheResult]metrics.Meter DNSLookupTimer metrics.Timer - TLSHandshakeTimer metrics.Timer // Metrics for OpenRTB requests specifically. So we can track what % of RequestsMeter are OpenRTB // and know when legacy requests have been abandoned. @@ -101,6 +100,7 @@ type AdapterMetrics struct { ConnCreated metrics.Counter ConnReused metrics.Counter ConnWaitTime metrics.Timer + TLSHandshakeTimer metrics.Timer } type MarkupDeliveryMetrics struct { @@ -131,18 +131,18 @@ func NewBlankMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderNa blankTimer := &metrics.NilTimer{} newMetrics := &Metrics{ - MetricsRegistry: registry, - RequestStatuses: make(map[RequestType]map[RequestStatus]metrics.Meter), - ConnectionCounter: metrics.NilCounter{}, - ConnectionAcceptErrorMeter: blankMeter, - ConnectionCloseErrorMeter: blankMeter, - ImpMeter: blankMeter, - LegacyImpMeter: blankMeter, - AppRequestMeter: blankMeter, - NoCookieMeter: blankMeter, - RequestTimer: blankTimer, - DNSLookupTimer: blankTimer, - TLSHandshakeTimer: blankTimer, + MetricsRegistry: registry, + RequestStatuses: make(map[RequestType]map[RequestStatus]metrics.Meter), + ConnectionCounter: metrics.NilCounter{}, + ConnectionAcceptErrorMeter: blankMeter, + ConnectionCloseErrorMeter: blankMeter, + ImpMeter: blankMeter, + LegacyImpMeter: blankMeter, + AppRequestMeter: blankMeter, + NoCookieMeter: blankMeter, + RequestTimer: blankTimer, + DNSLookupTimer: blankTimer, + //TLSHandshakeTimer: blankTimer, RequestsQueueTimer: make(map[RequestType]map[bool]metrics.Timer), PrebidCacheRequestTimerSuccess: blankTimer, PrebidCacheRequestTimerError: blankTimer, @@ -243,7 +243,7 @@ func NewMetrics(registry metrics.Registry, exchanges []openrtb_ext.BidderName, d newMetrics.AppRequestMeter = metrics.GetOrRegisterMeter("app_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.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) @@ -319,6 +319,7 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet newAdapter.ConnCreated = metrics.NilCounter{} newAdapter.ConnReused = metrics.NilCounter{} newAdapter.ConnWaitTime = &metrics.NilTimer{} + newAdapter.TLSHandshakeTimer = &metrics.NilTimer{} } for _, err := range AdapterErrors() { newAdapter.ErrorMeters[err] = blankMeter @@ -357,6 +358,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.connection_wait_time", adapterOrAccount, exchange), registry) for err := range am.ErrorMeters { am.ErrorMeters[err] = metrics.GetOrRegisterMeter(fmt.Sprintf("%s.%s.requests.%s", adapterOrAccount, exchange, err), registry) } @@ -559,8 +561,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[adapterName] + if !ok { + glog.Errorf("Trying to log adapter TLS Handshake metrics for %s: adapter not found", string(adapterName)) + return + } + + am.TLSHandshakeTimer.Update(tlsHandshakeTime) } // RecordAdapterBidReceived implements a part of the MetricsEngine interface. diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 2d0b9097b11..7ebe2f3c2fe 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -210,29 +210,51 @@ 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{ + adapterName: openrtb_ext.BidderAppnexus, + 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{ + adapterName: openrtb_ext.BidderAppnexus, + tLSHandshakeDuration: time.Duration(0), + adapterMetricsEnabled: true, + }, + out: testOut{ + expectedDuration: time.Duration(0), + }, }, } for _, test := range testCases { registry := metrics.NewRegistry() m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AccountAdapterDetails: true}) - m.RecordTLSHandshakeTime(test.tLSHandshakeDuration) + m.RecordTLSHandshakeTime(test.in.adapterName, test.in.tLSHandshakeDuration) - assert.Equal(t, test.expectedDuration.Nanoseconds(), m.TLSHandshakeTimer.Sum(), test.description) + assert.Equal(t, test.out.expectedDuration.Nanoseconds(), m.AdapterMetrics[openrtb_ext.BidderAppnexus].TLSHandshakeTimer.Sum(), test.description) } } diff --git a/metrics/metrics.go b/metrics/metrics.go index 4e6a6ea7275..5966b7716f3 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -348,7 +348,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) RecordAdapterPanic(labels AdapterLabels) // This records whether or not a bid of a particular type uses `adm` or `nurl`. // Since the legacy endpoints don't have a bid type, it can only count bids from OpenRTB and AMP. diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index 54b448bbe19..b211b2faa22 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) } // RecordAdapterBidReceived mock diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 33b36fbb61c..a0a13455493 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -15,33 +15,33 @@ type Metrics struct { Registry *prometheus.Registry // General Metrics - connectionsClosed prometheus.Counter - connectionsError *prometheus.CounterVec - connectionsOpened prometheus.Counter - cookieSync prometheus.Counter - impressions *prometheus.CounterVec - impressionsLegacy prometheus.Counter - prebidCacheWriteTimer *prometheus.HistogramVec - requests *prometheus.CounterVec - requestsTimer *prometheus.HistogramVec - requestsQueueTimer *prometheus.HistogramVec - requestsWithoutCookie *prometheus.CounterVec - storedImpressionsCacheResult *prometheus.CounterVec - storedRequestCacheResult *prometheus.CounterVec - accountCacheResult *prometheus.CounterVec - storedAccountFetchTimer *prometheus.HistogramVec - storedAccountErrors *prometheus.CounterVec - storedAMPFetchTimer *prometheus.HistogramVec - storedAMPErrors *prometheus.CounterVec - storedCategoryFetchTimer *prometheus.HistogramVec - storedCategoryErrors *prometheus.CounterVec - storedRequestFetchTimer *prometheus.HistogramVec - storedRequestErrors *prometheus.CounterVec - storedVideoFetchTimer *prometheus.HistogramVec - storedVideoErrors *prometheus.CounterVec - timeoutNotifications *prometheus.CounterVec - dnsLookupTimer prometheus.Histogram - tlsHandhakeTimer prometheus.Histogram + connectionsClosed prometheus.Counter + connectionsError *prometheus.CounterVec + connectionsOpened prometheus.Counter + cookieSync prometheus.Counter + impressions *prometheus.CounterVec + impressionsLegacy prometheus.Counter + prebidCacheWriteTimer *prometheus.HistogramVec + requests *prometheus.CounterVec + requestsTimer *prometheus.HistogramVec + requestsQueueTimer *prometheus.HistogramVec + requestsWithoutCookie *prometheus.CounterVec + storedImpressionsCacheResult *prometheus.CounterVec + storedRequestCacheResult *prometheus.CounterVec + accountCacheResult *prometheus.CounterVec + storedAccountFetchTimer *prometheus.HistogramVec + storedAccountErrors *prometheus.CounterVec + storedAMPFetchTimer *prometheus.HistogramVec + storedAMPErrors *prometheus.CounterVec + storedCategoryFetchTimer *prometheus.HistogramVec + storedCategoryErrors *prometheus.CounterVec + storedRequestFetchTimer *prometheus.HistogramVec + storedRequestErrors *prometheus.CounterVec + storedVideoFetchTimer *prometheus.HistogramVec + storedVideoErrors *prometheus.CounterVec + timeoutNotifications *prometheus.CounterVec + dnsLookupTimer prometheus.Histogram + //tlsHandhakeTimer prometheus.Histogram privacyCCPA *prometheus.CounterVec privacyCOPPA *prometheus.CounterVec privacyLMT *prometheus.CounterVec @@ -62,6 +62,7 @@ type Metrics struct { adapterConnectionWaitTime *prometheus.HistogramVec adapterDuplicateBidIDCounter *prometheus.CounterVec adapterVideoBidDuration *prometheus.HistogramVec + tlsHandhakeTimer *prometheus.HistogramVec // Account Metrics accountRequests *prometheus.CounterVec @@ -283,10 +284,10 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Seconds to resolve DNS", standardTimeBuckets) - metrics.tlsHandhakeTimer = newHistogram(cfg, metrics.Registry, - "tls_handshake_time", - "Seconds to perform TLS Handshake", - standardTimeBuckets) + //metrics.tlsHandhakeTimer = newHistogram(cfg, metrics.Registry, + // "tls_handshake_time", + // "Seconds to perform TLS Handshake", + // standardTimeBuckets) metrics.privacyCCPA = newCounter(cfg, metrics.Registry, "privacy_ccpa", @@ -355,6 +356,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, metrics.Registry, + "tls_handshake_time", + "Seconds to perform TLS Handshake", + []string{adapterLabel}, + standardTimeBuckets) } metrics.adapterRequestsTimer = newHistogramVec(cfg, metrics.Registry, @@ -622,8 +629,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) RecordAdapterPanic(labels metrics.AdapterLabels) { diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 9b4dc2aa09e..2cdf8702364 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1199,35 +1199,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]) } } @@ -1353,6 +1375,7 @@ func TestDisableAdapterConnections(t *testing.T) { 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") } func TestRecordRequestPrivacy(t *testing.T) { From b420a11bac0faa4797573337137d4dd75b52dd33 Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Fri, 9 Jul 2021 17:47:07 +0530 Subject: [PATCH 13/31] Fixed typo --- metrics/go_metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index f21ebc5907c..2529eaf4765 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -358,7 +358,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.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) } From 62934aa143f59103174ee1ac03dc4139caabaf77 Mon Sep 17 00:00:00 2001 From: Viral Vala <63396712+pm-viral-vala@users.noreply.github.com> Date: Fri, 9 Jul 2021 18:23:00 +0530 Subject: [PATCH 14/31] OTT-227 Fixing Panic Issue for Prebid Adapter (#176) * OTT-227 Fixing Panic Issue for Prebid Adapter --- exchange/exchange.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index 4a2ae73802b..992f756a5ba 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -438,9 +438,6 @@ func (e *exchange) getAllBids( reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed) - // Setting bidderCoreName in SeatBid - bids.bidderCoreName = bidderRequest.BidderCoreName - // Add in time reporting elapsed := time.Since(start) brw.adapterBids = bids @@ -449,6 +446,9 @@ func (e *exchange) getAllBids( ae.ResponseTimeMillis = int(elapsed / time.Millisecond) if bids != nil { ae.HttpCalls = bids.httpCalls + + // Setting bidderCoreName in SeatBid + bids.bidderCoreName = bidderRequest.BidderCoreName } // Timing statistics @@ -514,9 +514,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) From 6c8508d15d853937fef553fa8c9c3b04af5ddc12 Mon Sep 17 00:00:00 2001 From: Pubmatic-Dhruv-Sonone <83747371+Pubmatic-Dhruv-Sonone@users.noreply.github.com> Date: Tue, 27 Jul 2021 17:04:34 +0530 Subject: [PATCH 15/31] OTT-216: add all SupportDeal features (#183) --- exchange/exchange.go | 68 ++++++++++++++++++++------------------- exchange/exchange_test.go | 44 +++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 33 deletions(-) diff --git a/exchange/exchange.go b/exchange/exchange.go index 992f756a5ba..04af7729273 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -764,49 +764,51 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, 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(pb, 64) - if err != nil { - currBidPrice = 0 - } - if dupeBidPrice == currBidPrice { - if rand.Intn(100) < 50 { - dupeBidPrice = -1 - } else { - currBidPrice = -1 + dupeBidPrice, err := strconv.ParseFloat(dupe.bidPrice, 64) + if err != nil { + dupeBidPrice = 0 + } + currBidPrice, err := strconv.ParseFloat(pb, 64) + if err != nil { + currBidPrice = 0 + } + if dupeBidPrice == currBidPrice { + if rand.Intn(100) < 50 { + 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] - 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) rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") } else { - oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...) + // An older bid from a different seatBid we've already finished with + oldSeatBid := (seatBids)[dupe.bidderName] + if len(oldSeatBid.bids) == 1 { + seatBidsToRemove = append(seatBidsToRemove, dupe.bidderName) + rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") + } else { + oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...) + } } + delete(res, dupe.bidID) + } else { + // Remove this bid + bidsToRemove = append(bidsToRemove, bidInd) + 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 } + dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } res[bidID] = categoryDuration - dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } if len(bidsToRemove) > 0 { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 24fa2338e51..718fb98c8f1 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -3092,3 +3092,47 @@ type nilCategoryFetcher struct{} func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) { return "", nil } + +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{}, nil) + + for _, testcase := range testCases { + var adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid + if nil == testcase.bidderCollisions { + break + } + adapterBids = make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) + for bidder, collisions := range *testcase.bidderCollisions { + bids := make([]*pbsOrtbBid, 0) + testBidID := "bid_id_for_bidder_" + bidder + // add bids as per collisions value + bidCount := 0 + for ; bidCount < collisions; bidCount++ { + bids = append(bids, &pbsOrtbBid{ + bid: &openrtb2.Bid{ + ID: testBidID, + }, + }) + } + if nil == adapterBids[openrtb_ext.BidderName(bidder)] { + adapterBids[openrtb_ext.BidderName(bidder)] = new(pbsOrtbSeatBid) + } + adapterBids[openrtb_ext.BidderName(bidder)].bids = bids + } + assert.Equal(t, testcase.hasCollision, recordAdaptorDuplicateBidIDs(testEngine, adapterBids)) + } +} From 4ec85b03808ffe487090610db5c479d034d18654 Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Tue, 27 Jul 2021 17:31:02 +0530 Subject: [PATCH 16/31] UOE-6525: In-app OTT Add Support for dctr and pmzoneid (#170) * Url decoding value of keywords * Refactored code to rename dctr to key_val * Added pmzoneid support in keywords * Refactored code with removing single statement function * Refactored code, renaming variables * Refactored code: Removed size argument to make for impExtMap * Added test case for URL encoded dctr value * Added omitempty for fields in ExtImpPubmatic --- adapters/pubmatic/pubmatic.go | 43 ++++- .../pubmatictest/exemplary/simple-banner.json | 2 +- .../exemplary/video-rewarded.json | 2 +- .../pubmatictest/exemplary/video.json | 2 +- .../pubmatictest/params/race/banner.json | 5 +- .../pubmatictest/params/race/video.json | 3 +- .../pubmatictest/supplemental/app.json | 2 +- .../supplemental/dctrAndPmZoneID.json | 162 +++++++++++++++++ .../supplemental/gptSlotNameInImpExt.json | 2 +- .../supplemental/pmZoneIDInKeywords.json | 161 +++++++++++++++++ .../supplemental/trimPublisherID.json | 2 +- .../supplemental/urlEncodedDCTR.json | 166 ++++++++++++++++++ openrtb_ext/imp_pubmatic.go | 2 + static/bidder-params/pubmatic.json | 8 + 14 files changed, 549 insertions(+), 13 deletions(-) create mode 100644 adapters/pubmatic/pubmatictest/supplemental/dctrAndPmZoneID.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/pmZoneIDInKeywords.json create mode 100644 adapters/pubmatic/pubmatictest/supplemental/urlEncodedDCTR.json diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index a0ca5f50529..16223018614 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" "strconv" "strings" @@ -82,6 +83,13 @@ const ( INVALID_HEIGHT = "Invalid Height" INVALID_MEDIATYPE = "Invalid MediaType" INVALID_ADSLOT = "Invalid AdSlot" + + dctrKeyName = "key_val" + dctrKeywordName = "dctr" + pmZoneIDKeyName = "pmZoneId" + pmZoneIDRequestParamName = "pmzoneid" + + urlEncodedEqualChar = "%3D" ) func PrepareLogMessage(tID, pubId, adUnitId, bidID, details string, args ...interface{}) string { @@ -621,7 +629,14 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID impExtMap := make(map[string]interface{}) if pubmaticExt.Keywords != nil && len(pubmaticExt.Keywords) != 0 { - populateKeywordsInExt(pubmaticExt.Keywords, impExtMap) + addKeywordsToExt(pubmaticExt.Keywords, impExtMap) + } + //Give preference to direct values of 'dctr' & 'pmZoneId' params in extension + if pubmaticExt.Dctr != "" { + impExtMap[dctrKeyName] = pubmaticExt.Dctr + } + if pubmaticExt.PmZoneID != "" { + impExtMap[pmZoneIDKeyName] = pubmaticExt.PmZoneID } if bidderExt.Prebid != nil { @@ -638,23 +653,41 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID impExtMap[ImpExtAdUnitKey] = bidderExt.Data.AdServer.AdSlot } - if len(impExtMap) != 0 { + imp.Ext = nil + if len(impExtMap) > 0 { impExtBytes, err := json.Marshal(impExtMap) if err == nil { - imp.Ext = json.RawMessage(impExtBytes) + imp.Ext = impExtBytes } } + return nil } -func populateKeywordsInExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, impExtMap map[string]interface{}) { +func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) { for _, keyVal := range keywords { if len(keyVal.Values) == 0 { logf("No values present for key = %s", keyVal.Key) continue } else { - impExtMap[keyVal.Key] = strings.Join(keyVal.Values[:], ",") + val := strings.Join(keyVal.Values[:], ",") + + key := keyVal.Key + 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] = val } } } diff --git a/adapters/pubmatic/pubmatictest/exemplary/simple-banner.json b/adapters/pubmatic/pubmatictest/exemplary/simple-banner.json index 0cb2739b2d0..21b5c16e878 100644 --- a/adapters/pubmatic/pubmatictest/exemplary/simple-banner.json +++ b/adapters/pubmatic/pubmatictest/exemplary/simple-banner.json @@ -68,7 +68,7 @@ "w": 300 }, "ext": { - "pmZoneID": "Zone1,Zone2", + "pmZoneId": "Zone1,Zone2", "preference": "sports,movies" } } diff --git a/adapters/pubmatic/pubmatictest/exemplary/video-rewarded.json b/adapters/pubmatic/pubmatictest/exemplary/video-rewarded.json index af4220bd23e..ae71c315d6c 100644 --- a/adapters/pubmatic/pubmatictest/exemplary/video-rewarded.json +++ b/adapters/pubmatic/pubmatictest/exemplary/video-rewarded.json @@ -76,7 +76,7 @@ "maxbitrate": 10 }, "ext": { - "pmZoneID": "Zone1,Zone2", + "pmZoneId": "Zone1,Zone2", "reward": 1 } } diff --git a/adapters/pubmatic/pubmatictest/exemplary/video.json b/adapters/pubmatic/pubmatictest/exemplary/video.json index 4c874535a35..1715e4772c8 100644 --- a/adapters/pubmatic/pubmatictest/exemplary/video.json +++ b/adapters/pubmatic/pubmatictest/exemplary/video.json @@ -73,7 +73,7 @@ "maxbitrate": 10 }, "ext": { - "pmZoneID": "Zone1,Zone2" + "pmZoneId": "Zone1,Zone2" } } ], diff --git a/adapters/pubmatic/pubmatictest/params/race/banner.json b/adapters/pubmatic/pubmatictest/params/race/banner.json index 86ddc70b729..86317f86e4c 100644 --- a/adapters/pubmatic/pubmatictest/params/race/banner.json +++ b/adapters/pubmatic/pubmatictest/params/race/banner.json @@ -1,8 +1,11 @@ { "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", + "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": { diff --git a/adapters/pubmatic/pubmatictest/params/race/video.json b/adapters/pubmatic/pubmatictest/params/race/video.json index 86ddc70b729..770a3e1d4ab 100644 --- a/adapters/pubmatic/pubmatictest/params/race/video.json +++ b/adapters/pubmatic/pubmatictest/params/race/video.json @@ -2,7 +2,8 @@ "publisherId": "156209", "adSlot": "pubmatic_test2@300x250", "keywords": { - "pmZoneID": "Zone1,Zone2", + "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": { diff --git a/adapters/pubmatic/pubmatictest/supplemental/app.json b/adapters/pubmatic/pubmatictest/supplemental/app.json index 3aabe54a7dd..24949a6a2a1 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/app.json +++ b/adapters/pubmatic/pubmatictest/supplemental/app.json @@ -67,7 +67,7 @@ "w": 300 }, "ext": { - "pmZoneID": "Zone1,Zone2", + "pmZoneId": "Zone1,Zone2", "preference": "sports,movies", "skadn": { "skadnetids": ["k674qkevps.skadnetwork"], diff --git a/adapters/pubmatic/pubmatictest/supplemental/dctrAndPmZoneID.json b/adapters/pubmatic/pubmatictest/supplemental/dctrAndPmZoneID.json new file mode 100644 index 00000000000..d68517de560 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/dctrAndPmZoneID.json @@ -0,0 +1,162 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": " 999 ", + "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": [ + { + "key": "pmZoneID", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "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": "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", + "pmZoneId": "drama,sport", + "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": { + "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/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json index 9336ed63262..762691e955d 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json @@ -84,7 +84,7 @@ "w": 300 }, "ext": { - "pmZoneID": "Zone1,Zone2", + "pmZoneId": "Zone1,Zone2", "preference": "sports,movies", "dfp_ad_unit_code": "/1111/home" } diff --git a/adapters/pubmatic/pubmatictest/supplemental/pmZoneIDInKeywords.json b/adapters/pubmatic/pubmatictest/supplemental/pmZoneIDInKeywords.json new file mode 100644 index 00000000000..7698bde1ba5 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/pmZoneIDInKeywords.json @@ -0,0 +1,161 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "adSlot": "AdTag_Div1@300x250", + "publisherId": " 999 ", + "dctr": "key1=V1,V2,V3|key2=v1|key3=v3,v5", + "keywords": [ + { + "key": "pmzoneid", + "value": [ + "Zone1", + "Zone2" + ] + }, + { + "key": "preference", + "value": [ + "sports", + "movies" + ] + } + ], + "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": "key1=V1,V2,V3|key2=v1|key3=v3,v5", + "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": { + "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/pubmatic/pubmatictest/supplemental/trimPublisherID.json b/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json index 6a344ee091a..d207542a525 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json +++ b/adapters/pubmatic/pubmatictest/supplemental/trimPublisherID.json @@ -71,7 +71,7 @@ "w": 300 }, "ext": { - "pmZoneID": "Zone1,Zone2", + "pmZoneId": "Zone1,Zone2", "preference": "sports,movies" } } diff --git a/adapters/pubmatic/pubmatictest/supplemental/urlEncodedDCTR.json b/adapters/pubmatic/pubmatictest/supplemental/urlEncodedDCTR.json new file mode 100644 index 00000000000..501bf2fd165 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/urlEncodedDCTR.json @@ -0,0 +1,166 @@ +{ + "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": { + "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/openrtb_ext/imp_pubmatic.go b/openrtb_ext/imp_pubmatic.go index fd97836dd32..543237a0fe2 100644 --- a/openrtb_ext/imp_pubmatic.go +++ b/openrtb_ext/imp_pubmatic.go @@ -11,6 +11,8 @@ import "encoding/json" type ExtImpPubmatic struct { 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"` } diff --git a/static/bidder-params/pubmatic.json b/static/bidder-params/pubmatic.json index 1b6a2f03512..5d41b9fc68a 100644 --- a/static/bidder-params/pubmatic.json +++ b/static/bidder-params/pubmatic.json @@ -12,6 +12,14 @@ "type": "string", "description": "An ID which identifies the ad slot" }, + "pmzoneid": { + "type": "string", + "description": "Comma separated zone id. Used im deal targeting & site section targeting. e.g drama,sport" + }, + "dctr": { + "type": "string", + "description": "Deals Custom Targeting, pipe separated key-value pairs e.g key1=V1,V2,V3|key2=v1|key3=v3,v5" + }, "wrapper": { "type": "object", "description": "Specifies pubmatic openwrap configuration for a publisher", From 721dfc124040894f713456d0147736fe1fd4404b Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Tue, 27 Jul 2021 18:15:00 +0530 Subject: [PATCH 17/31] UOE-6534: OW Prebid-server: Use fallback mechanism for get slot name (#173) * UOE-6534: OW Prebid-server: Use fallback mechanism for get slot name * Updated test case to include override scenario for gpt-slot-name --- adapters/bidder.go | 1 - adapters/pubmatic/pubmatic.go | 26 ++- .../supplemental/gptSlotNameInImpExt.json | 1 + .../gptSlotNameInImpExtPbAdslot.json | 171 ++++++++++++++++++ openrtb_ext/imp.go | 9 - 5 files changed, 194 insertions(+), 14 deletions(-) create mode 100644 adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json diff --git a/adapters/bidder.go b/adapters/bidder.go index 395d4bd2201..15e16ce00b1 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -130,7 +130,6 @@ type ExtImpBidder struct { // Bidder implementations may safely assume that this JSON has been validated by their // static/bidder-params/{bidder}.json file. Bidder json.RawMessage `json:"bidder"` - Data *openrtb_ext.ExtData `json:"data,omitempty"` } func (r *RequestData) SetBasicAuth(username string, password string) { diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index 16223018614..d787846c76b 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -73,6 +73,21 @@ type pubmaticBidExt struct { VideoCreativeInfo *pubmaticBidExtVideo `json:"video,omitempty"` } +type ExtImpBidderPubmatic struct { + adapters.ExtImpBidder + Data *ExtData `json:"data,omitempty"` +} + +type ExtData struct { + AdServer *ExtAdServer `json:"adserver"` + PBAdSlot string `json:"pbadslot"` +} + +type ExtAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} + const ( INVALID_PARAMS = "Invalid BidParam" MISSING_PUBID = "Missing PubID" @@ -592,7 +607,7 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID imp.Audio = nil } - var bidderExt adapters.ExtImpBidder + var bidderExt ExtImpBidderPubmatic if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { return err } @@ -648,9 +663,12 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID } } - if bidderExt.Data != nil && bidderExt.Data.AdServer != nil && - bidderExt.Data.AdServer.Name == AdServerGAM && bidderExt.Data.AdServer.AdSlot != "" { - impExtMap[ImpExtAdUnitKey] = bidderExt.Data.AdServer.AdSlot + if bidderExt.Data != nil { + if bidderExt.Data.AdServer != nil && bidderExt.Data.AdServer.Name == AdServerGAM && bidderExt.Data.AdServer.AdSlot != "" { + impExtMap[ImpExtAdUnitKey] = bidderExt.Data.AdServer.AdSlot + } else if bidderExt.Data.PBAdSlot != "" { + impExtMap[ImpExtAdUnitKey] = bidderExt.Data.PBAdSlot + } } imp.Ext = nil diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json index 762691e955d..ce4e4f854b2 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json @@ -38,6 +38,7 @@ } }, "data": { + "pbadslot": "/2222/home", "adserver": { "name": "gam", "adslot": "/1111/home" diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json new file mode 100644 index 00000000000..dd656237680 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json @@ -0,0 +1,171 @@ +{ + "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" + ] + } + ], + "wrapper": { + "version": 1, + "profile": 5123 + } + }, + "data": { + "pbadslot": "/2222/home" + } + } + } + ], + "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" + } + }, + "ext": { + "prebid": { + "bidderparams": { + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } + } + }, + "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": { + "pmZoneID": "Zone1,Zone2", + "preference": "sports,movies", + "dfp_ad_unit_code": "/2222/home" + } + } + ], + "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": { + "wrapper": { + "profile": 5123, + "version": 1, + "wiid": "dwzafakjflan-tygannnvlla-mlljvj" + } + } + } + }, + "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/openrtb_ext/imp.go b/openrtb_ext/imp.go index 4a2c2bc5c77..f83fa63df84 100644 --- a/openrtb_ext/imp.go +++ b/openrtb_ext/imp.go @@ -22,12 +22,3 @@ type ExtImpPrebid struct { type ExtStoredRequest struct { ID string `json:"id"` } - -type ExtData struct { - AdServer *ExtAdServer `json:"adserver"` -} - -type ExtAdServer struct { - Name string `json:"name"` - AdSlot string `json:"adslot"` -} From 3c564c7434a3f9a18ea73077f3056eced5ecffbc Mon Sep 17 00:00:00 2001 From: Viral Vala <63396712+pm-viral-vala@users.noreply.github.com> Date: Tue, 27 Jul 2021 18:44:09 +0530 Subject: [PATCH 18/31] OTT-48 VAST Bidder Phase 1 (#186) --- adapters/bidder.go | 8 +- .../gptSlotNameInImpExtPbAdslot.json | 2 +- adapters/vastbidder/bidder_macro.go | 1233 ++++++++++++++++ adapters/vastbidder/bidder_macro_test.go | 1258 +++++++++++++++++ adapters/vastbidder/constant.go | 166 +++ adapters/vastbidder/ibidder_macro.go | 199 +++ adapters/vastbidder/itag_response_handler.go | 43 + adapters/vastbidder/macro_processor.go | 216 +++ adapters/vastbidder/macro_processor_test.go | 587 ++++++++ adapters/vastbidder/mapper.go | 180 +++ adapters/vastbidder/sample_spotx_macro.go.bak | 28 + adapters/vastbidder/tagbidder.go | 87 ++ adapters/vastbidder/tagbidder_test.go | 149 ++ adapters/vastbidder/util.go | 70 + .../vastbidder/vast_tag_response_handler.go | 334 +++++ .../vast_tag_response_handler_test.go | 385 +++++ config/config.go | 2 + endpoints/events/vtrack.go | 3 +- endpoints/events/vtrack_test.go | 3 +- endpoints/openrtb2/ctv/types/adpod_types.go | 10 +- endpoints/openrtb2/ctv_auction.go | 151 +- endpoints/openrtb2/ctv_auction_test.go | 230 ++- errortypes/code.go | 1 + errortypes/errortypes.go | 16 + exchange/adapter_builders.go | 2 + exchange/bidder.go | 6 + exchange/events.go | 4 +- exchange/events_test.go | 3 +- exchange/exchange.go | 86 ++ exchange/exchange_test.go | 557 ++++++++ go.mod | 4 +- go.sum | 2 + openrtb_ext/bid.go | 1 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_vastbidder.go | 18 + openrtb_ext/response.go | 1 + static/bidder-info/vastbidder.yaml | 9 + static/bidder-params/vastbidder.json | 27 + usersync/usersyncers/syncer_test.go | 1 + 39 files changed, 6060 insertions(+), 24 deletions(-) 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/vast_tag_response_handler.go create mode 100644 adapters/vastbidder/vast_tag_response_handler_test.go create mode 100644 openrtb_ext/imp_vastbidder.go create mode 100644 static/bidder-info/vastbidder.yaml create mode 100644 static/bidder-params/vastbidder.json diff --git a/adapters/bidder.go b/adapters/bidder.go index 15e16ce00b1..f758b2f8d83 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -110,8 +110,14 @@ 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 @@ -129,7 +135,7 @@ type ExtImpBidder struct { // // Bidder implementations may safely assume that this JSON has been validated by their // static/bidder-params/{bidder}.json file. - Bidder json.RawMessage `json:"bidder"` + Bidder json.RawMessage `json:"bidder"` } func (r *RequestData) SetBasicAuth(username string, password string) { diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json index dd656237680..f3cb4713c9d 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExtPbAdslot.json @@ -81,7 +81,7 @@ "w": 300 }, "ext": { - "pmZoneID": "Zone1,Zone2", + "pmZoneId": "Zone1,Zone2", "preference": "sports,movies", "dfp_ad_unit_code": "/2222/home" } diff --git a/adapters/vastbidder/bidder_macro.go b/adapters/vastbidder/bidder_macro.go new file mode 100644 index 00000000000..1df6fe2f444 --- /dev/null +++ b/adapters/vastbidder/bidder_macro.go @@ -0,0 +1,1233 @@ +package vastbidder + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/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 + + //Impression level Request Headers + ImpReqHeaders http.Header +} + +//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 + } + } +} + +//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 { + var keys 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{} +} + +/********************* 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 "" +} + +/********************* 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 "" +} + +//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) +} + +/********************* 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 == openrtb2.ProtocolVAST40 || protocol == openrtb2.ProtocolVAST40Wrapper { + expectedVastTags |= 1 << 1 + } + if protocol <= openrtb2.ProtocolVAST30Wrapper { + 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 "" != value { + 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..0e6afa74cf4 --- /dev/null +++ b/adapters/vastbidder/bidder_macro_test.go @@ -0,0 +1,1258 @@ +package vastbidder + +import ( + "fmt" + "net/http" + "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/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: []openrtb2.Protocol{ + openrtb2.ProtocolVAST40, + openrtb2.ProtocolDAAST10, + }, + }, + }, + }, + }, + }, + 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: []openrtb2.Protocol{ + openrtb2.ProtocolVAST20, + openrtb2.ProtocolDAAST10, + }, + }, + }, + }, + }, + }, + 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: []openrtb2.Protocol{ + openrtb2.ProtocolVAST40, + openrtb2.ProtocolVAST40Wrapper, + }, + }, + }, + }, + }, + }, + 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: []openrtb2.Protocol{ + openrtb2.ProtocolVAST40, + openrtb2.ProtocolVAST20Wrapper, + }, + }, + }, + }, + }, + }, + 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: ``, + 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: ``, + 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: []openrtb2.Protocol{openrtb2.ProtocolVAST30, openrtb2.ProtocolVAST40Wrapper}, + Protocol: openrtb2.ProtocolVAST40Wrapper, + W: 640, + H: 480, + StartDelay: new(openrtb2.StartDelay), + Placement: openrtb2.VideoPlacementTypeInStream, + Linearity: openrtb2.VideoLinearityLinearInStream, + Skip: new(int8), + SkipMin: 10, + SkipAfter: 5, + Sequence: 1, + BAttr: []openrtb2.CreativeAttribute{openrtb2.CreativeAttributeAudioAdAutoPlay, openrtb2.CreativeAttributeAudioAdUserInitiated}, + MaxExtended: 10, + MinBitRate: 360, + MaxBitRate: 1080, + BoxingAllowed: 1, + PlaybackMethod: []openrtb2.PlaybackMethod{openrtb2.PlaybackMethodPageLoadSoundOn, openrtb2.PlaybackMethodClickSoundOn}, + PlaybackEnd: openrtb2.PlaybackCessationModeVideoCompletionOrTerminatedByUser, + Delivery: []openrtb2.ContentDeliveryMethod{openrtb2.ContentDeliveryMethodStreaming, openrtb2.ContentDeliveryMethodDownload}, + Pos: new(openrtb2.AdPosition), + API: []openrtb2.APIFramework{openrtb2.APIFrameworkVPAID10, openrtb2.APIFrameworkVPAID20}, + }, + }, + }, + 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(openrtb2.ProductionQuality), + VideoQuality: new(openrtb2.ProductionQuality), + Context: openrtb2.ContentContextVideo, + ContentRating: `1.2`, + UserRating: `2.2`, + QAGMediaRating: openrtb2.IQGMediaRatingAll, + 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: openrtb2.DeviceTypeConnectedTV, + Make: `device-make`, + Model: `device-model`, + OS: `os`, + OSV: `os-version`, + H: 1024, + W: 2048, + JS: 1, + Language: `device-lang`, + ConnectionType: new(openrtb2.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, + }, + }, + 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`, + 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: []openrtb2.Protocol{openrtb2.ProtocolVAST30, openrtb2.ProtocolVAST40Wrapper}, + Protocol: openrtb2.ProtocolVAST40Wrapper, + W: 640, + H: 480, + StartDelay: new(openrtb2.StartDelay), + Placement: openrtb2.VideoPlacementTypeInStream, + Linearity: openrtb2.VideoLinearityLinearInStream, + Skip: new(int8), + SkipMin: 10, + SkipAfter: 5, + Sequence: 1, + BAttr: []openrtb2.CreativeAttribute{openrtb2.CreativeAttributeAudioAdAutoPlay, openrtb2.CreativeAttributeAudioAdUserInitiated}, + MaxExtended: 10, + MinBitRate: 360, + MaxBitRate: 1080, + BoxingAllowed: 1, + PlaybackMethod: []openrtb2.PlaybackMethod{openrtb2.PlaybackMethodPageLoadSoundOn, openrtb2.PlaybackMethodClickSoundOn}, + PlaybackEnd: openrtb2.PlaybackCessationModeVideoCompletionOrTerminatedByUser, + Delivery: []openrtb2.ContentDeliveryMethod{openrtb2.ContentDeliveryMethodStreaming, openrtb2.ContentDeliveryMethodDownload}, + Pos: new(openrtb2.AdPosition), + API: []openrtb2.APIFramework{openrtb2.APIFrameworkVPAID10, openrtb2.APIFrameworkVPAID20}, + }, + }, + }, + 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(openrtb2.ProductionQuality), + VideoQuality: new(openrtb2.ProductionQuality), + Context: openrtb2.ContentContextVideo, + ContentRating: `1.2`, + UserRating: `2.2`, + QAGMediaRating: openrtb2.IQGMediaRatingAll, + 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: openrtb2.DeviceTypeConnectedTV, + Make: `device-make`, + Model: `device-model`, + OS: `os`, + OSV: `os-version`, + H: 1024, + W: 2048, + JS: 1, + Language: `device-lang`, + ConnectionType: new(openrtb2.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, + }, + }, + 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`, + 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)) + } + } + }) + } +} diff --git a/adapters/vastbidder/constant.go b/adapters/vastbidder/constant.go new file mode 100644 index 00000000000..08b524d2bca --- /dev/null +++ b/adapters/vastbidder/constant.go @@ -0,0 +1,166 @@ +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` + + //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` + 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` +) + +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..d5531b70413 --- /dev/null +++ b/adapters/vastbidder/ibidder_macro.go @@ -0,0 +1,199 @@ +package vastbidder + +import ( + "net/http" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" +) + +//Flags of each tag bidder +type Flags struct { + RemoveEmptyParam bool `json:"remove_empty,omitempty"` +} + +//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 + //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 + + //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 + 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 +} + +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..fd6d8b3357a --- /dev/null +++ b/adapters/vastbidder/itag_response_handler.go @@ -0,0 +1,43 @@ +package vastbidder + +import ( + "errors" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/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..47e15d5178a --- /dev/null +++ b/adapters/vastbidder/macro_processor.go @@ -0,0 +1,216 @@ +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 { + break + } + } + + valueCallback, found = mp.mapper[tmpKey] + if found { + //found callback function + value = valueCallback.callback(mp.bidderMacro, tmpKey) + break + } else if strings.HasSuffix(tmpKey, macroEscapeSuffix) { + //escaping macro found + tmpKey = tmpKey[0 : len(tmpKey)-macroEscapeSuffixLen] + nEscaping++ + continue + } + 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 +} + +//ProcessString : Substitute macros in input string +func (mp *MacroProcessor) ProcessString(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 +} + +//ProcessURL : Substitute macros in input string +func (mp *MacroProcessor) ProcessURL(uri string, flags Flags) (response string) { + if !flags.RemoveEmptyParam { + return mp.ProcessString(uri) + } + + murl, _ := url.Parse(uri) + + murl.Path = mp.ProcessString(murl.Path) + murl.RawQuery = mp.processURLValues(murl.Query(), flags) + murl.Fragment = mp.ProcessString(murl.Fragment) + + response = murl.String() + + glog.V(3).Infof("[MACRO]:in:[%s] replaced:[%s]", uri, response) + return +} + +//processURLValues : returns replaced macro values of url.values +func (mp *MacroProcessor) processURLValues(values url.Values, flags Flags) (response string) { + var out bytes.Buffer + for k, v := range values { + macroKey := v[0] + found := false + value := "" + + if len(macroKey) > (macroPrefixLen+macroSuffixLen) && + strings.HasPrefix(macroKey, macroPrefix) && + strings.HasSuffix(macroKey, macroSuffix) { + //Check macro key directly if present + newKey := macroKey[macroPrefixLen : len(macroKey)-macroSuffixLen] + value, found = mp.processKey(newKey) + } + + if !found { + //if key is not present then process it as normal string + value = mp.ProcessString(macroKey) + } + + if flags.RemoveEmptyParam == false || len(value) > 0 { + //append + if out.Len() > 0 { + out.WriteByte('&') + } + out.WriteString(k) + out.WriteByte('=') + out.WriteString(url.QueryEscape(value)) + } + } + return out.String() +} + +//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..2b2b513df2c --- /dev/null +++ b/adapters/vastbidder/macro_processor_test.go @@ -0,0 +1,587 @@ +package vastbidder + +import ( + "encoding/json" + "net/url" + "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/stretchr/testify/assert" +) + +func getBidRequest(requestJSON string) *openrtb2.BidRequest { + bidRequest := &openrtb2.BidRequest{} + json.Unmarshal([]byte(requestJSON), bidRequest) + return bidRequest +} +func TestMacroProcessor_ProcessString(t *testing.T) { + testMacroValues := map[string]string{ + MacroPubID: `pubID`, + MacroTagID: `tagid value`, + MacroTagID + macroEscapeSuffix: `tagid+value`, + MacroTagID + macroEscapeSuffix + macroEscapeSuffix: `tagid%2Bvalue`, + } + + sampleBidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {TagID: testMacroValues[MacroTagID]}, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: testMacroValues[MacroPubID], + }, + }, + } + + type fields struct { + bidRequest *openrtb2.BidRequest + } + tests := []struct { + name string + in string + expected string + }{ + { + name: "Empty Input", + in: "", + expected: "", + }, + { + name: "No Macro Replacement", + in: "Hello Test No Macro", + expected: "Hello Test No Macro", + }, + { + name: "Start Macro", + in: GetMacroKey(MacroTagID) + "HELLO", + expected: testMacroValues[MacroTagID] + "HELLO", + }, + { + name: "End Macro", + in: "HELLO" + GetMacroKey(MacroTagID), + expected: "HELLO" + testMacroValues[MacroTagID], + }, + { + name: "Start-End Macro", + in: GetMacroKey(MacroTagID) + "HELLO" + GetMacroKey(MacroTagID), + expected: testMacroValues[MacroTagID] + "HELLO" + testMacroValues[MacroTagID], + }, + { + name: "Half Start Macro", + in: macroPrefix + GetMacroKey(MacroTagID) + "HELLO", + expected: macroPrefix + testMacroValues[MacroTagID] + "HELLO", + }, + { + name: "Half End Macro", + in: "HELLO" + GetMacroKey(MacroTagID) + macroSuffix, + expected: "HELLO" + testMacroValues[MacroTagID] + macroSuffix, + }, + { + name: "Concatenated Macro", + in: GetMacroKey(MacroTagID) + GetMacroKey(MacroTagID) + "HELLO", + expected: testMacroValues[MacroTagID] + testMacroValues[MacroTagID] + "HELLO", + }, + { + name: "Incomplete Concatenation Macro", + in: GetMacroKey(MacroTagID) + macroSuffix + "LINKHELLO", + expected: testMacroValues[MacroTagID] + macroSuffix + "LINKHELLO", + }, + { + name: "Concatenation with Suffix Macro", + in: GetMacroKey(MacroTagID) + macroPrefix + GetMacroKey(MacroTagID) + "HELLO", + expected: testMacroValues[MacroTagID] + macroPrefix + testMacroValues[MacroTagID] + "HELLO", + }, + { + name: "Unknown Macro", + in: GetMacroKey(`UNKNOWN`) + `ABC`, + expected: GetMacroKey(`UNKNOWN`) + `ABC`, + }, + { + name: "Incomplete macro suffix", + in: "START" + macroSuffix, + expected: "START" + macroSuffix, + }, + { + name: "Incomplete Start and End", + 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: "Special Character", + in: macroPrefix + MacroTagID + `\n` + macroSuffix + "Sample \"" + GetMacroKey(MacroTagID) + "\" Data", + expected: macroPrefix + MacroTagID + `\n` + macroSuffix + "Sample \"" + testMacroValues[MacroTagID] + "\" Data", + }, + { + name: "Empty Value", + in: GetMacroKey(MacroTimeout) + "Hello", + expected: "Hello", + }, + { + name: "EscapingMacræo", + 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.ProcessString(tt.in) + assert.Equal(t, tt.expected, gotResponse) + }) + } +} + +func TestMacroProcessor_processKey(t *testing.T) { + testMacroValues := map[string]string{ + MacroPubID: `pub id`, + MacroPubID + macroEscapeSuffix: `pub+id`, + MacroTagID: `tagid value`, + MacroTagID + macroEscapeSuffix: `tagid+value`, + MacroTagID + macroEscapeSuffix + macroEscapeSuffix: `tagid%2Bvalue`, + } + + sampleBidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {TagID: testMacroValues[MacroTagID]}, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: testMacroValues[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) + }) + } +} + +func TestMacroProcessor_processURLValues(t *testing.T) { + testMacroValues := map[string]string{ + MacroPubID: `pub id`, + MacroPubID + macroEscapeSuffix: `pub+id`, + MacroTagID: `tagid value`, + MacroTagID + macroEscapeSuffix: `tagid+value`, + MacroTagID + macroEscapeSuffix + macroEscapeSuffix: `tagid%2Bvalue`, + } + + sampleBidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {TagID: testMacroValues[MacroTagID]}, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: testMacroValues[MacroPubID], + }, + }, + } + type args struct { + values url.Values + flags Flags + } + tests := []struct { + name string + args args + want url.Values + }{ + { + name: `AllEmptyParamsRemovedEmptyParams`, + args: args{ + values: url.Values{ + `k1`: []string{GetMacroKey(MacroPubName)}, + `k2`: []string{GetMacroKey(MacroPubName)}, + `k3`: []string{GetMacroKey(MacroPubName)}, + }, + flags: Flags{ + RemoveEmptyParam: true, + }, + }, + want: url.Values{}, + }, + { + name: `AllEmptyParamsKeepEmptyParams`, + args: args{ + values: url.Values{ + `k1`: []string{GetMacroKey(MacroPubName)}, + `k2`: []string{GetMacroKey(MacroPubName)}, + `k3`: []string{GetMacroKey(MacroPubName)}, + }, + flags: Flags{ + RemoveEmptyParam: false, + }, + }, + want: url.Values{ + `k1`: []string{""}, + `k2`: []string{""}, + `k3`: []string{""}, + }, + }, + { + name: `MixedParamsRemoveEmptyParams`, + args: args{ + values: url.Values{ + `k1`: []string{GetMacroKey(MacroPubID)}, + `k2`: []string{GetMacroKey(MacroPubName)}, + `k3`: []string{GetMacroKey(MacroTagID)}, + }, + flags: Flags{ + RemoveEmptyParam: true, + }, + }, + want: url.Values{ + `k1`: []string{testMacroValues[MacroPubID]}, + `k3`: []string{testMacroValues[MacroTagID]}, + }, + }, + { + name: `MixedParamsKeepEmptyParams`, + args: args{ + values: url.Values{ + `k1`: []string{GetMacroKey(MacroPubID)}, + `k2`: []string{GetMacroKey(MacroPubName)}, + `k3`: []string{GetMacroKey(MacroTagID)}, + `k4`: []string{`UNKNOWN`}, + `k5`: []string{GetMacroKey(`UNKNOWN`)}, + }, + flags: Flags{ + RemoveEmptyParam: false, + }, + }, + want: url.Values{ + `k1`: []string{testMacroValues[MacroPubID]}, + `k2`: []string{""}, + `k3`: []string{testMacroValues[MacroTagID]}, + `k4`: []string{`UNKNOWN`}, + `k5`: []string{GetMacroKey(`UNKNOWN`)}, + }, + }, + // 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]) + + actual := mp.processURLValues(tt.args.values, tt.args.flags) + + actualValues, _ := url.ParseQuery(actual) + assert.Equal(t, tt.want, actualValues) + }) + } +} + +func TestMacroProcessor_processURLValuesEscapingKeys(t *testing.T) { + testMacroImpValues := map[string]string{ + MacroPubID: `pub id`, + MacroTagID: `tagid value`, + } + + testMacroValues := map[string]string{ + MacroPubID: `pub+id`, + MacroTagID: `tagid+value`, + MacroTagID + macroEscapeSuffix: `tagid%2Bvalue`, + MacroTagID + macroEscapeSuffix + macroEscapeSuffix: `tagid%252Bvalue`, + } + + sampleBidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {TagID: testMacroImpValues[MacroTagID]}, + }, + Site: &openrtb2.Site{ + Publisher: &openrtb2.Publisher{ + ID: testMacroImpValues[MacroPubID], + }, + }, + } + type args struct { + key string + value string + } + tests := []struct { + name string + args args + want string + }{ + { + name: `EmptyKeyValue`, + args: args{}, + want: ``, + }, + { + name: `WithoutEscaping`, + args: args{key: `k1`, value: GetMacroKey(MacroTagID)}, + want: `k1=` + testMacroValues[MacroTagID], + }, + { + name: `WithEscaping`, + args: args{key: `k1`, value: GetMacroKey(MacroTagID + macroEscapeSuffix)}, + want: `k1=` + testMacroValues[MacroTagID+macroEscapeSuffix], + }, + { + name: `With2LevelEscaping`, + args: args{key: `k1`, value: GetMacroKey(MacroTagID + macroEscapeSuffix + macroEscapeSuffix)}, + want: `k1=` + 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]) + + values := url.Values{} + if len(tt.args.key) > 0 { + values.Add(tt.args.key, tt.args.value) + } + + actual := mp.processURLValues(values, Flags{}) + assert.Equal(t, tt.want, actual) + }) + } +} + +func TestMacroProcessor_ProcessURL(t *testing.T) { + testMacroImpValues := map[string]string{ + MacroPubID: `123`, + MacroSiteID: `567`, + MacroTagID: `tagid value`, + } + + sampleBidRequest := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {TagID: testMacroImpValues[MacroTagID]}, + }, + Site: &openrtb2.Site{ + ID: testMacroImpValues[MacroSiteID], + Publisher: &openrtb2.Publisher{ + ID: testMacroImpValues[MacroPubID], + }, + }, + } + + type args struct { + uri string + flags Flags + } + tests := []struct { + name string + args args + wantResponse string + }{ + { + name: "EmptyURI", + args: args{ + uri: ``, + flags: Flags{RemoveEmptyParam: true}, + }, + wantResponse: ``, + }, + { + name: "RemovedEmptyParams1", + args: args{ + uri: `http://xyz.domain.com/` + GetMacroKey(MacroPubID) + `/` + GetMacroKey(MacroSiteID) + `?tagID=` + GetMacroKey(MacroTagID) + `¬found=` + GetMacroKey(MacroTimeout) + `&k1=v1&k2=v2`, + flags: Flags{RemoveEmptyParam: true}, + }, + wantResponse: `http://xyz.domain.com/123/567?tagID=tagid+value&k1=v1&k2=v2`, + }, + { + name: "RemovedEmptyParams2", + args: args{ + uri: `http://xyz.domain.com/` + GetMacroKey(MacroPubID) + `/` + GetMacroKey(MacroSiteID) + `?tagID=` + GetMacroKey(MacroTagID+macroEscapeSuffix) + `¬found=` + GetMacroKey(MacroTimeout) + `&k1=v1&k2=v2`, + flags: Flags{RemoveEmptyParam: false}, + }, + wantResponse: `http://xyz.domain.com/123/567?tagID=tagid+value¬found=&k1=v1&k2=v2`, + }, + // 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.ProcessURL(tt.args.uri, tt.args.flags) + assertURL(t, tt.wantResponse, gotResponse) + }) + } +} + +func assertURL(t *testing.T, expected, actual string) { + actualURL, _ := url.Parse(actual) + expectedURL, _ := url.Parse(expected) + + if nil == actualURL || nil == expectedURL { + assert.True(t, (nil == actualURL) == (nil == expectedURL), `actual or expected url parsing failed`) + } else { + assert.Equal(t, expectedURL.Scheme, actualURL.Scheme) + assert.Equal(t, expectedURL.Opaque, actualURL.Opaque) + assert.Equal(t, expectedURL.User, actualURL.User) + assert.Equal(t, expectedURL.Host, actualURL.Host) + assert.Equal(t, expectedURL.Path, actualURL.Path) + assert.Equal(t, expectedURL.RawPath, actualURL.RawPath) + assert.Equal(t, expectedURL.ForceQuery, actualURL.ForceQuery) + assert.Equal(t, expectedURL.Query(), actualURL.Query()) + assert.Equal(t, expectedURL.Fragment, actualURL.Fragment) + } +} diff --git a/adapters/vastbidder/mapper.go b/adapters/vastbidder/mapper.go new file mode 100644 index 00000000000..cbbfe34c119 --- /dev/null +++ b/adapters/vastbidder/mapper.go @@ -0,0 +1,180 @@ +package vastbidder + +type macroCallBack struct { + cached 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, callback: IBidderMacro.MacroWhitelistSeat}, + MacroWhitelistLang: ¯oCallBack{cached: true, callback: IBidderMacro.MacroWhitelistLang}, + MacroBlockedSeat: ¯oCallBack{cached: true, callback: IBidderMacro.MacroBlockedSeat}, + MacroCurrency: ¯oCallBack{cached: true, callback: IBidderMacro.MacroCurrency}, + MacroBlockedCategory: ¯oCallBack{cached: true, callback: IBidderMacro.MacroBlockedCategory}, + MacroBlockedAdvertiser: ¯oCallBack{cached: true, callback: IBidderMacro.MacroBlockedAdvertiser}, + MacroBlockedApp: ¯oCallBack{cached: true, callback: IBidderMacro.MacroBlockedApp}, + + //Source + MacroFD: ¯oCallBack{cached: true, callback: IBidderMacro.MacroFD}, + MacroTransactionID: ¯oCallBack{cached: true, callback: IBidderMacro.MacroTransactionID}, + MacroPaymentIDChain: ¯oCallBack{cached: true, callback: IBidderMacro.MacroPaymentIDChain}, + + //Regs + MacroCoppa: ¯oCallBack{cached: true, callback: IBidderMacro.MacroCoppa}, + + //Impression + MacroDisplayManager: ¯oCallBack{cached: false, callback: IBidderMacro.MacroDisplayManager}, + MacroDisplayManagerVersion: ¯oCallBack{cached: false, callback: IBidderMacro.MacroDisplayManagerVersion}, + MacroInterstitial: ¯oCallBack{cached: false, callback: IBidderMacro.MacroInterstitial}, + MacroTagID: ¯oCallBack{cached: false, 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, callback: IBidderMacro.MacroPMP}, + + //Video + MacroVideoMIMES: ¯oCallBack{cached: false, 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, callback: IBidderMacro.MacroSiteID}, + MacroSiteName: ¯oCallBack{cached: true, callback: IBidderMacro.MacroSiteName}, + MacroSitePage: ¯oCallBack{cached: true, callback: IBidderMacro.MacroSitePage}, + MacroSiteReferrer: ¯oCallBack{cached: true, callback: IBidderMacro.MacroSiteReferrer}, + MacroSiteSearch: ¯oCallBack{cached: true, callback: IBidderMacro.MacroSiteSearch}, + MacroSiteMobile: ¯oCallBack{cached: true, callback: IBidderMacro.MacroSiteMobile}, + + //App + MacroAppID: ¯oCallBack{cached: true, callback: IBidderMacro.MacroAppID}, + MacroAppName: ¯oCallBack{cached: true, callback: IBidderMacro.MacroAppName}, + MacroAppBundle: ¯oCallBack{cached: true, callback: IBidderMacro.MacroAppBundle}, + MacroAppStoreURL: ¯oCallBack{cached: true, callback: IBidderMacro.MacroAppStoreURL}, + MacroAppVersion: ¯oCallBack{cached: true, callback: IBidderMacro.MacroAppVersion}, + MacroAppPaid: ¯oCallBack{cached: true, callback: IBidderMacro.MacroAppPaid}, + + //SiteAppCommon + MacroCategory: ¯oCallBack{cached: true, callback: IBidderMacro.MacroCategory}, + MacroDomain: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDomain}, + MacroSectionCategory: ¯oCallBack{cached: true, callback: IBidderMacro.MacroSectionCategory}, + MacroPageCategory: ¯oCallBack{cached: true, callback: IBidderMacro.MacroPageCategory}, + MacroPrivacyPolicy: ¯oCallBack{cached: true, callback: IBidderMacro.MacroPrivacyPolicy}, + MacroKeywords: ¯oCallBack{cached: true, callback: IBidderMacro.MacroKeywords}, + + //Publisher + MacroPubID: ¯oCallBack{cached: true, callback: IBidderMacro.MacroPubID}, + MacroPubName: ¯oCallBack{cached: true, callback: IBidderMacro.MacroPubName}, + MacroPubDomain: ¯oCallBack{cached: true, callback: IBidderMacro.MacroPubDomain}, + + //Content + MacroContentID: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentID}, + MacroContentEpisode: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentEpisode}, + MacroContentTitle: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentTitle}, + MacroContentSeries: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentSeries}, + MacroContentSeason: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentSeason}, + MacroContentArtist: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentArtist}, + MacroContentGenre: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentGenre}, + MacroContentAlbum: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentAlbum}, + MacroContentISrc: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentISrc}, + MacroContentURL: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentURL}, + MacroContentCategory: ¯oCallBack{cached: 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, callback: IBidderMacro.MacroContentContentRating}, + MacroContentUserRating: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentUserRating}, + MacroContentQAGMediaRating: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentQAGMediaRating}, + MacroContentKeywords: ¯oCallBack{cached: 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, callback: IBidderMacro.MacroContentLanguage}, + MacroContentEmbeddable: ¯oCallBack{cached: true, callback: IBidderMacro.MacroContentEmbeddable}, + + //Producer + MacroProducerID: ¯oCallBack{cached: true, callback: IBidderMacro.MacroProducerID}, + MacroProducerName: ¯oCallBack{cached: true, callback: IBidderMacro.MacroProducerName}, + + //Device + MacroUserAgent: ¯oCallBack{cached: 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, callback: IBidderMacro.MacroMake}, + MacroModel: ¯oCallBack{cached: true, callback: IBidderMacro.MacroModel}, + MacroDeviceOS: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceOS}, + MacroDeviceOSVersion: ¯oCallBack{cached: 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, callback: IBidderMacro.MacroDeviceLanguage}, + MacroDeviceIFA: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceIFA}, + MacroDeviceDIDSHA1: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceDIDSHA1}, + MacroDeviceDIDMD5: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceDIDMD5}, + MacroDeviceDPIDSHA1: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceDPIDSHA1}, + MacroDeviceDPIDMD5: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceDPIDMD5}, + MacroDeviceMACSHA1: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceMACSHA1}, + MacroDeviceMACMD5: ¯oCallBack{cached: true, callback: IBidderMacro.MacroDeviceMACMD5}, + + //Geo + MacroLatitude: ¯oCallBack{cached: true, callback: IBidderMacro.MacroLatitude}, + MacroLongitude: ¯oCallBack{cached: true, callback: IBidderMacro.MacroLongitude}, + MacroCountry: ¯oCallBack{cached: true, callback: IBidderMacro.MacroCountry}, + MacroRegion: ¯oCallBack{cached: true, callback: IBidderMacro.MacroRegion}, + MacroCity: ¯oCallBack{cached: true, callback: IBidderMacro.MacroCity}, + MacroZip: ¯oCallBack{cached: true, callback: IBidderMacro.MacroZip}, + MacroUTCOffset: ¯oCallBack{cached: true, callback: IBidderMacro.MacroUTCOffset}, + + //User + MacroUserID: ¯oCallBack{cached: true, callback: IBidderMacro.MacroUserID}, + MacroYearOfBirth: ¯oCallBack{cached: true, callback: IBidderMacro.MacroYearOfBirth}, + MacroGender: ¯oCallBack{cached: true, callback: IBidderMacro.MacroGender}, + + //Extension + MacroGDPRConsent: ¯oCallBack{cached: true, callback: IBidderMacro.MacroGDPRConsent}, + MacroGDPR: ¯oCallBack{cached: true, callback: IBidderMacro.MacroGDPR}, + MacroUSPrivacy: ¯oCallBack{cached: true, callback: IBidderMacro.MacroUSPrivacy}, + + //Additional + MacroCacheBuster: ¯oCallBack{cached: false, callback: IBidderMacro.MacroCacheBuster}, +} + +//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..8f3aafbdcc7 --- /dev/null +++ b/adapters/vastbidder/sample_spotx_macro.go.bak @@ -0,0 +1,28 @@ +package vastbidder + +import ( + "github.com/prebid/prebid-server/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..4b7e5efc82f --- /dev/null +++ b/adapters/vastbidder/tagbidder.go @@ -0,0 +1,87 @@ +package vastbidder + +import ( + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/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.ProcessURL(bidderMacro.GetURI(), Flags{RemoveEmptyParam: true}) + + // 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) (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..086e3a1ad3a --- /dev/null +++ b/adapters/vastbidder/tagbidder_test.go @@ -0,0 +1,149 @@ +package vastbidder + +import ( + "net/http" + "testing" + + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/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: []openrtb2.Protocol{ + openrtb2.ProtocolVAST20, + }, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + { + ID: "vast_4_0_imp_req", + Video: &openrtb2.Video{ // vast 4.0 + Protocols: []openrtb2.Protocol{ + openrtb2.ProtocolVAST40, + }, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + { + ID: "vast_2_0_4_0_wrapper_imp_req", + Video: &openrtb2.Video{ // vast 2 and 4.0 wrapper + Protocols: []openrtb2.Protocol{ + openrtb2.ProtocolVAST40Wrapper, + openrtb2.ProtocolVAST20, + }, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + { + ID: "other_non_vast_protocol", + Video: &openrtb2.Video{ // DAAST 1.0 + Protocols: []openrtb2.Protocol{ + openrtb2.ProtocolDAAST10, + }, + }, + Ext: []byte(`{"bidder" :{}}`), + }, + { + + ID: "no_protocol_field_set", + Video: &openrtb2.Video{ // vast 2 and 4.0 wrapper + Protocols: []openrtb2.Protocol{}, + }, + 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..8ad02535ec6 --- /dev/null +++ b/adapters/vastbidder/util.go @@ -0,0 +1,70 @@ +package vastbidder + +import ( + "bytes" + "encoding/json" + "fmt" + "math/rand" + "strconv" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/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) +} diff --git a/adapters/vastbidder/vast_tag_response_handler.go b/adapters/vastbidder/vast_tag_response_handler.go new file mode 100644 index 00000000000..f3436370854 --- /dev/null +++ b/adapters/vastbidder/vast_tag_response_handler.go @@ -0,0 +1,334 @@ +package vastbidder + +import ( + "encoding/json" + "errors" + "net/http" + "regexp" + "strconv" + "strings" + "time" + + "github.com/beevik/etree" + "github.com/golang/glog" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/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() + } + + 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 `2.0` == version { + node = ad.FindElement(`./Extensions/Extension/Price`) + } else { + node = ad.FindElement(`./Pricing`) + } + + if nil == node { + 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..28c29ef6776 --- /dev/null +++ b/adapters/vastbidder/vast_tag_response_handler_test.go @@ -0,0 +1,385 @@ +package vastbidder + +import ( + "errors" + "fmt" + "sort" + "testing" + + "github.com/beevik/etree" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/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", + }, + 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/config/config.go b/config/config.go index 33855cb1f43..83001b10f3c 100755 --- a/config/config.go +++ b/config/config.go @@ -650,6 +650,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUcfunnel, "https://sync.aralego.com/idsync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&usprivacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ducfunnel%26uid%3DSspCookieUserId") // openrtb_ext.BidderUnicorn doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderUnruly, "https://sync.1rx.io/usersync2/rmpssp?sub=openwrap&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dunruly%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + // openrtb_ext.BidderVASTBidder doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderValueImpression, "https://rtb.valueimpression.com/usersync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvalueimpression%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderVisx, "https://t.visx.net/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dvisx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderVrtcal doesn't have a good default. @@ -920,6 +921,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.unicorn.endpoint", "https://ds.uncn.jp/pb/0/bid.json") v.SetDefault("adapters.unruly.endpoint", "http://targeting.unrulymedia.com/openrtb/2.2") v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") + v.SetDefault("adapters.vastbidder.endpoint", "https://test.com") v.SetDefault("adapters.verizonmedia.disabled", true) v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1812") diff --git a/endpoints/events/vtrack.go b/endpoints/events/vtrack.go index b58d758d877..d547158b6c3 100644 --- a/endpoints/events/vtrack.go +++ b/endpoints/events/vtrack.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/etree" + "github.com/beevik/etree" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" @@ -563,6 +563,5 @@ func getDomain(site *openrtb2.Site) string { hostname = pageURL.Host } } - return hostname } diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 0980843e650..6f290b22499 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -11,9 +11,8 @@ import ( "strings" "testing" - "github.com/PubMatic-OpenWrap/etree" + "github.com/beevik/etree" "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/prebid_cache_client" "github.com/prebid/prebid-server/stored_requests" diff --git a/endpoints/openrtb2/ctv/types/adpod_types.go b/endpoints/openrtb2/ctv/types/adpod_types.go index 6e46929547d..275a01c2fbc 100644 --- a/endpoints/openrtb2/ctv/types/adpod_types.go +++ b/endpoints/openrtb2/ctv/types/adpod_types.go @@ -53,8 +53,10 @@ type ImpAdPodConfig struct { //ImpData example type ImpData struct { //AdPodGenerator - VideoExt *openrtb_ext.ExtVideoAdPod `json:"vidext,omitempty"` - Config []*ImpAdPodConfig `json:"imp,omitempty"` - ErrorCode *int `json:"ec,omitempty"` - Bid *AdPodBid `json:"-"` + ImpID string `json:"-"` + VideoExt *openrtb_ext.ExtVideoAdPod `json:"vidext,omitempty"` + Config []*ImpAdPodConfig `json:"imp,omitempty"` + ErrorCode *int `json:"ec,omitempty"` + BlockedVASTTags map[string][]string `json:"blockedtags,omitempty"` + Bid *AdPodBid `json:"-"` } diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go index c8f5a32e307..a2299517695 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/prebid/prebid-server/endpoints/events" "math" "net/http" "net/url" @@ -14,7 +13,7 @@ import ( "strings" "time" - "github.com/PubMatic-OpenWrap/etree" + "github.com/beevik/etree" "github.com/buger/jsonparser" uuid "github.com/gofrs/uuid" "github.com/golang/glog" @@ -23,6 +22,7 @@ import ( accountService "github.com/prebid/prebid-server/account" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/endpoints/events" "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/combination" "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/constant" "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/impressions" @@ -41,12 +41,14 @@ import ( //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 + request *openrtb2.BidRequest + reqExt *openrtb_ext.ExtRequestAdPod + impData []*types.ImpData + videoSeats []*openrtb2.SeatBid //stores pure video impression bids + impIndices map[string]int + isAdPodRequest bool + impsExt map[string]map[string]map[string]interface{} + impPartnerBlockedTagIDMap map[string]map[string][]string //Prebid Specific ctx context.Context @@ -382,6 +384,13 @@ func (deps *ctvEndpointDeps) setDefaultValues() { //set request is adpod request or normal request deps.setIsAdPodRequest() + + //TODO: OTT-217, OTT-161 commenting code of filtering vast tags + /* + if deps.isAdPodRequest { + deps.readImpExtensionsAndTags() + } + */ } //validateBidRequest will validate AdPod specific mandatory Parameters and returns error @@ -409,6 +418,42 @@ func (deps *ctvEndpointDeps) validateBidRequest() (err []error) { return } +//readImpExtensionsAndTags will read the impression extensions +func (deps *ctvEndpointDeps) readImpExtensionsAndTags() (errs []error) { + deps.impsExt = 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 { + var impExt map[string]map[string]interface{} + if err := json.Unmarshal(imp.Ext, &impExt); err != nil { + errs = append(errs, err) + continue + } + + deps.impPartnerBlockedTagIDMap[imp.ID] = make(map[string][]string) + + for partnerName, partnerExt := range impExt { + 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.impsExt[imp.ID] = impExt + } + + return errs +} + /********************* Creating CTV BidRequest *********************/ //createBidRequest will return new bid request with all things copy from bid request except impression objects @@ -421,16 +466,106 @@ func (deps *ctvEndpointDeps) createBidRequest(req *openrtb2.BidRequest) *openrtb //createImpressions ctvRequest.Imp = deps.createImpressions() + //TODO: OTT-217, OTT-161 commenting code of filtering vast tags + //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] + + impExtMap := deps.impsExt[originalImpID] + newImpExtMap := make(map[string]map[string]interface{}) + for k, v := range impExtMap { + newImpExtMap[k] = v + } + + for partnerName, partnerExt := range newImpExtMap { + 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(newImpExtMap, partnerName) + } else { + newImpExtMap[partnerName] = map[string]interface{}{ + "tags": compatibleVasts, + } + } + + bExt, err := json.Marshal(newImpExtMap) + if err != nil { + continue + } + imp.Ext = bExt + } + } + 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 deps.impData[index].Config = deps.getAdPodImpsConfigs(&imp, deps.impData[index].VideoExt.AdPod) if 0 == len(deps.impData[index].Config) { errorCode := new(int) diff --git a/endpoints/openrtb2/ctv_auction_test.go b/endpoints/openrtb2/ctv_auction_test.go index f03312ead9c..51fa4b499f7 100644 --- a/endpoints/openrtb2/ctv_auction_test.go +++ b/endpoints/openrtb2/ctv_auction_test.go @@ -3,11 +3,12 @@ package openrtb2 import ( "encoding/json" "fmt" - "github.com/PubMatic-OpenWrap/etree" + "github.com/prebid/prebid-server/endpoints/openrtb2/ctv/types" "net/url" "strings" "testing" + "github.com/beevik/etree" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" @@ -149,3 +150,230 @@ func TestAdjustBidIDInVideoEventTrackers(t *testing.T) { } } } + +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(`{"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}}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 35}}, + }, + }, + impData: []*types.ImpData{}, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 35}, Ext: []byte(`{"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(`{"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}}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}}, + }, + }, + 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(`{}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"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(`{"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}}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}}, + }, + }, + impData: []*types.ImpData{}, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"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(`{"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(`{"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}}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}}, + }, + }, + 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(`{}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"spotx_vast_bidder":{"tags":[{"dur":15,"tagid":"spotx_15"}]}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"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(`{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}`)}, + {ID: "imp2", Ext: []byte(`{"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}}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}}, + {ID: "imp2_1", Video: &openrtb2.Video{MinDuration: 5, MaxDuration: 30}}, + }, + }, + impData: nil, + }, + expectedOutput: output{ + reqs: openrtb2.BidRequest{ + Imp: []openrtb2.Imp{ + {ID: "imp1_1", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 10}, Ext: []byte(`{}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"openx_vast_bidder":{"tags":[{"dur":25,"tagid":"openx_25"}]}}`)}, + {ID: "imp2_1", Video: &openrtb2.Video{MinDuration: 5, MaxDuration: 30}, Ext: []byte(`{"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(`{"openx_vast_bidder":{"tags":[{"dur":35,"tagid":"openx_35"},{"dur":25,"tagid":"openx_25"},{"dur":20,"tagid":"openx_20"}]}}`)}, + {ID: "imp2", Ext: []byte(`{"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}}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}}, + {ID: "imp2_1", Video: &openrtb2.Video{MinDuration: 5, MaxDuration: 30}}, + }, + }, + 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(`{}`)}, + {ID: "imp1_2", Video: &openrtb2.Video{MinDuration: 10, MaxDuration: 20}, Ext: []byte(`{"openx_vast_bidder":{"tags":[{"dur":20,"tagid":"openx_20"}]}}`)}, + {ID: "imp1_3", Video: &openrtb2.Video{MinDuration: 25, MaxDuration: 30}, Ext: []byte(`{"openx_vast_bidder":{"tags":[{"dur":25,"tagid":"openx_25"}]}}`)}, + {ID: "imp2_1", Video: &openrtb2.Video{MinDuration: 5, MaxDuration: 30}, Ext: []byte(`{"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") + } + }) + } +} diff --git a/errortypes/code.go b/errortypes/code.go index 2749b978006..f8206525b27 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -11,6 +11,7 @@ const ( BidderTemporarilyDisabledErrorCode BlacklistedAcctErrorCode AcctRequiredErrorCode + NoBidPriceErrorCode ) // Defines numeric codes for well-known warnings. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index 1fed2d7da6e..6bac21fcb0d 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -182,3 +182,19 @@ func (err *Warning) Code() int { func (err *Warning) Severity() Severity { return SeverityWarning } + +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 +} diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index e3924e5b8cc..6ac494448ca 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -99,6 +99,7 @@ import ( "github.com/prebid/prebid-server/adapters/unicorn" "github.com/prebid/prebid-server/adapters/unruly" "github.com/prebid/prebid-server/adapters/valueimpression" + "github.com/prebid/prebid-server/adapters/vastbidder" "github.com/prebid/prebid-server/adapters/verizonmedia" "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" @@ -214,6 +215,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { openrtb_ext.BidderUnicorn: unicorn.Builder, openrtb_ext.BidderUnruly: unruly.Builder, openrtb_ext.BidderValueImpression: valueimpression.Builder, + openrtb_ext.BidderVASTBidder: vastbidder.Builder, openrtb_ext.BidderVerizonMedia: verizonmedia.Builder, openrtb_ext.BidderVisx: visx.Builder, openrtb_ext.BidderVrtcal: vrtcal.Builder, diff --git a/exchange/bidder.go b/exchange/bidder.go index 262d2d8d3f3..07d222c9602 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -357,6 +357,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 diff --git a/exchange/events.go b/exchange/events.go index 35929d8e604..9742e50e424 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -4,9 +4,8 @@ import ( "encoding/json" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" - jsonpatch "github.com/evanphx/json-patch" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/endpoints/events" @@ -63,7 +62,6 @@ func (ev *eventTracking) modifyBidVAST(pbsBid *pbsOrtbBid, bidderName openrtb_ex if pbsBid.bidType != openrtb_ext.BidTypeVideo || len(bid.AdM) == 0 && len(bid.NURL) == 0 { return } - vastXML := makeVAST(bid) bidID := bid.ID if len(pbsBid.generatedBidID) > 0 { diff --git a/exchange/events_test.go b/exchange/events_test.go index a4fe03601b7..217cca5351d 100644 --- a/exchange/events_test.go +++ b/exchange/events_test.go @@ -1,10 +1,11 @@ package exchange import ( - "github.com/prebid/prebid-server/config" "strings" "testing" + "github.com/prebid/prebid-server/config" + "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" diff --git a/exchange/exchange.go b/exchange/exchange.go index 04af7729273..b28c60e4fbe 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -27,6 +27,7 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/prebid_cache_client" + "golang.org/x/net/publicsuffix" ) type ContextKey string @@ -202,6 +203,12 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * var bidResponseExt *openrtb_ext.ExtBidResponse if anyBidsReturned { + adapterBids, rejections := applyAdvertiserBlocking(r.BidRequest, adapterBids) + // 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 requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil { @@ -444,7 +451,11 @@ func (e *exchange) getAllBids( // Structure to record extra tracking data generated during bidding ae := new(seatResponseExtra) ae.ResponseTimeMillis = int(elapsed / time.Millisecond) + if bids != nil { + // Setting bidderCoreName in SeatBid + bids.bidderCoreName = bidderRequest.BidderCoreName + ae.HttpCalls = bids.httpCalls // Setting bidderCoreName in SeatBid @@ -1045,3 +1056,78 @@ func recordAdaptorDuplicateBidIDs(metricsEngine metrics.MetricsEngine, adapterBi } 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(bidRequest *openrtb2.BidRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid) (map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string) { + 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) + } + } + } + + for bidderName, seatBid := range seatBids { + if seatBid.bidderCoreName == openrtb_ext.BidderVASTBidder && len(nBadvs) > 0 { + 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 { + // 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 +} diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 718fb98c8f1..fbce03f6810 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -17,6 +17,7 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/adapters/vastbidder" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" @@ -3093,6 +3094,562 @@ func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, return "", nil } +//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 *openrtb2.BidRequest + adaptorSeatBids map[*bidderAdapter]*pbsOrtbSeatBid // bidder adaptor and its dummy seat bids map + } + type want struct { + rejectedBidIds []string + validBidCountPerSeat map[string]int + } + tests := []struct { + name string + args args + want want + }{ + { + name: "reject_bid_of_blocked_adv_from_tag_bidder", + args: args{ + advBlockReq: &openrtb2.BidRequest{ + BAdv: []string{"a.com"}, // block bids returned by a.com + }, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("vast_tag_bidder"): { // tag bidder returning 1 bid from blocked advertiser + bids: []*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{ + 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: &openrtb2.BidRequest{BAdv: nil}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tab_bidder_1"): { + bids: []*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, + }, + }, + }, + { + name: "adomain_is_not_present_but_Badv_is_set", // reject bids without adomain as badv is set + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"advertiser_1.com"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder_1"): { + bids: []*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: []*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 + }, + }, + }, + { + name: "adomain_and_badv_is_not_present", // expect no advertiser blocking + args: args{ + advBlockReq: &openrtb2.BidRequest{}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_adaptor_1"): { + bids: []*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, + }, + }, + }, + { + name: "empty_badv", // expect no advertiser blocking + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder_1"): { + bids: []*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: []*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, + }, + }, + }, + { + name: "nil_badv", // expect no advertiser blocking + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: nil}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder_1"): { + bids: []*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: []*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, + }, + }, + }, + { + name: "ad_domains_normalized_and_checked", + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"a.com"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("my_adapter"): { + bids: []*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}, + }, + }, { + name: "multiple_badv", + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"advertiser_1.com", "advertiser_2.com", "www.advertiser_3.com"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_adapter_1"): { + bids: []*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: []*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: []*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, + }, + }, + }, { + name: "multiple_adomain", + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"www.advertiser_3.com"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_adapter_1"): { + bids: []*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: []*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: []*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, + }, + }, + }, { + name: "case_insensitive_badv", // case of domain not matters + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"ADVERTISER_1.COM"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_adapter_1"): { + bids: []*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 + }, + }, + }, + { + name: "case_insensitive_adomain", + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"advertiser_1.com"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_adapter_1"): { + bids: []*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 + }, + }, + }, + { + name: "various_tld_combinations", + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"http://blockme.shri"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("block_bidder"): { + bids: []*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: []*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, + }, + }, + }, + { + name: "subdomain_tests", + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"10th.college.puneunv.edu"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("block_bidder"): { + bids: []*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, + }, + }, + }, { + name: "only_domain_test", // do not expect bid rejection. edu is valid domain + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"edu"}}, + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder"): { + bids: []*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, + }, + }, + }, + { + name: "public_suffix_in_badv", + args: args{ + advBlockReq: &openrtb2.BidRequest{BAdv: []string{"co.in"}}, // co.in is valid public suffix + adaptorSeatBids: map[*bidderAdapter]*pbsOrtbSeatBid{ + newTestTagAdapter("tag_bidder"): { + bids: []*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, + }, + }, + }, + } + 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]*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 + } + + // applyAdvertiserBlocking internally uses tagBidders from (adapter_map.go) + // not testing alias here + seatBids, rejections := applyAdvertiserBlocking(tt.args.advBlockReq, seatBids) + + 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 + } + // verify eligible bids not belongs to blocked advertisers + for _, bid := range sBid.bids { + if nil != bid.bid.ADomain { + for _, adomain := range bid.bid.ADomain { + for _, blockDomain := range tt.args.advBlockReq.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 { diff --git a/go.mod b/go.mod index 13cd3748779..dee3615b79b 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/DATA-DOG/go-sqlmock v1.3.0 github.com/NYTimes/gziphandler v1.1.1 github.com/OneOfOne/xxhash v1.2.5 // indirect - github.com/PubMatic-OpenWrap/etree v1.0.2-0.20210129100623-8f30cfecf9f4 github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf + github.com/beevik/etree v1.0.2 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/blang/semver v3.5.1+incompatible github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 @@ -60,3 +60,5 @@ require ( ) replace github.com/mxmCherry/openrtb/v15 v15.0.0 => github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425063110-b01110089669 + +replace github.com/beevik/etree v1.0.2 => github.com/PubMatic-OpenWrap/etree v1.0.2-0.20210129100623-8f30cfecf9f4 diff --git a/go.sum b/go.sum index ce383174fb8..0ccf122d248 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425134848-adbedb6f42c6 h1: github.com/PubMatic-OpenWrap/openrtb/v15 v15.0.0-20210425134848-adbedb6f42c6/go.mod h1:XMFHmDvfVyIhz5JGzvNXVt0afDLN2mrdYviUOKIYqAo= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +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/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go index a568392beba..f3cfb3df6a4 100644 --- a/openrtb_ext/bid.go +++ b/openrtb_ext/bid.go @@ -58,6 +58,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 diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index ef114914cd6..7d5684600bb 100755 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -173,6 +173,7 @@ const ( BidderUnicorn BidderName = "unicorn" BidderUnruly BidderName = "unruly" BidderValueImpression BidderName = "valueimpression" + BidderVASTBidder BidderName = "vastbidder" BidderVerizonMedia BidderName = "verizonmedia" BidderVisx BidderName = "visx" BidderVrtcal BidderName = "vrtcal" @@ -286,6 +287,7 @@ func CoreBidderNames() []BidderName { BidderUnicorn, BidderUnruly, BidderValueImpression, + BidderVASTBidder, BidderVerizonMedia, BidderVisx, BidderVrtcal, 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/response.go b/openrtb_ext/response.go index 1c7177daf49..a1d1e18d380 100644 --- a/openrtb_ext/response.go +++ b/openrtb_ext/response.go @@ -59,6 +59,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 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/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/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index 7e10c41cd76..10a95fb4b67 100755 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -124,6 +124,7 @@ func TestNewSyncerMap(t *testing.T) { openrtb_ext.BidderSilverMob: true, openrtb_ext.BidderSmaato: true, openrtb_ext.BidderSpotX: true, + openrtb_ext.BidderVASTBidder: true, openrtb_ext.BidderUnicorn: true, openrtb_ext.BidderYeahmobi: true, } From b594df47292893594f2d2cbcc6e459fcb055d90f Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Tue, 17 Aug 2021 21:59:12 +0530 Subject: [PATCH 19/31] UOE-6610: Upgrade prebid-server to 0.170.0 (#190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Smaato: Add support for app (#1767) Co-authored-by: Bernhard Pickenbrock * Update sync types (#1770) * 33across: Fix Shared Memory Overwriting (#1764) This reverts commit f7df258f061788ef7e72529115aa5fd554fa9f16. * Fix race condition in Yeahmobi adapter (#1761) Co-authored-by: Gus Carreon * Pubnative: Fix Shared Memory Overwriting (#1760) * Add request for registration (#1780) * Update OpenRTB Library (#1733) * Tappx changes - Backward compatible change of version (#1777) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * DMX: Enforcing w and h in imp (#1778) Co-authored-by: steve-a-districtm * Remove Authorization Headers From Debug Response (#1779) * Hide Authorization Headers In Debug Response * MakeExtHeaders Tests * Add Empty Test * Use http.Header Methods * Updates From Code Review * Fix Merge Conflict * New Adapter: Bidmachine (#1769) * New Adapter: Criteo (#1775) * Fix shared memory issue when stripping authorization header from bid requests (#1790) * RTB House: update parameters (#1785) * update parameters required by RTB House adapter * tabs to spaces Co-authored-by: Przemyslaw Iwanczak * Generate seatbid[].bid[].ext.prebid.bidid (#1772) * Basic GDPR enforcement for specific publisher-vendors. (#1782) * New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior * Zemanta: Rename Adapter To Outbrain (#1797) * Update openrtb library to v15 (#1802) * IX: Set bidVideo when category and duration is available (#1794) * Update IX defaults (#1799) Co-authored-by: Mike Burns * Update Adyoulike endpoint to hit production servers (#1805) * Openx: use bidfloor if set - prebid.js adapter behavior (#1795) * [ORBIDDER] add gvlVendorID and set bid response currency (#1798) * New Adapter: ADXCG (#1803) * Update kidoz properties to type string (#1808) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * Update bidmachine properties to type string (#1809) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * transform native eventtrackers to imptrackers and jstracker (#1811) * TheMediaGrid: Added processing of imp[].ext.data (#1807) * New Adapter: adf (adformOpenRTB) (#1815) * initial adformOpenRTB adapter implementation * do not make request copy * rename adfromOpenRTB adapter to adf * fix user sync url * Set Adhese gvl id and vast modification flag (#1821) * Added gvlVendorID for mobilefuse (#1822) * AppNexus: reform bid floor handling (#1814) * PubNative: Add GVL Vendor ID (#1824) * InMobi: adding gvlVendorID to static yaml (#1826) * Epom Adapter: configure vendor id (GVL ID) (#1828) Co-authored-by: Vasyl Zarva * Update Adtarget gvlid (#1829) * Adding site to static yaml, and exemplary tests (#1827) * AdOcean adapter - add support for mobile apps (#1830) * Allow Native Ad Exchange Specific Types (#1810) * PubMatic: Fix Banner Size Assignment When No AdSlot Provided (#1825) * New Adapter: Interactive Offers (#1835) * IX: Set category in bid.cat (#1837) * New Adapter: Madvertise (#1834) * Conversant bid floor handling (#1840) * Adf adapter: banner and video mediatype support (#1841) * Test for data race conditions in adapters (#1756) * Revcontent adapter: add vendor id (GVL ID) (#1849) * Refactor: Removed unused GDPR return value (#1839) * New Adapter : Kayzen (#1838) * Add Kayzen Adapter * Beachfront: Add schain support (#1844) Co-authored-by: jim naumann * Pangle: add appid & placementid to bidder param (#1842) Co-authored-by: hcai * New Adapter: BidsCube (#1843) * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter Co-authored-by: vlad * Add Viewdeos alias (#1846) * [Smaato] Adding TCF 2.0 vendor id (#1852) * Pass Global Privacy Control header to bidders (#1789) * Feature Request: Ability to pass Sec-GPC header to the bidder endpoints (#1712) * making Sec-GPC value check more strict * minor syntax change * gofmt fixes * updates against draft-code-review:one, more to come soon. * adding a unit test * Adding a test and request header clone update * modified one test and related logic * modifying the last test added with slight more modification of the logic * GDPR: Don't Call Bidder If It Lacks Purpose 2 Legal Basis (#1851) * Update go-gdpr package to v0.9.0 (#1856) * Marsmedia - add GVL ID to bidder config file (#1864) Co-authored-by: Vladi Izgayev * PubMatic: Added parameters dctr & pmzoneid (#1865) * Better Support For Go Modules (#1862) * IX: Update usersync default id (#1873) * AppNexus: Make Ad Pod Id Optional (#1792) * Bugfix for applyCategoryMapping (#1857) * Facebook: Drop consented providers (#1867) * Between: Fix for bid floor issue#1787 (#1870) Co-authored-by: Egor Skorokhodov * Beachfront: Fix for bid floor issue#1787 (#1878) Co-authored-by: jim naumann * Updating interactiveoffers contact info (#1881) * Docs metrics configuration (#1850) * Criteo: update maintainer email address (#1884) * New Adapter: BrightMountainMedia (#1855) New Adapter : BrightMountainMedia * New Adapter: AlgoriX (#1861) * Remove LifeStreet + Legacy Cleanup (#1883) * New Adapter: E-Volution (#1868) * [criteo] accept zoneId and networkId alternate case (#1869) * Unit test random map order fix (#1887) Co-authored-by: Veronika Solovei * Request Provided Currency Rates (#1753) * Debug override header (#1853) * Remove GDPR TCF1 (#1854) * Rename GDPR UserSyncIfAmbiguous to DefaultValue (#1858) * Accept bidfloor from impression to fix issue #1787 for sovrn adapter (#1886) * GDPR: require host specify default value (#1859) * New Adapter: Smile Wanted (#1877) * New Adapter: Smile Wanted * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-676968474 * Improvement of test coverage as requested. * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-683853119 * Fix a weak vendor enforcement bug where vendor does not exist (#1890) * Pubmatic: Sending GPT slotname in impression extension (#1880) * Update To Go 1.16 (#1888) * Friendlier Startup Error Messages (#1894) * Second fix for weak vendor enforcement (#1896) * Rubicon: hardcode EUR to USD for floors (#1899) Co-authored-by: Serhii Nahornyi * Outbrain adapter: overwrite tagid only if it exists (#1895) * New Adapter: BidMyAdz (#1882) Co-authored-by: BidMyAdz * Currency Conversion Utility Function (#1901) * New Adapter: SA Lunamedia (#1891) * Removed Digitrust From Prebid Server (#1892) Co-authored-by: avolcy * IX: merge eventtrackers with imptrackers for native bid responses (#1900) * Inmobi: user sync (#1911) * Rubicon: Update segtax logic (#1909) Co-authored-by: Serhii Nahornyi * New Adapter: Axonix (#1912) * New Axonix adapter * Changed endpoint * Rename adapter type * Leave in examplary only the basic test fixtures * PR comments * Rubicon: Fix Nil Reference Panic (#1918) * GDPR: host-level per-purpose vendor exceptions config (#1893) Co-authored-by: Scott Kay * Criteo - Fix fields mapping error when building bid from bidder response (#1917) * Smaato: Rework multi imp support and add adpod support (#1902) * Allowed $0.00 price bids if there are deals (#1910) * GDPR: host-level per-purpose enforce vendor signals config (#1921) * Add GDPR host-level per-purpose enforce vendor signals config * Update config defaults test with TCF2 object compare * Fix for fetcher warning at server startup (#1914) Co-authored-by: Veronika Solovei * Request Wrapper first pass (#1784) * Rubicon: Use currency conversion function (#1924) Co-authored-by: Serhii Nahornyi * New Adapter: operaads (#1916) * Fix Beachfront data race condition (#1915) Co-authored-by: Jim Naumann * Sharethrough: Add support for GPID (#1925) * Admixer: Fix for bid floor issue#1787 (#1872) * InMobi: adding native support (#1928) * Tappx: new bidder params (#1931) Co-authored-by: Albert Grandes * Fix CVE-2020-35381 (#1942) * Smaato: Split multiple media types (#1930) Co-authored-by: Bernhard Pickenbrock * New adapter: Adagio (#1907) * IX: update required site id field to be more flexible (#1934) Co-authored-by: Joshua Gross * Add SmartRTB adapter (#1071) * Adds timeout notifications for Facebook (#1182) * Add Adoppler bidder support. (#1186) * Add Adoppler bidder support. * Address code review comments. Use JSON-templates for testing. * Fix misprint; Add url.PathEscape call for adunit URL parameter. * Kidoz adapter (#1210) Co-authored-by: Ryan Haksi * AMP CCPA Fix (#1187) * Add kidoz bidder info (#1257) got this info from email communication with kidoz * populate the app ID in the FAN timeout notif url with the publisher ID (#1265) and the auction with the request ID Co-authored-by: Aadesh Patel * * Add PubMatic bidder doc file (#1255) * Add app video capability to PubMatic bidder info file * Added OpenX Bidder adapter documentation (#1291) * Restore the AMP privacy exception as an option. (#1311) * Restore the AMP privacy exception as an option. * Adds missing test case * More PR feedback * Remove unused constant * Comment tweak * Add Yieldlab Adapter (#1287) Co-authored-by: Mirko Feddern Signed-off-by: Alex Klinkert Co-authored-by: Alexander Pinnecke Co-authored-by: Alex Klinkert Co-authored-by: Mirko Feddern * Add Pubnative bidder documentation (#1340) * Add Adtarget server adapter (#1319) * Add Adtarget server adapter * Suggested changes for Adtarget * Avoid overriding AMP request original size with mutli-size (#1352) * Adds Avocet adapter (#1354) * Adding Smartadserver adapter (#1346) Co-authored-by: tadam * Metrics for TCF 2 adoption (#1360) * Add support for multiple root schain nodes (#1374) * Facebook Only Supports App Impressions (#1396) * Add Outgoing Connection Metrics (#1343) * OpenX adapter: pass optional platform (PBID-598) (#1421) * Adds keyvalue hb_format support (#1414) * feat: Add new logger module - Pubstack Analytics Module (#1331) * Pubstack Analytics V1 (#11) * V1 Pubstack (#7) * feat: Add Pubstack Logger (#6) * first version of pubstack analytics * bypass viperconfig * commit #1 * gofmt * update configuration and make the tests pass * add readme on how to configure the adapter and update the network calls * update logging and fix intake url definition * feat: Pubstack Analytics Connector * fixing go mod * fix: bad behaviour on appending path to auction url * add buffering * support bootstyrap like configuration * implement route for all the objects * supports termination signal handling for goroutines * move readme to the correct location * wording * enable configuration reload + add tests * fix logs messages * fix tests * fix log line * conclude merge * merge * update go mod Co-authored-by: Amaury Ravanel * fix duplicated channel keys Co-authored-by: Amaury Ravanel * first pass - PR reviews * rename channel* -> eventChannel * dead code * Review (#10) * use json.Decoder * update documentation * use nil instead []byte("") * clean code * do not use http.DefaultClient * fix race condition (need validation) * separate the sender and buffer logics * refactor the default configuration * remove error counter * Review GP + AR * updating default config * add more logs * remove alias fields in json * fix json serializer * close event channels Co-authored-by: Amaury Ravanel * fix race condition * first pass (pr reviews) * refactor: store enabled modules into a dedicated struct * stop goroutine * test: improve coverage * PR Review * Revert "refactor: store enabled modules into a dedicated struct" This reverts commit f57d9d61680c74244effc39a5d96d6cbb2f19f7d. # Conflicts: # analytics/config/config_test.go Co-authored-by: Amaury Ravanel * New bid adapter for Smaato (#1413) Co-authored-by: vikram Co-authored-by: Stephan * New Adprime adapter (#1418) Co-authored-by: Aiholkin * Enable geo activation of GDPR flag (#1427) * moving docs to website repo (#1443) * Add support for Account configuration (PBID-727, #1395) (#1426) * Pass Through First Party Context Data (#1479) * between adapter (#1437) Co-authored-by: Alexey Elymanov * Bidder Uniqueness Gatekeeping Test (#1506) * Smarty ads adapter (#1500) Co-authored-by: Kushneryk Pavlo Co-authored-by: user * Vtrack and event endpoints (#1467) * Add bidder name key support (#1496) * Add metrics to capture stored data fetch all/delta durations with fetch status (#1515) * Acuity ads adapter (#1537) Co-authored-by: Kushneryk Pavlo * Yieldmo app support in yaml file (#1542) Co-authored-by: Winston * Add client/AccountID support into Adoppler adapter. (#1535) * 33Across: Add video support in adapter (#1557) * Fix bug in request.imp.ext Validation (#1575) * First draft * Brian's reivew * Removed leftover comments Co-authored-by: Gus Carreon * New Adapter Initialization Framework (#1532) * Fix 33Across App Handling (#1602) * Fix adapter JSON tests to have the right test structure (#1589) * Fix JSON EMX Digital * Fix JSON Brightroll * Fix JSON Beintoo * Fix JSON Gamoshi * Fix JSON Kubient * Fix JSON Marsmedia * Fix JSON Nanointeractive * Fix JSON Telaria * Fix JSON valueimpression * Fix JSON smartyads * Fix JSON rhythmone * Fix JSON krushmedia * Fix JSON cpmstar * Fix JSON acuityads * Fix JSON avocet * Rename wrongly named acuity ads test file * Fix JSON gamma * Add expected no bid responses * Fixed indentation and asesome-markup * Added usersync support to Between SSP adapter; Major fixes and refactor (#1587) Co-authored-by: Egor Skorokhodov * 33Across: Add support for multi-imp requests (#1609) * Remove legacy GDPR AMP config flag used to prevent buyer ID scrub on AMP requests (#1565) * New Adapter: Mobfox (#1585) Co-authored-by: mobfox * New Adapter: Revcontent (#1622) * Audit beachfront tests and change some videoResponseType details (#1638) * Tappx User Syncer + Site Update (#1674) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * Beachfront Additional tests (#1679) * added place holder files for all but a couple of the intended new tests. I need to grok what those couple mean before being able to name a file. * This covers most of the suggested cases and a couple more that occured to me. I'll look at the couple that I held off on next. * added the unmarshal tests and found a couple problems to address in the process. * removed my __debug_bin. should be in gitignore. * A bit of clean up and commenting. Bumped version number. Added __debug_bin to .gitignore. This is the debugging binary created by Visual Studio Code, or at least version 1.52.1. * missed a bunch of version strings * removed IP faker * If IP is not included in an AdM request, an error is now thrown for the AdM imp instead of faking it. The AdM endpoint is the only one that requires an IP. Also, added several "no-ip" test cases. * Whent back to the fake IP solution instead of the error. Removed most of the "no-ip" test cases, leaving one. * changed ip in adm-video.json to not match the faker ip * removed a debugging comment * Mobfox: Add rout to adexcange (#1702) Co-authored-by: mobfox * Add Support For SkAdN + Refactor Split Imps (#1741) * No Longer Move bid.ext To bid.ext.bidder (#1742) * No Longer Move bid.ext To bid.ext.bidder * Remove Similar Behavior From seatbid.ext * Avoid Second Bid Copy * Removed Unused seatbid.ext * Debug warnings (#1724) Co-authored-by: Veronika Solovei * FPD: Allow imp.ext.data To Passthrough To Adapters (#1765) * Basic GDPR enforcement for specific publisher-vendors. (#1782) * New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior * Zemanta: Rename Adapter To Outbrain (#1797) * New Adapter: adf (adformOpenRTB) (#1815) * initial adformOpenRTB adapter implementation * do not make request copy * rename adfromOpenRTB adapter to adf * fix user sync url * Set Adhese gvl id and vast modification flag (#1821) * Added gvlVendorID for mobilefuse (#1822) * AppNexus: reform bid floor handling (#1814) * PubNative: Add GVL Vendor ID (#1824) * InMobi: adding gvlVendorID to static yaml (#1826) * Epom Adapter: configure vendor id (GVL ID) (#1828) Co-authored-by: Vasyl Zarva * Update Adtarget gvlid (#1829) * Adding site to static yaml, and exemplary tests (#1827) * AdOcean adapter - add support for mobile apps (#1830) * Allow Native Ad Exchange Specific Types (#1810) * PubMatic: Fix Banner Size Assignment When No AdSlot Provided (#1825) * New Adapter: Interactive Offers (#1835) * IX: Set category in bid.cat (#1837) * New Adapter: Madvertise (#1834) * Conversant bid floor handling (#1840) * Adf adapter: banner and video mediatype support (#1841) * Test for data race conditions in adapters (#1756) * Revcontent adapter: add vendor id (GVL ID) (#1849) * Refactor: Removed unused GDPR return value (#1839) * New Adapter : Kayzen (#1838) * Add Kayzen Adapter * Beachfront: Add schain support (#1844) Co-authored-by: jim naumann * Pangle: add appid & placementid to bidder param (#1842) Co-authored-by: hcai * New Adapter: BidsCube (#1843) * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter Co-authored-by: vlad * Add Viewdeos alias (#1846) * [Smaato] Adding TCF 2.0 vendor id (#1852) * Pass Global Privacy Control header to bidders (#1789) * Feature Request: Ability to pass Sec-GPC header to the bidder endpoints (#1712) * making Sec-GPC value check more strict * minor syntax change * gofmt fixes * updates against draft-code-review:one, more to come soon. * adding a unit test * Adding a test and request header clone update * modified one test and related logic * modifying the last test added with slight more modification of the logic * GDPR: Don't Call Bidder If It Lacks Purpose 2 Legal Basis (#1851) * Update go-gdpr package to v0.9.0 (#1856) * Marsmedia - add GVL ID to bidder config file (#1864) Co-authored-by: Vladi Izgayev * PubMatic: Added parameters dctr & pmzoneid (#1865) * Better Support For Go Modules (#1862) * AppNexus: Make Ad Pod Id Optional (#1792) * Facebook: Drop consented providers (#1867) * Between: Fix for bid floor issue#1787 (#1870) Co-authored-by: Egor Skorokhodov * Beachfront: Fix for bid floor issue#1787 (#1878) Co-authored-by: jim naumann * Updating interactiveoffers contact info (#1881) * Docs metrics configuration (#1850) * Criteo: update maintainer email address (#1884) * New Adapter: BrightMountainMedia (#1855) New Adapter : BrightMountainMedia * New Adapter: AlgoriX (#1861) * Remove LifeStreet + Legacy Cleanup (#1883) * New Adapter: E-Volution (#1868) * [criteo] accept zoneId and networkId alternate case (#1869) * Request Provided Currency Rates (#1753) * Debug override header (#1853) * Remove GDPR TCF1 (#1854) * Rename GDPR UserSyncIfAmbiguous to DefaultValue (#1858) * Accept bidfloor from impression to fix issue #1787 for sovrn adapter (#1886) * GDPR: require host specify default value (#1859) * New Adapter: Smile Wanted (#1877) * New Adapter: Smile Wanted * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-676968474 * Improvement of test coverage as requested. * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-683853119 * Fix a weak vendor enforcement bug where vendor does not exist (#1890) * Update To Go 1.16 (#1888) * Friendlier Startup Error Messages (#1894) * Second fix for weak vendor enforcement (#1896) * Rubicon: hardcode EUR to USD for floors (#1899) Co-authored-by: Serhii Nahornyi * Outbrain adapter: overwrite tagid only if it exists (#1895) * New Adapter: BidMyAdz (#1882) Co-authored-by: BidMyAdz * Currency Conversion Utility Function (#1901) * New Adapter: SA Lunamedia (#1891) * Removed Digitrust From Prebid Server (#1892) Co-authored-by: avolcy * IX: merge eventtrackers with imptrackers for native bid responses (#1900) * Inmobi: user sync (#1911) * Rubicon: Update segtax logic (#1909) Co-authored-by: Serhii Nahornyi * New Adapter: Axonix (#1912) * New Axonix adapter * Changed endpoint * Rename adapter type * Leave in examplary only the basic test fixtures * PR comments * Rubicon: Fix Nil Reference Panic (#1918) * git rebase * Reverted some changes after prebid-server upgrade * Fixed ctv_auction.go after merging prebid-0.170.0 * Added missing gdpr.default_value * Updated usersync url for bidder Unruly Co-authored-by: el-chuck Co-authored-by: Bernhard Pickenbrock Co-authored-by: Gena Co-authored-by: guscarreon Co-authored-by: Gus Carreon Co-authored-by: bretg Co-authored-by: Scott Kay Co-authored-by: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Co-authored-by: ubuntu Co-authored-by: Albert Grandes Co-authored-by: Steve Alliance Co-authored-by: steve-a-districtm Co-authored-by: Pavel Dunyashev Co-authored-by: Benjamin Co-authored-by: Brian Sardo <1168933+bsardo@users.noreply.github.com> Co-authored-by: Przemysław Iwańczak <36727380+piwanczak@users.noreply.github.com> Co-authored-by: Przemyslaw Iwanczak Co-authored-by: Veronika Solovei Co-authored-by: hhhjort <31041505+hhhjort@users.noreply.github.com> Co-authored-by: Rok Sušnik Co-authored-by: Rok Sušnik Co-authored-by: ixjohnny <75964135+ixjohnny@users.noreply.github.com> Co-authored-by: Michael Burns Co-authored-by: Mike Burns Co-authored-by: guiann Co-authored-by: Laurentiu Badea Co-authored-by: Arne Schulz Co-authored-by: adxcgcom <31470944+adxcgcom@users.noreply.github.com> Co-authored-by: agilfix Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: Jurij Sinickij Co-authored-by: mefjush Co-authored-by: dtbarne <7635750+dtbarne@users.noreply.github.com> Co-authored-by: Pillsoo Shin Co-authored-by: Daniel Lawrence Co-authored-by: epomrnd Co-authored-by: Vasyl Zarva Co-authored-by: Marcin Muras <47107445+mmuras@users.noreply.github.com> Co-authored-by: IOTiagoFaria <76956619+IOTiagoFaria@users.noreply.github.com> Co-authored-by: notmani Co-authored-by: johnwier <49074029+johnwier@users.noreply.github.com> Co-authored-by: jcamp-revc <68560678+jcamp-revc@users.noreply.github.com> Co-authored-by: Raghu Teja <2473294+raghuteja@users.noreply.github.com> Co-authored-by: Jim Naumann Co-authored-by: jim naumann Co-authored-by: Hengsheng Cai Co-authored-by: hcai Co-authored-by: Vladyslav Laktionov Co-authored-by: vlad Co-authored-by: Ruslan Sibgatullin Co-authored-by: Vivek Narang Co-authored-by: vladi-mmg Co-authored-by: Vladi Izgayev Co-authored-by: egsk Co-authored-by: Egor Skorokhodov Co-authored-by: timoshas Co-authored-by: Léonard Labat Co-authored-by: BrightMountainMedia <69471268+BrightMountainMediaInc@users.noreply.github.com> Co-authored-by: Bugxyb Co-authored-by: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Co-authored-by: Léonard Labat Co-authored-by: Veronika Solovei Co-authored-by: Rachel Joyce Co-authored-by: Maxime DEYMÈS <47388595+MaxSmileWanted@users.noreply.github.com> Co-authored-by: Serhii Nahornyi Co-authored-by: Serhii Nahornyi Co-authored-by: bidmyadz <82382704+bidmyadz@users.noreply.github.com> Co-authored-by: BidMyAdz Co-authored-by: lunamedia <73552749+lunamedia@users.noreply.github.com> Co-authored-by: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Co-authored-by: avolcy Co-authored-by: Mani Gandham Co-authored-by: armon823 <86739148+armon823@users.noreply.github.com> Co-authored-by: César Fernández Co-authored-by: jizeyopera <70930512+jizeyopera@users.noreply.github.com> Co-authored-by: Mansi Nahar Co-authored-by: Jim Naumann Co-authored-by: Eddy Pechuzal <46331062+epechuzal@users.noreply.github.com> Co-authored-by: avolokha <84977155+avolokha@users.noreply.github.com> Co-authored-by: Olivier Co-authored-by: Joshua Gross <820727+grossjo@users.noreply.github.com> Co-authored-by: Joshua Gross Co-authored-by: evanmsmrtb Co-authored-by: Viacheslav Chimishuk Co-authored-by: rhaksi-kidoz <61601767+rhaksi-kidoz@users.noreply.github.com> Co-authored-by: Ryan Haksi Co-authored-by: Aadesh Co-authored-by: Aadesh Patel Co-authored-by: Mike Chowla Co-authored-by: Jimmy Tu Co-authored-by: Mirko Feddern <3244291+mirkorean@users.noreply.github.com> Co-authored-by: Alexander Pinnecke Co-authored-by: Alex Klinkert Co-authored-by: Mirko Feddern Co-authored-by: Artur Aleksanyan Co-authored-by: Richard Lee <14349+dlackty@users.noreply.github.com> Co-authored-by: Simon Critchley Co-authored-by: tadam75 Co-authored-by: tadam Co-authored-by: gpolaert Co-authored-by: Amaury Ravanel Co-authored-by: Vikram Co-authored-by: vikram Co-authored-by: Stephan Co-authored-by: Adprime <64427228+Adprime@users.noreply.github.com> Co-authored-by: Aiholkin Co-authored-by: Alexey Elymanov Co-authored-by: Alexey Elymanov Co-authored-by: Kushneryk Pavel Co-authored-by: Kushneryk Pavlo Co-authored-by: user Co-authored-by: Daniel Barrigas Co-authored-by: Cameron Rice <37162584+camrice@users.noreply.github.com> Co-authored-by: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Co-authored-by: Winston-Yieldmo <46379634+Winston-Yieldmo@users.noreply.github.com> Co-authored-by: Winston Co-authored-by: Aparna Rao Co-authored-by: Gus Carreon Co-authored-by: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> --- .devcontainer/devcontainer.json | 4 +- .github/workflows/release.yml | 8 +- .github/workflows/validate-merge.yml | 2 +- .github/workflows/validate.yml | 2 +- Dockerfile | 4 +- README.md | 15 +- adapters/adagio/adagio.go | 139 +++ adapters/adagio/adagio_test.go | 75 ++ .../adagiotest/exemplary/banner-web.json | 155 +++ .../adagiotest/exemplary/multi-format.json | 165 +++ .../adagiotest/exemplary/multi-imp.json | 222 ++++ .../adagiotest/exemplary/native-web.json | 151 +++ .../adagiotest/exemplary/video-web.json | 175 +++ .../adagio/adagiotest/params/race/banner.json | 5 + .../adagio/adagiotest/params/race/native.json | 5 + .../adagio/adagiotest/params/race/video.json | 5 + .../response-miss-ext-bid-type.json | 130 ++ .../adagiotest/supplemental/status-204.json | 62 + .../adagiotest/supplemental/status-400.json | 67 ++ .../adagiotest/supplemental/status-401.json | 67 ++ .../adagiotest/supplemental/status-403.json | 67 ++ .../adagiotest/supplemental/status-500.json | 68 ++ .../adagiotest/supplemental/status-503.json | 67 ++ .../adagiotest/supplemental/status-504.json | 67 ++ adapters/adagio/params_test.go | 57 + adapters/adagio/usersync.go | 12 + adapters/adagio/usersync_test.go | 34 + adapters/adapterstest/test_json.go | 120 +- adapters/adf/adf.go | 129 ++ adapters/adf/adf_test.go | 20 + .../adf/adftest/exemplary/multi-format.json | 118 ++ .../adf/adftest/exemplary/multi-native.json | 108 ++ .../adf/adftest/exemplary/single-banner.json | 95 ++ .../adf/adftest/exemplary/single-native.json | 90 ++ .../adf/adftest/exemplary/single-video.json | 93 ++ adapters/adf/adftest/params/race/native.json | 3 + .../adf/adftest/supplemental/bad-request.json | 48 + .../adftest/supplemental/empty-response.json | 42 + .../supplemental/invalid-imp-mediatype.json | 59 + .../adftest/supplemental/nobid-response.json | 49 + .../adftest/supplemental/server-error.json | 49 + .../supplemental/unparsable-response.json | 49 + adapters/adf/params_test.go | 57 + adapters/adf/usersync.go | 12 + adapters/adf/usersync_test.go | 31 + .../adformtest/supplemental/user-nil.json | 7 +- adapters/admixer/admixer.go | 7 +- .../exemplary/optional-params.json | 133 +++ adapters/admixer/params_test.go | 4 + adapters/adocean/adocean.go | 62 +- .../exemplary/multi-banner-impression.json | 5 +- .../exemplary/single-banner-impression.json | 5 +- .../adocean/adoceantest/supplemental/app.json | 71 ++ .../supplemental/bad-response.json | 2 +- .../supplemental/encode-error.json | 2 +- .../supplemental/network-error.json | 2 +- .../adoceantest/supplemental/no-bid.json | 4 +- .../adoceantest/supplemental/no-sizes.json | 4 +- .../supplemental/requests-merge.json | 4 +- adapters/algorix/algorix.go | 166 +++ adapters/algorix/algorix_test.go | 27 + .../algorixtest/exemplary/sample-banner.json | 83 ++ .../algorixtest/exemplary/sample-native.json | 82 ++ .../algorixtest/exemplary/sample-nobid.json | 53 + .../algorixtest/exemplary/sample-video.json | 89 ++ .../algorixtest/supplemental/bad_imp_ext.json | 21 + .../supplemental/bad_impext_bidder.json | 23 + .../supplemental/bad_response.json | 57 + .../algorixtest/supplemental/status_400.json | 57 + .../algorixtest/supplemental/status_500.json | 57 + adapters/algorix/params_test.go | 47 + adapters/appnexus/appnexus.go | 31 +- adapters/appnexus/appnexus_test.go | 14 +- .../supplemental/reserve-ignored.json | 144 +++ .../supplemental/reserve-test.json | 143 +++ ...deo-no-adpodid-two-imps-different-pod.json | 94 ++ .../video-same-adpodid-two-imps-same-pod.json | 96 ++ ...erent-adpodid-two-imps-different-pods.json | 48 + ...o-different-adpodid-two-imps-same-pod.json | 48 + .../exemplary/video_consented_providers.json | 145 +++ adapters/audienceNetwork/facebook.go | 7 + adapters/axonix/axonix.go | 116 ++ adapters/axonix/axonix_test.go | 30 + .../exemplary/banner-and-video.json | 133 +++ .../exemplary/banner-video-native.json | 157 +++ .../axonixtest/exemplary/simple-banner.json | 105 ++ .../axonixtest/exemplary/simple-video.json | 86 ++ .../axonix/axonixtest/params/race/banner.json | 3 + .../axonix/axonixtest/params/race/video.json | 3 + .../supplemental/bad-response-no-body.json | 57 + .../supplemental/status-bad-request.json | 58 + .../supplemental/status-no-content.json | 53 + .../supplemental/unexpected-status-code.json | 58 + .../supplemental/valid-extension.json | 86 ++ .../supplemental/valid-with-device.json | 93 ++ adapters/axonix/params_test.go | 59 + adapters/beachfront/beachfront.go | 91 +- .../exemplary/adm-video-app.json | 124 ++ .../beachfronttest/exemplary/banner.json | 8 +- .../adm-video-app-alphanum-bundle.json | 123 ++ .../adm-video-app-malformed-bundle.json | 124 ++ .../supplemental/adm-video-schain.json | 158 +++ .../supplemental/banner-204-with-body.json | 5 + .../supplemental/banner-204.json | 5 + .../banner-and-adm-video-by-explicit.json | 7 +- ...video-expected-204-response-on-banner.json | 7 +- .../supplemental/banner-and-adm-video.json | 7 +- .../supplemental/banner-and-nurl-video.json | 7 +- .../supplemental/banner-bad-request-400.json | 9 +- ...loor-below-min.json => banner-schain.json} | 39 +- ...idder_response_unmarshal_error_banner.json | 7 +- ...-four-variations-on-nothing-adm-video.json | 428 +++++++ .../bidfloor-test-ext-wins-adm-video.json | 124 ++ .../bidfloor-test-imp-wins-adm-video.json | 125 ++ .../supplemental/six-nine-combo.json | 7 +- .../supplemental/two-four-combo.json | 7 +- adapters/beachfront/params_test.go | 36 +- adapters/between/between.go | 12 - .../betweentest/exemplary/multi-request.json | 2 - .../betweentest/exemplary/secure-detect.json | 1 - .../exemplary/simple-site-banner.json | 10 +- .../supplemental/bad-dsp-request-example.json | 1 - .../supplemental/bad-response-body.json | 1 - .../dsp-server-internal-error-example.json | 1 - .../betweentest/supplemental/no-bids.json | 1 - adapters/between/params_test.go | 9 +- adapters/bidder.go | 23 +- adapters/bidder_test.go | 63 + adapters/bidmyadz/bidmyadz.go | 157 +++ adapters/bidmyadz/bidmyadz_test.go | 18 + .../bidmyadztest/exemplary/banner.json | 146 +++ .../bidmyadztest/exemplary/native.json | 141 +++ .../bidmyadztest/exemplary/video.json | 160 +++ .../bidmyadztest/params/race/banner.json | 3 + .../bidmyadztest/params/race/native.json | 3 + .../bidmyadztest/params/race/video.json | 3 + .../supplemental/invalid-device-fields.json | 48 + .../supplemental/invalid-multi-imps.json | 61 + .../supplemental/missing-mediatype.json | 122 ++ .../supplemental/response-without-bids.json | 109 ++ .../response-without-seatbid.json | 106 ++ .../bidmyadztest/supplemental/status-204.json | 94 ++ .../bidmyadztest/supplemental/status-400.json | 101 ++ .../status-service-unavailable.json | 100 ++ .../supplemental/status-unknown.json | 101 ++ adapters/bidmyadz/params_test.go | 49 + adapters/bidmyadz/usersync.go | 12 + adapters/bidmyadz/usersync_test.go | 33 + adapters/bidscube/bidscube.go | 125 ++ adapters/bidscube/bidscube_test.go | 18 + .../bidscubetest/exemplary/simple-banner.json | 128 ++ .../bidscubetest/exemplary/simple-native.json | 112 ++ .../bidscubetest/exemplary/simple-video.json | 116 ++ .../exemplary/simple-web-banner.json | 126 ++ .../bidscubetest/params/race/banner.json | 3 + .../bidscubetest/params/race/native.json | 3 + .../bidscubetest/params/race/video.json | 3 + .../supplemental/bad-imp-ext.json | 41 + .../supplemental/bad_bidtype_response.json | 105 ++ .../supplemental/bad_response.json | 83 ++ .../supplemental/bad_status_code.json | 77 ++ .../supplemental/imp_ext_empty_object.json | 37 + .../supplemental/imp_ext_string.json | 37 + .../bidscubetest/supplemental/status-204.json | 78 ++ .../bidscubetest/supplemental/status-404.json | 83 ++ adapters/bidscube/params_test.go | 48 + adapters/bmtm/brightmountainmedia.go | 153 +++ adapters/bmtm/brightmountainmedia_test.go | 20 + .../exemplary/banner.json | 103 ++ .../exemplary/multi-imp.json | 198 ++++ .../exemplary/video.json | 113 ++ .../params/race/banner.json | 3 + .../params/race/video.json | 3 + .../multi-imp-mixed-validation.json | 117 ++ .../supplemental/status-not-ok.json | 80 ++ adapters/bmtm/params_test.go | 44 + adapters/bmtm/usersync.go | 12 + adapters/bmtm/usersync_test.go | 29 + adapters/conversant/conversant.go | 10 +- .../supplemental/test_params.json | 306 +++++ adapters/criteo/criteo.go | 4 +- .../exemplary/simple-banner-cookie-uid.json | 230 ++-- .../exemplary/simple-banner-inapp.json | 220 ++-- .../exemplary/simple-banner-uid.json | 264 ++--- ...ccpa-with-consent-simple-banner-inapp.json | 229 ++-- .../ccpa-with-consent-simple-banner-uid.json | 277 +++-- ...gdpr-with-consent-simple-banner-inapp.json | 251 ++-- .../gdpr-with-consent-simple-banner-uid.json | 283 +++-- .../supplemental/multislots-alt-case.json | 232 ++++ .../multislots-simple-banner-inapp.json | 425 ++++--- .../multislots-simple-banner-uid.json | 465 ++++---- ...-direct-size-and-formats-not-included.json | 264 ++--- ...e-banner-with-direct-size-and-formats.json | 264 ++--- .../simple-banner-with-direct-size.json | 252 ++-- .../supplemental/simple-banner-with-ipv6.json | 252 ++-- adapters/criteo/models.go | 20 +- adapters/criteo/params_test.go | 7 + adapters/dmx/dmx.go | 2 +- adapters/dmx/dmx_test.go | 77 +- adapters/e_volution/evolution.go | 112 ++ adapters/e_volution/evolution_test.go | 18 + .../exemplary/banner-without-mediatype.json | 168 +++ .../evolutiontest/exemplary/banner.json | 174 +++ .../evolutiontest/exemplary/native.json | 181 +++ .../evolutiontest/exemplary/video.json | 200 ++++ .../evolutiontest/params/race/banner.json | 3 + .../evolutiontest/params/race/native.json | 3 + .../evolutiontest/params/race/video.json | 3 + .../supplemental/bad-response.json | 158 +++ .../supplemental/empty-seatbid.json | 149 +++ .../supplemental/status-204.json | 126 ++ .../supplemental/status-400.json | 133 +++ .../supplemental/status-503.json | 125 ++ .../supplemental/unexpected-status.json | 132 +++ adapters/e_volution/params_test.go | 49 + adapters/e_volution/usersync.go | 12 + adapters/e_volution/usersync_test.go | 33 + adapters/inmobi/inmobi.go | 3 + ...ple-banner.json => simple-app-banner.json} | 0 .../exemplary/simple-app-native.json | 105 ++ ...imple-video.json => simple-app-video.json} | 0 .../exemplary/simple-web-banner.json | 105 ++ .../exemplary/simple-web-video.json | 109 ++ adapters/inmobi/usersync.go | 12 + .../interactiveoffers/interactiveoffers.go | 79 ++ .../interactiveoffers_test.go | 20 + .../exemplary/goodmultiplebidrequest.json | 136 +++ .../exemplary/goodsinglebidrequest.json | 87 ++ .../params/race/banner.json | 3 + .../supplemental/204.json | 55 + .../supplemental/400.json | 60 + .../supplemental/not200.json | 60 + .../supplemental/wrongjsonresponse.json | 60 + adapters/interactiveoffers/params_test.go | 44 + adapters/ix/ix.go | 63 +- adapters/ix/ix_test.go | 4 +- .../native-eventtrackers-compat-12.json | 104 ++ .../ix/ixtest/supplemental/bad-imp-id.json | 2 +- .../native-eventtrackers-empty.json | 104 ++ .../native-eventtrackers-missing.json | 104 ++ .../ixtest/supplemental/native-missing.json | 104 ++ adapters/ix/params_test.go | 59 + adapters/kayzen/kayzen.go | 154 +++ adapters/kayzen/kayzen_test.go | 29 + .../kayzentest/exemplary/banner-app.json | 142 +++ .../kayzentest/exemplary/banner-web.json | 129 ++ .../kayzentest/exemplary/native-app.json | 137 +++ .../kayzentest/exemplary/native-web.json | 124 ++ .../kayzentest/exemplary/video-app.json | 150 +++ .../kayzentest/exemplary/video-web.json | 149 +++ .../kayzen/kayzentest/params/race/banner.json | 5 + .../kayzen/kayzentest/params/race/native.json | 5 + .../kayzen/kayzentest/params/race/video.json | 5 + .../supplemental/invalid-ext-object.json | 29 + .../supplemental/invalid-response.json | 101 ++ .../supplemental/requires-imp-object.json | 16 + .../supplemental/status-code-bad-request.json | 91 ++ .../supplemental/status-code-no-content.json | 72 ++ .../supplemental/status-code-other-error.json | 77 ++ adapters/kayzen/params_test.go | 55 + adapters/lifestreet/lifestreet.go | 219 ---- adapters/lifestreet/lifestreet_test.go | 291 ----- .../lifestreettest/params/race/banner.json | 3 - .../lifestreettest/params/race/video.json | 3 - adapters/lifestreet/usersync.go | 12 - adapters/lifestreet/usersync_test.go | 29 - adapters/madvertise/madvertise.go | 163 +++ adapters/madvertise/madvertise_test.go | 26 + .../exemplary/simple-banner.json | 203 ++++ .../exemplary/simple-video.json | 256 ++++ .../madvertisetest/params/race/banner.json | 4 + .../madvertisetest/params/race/video.json | 3 + .../supplemental/display-site-test.json | 203 ++++ .../supplemental/length-zoneid.json | 29 + .../supplemental/required-ext.json | 19 + .../supplemental/required-zoneid-.json | 27 + .../supplemental/response-204.json | 189 +++ .../supplemental/response-400.json | 194 +++ .../supplemental/response-500.json | 194 +++ .../supplemental/unique-zone-id.json | 45 + adapters/madvertise/params_test.go | 55 + adapters/operaads/operaads.go | 215 ++++ adapters/operaads/operaads_test.go | 20 + .../operaadstest/exemplary/native.json | 137 +++ .../operaadstest/exemplary/simple-banner.json | 156 +++ .../operaadstest/exemplary/video.json | 173 +++ .../operaadstest/supplemental/badrequest.json | 84 ++ .../supplemental/banner-size-miss.json | 50 + .../supplemental/miss-native.json | 137 +++ .../supplemental/missing-device.json | 33 + .../operaadstest/supplemental/nocontent.json | 82 ++ .../supplemental/unexcept-statuscode.json | 84 ++ adapters/operaads/params_test.go | 54 + adapters/operaads/usersync.go | 11 + adapters/operaads/usersync_test.go | 33 + adapters/outbrain/outbrain.go | 6 +- .../supplemental/general_params.json | 139 +++ .../supplemental/optional_params.json | 3 + adapters/pangle/pangle.go | 44 +- .../pangletest/exemplary/app_banner.json | 1 + .../exemplary/app_banner_instl.json | 1 + .../pangletest/exemplary/app_native.json | 1 + .../pangletest/exemplary/app_video_instl.json | 1 + .../exemplary/app_video_rewarded.json | 1 + .../pangle/pangletest/params/race/banner.json | 4 +- .../pangle/pangletest/params/race/native.json | 4 +- .../pangle/pangletest/params/race/video.json | 4 +- .../supplemental/appid_placementid_check.json | 155 +++ .../missing_appid_or_placementid.json | 47 + .../supplemental/pangle_ext_check.json | 1 + .../supplemental/response_code_204.json | 1 + .../supplemental/response_code_400.json | 1 + .../supplemental/response_code_non_200.json | 1 + .../supplemental/unrecognized_adtype.json | 1 + adapters/pangle/param_test.go | 7 + adapters/pubmatic/pubmatic.go | 37 +- adapters/pubmatic/pubmatic_test.go | 6 +- .../{simple-banner.json => banner.json} | 0 .../pubmatictest/params/race/video.json | 2 + .../supplemental/gptSlotNameInImpExt.json | 5 +- .../pubmatictest/supplemental/noAdSlot.json | 129 ++ adapters/rubicon/rubicon.go | 125 +- adapters/rubicon/rubicon_test.go | 159 ++- .../rubicontest/exemplary/simple-video.json | 154 ++- .../supplemental/no-site-content-data.json | 289 +++++ .../supplemental/no-site-content.json | 285 +++++ adapters/sa_lunamedia/params_test.go | 52 + adapters/sa_lunamedia/salunamedia.go | 132 +++ adapters/sa_lunamedia/salunamedia_test.go | 18 + .../salunamediatest/exemplary/banner.json | 142 +++ .../salunamediatest/exemplary/native.json | 132 +++ .../salunamediatest/exemplary/video.json | 169 +++ .../salunamediatest/params/race/banner.json | 3 + .../salunamediatest/params/race/native.json | 3 + .../salunamediatest/params/race/video.json | 3 + .../supplemental/bad-response.json | 98 ++ .../supplemental/empty-seatbid.json | 102 ++ .../supplemental/status-204.json | 92 ++ .../supplemental/status-400.json | 99 ++ .../supplemental/status-503.json | 98 ++ .../supplemental/unexpected-status.json | 99 ++ adapters/sa_lunamedia/usersync.go | 12 + adapters/sa_lunamedia/usersync_test.go | 33 + adapters/sharethrough/butler.go | 10 + adapters/sharethrough/butler_test.go | 4 +- adapters/smaato/image.go | 42 +- adapters/smaato/image_test.go | 59 +- adapters/smaato/params_test.go | 2 + adapters/smaato/richmedia.go | 41 +- adapters/smaato/richmedia_test.go | 53 +- adapters/smaato/smaato.go | 570 ++++++--- adapters/smaato/smaato_test.go | 93 ++ .../exemplary/multiple-impressions.json | 358 ++++++ .../exemplary/multiple-media-types.json | 348 ++++++ .../exemplary/simple-banner-app.json | 5 +- .../simple-banner-richMedia-app.json | 5 +- .../exemplary/simple-banner-richMedia.json | 5 +- .../smaatotest/exemplary/simple-banner.json | 7 +- .../smaatotest/exemplary/video-app.json | 5 +- .../smaato/smaatotest/exemplary/video.json | 7 +- .../smaatotest/params/{ => race}/banner.json | 0 .../smaato/smaatotest/params/race/video.json | 4 + .../supplemental/adtype-header-response.json | 194 +++ .../supplemental/bad-adm-response.json | 5 +- .../bad-adtype-header-response.json | 174 +++ .../bad-expires-header-response.json | 194 +++ .../smaatotest/supplemental/bad-ext-req.json | 54 - .../bad-imp-banner-format-req.json | 61 - .../bad-imp-banner-format-request.json | 28 + .../supplemental/bad-media-type-request.json | 27 + .../supplemental/bad-site-ext-request.json | 34 + ...400.json => bad-status-code-response.json} | 4 +- ...eq.json => bad-user-ext-data-request.json} | 14 +- .../supplemental/bad-user-ext-request.json | 36 + .../supplemental/banner-w-and-h.json | 173 +++ .../supplemental/expires-header-response.json | 194 +++ ...ta-req.json => no-adspace-id-request.json} | 27 +- .../supplemental/no-app-site-request.json | 30 + ...tus-code-204.json => no-bid-response.json} | 6 +- ...info.json => no-consent-info-request.json} | 5 +- .../{no-imp-req.json => no-imp-request.json} | 7 +- .../supplemental/no-publisher-id-request.json | 29 + .../outdated-expires-header-response.json | 193 +++ .../smaatotest/video/multiple-adpods.json | 555 +++++++++ .../smaato/smaatotest/video/single-adpod.json | 297 +++++ .../bad-adbreak-id-request.json | 90 ++ .../videosupplemental/bad-adm-response.json | 276 +++++ .../bad-bid-ext-response.json | 274 +++++ .../bad-media-type-request.json | 37 + .../bad-publisher-id-request.json | 90 ++ adapters/smilewanted/params_test.go | 58 + adapters/smilewanted/smilewanted.go | 106 ++ adapters/smilewanted/smilewanted_test.go | 20 + .../exemplary/simple-banner.json | 94 ++ .../exemplary/simple-video.json | 87 ++ .../smilewantedtest/params/race/banner.json | 3 + .../smilewantedtest/params/race/video.json | 3 + .../supplemental/bad-server-response.json | 63 + .../supplemental/status-code-204.json | 59 + .../supplemental/status-code-400.json | 64 + .../supplemental/unexpected-status-code.json | 64 + adapters/smilewanted/usersync.go | 12 + adapters/smilewanted/usersync_test.go | 34 + adapters/sovrn/sovrn.go | 5 +- .../both-custom-default-bidfloor.json | 126 ++ .../sovrntest/supplemental/no-bidfloor.json | 122 ++ .../supplemental/only-custom-bidfloor.json | 125 ++ .../supplemental/only-default-bidfloor.json | 124 ++ adapters/tappx/params_test.go | 15 + adapters/tappx/tappx.go | 31 +- adapters/tappx/tappx_test.go | 2 +- .../single-banner-impression-extra.json | 130 ++ ...ngle-banner-impression-future-feature.json | 9 +- .../exemplary/single-banner-impression.json | 7 +- .../exemplary/single-banner-site.json | 7 +- .../exemplary/single-video-impression.json | 7 +- .../exemplary/single-video-site.json | 7 +- .../tappxtest/supplemental/204status.json | 7 +- .../tappxtest/supplemental/bidfloor.json | 7 +- .../supplemental/http-err-status.json | 7 +- .../supplemental/http-err-status2.json | 7 +- adapters/viewdeos/usersync.go | 12 + adapters/viewdeos/usersync_test.go | 29 + analytics/config/config_test.go | 3 +- analytics/filesystem/file_module_test.go | 3 +- config/adapter.go | 2 + config/config.go | 168 ++- config/config_test.go | 350 +++++- currency/aggregate_conversions.go | 41 + currency/aggregate_conversions_test.go | 89 ++ currency/constant_rates.go | 4 +- currency/errors.go | 13 + currency/rates.go | 16 +- docs/developers/images/img_grafana.png | Bin 0 -> 271788 bytes docs/developers/metrics-configuration.md | 79 ++ endpoints/auction.go | 2 + endpoints/auction_test.go | 11 +- endpoints/cookie_sync.go | 6 +- endpoints/cookie_sync_test.go | 16 +- endpoints/events/vtrack_test.go | 2 +- endpoints/openrtb2/amp_auction.go | 17 +- endpoints/openrtb2/amp_auction_test.go | 13 +- endpoints/openrtb2/auction.go | 307 ++--- endpoints/openrtb2/auction_test.go | 692 ++++++++++- endpoints/openrtb2/ctv_auction.go | 4 +- endpoints/openrtb2/interstitial.go | 18 +- endpoints/openrtb2/interstitial_test.go | 3 +- .../no-account/not-required-no-acct.json | 1 + .../with-account/required-with-acct.json | 1 + .../aliased/multiple-alias.json | 1 + .../sample-requests/aliased/simple.json | 25 +- .../errors/conversion-disabled.json | 46 + ...rates-currency-missing-usepbs-default.json | 52 + ...m-rates-currency-missing-usepbs-false.json | 53 + .../custom-rates-empty-usepbs-false.json | 49 + .../custom-rates-invalid-usepbs-false.json | 53 + .../valid/conversion-disabled.json | 62 + ...om-rate-not-found-usepbsrates-default.json | 70 ++ ...om-rates-override-usepbsrates-default.json | 69 ++ ...stom-rates-override-usepbsrates-false.json | 70 ++ ...ustom-rates-override-usepbsrates-true.json | 70 ++ .../valid/reverse-currency-conversion.json | 66 ++ .../valid/server-rates-usepbsrates-true.json | 70 ++ .../errors/no-conversion-found.json | 38 + .../server-rates/valid/simple-conversion.json | 55 + .../disabled/good/partial.json | 1 + .../valid-fpd-allowed-with-ext-bidder.json | 3 +- .../valid-fpd-allowed-with-prebid-bidder.json | 3 +- .../contextsubtype-greater-than-max.json | 26 - .../invalid-whole/digitrust.json | 46 - .../invalid-whole/invalid-source.json | 2 +- .../invalid-whole/regs-ext-gdpr-string.json | 2 +- .../invalid-whole/regs-ext-malformed.json | 2 +- .../invalid-whole/user-ext-consent-int.json | 2 +- .../user-gdpr-consent-invalid.json | 2 +- .../valid-native/asset-img-no-hmin.json | 25 +- .../valid-native/asset-img-no-wmin.json | 25 +- .../valid-native/asset-with-id.json | 1 + .../valid-native/asset-with-no-id.json | 1 + .../valid-native/assets-with-unique-ids.json | 1 + .../context-product-compatible-subtype.json | 25 +- .../context-social-compatible-subtype.json | 25 +- .../eventtracker-exchange-specific.json | 36 + .../valid-native/request-no-context.json | 1 + .../valid-native/request-plcmttype-empty.json | 1 + .../video-asset-event-tracker.json | 1 + .../valid-native/with-video-asset.json | 1 + .../valid-whole/exemplary/all-ext.json | 1 + .../valid-whole/exemplary/prebid-test-ad.json | 1 + .../valid-whole/exemplary/skadn.json | 3 +- .../valid-whole/supplementary/digitrust.json | 50 - endpoints/openrtb2/video_auction.go | 35 +- endpoints/openrtb2/video_auction_test.go | 10 +- endpoints/setuid_test.go | 16 +- errortypes/code.go | 2 + exchange/adapter_builders.go | 245 ++-- exchange/adapter_util.go | 66 +- exchange/adapter_util_test.go | 166 +-- exchange/auction.go | 27 +- exchange/auction_test.go | 50 + exchange/bidder.go | 41 +- exchange/bidder_test.go | 113 +- exchange/bidder_validate_bids.go | 11 +- exchange/bidder_validate_bids_test.go | 70 +- exchange/cachetest/debuglog_enabled.json | 2 + exchange/events.go | 4 +- exchange/exchange.go | 177 ++- exchange/exchange_test.go | 785 +++++++++++- exchange/exchangetest/debuglog_enabled.json | 2 + .../debuglog_enabled_no_bids.json | 2 + ...data-imp-ext-multiple-prebid-bidders.json} | 0 .../exchangetest/request-other-user-ext.json | 14 +- .../exchangetest/request-user-no-prebid.json | 10 - exchange/legacy.go | 375 ------ exchange/legacy_test.go | 505 -------- exchange/targeting_test.go | 18 +- exchange/utils.go | 62 +- exchange/utils_test.go | 256 ++-- gdpr/gdpr.go | 31 +- gdpr/impl.go | 196 +-- gdpr/impl_test.go | 1053 ++++++++++------- gdpr/vendorlist-fetching.go | 28 +- gdpr/vendorlist-fetching_test.go | 173 +-- go.mod | 12 +- go.sum | 31 +- main.go | 6 +- metrics/config/metrics.go | 55 +- metrics/config/metrics_test.go | 4 + metrics/go_metrics.go | 45 +- metrics/go_metrics_test.go | 49 +- metrics/metrics.go | 5 +- metrics/metrics_mock.go | 7 +- metrics/prometheus/preload.go | 6 + metrics/prometheus/prometheus.go | 20 +- metrics/prometheus/prometheus_test.go | 38 +- openrtb_ext/bidders.go | 248 ++-- openrtb_ext/device.go | 8 +- openrtb_ext/imp_adf.go | 9 + openrtb_ext/imp_algorix.go | 7 + openrtb_ext/imp_appnexus.go | 1 + openrtb_ext/imp_axonix.go | 5 + openrtb_ext/imp_between.go | 6 +- openrtb_ext/imp_bidscube.go | 5 + openrtb_ext/imp_bmtm.go | 5 + openrtb_ext/imp_interactiveoffers.go | 5 + openrtb_ext/imp_kayzen.go | 7 + openrtb_ext/imp_madvertise.go | 6 + openrtb_ext/imp_operaads.go | 7 + openrtb_ext/imp_pangle.go | 4 +- openrtb_ext/imp_sa_lunamedia.go | 6 + openrtb_ext/imp_sharethrough.go | 15 +- openrtb_ext/imp_smaato.go | 5 +- openrtb_ext/imp_tappx.go | 11 +- openrtb_ext/request.go | 10 + openrtb_ext/request_wrapper.go | 787 ++++++++++++ openrtb_ext/request_wrapper_test.go | 22 + openrtb_ext/user.go | 13 - prebid_cache_client/prebid_cache_test.go | 27 +- privacy/ccpa/consentwriter.go | 19 +- privacy/ccpa/consentwriter_test.go | 50 + privacy/ccpa/policy.go | 184 +-- privacy/ccpa/policy_test.go | 123 +- privacy/gdpr/policy_test.go | 7 +- privacy/scrubber.go | 5 +- privacy/scrubber_test.go | 35 +- router/router.go | 11 +- static/bidder-info/adagio.yaml | 12 + static/bidder-info/adf.yaml | 14 + static/bidder-info/adhese.yaml | 4 +- static/bidder-info/adocean.yaml | 3 + static/bidder-info/adtarget.yaml | 1 + static/bidder-info/algorix.yaml | 8 + static/bidder-info/axonix.yaml | 15 + static/bidder-info/bidmyadz.yaml | 13 + static/bidder-info/bidscube.yaml | 14 + static/bidder-info/bmtm.yaml | 8 + static/bidder-info/criteo.yaml | 2 +- static/bidder-info/e_volution.yaml | 14 + static/bidder-info/epom.yaml | 1 + static/bidder-info/inmobi.yaml | 9 +- static/bidder-info/interactiveoffers.yaml | 9 + static/bidder-info/kayzen.yaml | 15 + .../{lifestreet.yaml => madvertise.yaml} | 6 +- static/bidder-info/marsmedia.yaml | 1 + static/bidder-info/mobilefuse.yaml | 1 + static/bidder-info/operaads.yaml | 14 + static/bidder-info/pubnative.yaml | 1 + static/bidder-info/revcontent.yaml | 3 +- static/bidder-info/sa_lunamedia.yaml | 14 + static/bidder-info/smaato.yaml | 1 + static/bidder-info/smilewanted.yaml | 12 + static/bidder-info/viewdeos.yaml | 12 + static/bidder-params/adagio.json | 94 ++ static/bidder-params/adf.json | 14 + static/bidder-params/admixer.json | 4 +- static/bidder-params/algorix.json | 19 + static/bidder-params/appnexus.json | 4 + .../{lifestreet.json => axonix.json} | 11 +- static/bidder-params/beachfront.json | 21 +- static/bidder-params/between.json | 9 - static/bidder-params/bidmyadz.json | 12 + static/bidder-params/bidscube.json | 18 + static/bidder-params/bmtm.json | 16 + static/bidder-params/criteo.json | 78 +- static/bidder-params/e_volution.json | 13 + static/bidder-params/interactiveoffers.json | 13 + static/bidder-params/ix.json | 18 +- static/bidder-params/kayzen.json | 21 + static/bidder-params/madvertise.json | 16 + static/bidder-params/operaads.json | 28 + static/bidder-params/pangle.json | 22 +- static/bidder-params/sa_lunamedia.json | 17 + static/bidder-params/sharethrough.json | 10 + static/bidder-params/smaato.json | 20 +- static/bidder-params/smilewanted.json | 14 + static/bidder-params/tappx.json | 18 + static/bidder-params/viewdeos.json | 26 + static/tcf1/fallback_gvl.json | 1 - stored_requests/config/config.go | 3 + stored_requests/config/config_test.go | 78 +- usersync/usersyncers/syncer.go | 22 +- usersync/usersyncers/syncer_test.go | 64 +- util/jsonutil/jsonutil.go | 57 + util/jsonutil/jsonutil_test.go | 122 ++ 624 files changed, 36905 insertions(+), 6419 deletions(-) create mode 100644 adapters/adagio/adagio.go create mode 100644 adapters/adagio/adagio_test.go create mode 100644 adapters/adagio/adagiotest/exemplary/banner-web.json create mode 100644 adapters/adagio/adagiotest/exemplary/multi-format.json create mode 100644 adapters/adagio/adagiotest/exemplary/multi-imp.json create mode 100644 adapters/adagio/adagiotest/exemplary/native-web.json create mode 100644 adapters/adagio/adagiotest/exemplary/video-web.json create mode 100644 adapters/adagio/adagiotest/params/race/banner.json create mode 100644 adapters/adagio/adagiotest/params/race/native.json create mode 100644 adapters/adagio/adagiotest/params/race/video.json create mode 100644 adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-204.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-400.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-401.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-403.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-500.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-503.json create mode 100644 adapters/adagio/adagiotest/supplemental/status-504.json create mode 100644 adapters/adagio/params_test.go create mode 100644 adapters/adagio/usersync.go create mode 100644 adapters/adagio/usersync_test.go create mode 100644 adapters/adf/adf.go create mode 100644 adapters/adf/adf_test.go create mode 100644 adapters/adf/adftest/exemplary/multi-format.json create mode 100644 adapters/adf/adftest/exemplary/multi-native.json create mode 100644 adapters/adf/adftest/exemplary/single-banner.json create mode 100644 adapters/adf/adftest/exemplary/single-native.json create mode 100644 adapters/adf/adftest/exemplary/single-video.json create mode 100644 adapters/adf/adftest/params/race/native.json create mode 100644 adapters/adf/adftest/supplemental/bad-request.json create mode 100644 adapters/adf/adftest/supplemental/empty-response.json create mode 100644 adapters/adf/adftest/supplemental/invalid-imp-mediatype.json create mode 100644 adapters/adf/adftest/supplemental/nobid-response.json create mode 100644 adapters/adf/adftest/supplemental/server-error.json create mode 100644 adapters/adf/adftest/supplemental/unparsable-response.json create mode 100644 adapters/adf/params_test.go create mode 100644 adapters/adf/usersync.go create mode 100644 adapters/adf/usersync_test.go create mode 100644 adapters/adocean/adoceantest/supplemental/app.json create mode 100644 adapters/algorix/algorix.go create mode 100644 adapters/algorix/algorix_test.go create mode 100644 adapters/algorix/algorixtest/exemplary/sample-banner.json create mode 100644 adapters/algorix/algorixtest/exemplary/sample-native.json create mode 100644 adapters/algorix/algorixtest/exemplary/sample-nobid.json create mode 100644 adapters/algorix/algorixtest/exemplary/sample-video.json create mode 100644 adapters/algorix/algorixtest/supplemental/bad_imp_ext.json create mode 100644 adapters/algorix/algorixtest/supplemental/bad_impext_bidder.json create mode 100644 adapters/algorix/algorixtest/supplemental/bad_response.json create mode 100644 adapters/algorix/algorixtest/supplemental/status_400.json create mode 100644 adapters/algorix/algorixtest/supplemental/status_500.json create mode 100644 adapters/algorix/params_test.go create mode 100644 adapters/appnexus/appnexustest/supplemental/reserve-ignored.json create mode 100644 adapters/appnexus/appnexustest/supplemental/reserve-test.json create mode 100644 adapters/appnexus/appnexustest/video/video-no-adpodid-two-imps-different-pod.json create mode 100644 adapters/appnexus/appnexustest/video/video-same-adpodid-two-imps-same-pod.json create mode 100644 adapters/appnexus/appnexustest/videosupplemental/video-different-adpodid-two-imps-different-pods.json create mode 100644 adapters/appnexus/appnexustest/videosupplemental/video-different-adpodid-two-imps-same-pod.json create mode 100644 adapters/audienceNetwork/audienceNetworktest/exemplary/video_consented_providers.json create mode 100644 adapters/axonix/axonix.go create mode 100644 adapters/axonix/axonix_test.go create mode 100644 adapters/axonix/axonixtest/exemplary/banner-and-video.json create mode 100644 adapters/axonix/axonixtest/exemplary/banner-video-native.json create mode 100644 adapters/axonix/axonixtest/exemplary/simple-banner.json create mode 100644 adapters/axonix/axonixtest/exemplary/simple-video.json create mode 100644 adapters/axonix/axonixtest/params/race/banner.json create mode 100644 adapters/axonix/axonixtest/params/race/video.json create mode 100644 adapters/axonix/axonixtest/supplemental/bad-response-no-body.json create mode 100644 adapters/axonix/axonixtest/supplemental/status-bad-request.json create mode 100644 adapters/axonix/axonixtest/supplemental/status-no-content.json create mode 100644 adapters/axonix/axonixtest/supplemental/unexpected-status-code.json create mode 100644 adapters/axonix/axonixtest/supplemental/valid-extension.json create mode 100644 adapters/axonix/axonixtest/supplemental/valid-with-device.json create mode 100644 adapters/axonix/params_test.go create mode 100644 adapters/beachfront/beachfronttest/exemplary/adm-video-app.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-app-alphanum-bundle.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-app-malformed-bundle.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/adm-video-schain.json rename adapters/beachfront/beachfronttest/supplemental/{bidfloor-below-min.json => banner-schain.json} (74%) create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-four-variations-on-nothing-adm-video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-test-ext-wins-adm-video.json create mode 100644 adapters/beachfront/beachfronttest/supplemental/bidfloor-test-imp-wins-adm-video.json create mode 100644 adapters/bidder_test.go create mode 100644 adapters/bidmyadz/bidmyadz.go create mode 100644 adapters/bidmyadz/bidmyadz_test.go create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/banner.json create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/native.json create mode 100644 adapters/bidmyadz/bidmyadztest/exemplary/video.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/banner.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/native.json create mode 100644 adapters/bidmyadz/bidmyadztest/params/race/video.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/invalid-device-fields.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/invalid-multi-imps.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/missing-mediatype.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/response-without-bids.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/response-without-seatbid.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-204.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-400.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-service-unavailable.json create mode 100644 adapters/bidmyadz/bidmyadztest/supplemental/status-unknown.json create mode 100644 adapters/bidmyadz/params_test.go create mode 100644 adapters/bidmyadz/usersync.go create mode 100644 adapters/bidmyadz/usersync_test.go create mode 100644 adapters/bidscube/bidscube.go create mode 100644 adapters/bidscube/bidscube_test.go create mode 100644 adapters/bidscube/bidscubetest/exemplary/simple-banner.json create mode 100644 adapters/bidscube/bidscubetest/exemplary/simple-native.json create mode 100644 adapters/bidscube/bidscubetest/exemplary/simple-video.json create mode 100644 adapters/bidscube/bidscubetest/exemplary/simple-web-banner.json create mode 100644 adapters/bidscube/bidscubetest/params/race/banner.json create mode 100644 adapters/bidscube/bidscubetest/params/race/native.json create mode 100644 adapters/bidscube/bidscubetest/params/race/video.json create mode 100644 adapters/bidscube/bidscubetest/supplemental/bad-imp-ext.json create mode 100644 adapters/bidscube/bidscubetest/supplemental/bad_bidtype_response.json create mode 100644 adapters/bidscube/bidscubetest/supplemental/bad_response.json create mode 100644 adapters/bidscube/bidscubetest/supplemental/bad_status_code.json create mode 100644 adapters/bidscube/bidscubetest/supplemental/imp_ext_empty_object.json create mode 100644 adapters/bidscube/bidscubetest/supplemental/imp_ext_string.json create mode 100644 adapters/bidscube/bidscubetest/supplemental/status-204.json create mode 100644 adapters/bidscube/bidscubetest/supplemental/status-404.json create mode 100644 adapters/bidscube/params_test.go create mode 100644 adapters/bmtm/brightmountainmedia.go create mode 100644 adapters/bmtm/brightmountainmedia_test.go create mode 100644 adapters/bmtm/brightmountainmediatest/exemplary/banner.json create mode 100644 adapters/bmtm/brightmountainmediatest/exemplary/multi-imp.json create mode 100644 adapters/bmtm/brightmountainmediatest/exemplary/video.json create mode 100644 adapters/bmtm/brightmountainmediatest/params/race/banner.json create mode 100644 adapters/bmtm/brightmountainmediatest/params/race/video.json create mode 100644 adapters/bmtm/brightmountainmediatest/supplemental/multi-imp-mixed-validation.json create mode 100644 adapters/bmtm/brightmountainmediatest/supplemental/status-not-ok.json create mode 100644 adapters/bmtm/params_test.go create mode 100644 adapters/bmtm/usersync.go create mode 100644 adapters/bmtm/usersync_test.go create mode 100644 adapters/conversant/conversanttest/supplemental/test_params.json create mode 100644 adapters/criteo/criteotest/supplemental/multislots-alt-case.json create mode 100644 adapters/e_volution/evolution.go create mode 100644 adapters/e_volution/evolution_test.go create mode 100644 adapters/e_volution/evolutiontest/exemplary/banner-without-mediatype.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/banner.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/native.json create mode 100644 adapters/e_volution/evolutiontest/exemplary/video.json create mode 100644 adapters/e_volution/evolutiontest/params/race/banner.json create mode 100644 adapters/e_volution/evolutiontest/params/race/native.json create mode 100644 adapters/e_volution/evolutiontest/params/race/video.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/bad-response.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-204.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-400.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/status-503.json create mode 100644 adapters/e_volution/evolutiontest/supplemental/unexpected-status.json create mode 100644 adapters/e_volution/params_test.go create mode 100644 adapters/e_volution/usersync.go create mode 100644 adapters/e_volution/usersync_test.go rename adapters/inmobi/inmobitest/exemplary/{simple-banner.json => simple-app-banner.json} (100%) create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-app-native.json rename adapters/inmobi/inmobitest/exemplary/{simple-video.json => simple-app-video.json} (100%) create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-web-banner.json create mode 100644 adapters/inmobi/inmobitest/exemplary/simple-web-video.json create mode 100644 adapters/inmobi/usersync.go create mode 100644 adapters/interactiveoffers/interactiveoffers.go create mode 100644 adapters/interactiveoffers/interactiveoffers_test.go create mode 100644 adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json create mode 100644 adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json create mode 100644 adapters/interactiveoffers/interactiveofferstest/params/race/banner.json create mode 100644 adapters/interactiveoffers/interactiveofferstest/supplemental/204.json create mode 100644 adapters/interactiveoffers/interactiveofferstest/supplemental/400.json create mode 100644 adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json create mode 100644 adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json create mode 100644 adapters/interactiveoffers/params_test.go create mode 100644 adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json create mode 100644 adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json create mode 100644 adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json create mode 100644 adapters/ix/ixtest/supplemental/native-missing.json create mode 100644 adapters/ix/params_test.go create mode 100644 adapters/kayzen/kayzen.go create mode 100644 adapters/kayzen/kayzen_test.go create mode 100644 adapters/kayzen/kayzentest/exemplary/banner-app.json create mode 100644 adapters/kayzen/kayzentest/exemplary/banner-web.json create mode 100644 adapters/kayzen/kayzentest/exemplary/native-app.json create mode 100644 adapters/kayzen/kayzentest/exemplary/native-web.json create mode 100644 adapters/kayzen/kayzentest/exemplary/video-app.json create mode 100644 adapters/kayzen/kayzentest/exemplary/video-web.json create mode 100644 adapters/kayzen/kayzentest/params/race/banner.json create mode 100644 adapters/kayzen/kayzentest/params/race/native.json create mode 100644 adapters/kayzen/kayzentest/params/race/video.json create mode 100644 adapters/kayzen/kayzentest/supplemental/invalid-ext-object.json create mode 100644 adapters/kayzen/kayzentest/supplemental/invalid-response.json create mode 100644 adapters/kayzen/kayzentest/supplemental/requires-imp-object.json create mode 100644 adapters/kayzen/kayzentest/supplemental/status-code-bad-request.json create mode 100644 adapters/kayzen/kayzentest/supplemental/status-code-no-content.json create mode 100644 adapters/kayzen/kayzentest/supplemental/status-code-other-error.json create mode 100644 adapters/kayzen/params_test.go delete mode 100644 adapters/lifestreet/lifestreet.go delete mode 100644 adapters/lifestreet/lifestreet_test.go delete mode 100644 adapters/lifestreet/lifestreettest/params/race/banner.json delete mode 100644 adapters/lifestreet/lifestreettest/params/race/video.json delete mode 100644 adapters/lifestreet/usersync.go delete mode 100644 adapters/lifestreet/usersync_test.go create mode 100644 adapters/madvertise/madvertise.go create mode 100644 adapters/madvertise/madvertise_test.go create mode 100644 adapters/madvertise/madvertisetest/exemplary/simple-banner.json create mode 100644 adapters/madvertise/madvertisetest/exemplary/simple-video.json create mode 100644 adapters/madvertise/madvertisetest/params/race/banner.json create mode 100644 adapters/madvertise/madvertisetest/params/race/video.json create mode 100644 adapters/madvertise/madvertisetest/supplemental/display-site-test.json create mode 100644 adapters/madvertise/madvertisetest/supplemental/length-zoneid.json create mode 100644 adapters/madvertise/madvertisetest/supplemental/required-ext.json create mode 100644 adapters/madvertise/madvertisetest/supplemental/required-zoneid-.json create mode 100644 adapters/madvertise/madvertisetest/supplemental/response-204.json create mode 100644 adapters/madvertise/madvertisetest/supplemental/response-400.json create mode 100644 adapters/madvertise/madvertisetest/supplemental/response-500.json create mode 100644 adapters/madvertise/madvertisetest/supplemental/unique-zone-id.json create mode 100644 adapters/madvertise/params_test.go create mode 100644 adapters/operaads/operaads.go create mode 100644 adapters/operaads/operaads_test.go create mode 100644 adapters/operaads/operaadstest/exemplary/native.json create mode 100644 adapters/operaads/operaadstest/exemplary/simple-banner.json create mode 100644 adapters/operaads/operaadstest/exemplary/video.json create mode 100644 adapters/operaads/operaadstest/supplemental/badrequest.json create mode 100644 adapters/operaads/operaadstest/supplemental/banner-size-miss.json create mode 100644 adapters/operaads/operaadstest/supplemental/miss-native.json create mode 100644 adapters/operaads/operaadstest/supplemental/missing-device.json create mode 100644 adapters/operaads/operaadstest/supplemental/nocontent.json create mode 100644 adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json create mode 100644 adapters/operaads/params_test.go create mode 100644 adapters/operaads/usersync.go create mode 100644 adapters/operaads/usersync_test.go create mode 100644 adapters/outbrain/outbraintest/supplemental/general_params.json create mode 100644 adapters/pangle/pangletest/supplemental/appid_placementid_check.json create mode 100644 adapters/pangle/pangletest/supplemental/missing_appid_or_placementid.json rename adapters/pubmatic/pubmatictest/exemplary/{simple-banner.json => banner.json} (100%) create mode 100644 adapters/pubmatic/pubmatictest/supplemental/noAdSlot.json create mode 100644 adapters/rubicon/rubicontest/supplemental/no-site-content-data.json create mode 100644 adapters/rubicon/rubicontest/supplemental/no-site-content.json create mode 100644 adapters/sa_lunamedia/params_test.go create mode 100644 adapters/sa_lunamedia/salunamedia.go create mode 100644 adapters/sa_lunamedia/salunamedia_test.go create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/banner.json create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/native.json create mode 100644 adapters/sa_lunamedia/salunamediatest/exemplary/video.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/banner.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/native.json create mode 100644 adapters/sa_lunamedia/salunamediatest/params/race/video.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json create mode 100644 adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json create mode 100644 adapters/sa_lunamedia/usersync.go create mode 100644 adapters/sa_lunamedia/usersync_test.go create mode 100644 adapters/smaato/smaatotest/exemplary/multiple-impressions.json create mode 100644 adapters/smaato/smaatotest/exemplary/multiple-media-types.json rename adapters/smaato/smaatotest/params/{ => race}/banner.json (100%) create mode 100644 adapters/smaato/smaatotest/params/race/video.json create mode 100644 adapters/smaato/smaatotest/supplemental/adtype-header-response.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json delete mode 100644 adapters/smaato/smaatotest/supplemental/bad-ext-req.json delete mode 100644 adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-media-type-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json rename adapters/smaato/smaatotest/supplemental/{status-code-400.json => bad-status-code-response.json} (97%) rename adapters/smaato/smaatotest/supplemental/{bad-user-ext-req.json => bad-user-ext-data-request.json} (81%) create mode 100644 adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/banner-w-and-h.json create mode 100644 adapters/smaato/smaatotest/supplemental/expires-header-response.json rename adapters/smaato/smaatotest/supplemental/{bad-user-ext-data-req.json => no-adspace-id-request.json} (65%) create mode 100644 adapters/smaato/smaatotest/supplemental/no-app-site-request.json rename adapters/smaato/smaatotest/supplemental/{status-code-204.json => no-bid-response.json} (96%) rename adapters/smaato/smaatotest/supplemental/{no-consent-info.json => no-consent-info-request.json} (98%) rename adapters/smaato/smaatotest/supplemental/{no-imp-req.json => no-imp-request.json} (59%) create mode 100644 adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json create mode 100644 adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json create mode 100644 adapters/smaato/smaatotest/video/multiple-adpods.json create mode 100644 adapters/smaato/smaatotest/video/single-adpod.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json create mode 100644 adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json create mode 100644 adapters/smilewanted/params_test.go create mode 100644 adapters/smilewanted/smilewanted.go create mode 100644 adapters/smilewanted/smilewanted_test.go create mode 100644 adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json create mode 100644 adapters/smilewanted/smilewantedtest/exemplary/simple-video.json create mode 100644 adapters/smilewanted/smilewantedtest/params/race/banner.json create mode 100644 adapters/smilewanted/smilewantedtest/params/race/video.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json create mode 100644 adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json create mode 100644 adapters/smilewanted/usersync.go create mode 100644 adapters/smilewanted/usersync_test.go create mode 100644 adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/no-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json create mode 100644 adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json create mode 100644 adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json create mode 100644 adapters/viewdeos/usersync.go create mode 100644 adapters/viewdeos/usersync_test.go mode change 100755 => 100644 config/config.go create mode 100644 currency/aggregate_conversions.go create mode 100644 currency/aggregate_conversions_test.go create mode 100644 currency/errors.go create mode 100644 docs/developers/images/img_grafana.png create mode 100644 docs/developers/metrics-configuration.md create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json create mode 100644 endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json delete mode 100644 endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json delete mode 100644 endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json create mode 100644 endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json delete mode 100644 endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json rename exchange/exchangetest/{firstpartydata-imp-ext-multiple-prebid-bidders.json => firstpartydata-imp-ext-multiple-prebid-bidders.json} (100%) delete mode 100644 exchange/legacy.go delete mode 100644 exchange/legacy_test.go mode change 100755 => 100644 openrtb_ext/bidders.go create mode 100644 openrtb_ext/imp_adf.go create mode 100644 openrtb_ext/imp_algorix.go create mode 100644 openrtb_ext/imp_axonix.go create mode 100644 openrtb_ext/imp_bidscube.go create mode 100644 openrtb_ext/imp_bmtm.go create mode 100644 openrtb_ext/imp_interactiveoffers.go create mode 100644 openrtb_ext/imp_kayzen.go create mode 100644 openrtb_ext/imp_madvertise.go create mode 100644 openrtb_ext/imp_operaads.go create mode 100644 openrtb_ext/imp_sa_lunamedia.go create mode 100644 openrtb_ext/request_wrapper.go create mode 100644 openrtb_ext/request_wrapper_test.go create mode 100644 static/bidder-info/adagio.yaml create mode 100644 static/bidder-info/adf.yaml create mode 100644 static/bidder-info/algorix.yaml create mode 100644 static/bidder-info/axonix.yaml create mode 100644 static/bidder-info/bidmyadz.yaml create mode 100644 static/bidder-info/bidscube.yaml create mode 100644 static/bidder-info/bmtm.yaml create mode 100644 static/bidder-info/e_volution.yaml create mode 100644 static/bidder-info/interactiveoffers.yaml create mode 100644 static/bidder-info/kayzen.yaml rename static/bidder-info/{lifestreet.yaml => madvertise.yaml} (68%) create mode 100644 static/bidder-info/operaads.yaml create mode 100644 static/bidder-info/sa_lunamedia.yaml create mode 100644 static/bidder-info/smilewanted.yaml create mode 100644 static/bidder-info/viewdeos.yaml create mode 100644 static/bidder-params/adagio.json create mode 100644 static/bidder-params/adf.json create mode 100644 static/bidder-params/algorix.json rename static/bidder-params/{lifestreet.json => axonix.json} (53%) create mode 100644 static/bidder-params/bidmyadz.json create mode 100644 static/bidder-params/bidscube.json create mode 100644 static/bidder-params/bmtm.json create mode 100644 static/bidder-params/e_volution.json create mode 100644 static/bidder-params/interactiveoffers.json create mode 100644 static/bidder-params/kayzen.json create mode 100644 static/bidder-params/madvertise.json create mode 100644 static/bidder-params/operaads.json create mode 100644 static/bidder-params/sa_lunamedia.json create mode 100644 static/bidder-params/smilewanted.json create mode 100644 static/bidder-params/viewdeos.json delete mode 100644 static/tcf1/fallback_gvl.json mode change 100755 => 100644 usersync/usersyncers/syncer_test.go create mode 100644 util/jsonutil/jsonutil.go create mode 100644 util/jsonutil/jsonutil_test.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b2c53776ad4..bbb76d3675c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,8 +5,8 @@ "build": { "dockerfile": "Dockerfile", "args": { - // Update the VARIANT arg to pick a version of Go: 1, 1.15, 1.14 - "VARIANT": "1.14", + // Update the VARIANT arg to pick a version of Go + "VARIANT": "1.16", // Options "INSTALL_NODE": "false", "NODE_VERSION": "lts/*", diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fb9b6592308..a14596263c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: tags: - - '[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+' jobs: release: @@ -13,13 +13,15 @@ jobs: steps: - name: Get Version id: get_version - run: echo ::set-output name=version::${GITHUB_REF/refs\/tags\//} + run: | + echo ::set-output name=tag::${GITHUB_REF/refs\/tags\/} + echo ::set-output name=version::${GITHUB_REF/refs\/tags\/v} - name: Create & Publish Release uses: release-drafter/release-drafter@v5.12.1 with: name: ${{ steps.get_version.outputs.version }} - tag: ${{ steps.get_version.outputs.version }} + tag: ${{ steps.get_version.outputs.tag }} version: ${{ steps.get_version.outputs.version }} publish: true env: diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml index 30370178ca8..9cf371ca168 100644 --- a/.github/workflows/validate-merge.yml +++ b/.github/workflows/validate-merge.yml @@ -12,7 +12,7 @@ jobs: - name: Install Go uses: actions/setup-go@v2 with: - go-version: 1.14.2 + go-version: 1.16.4 - name: Checkout Merged Branch uses: actions/checkout@v2 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 3efc51d287a..1eb137467ec 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,7 +10,7 @@ jobs: validate: strategy: matrix: - go-version: [1.14.x, 1.15.x] + go-version: [1.15.x, 1.16.x] os: [ubuntu-18.04] runs-on: ${{ matrix.os }} diff --git a/Dockerfile b/Dockerfile index d76398dc6d3..defb64c8586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,8 @@ RUN apt-get update && \ apt-get -y upgrade && \ apt-get install -y wget RUN cd /tmp && \ - wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz && \ - tar -xf go1.14.2.linux-amd64.tar.gz && \ + wget https://dl.google.com/go/go1.16.4.linux-amd64.tar.gz && \ + tar -xf go1.16.4.linux-amd64.tar.gz && \ mv go /usr/local RUN mkdir -p /app/prebid-server/ WORKDIR /app/prebid-server/ diff --git a/README.md b/README.md index 9189421bd9d..8d40cafc6ce 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Please consider [registering your Prebid Server](https://docs.prebid.org/prebid- ## Installation -First install [Go](https://golang.org/doc/install) version 1.14 or newer. +First install [Go](https://golang.org/doc/install) version 1.15 or newer. Note that prebid-server is using [Go modules](https://blog.golang.org/using-go-modules). We officially support the most recent two major versions of the Go runtime. However, if you'd like to use a version <1.13 and are inside GOPATH `GO111MODULE` needs to be set to `GO111MODULE=on`. @@ -50,16 +50,19 @@ go build . Load the landing page in your browser at `http://localhost:8000/`. For the full API reference, see [the endpoint documentation](https://docs.prebid.org/prebid-server/endpoints/pbs-endpoint-overview.html) +## Go Modules + +The packages within this repository are intended to be used as part of the Prebid Server compiled binary. If you +choose to import Prebid Server packages in other projects, please understand we make no promises on the stability +of exported types. ## Contributing 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/PubMatic-OpenWrap/prebid-server/issues). - Or better yet, [open a pull request](https://github.com/PubMatic-OpenWrap/prebid-server/compare) with the changes you'd like to see. -## IDE Setup for PBS-Go development +## IDE Recommendations -The quickest way to start developing PBS-Go in a reproducible environment isolated from your host OS -is by using this [VScode Remote Container Setup](devcontainer.md) +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/adagio/adagio.go b/adapters/adagio/adagio.go new file mode 100644 index 00000000000..0da4d6ac9e4 --- /dev/null +++ b/adapters/adagio/adagio.go @@ -0,0 +1,139 @@ +package adagio + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/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" +) + +// Builder builds a new instance of the Adagio adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +type adapter struct { + endpoint string +} + +type extBid struct { + Prebid *openrtb_ext.ExtBidPrebid +} + +// MakeRequests prepares the HTTP requests which should be made to fetch bids. +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + json, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + if request.Device != nil { + 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) + } + } + + if request.Test == 0 { + // Gzip the body + // Note: Gzipping could be handled natively later: https://github.com/prebid/prebid-server/issues/1812 + var bodyBuf bytes.Buffer + gz := gzip.NewWriter(&bodyBuf) + if _, err = gz.Write(json); err == nil { + if err = gz.Close(); err == nil { + json = bodyBuf.Bytes() + headers.Add("Content-Encoding", "gzip") + // /!\ Go already sets the `Accept-Encoding: gzip` header. Never add it manually, or Go won't decompress the response. + //headers.Add("Accept-Encoding", "gzip") + } + } + } + + requestToBidder := &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint, + Body: json, + Headers: headers, + } + + return []*adapters.RequestData{requestToBidder}, nil +} + +const unexpectedStatusCodeFormat = "Unexpected status code: %d. Run with request.debug = 1 for more info" + +// MakeBids unpacks the server's response into Bids. +func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + switch response.StatusCode { + case http.StatusOK: + break + case http.StatusNoContent: + return nil, nil + case http.StatusServiceUnavailable: + fallthrough + case http.StatusBadRequest: + fallthrough + case http.StatusUnauthorized: + fallthrough + case http.StatusForbidden: + err := &errortypes.BadInput{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, response.StatusCode), + } + return nil, []error{err} + default: + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf(unexpectedStatusCodeFormat, response.StatusCode), + } + return nil, []error{err} + } + + var openRTBBidderResponse openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &openRTBBidderResponse); err != nil { + return nil, []error{err} + } + + bidsCapacity := len(internalRequest.Imp) + errs := make([]error, 0, bidsCapacity) + bidderResponse := adapters.NewBidderResponseWithBidsCapacity(bidsCapacity) + var typedBid *adapters.TypedBid + for _, seatBid := range openRTBBidderResponse.SeatBid { + for _, bid := range seatBid.Bid { + activeBid := bid + + activeExt := &extBid{} + if err := json.Unmarshal(activeBid.Ext, activeExt); err != nil { + errs = append(errs, err) + } + + var bidType openrtb_ext.BidType + if activeExt.Prebid != nil && activeExt.Prebid.Type != "" { + bidType = activeExt.Prebid.Type + } else { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Failed to find native/banner/video mediaType \"%s\" ", activeBid.ImpID), + } + errs = append(errs, err) + continue + } + + typedBid = &adapters.TypedBid{Bid: &activeBid, BidType: bidType} + bidderResponse.Bids = append(bidderResponse.Bids, typedBid) + } + } + + return bidderResponse, nil +} diff --git a/adapters/adagio/adagio_test.go b/adapters/adagio/adagio_test.go new file mode 100644 index 00000000000..d5e25c7836d --- /dev/null +++ b/adapters/adagio/adagio_test.go @@ -0,0 +1,75 @@ +package adagio + +import ( + "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "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" +) + +func buildFakeBidRequest() openrtb2.BidRequest { + imp1 := openrtb2.Imp{ + ID: "some-impression-id", + Banner: &openrtb2.Banner{}, + Ext: json.RawMessage(`{"bidder": {"organizationId": "1000", "site": "site-name", "placement": "ban_atf"}}`), + } + + fakeBidRequest := openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{imp1}, + } + + return fakeBidRequest +} + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{ + Endpoint: "http://localhost/prebid_server"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adagiotest", bidder) +} + +func TestMakeRequests_NoGzip(t *testing.T) { + fakeBidRequest := buildFakeBidRequest() + fakeBidRequest.Test = 1 // Do not use Gzip in Test Mode. + + bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{ + Endpoint: "http://localhost/prebid_server"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + requestData, errs := bidder.MakeRequests(&fakeBidRequest, nil) + + assert.Nil(t, errs) + assert.Equal(t, 1, len(requestData)) + + body := &openrtb2.BidRequest{} + err := json.Unmarshal(requestData[0].Body, body) + assert.NoError(t, err, "Request body unmarshalling error should be nil") + assert.Equal(t, 1, len(body.Imp)) +} + +func TestMakeRequests_Gzip(t *testing.T) { + fakeBidRequest := buildFakeBidRequest() + + bidder, buildErr := Builder(openrtb_ext.BidderAdagio, config.Adapter{ + Endpoint: "http://localhost/prebid_server"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + requestData, errs := bidder.MakeRequests(&fakeBidRequest, nil) + assert.Empty(t, errs, "Got errors while making requests") + assert.Equal(t, []string{"gzip"}, requestData[0].Headers["Content-Encoding"]) +} diff --git a/adapters/adagio/adagiotest/exemplary/banner-web.json b/adapters/adagio/adagiotest/exemplary/banner-web.json new file mode 100644 index 00000000000..732b40d2c1d --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/banner-web.json @@ -0,0 +1,155 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adagio/adagiotest/exemplary/multi-format.json b/adapters/adagio/adagiotest/exemplary/multi-format.json new file mode 100644 index 00000000000..85e0be26131 --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/multi-format.json @@ -0,0 +1,165 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "multi-format-id", + "banner": { + "w":320, + "h":50 + }, + "video": { + "mimes": ["video\/mp4"], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "multi-format-id", + "banner": { + "w":320, + "h":50 + }, + "video": { + "mimes": ["video\/mp4"], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "multi-format-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "multi-format-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/adagio/adagiotest/exemplary/multi-imp.json b/adapters/adagio/adagiotest/exemplary/multi-imp.json new file mode 100644 index 00000000000..66af28ea559 --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/multi-imp.json @@ -0,0 +1,222 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "banner-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + }, + { + "id": "video-impression-id", + "video": { + "mimes": ["video\/mp4"], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "banner-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + }, + { + "id": "video-impression-id", + "video": { + "mimes": ["video\/mp4"], + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "banner-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + { + "id": "b4ae1b4e2fc24a4fb45540082e98e162", + "impid": "video-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "banner-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50, + "ext": { + "prebid": { + "type": "banner" + } + } + }, + "type": "banner" + }, + { + "bid": { + "id": "b4ae1b4e2fc24a4fb45540082e98e162", + "impid": "video-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 640, + "h": 480, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/adagio/adagiotest/exemplary/native-web.json b/adapters/adagio/adagiotest/exemplary/native-web.json new file mode 100644 index 00000000000..0085aaddc64 --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/native-web.json @@ -0,0 +1,151 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "2607:fb90:f27:4512:d800:cb23:a603:e245" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ipv6": "2607:fb90:f27:4512:d800:cb23:a603:e245", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "ext": { + "prebid": { + "type": "native" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "acuityads": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "ext": { + "prebid": { + "type": "native" + } + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/adagio/adagiotest/exemplary/video-web.json b/adapters/adagio/adagiotest/exemplary/video-web.json new file mode 100644 index 00000000000..353420ee962 --- /dev/null +++ b/adapters/adagio/adagiotest/exemplary/video-web.json @@ -0,0 +1,175 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video\/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video\/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "adagio": { + "outstream": { + "bvwUrl": "https://cool.io" + } + }, + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "adagio": { + "outstream": { + "bvwUrl": "https://cool.io" + } + }, + "prebid": { + "type": "video" + } + } + }, + "type":"video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/params/race/banner.json b/adapters/adagio/adagiotest/params/race/banner.json new file mode 100644 index 00000000000..66694466d84 --- /dev/null +++ b/adapters/adagio/adagiotest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" +} diff --git a/adapters/adagio/adagiotest/params/race/native.json b/adapters/adagio/adagiotest/params/race/native.json new file mode 100644 index 00000000000..4affd5b232c --- /dev/null +++ b/adapters/adagio/adagiotest/params/race/native.json @@ -0,0 +1,5 @@ +{ + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" +} diff --git a/adapters/adagio/adagiotest/params/race/video.json b/adapters/adagio/adagiotest/params/race/video.json new file mode 100644 index 00000000000..4affd5b232c --- /dev/null +++ b/adapters/adagio/adagiotest/params/race/video.json @@ -0,0 +1,5 @@ +{ + "organizationId": "1000", + "site": "site-name", + "placement": "in_article" +} diff --git a/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json b/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json new file mode 100644 index 00000000000..7b1267f6d50 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/response-miss-ext-bid-type.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "awesome-user" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "awesome-user" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50, + "ext": {} + } + ], + "seat": "adagio" + } + ], + "cur": "USD", + "ext": { + "responsetimemillis": { + "adagio": 154 + }, + "tmaxrequest": 1000 + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [] + } + ] +} diff --git a/adapters/adagio/adagiotest/supplemental/status-204.json b/adapters/adagio/adagiotest/supplemental/status-204.json new file mode 100644 index 00000000000..4d604a01fb9 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-204.json @@ -0,0 +1,62 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adagio/adagiotest/supplemental/status-400.json b/adapters/adagio/adagiotest/supplemental/status-400.json new file mode 100644 index 00000000000..093c5458c0a --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-400.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400, + "body": "bad request" + } + } + ], + "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/adagio/adagiotest/supplemental/status-401.json b/adapters/adagio/adagiotest/supplemental/status-401.json new file mode 100644 index 00000000000..a33aca203d0 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-401.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 401, + "body": "unauthorized" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 401. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/supplemental/status-403.json b/adapters/adagio/adagiotest/supplemental/status-403.json new file mode 100644 index 00000000000..59c5a9cbf6b --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-403.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 403, + "body": "forbidden" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 403. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/adagiotest/supplemental/status-500.json b/adapters/adagio/adagiotest/supplemental/status-500.json new file mode 100644 index 00000000000..0077d7457f9 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-500.json @@ -0,0 +1,68 @@ +{ + "mockBidRequest": { + "test": 1, + "debug": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500, + "body": "internal error" + } + } + ], + "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/adagio/adagiotest/supplemental/status-503.json b/adapters/adagio/adagiotest/supplemental/status-503.json new file mode 100644 index 00000000000..d2a52893de5 --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-503.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 503, + "body": "Service unavailable" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 503. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} diff --git a/adapters/adagio/adagiotest/supplemental/status-504.json b/adapters/adagio/adagiotest/supplemental/status-504.json new file mode 100644 index 00000000000..1b779d5c83f --- /dev/null +++ b/adapters/adagio/adagiotest/supplemental/status-504.json @@ -0,0 +1,67 @@ +{ + "mockBidRequest": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "http://localhost/prebid_server", + "body": { + "test": 1, + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "organizationId": "1000", + "site": "site-name", + "placement": "ban_atf" + } + } + } + ] + } + }, + "mockResponse": { + "status": 504, + "body": "gateway timeout" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 504. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/adagio/params_test.go b/adapters/adagio/params_test.go new file mode 100644 index 00000000000..ee8f702e451 --- /dev/null +++ b/adapters/adagio/params_test.go @@ -0,0 +1,57 @@ +package adagio + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf" }`, + `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "_unknown": "ban_atf"}`, + `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "features": {"a": "a", "b": "b"}}`, +} + +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.BidderAdagio, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Adagio params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"adCode": "string", "seatCode": 5, "originalPublisherid": "string"}`, + `{ "organizationId": "", "site": "", "placement": "" }`, + `{ "organizationId": "", "site": "2", "placement": "3" }`, + `{ "organizationId": "1", "site": "", "placement": "3" }`, + `{ "organizationId": "1", "site": "2", "placement": "" }`, + `{ "organizationId": 1, "site": "2", "placement": "3" }`, + `{ "organizationId": "1234", "site": "adagio-io", "placement": "ban_atf", "features": {"a": "a", "notastring": true}}`, +} + +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.BidderAdagio, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/adagio/usersync.go b/adapters/adagio/usersync.go new file mode 100644 index 00000000000..a7230feaada --- /dev/null +++ b/adapters/adagio/usersync.go @@ -0,0 +1,12 @@ +package adagio + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdagioSyncer(tmpl *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adagio", tmpl, adapters.SyncTypeRedirect) +} diff --git a/adapters/adagio/usersync_test.go b/adapters/adagio/usersync_test.go new file mode 100644 index 00000000000..cd4195e16df --- /dev/null +++ b/adapters/adagio/usersync_test.go @@ -0,0 +1,34 @@ +package adagio + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestAdagioSyncer(t *testing.T) { + syncURL := "https://mp.4dex.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%5BUID%5D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdagioSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "ANDFJDS", + }, + CCPA: ccpa.Policy{ + Consent: "1-YY", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://mp.4dex.io/sync?gdpr=0&gdpr_consent=ANDFJDS&us_privacy=1-YY&redirect=http%3A%2F%2Flocalhost%3A8000%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%5BUID%5D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/adapterstest/test_json.go b/adapters/adapterstest/test_json.go index 95319f1e328..1ec2fc08d86 100644 --- a/adapters/adapterstest/test_json.go +++ b/adapters/adapterstest/test_json.go @@ -7,8 +7,10 @@ import ( "regexp" "testing" + "github.com/mitchellh/copystructure" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" + "github.com/stretchr/testify/assert" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" @@ -55,6 +57,7 @@ func RunJSONBidderTest(t *testing.T, rootDir string, bidder adapters.Bidder) { runTests(t, fmt.Sprintf("%s/supplemental", rootDir), bidder, true, false, false) runTests(t, fmt.Sprintf("%s/amp", rootDir), bidder, true, true, false) runTests(t, fmt.Sprintf("%s/video", rootDir), bidder, false, false, true) + runTests(t, fmt.Sprintf("%s/videosupplemental", rootDir), bidder, true, false, true) } // runTests runs all the *.json files in a directory. If allowErrors is false, and one of the test files @@ -107,24 +110,10 @@ func runSpec(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidd } else if isVideoTest { reqInfo.PbsEntryPoint = "video" } - actualReqs, errs := bidder.MakeRequests(&spec.BidRequest, &reqInfo) - diffErrorLists(t, fmt.Sprintf("%s: MakeRequests", filename), errs, spec.MakeRequestErrors) - diffHttpRequestLists(t, filename, actualReqs, spec.HttpCalls) - bidResponses := make([]*adapters.BidderResponse, 0) + requests := testMakeRequestsImpl(t, filename, spec, bidder, &reqInfo) - var bidsErrs = make([]error, 0, len(spec.MakeBidsErrors)) - for i := 0; i < len(actualReqs); i++ { - thisBidResponse, theseErrs := bidder.MakeBids(&spec.BidRequest, spec.HttpCalls[i].Request.ToRequestData(t), spec.HttpCalls[i].Response.ToResponseData(t)) - bidsErrs = append(bidsErrs, theseErrs...) - bidResponses = append(bidResponses, thisBidResponse) - } - - diffErrorLists(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors) - - for i := 0; i < len(spec.BidResponses); i++ { - diffBidLists(t, filename, bidResponses[i], spec.BidResponses[i].Bids) - } + testMakeBidsImpl(t, filename, spec, bidder, requests) } type testSpec struct { @@ -194,8 +183,8 @@ type expectedBid struct { // // Marshalling the structs and then using a JSON-diff library isn't great either, since -// diffHttpRequests compares the actual http requests to the expected ones. -func diffHttpRequestLists(t *testing.T, filename string, actual []*adapters.RequestData, expected []httpCall) { +// assertMakeRequestsOutput compares the actual http requests to the expected ones. +func assertMakeRequestsOutput(t *testing.T, filename string, actual []*adapters.RequestData, expected []httpCall) { t.Helper() if len(expected) != len(actual) { @@ -206,7 +195,7 @@ func diffHttpRequestLists(t *testing.T, filename string, actual []*adapters.Requ } } -func diffErrorLists(t *testing.T, description string, actual []error, expected []testSpecExpectedError) { +func assertErrorList(t *testing.T, description string, actual []error, expected []testSpecExpectedError) { t.Helper() if len(expected) != len(actual) { @@ -227,10 +216,10 @@ func diffErrorLists(t *testing.T, description string, actual []error, expected [ } } -func diffBidLists(t *testing.T, filename string, response *adapters.BidderResponse, expected []expectedBid) { +func assertMakeBidsOutput(t *testing.T, filename string, bidderResponse *adapters.BidderResponse, expected []expectedBid) { t.Helper() - if (response == nil || len(response.Bids) == 0) != (len(expected) == 0) { + if (bidderResponse == nil || len(bidderResponse.Bids) == 0) != (len(expected) == 0) { if len(expected) == 0 { t.Fatalf("%s: expectedBidResponses indicated a nil response, but mockResponses supplied a non-nil response", filename) } @@ -239,17 +228,15 @@ func diffBidLists(t *testing.T, filename string, response *adapters.BidderRespon } // Expected nil response - give diffBids something to work with. - if response == nil { - response = new(adapters.BidderResponse) + if bidderResponse == nil { + bidderResponse = new(adapters.BidderResponse) } - actual := response.Bids - - if len(actual) != len(expected) { - t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(actual)) + if len(bidderResponse.Bids) != len(expected) { + t.Fatalf("%s: MakeBids returned wrong bid count. Expected %d, got %d", filename, len(expected), len(bidderResponse.Bids)) } - for i := 0; i < len(actual); i++ { - diffBids(t, fmt.Sprintf("%s: typedBid[%d]", filename, i), actual[i], &(expected[i])) + for i := 0; i < len(bidderResponse.Bids); i++ { + diffBids(t, fmt.Sprintf("%s: typedBid[%d]", filename, i), bidderResponse.Bids[i], &(expected[i])) } } @@ -331,3 +318,78 @@ func diffJson(t *testing.T, description string, actual []byte, expected []byte) } } } + +// testMakeRequestsImpl asserts the resulting values of the bidder's `MakeRequests()` implementation +// against the expected JSON-defined results and ensures we do not encounter data races in the process. +// To assert no data races happen we make use of: +// 1) A shallow copy of the unmarshalled openrtb2.BidRequest that will provide reference values to +// shared memory that we don't want the adapters' implementation of `MakeRequests()` to modify. +// 2) A deep copy that will preserve the original values of all the fields. This copy remains untouched +// by the adapters' processes and serves as reference of what the shared memory values should still +// be after the `MakeRequests()` call. +func testMakeRequestsImpl(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, reqInfo *adapters.ExtraRequestInfo) []*adapters.RequestData { + t.Helper() + + deepBidReqCopy, shallowBidReqCopy, err := getDataRaceTestCopies(&spec.BidRequest) + assert.NoError(t, err, "Could not create request copies. %s", filename) + + // Run MakeRequests + requests, errs := bidder.MakeRequests(&spec.BidRequest, reqInfo) + + // Compare MakeRequests actual output versus expected values found in JSON file + assertErrorList(t, fmt.Sprintf("%s: MakeRequests", filename), errs, spec.MakeRequestErrors) + assertMakeRequestsOutput(t, filename, requests, spec.HttpCalls) + + // Assert no data races occur using original bidRequest copies of references and values + assert.Equal(t, deepBidReqCopy, shallowBidReqCopy, "Data race found. Test: %s", filename) + + return requests +} + +// getDataRaceTestCopies returns a deep copy and a shallow copy of the original bidRequest that will get +// compared to verify no data races occur. +func getDataRaceTestCopies(original *openrtb2.BidRequest) (*openrtb2.BidRequest, *openrtb2.BidRequest, error) { + cpy, err := copystructure.Copy(original) + if err != nil { + return nil, nil, err + } + deepReqCopy := cpy.(*openrtb2.BidRequest) + + shallowReqCopy := *original + + // Prebid Server core makes shallow copies of imp elements and adapters are allowed to make changes + // to them. Therefore, we need shallow copies of Imp elements here so our test replicates that + // functionality and only fail when actual shared momory gets modified. + if original.Imp != nil { + shallowReqCopy.Imp = make([]openrtb2.Imp, len(original.Imp)) + copy(shallowReqCopy.Imp, original.Imp) + } + + return deepReqCopy, &shallowReqCopy, nil +} + +// testMakeBidsImpl asserts the results of the bidder MakeBids implementation against the expected JSON-defined results +func testMakeBidsImpl(t *testing.T, filename string, spec *testSpec, bidder adapters.Bidder, makeRequestsOut []*adapters.RequestData) { + t.Helper() + + bidResponses := make([]*adapters.BidderResponse, 0) + var bidsErrs = make([]error, 0, len(spec.MakeBidsErrors)) + + // We should have as many bids as number of adapters.RequestData found in MakeRequests output + for i := 0; i < len(makeRequestsOut); i++ { + // Run MakeBids with JSON refined spec.HttpCalls info that was asserted to match MakeRequests + // output inside testMakeRequestsImpl + thisBidResponse, theseErrs := bidder.MakeBids(&spec.BidRequest, spec.HttpCalls[i].Request.ToRequestData(t), spec.HttpCalls[i].Response.ToResponseData(t)) + + bidsErrs = append(bidsErrs, theseErrs...) + bidResponses = append(bidResponses, thisBidResponse) + } + + // Assert actual errors thrown by MakeBids implementation versus expected JSON-defined spec.MakeBidsErrors + assertErrorList(t, fmt.Sprintf("%s: MakeBids", filename), bidsErrs, spec.MakeBidsErrors) + + // Assert MakeBids implementation BidResponses with expected JSON-defined spec.BidResponses[i].Bids + for i := 0; i < len(spec.BidResponses); i++ { + assertMakeBidsOutput(t, filename, bidResponses[i], spec.BidResponses[i].Bids) + } +} diff --git a/adapters/adf/adf.go b/adapters/adf/adf.go new file mode 100644 index 00000000000..f73e23aa07d --- /dev/null +++ b/adapters/adf/adf.go @@ -0,0 +1,129 @@ +package adf + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/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 Adf adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + var errors []error + var validImps = make([]openrtb2.Imp, 0, len(request.Imp)) + + for _, imp := range request.Imp { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + var adfImpExt openrtb_ext.ExtImpAdf + if err := json.Unmarshal(bidderExt.Bidder, &adfImpExt); err != nil { + errors = append(errors, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + imp.TagID = adfImpExt.MasterTagID.String() + validImps = append(validImps, imp) + } + + request.Imp = validImps + + 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(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.", + } + return nil, []error{err} + } + + if responseData.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d.", 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)) + bidResponse.Currency = response.Cur + var errors []error + for _, seatBid := range response.SeatBid { + for i, bid := range seatBid.Bid { + bidType, err := getMediaTypeForImp(bid.ImpID, request.Imp) + if err != nil { + errors = append(errors, err) + continue + } + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &seatBid.Bid[i], + BidType: bidType, + }) + } + } + + return bidResponse, errors +} + +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 + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo, nil + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative, nil + } + } + } + + return "", &errortypes.BadInput{ + Message: fmt.Sprintf("Failed to find supported impression \"%s\" mediatype", impID), + } +} diff --git a/adapters/adf/adf_test.go b/adapters/adf/adf_test.go new file mode 100644 index 00000000000..ab348db36ae --- /dev/null +++ b/adapters/adf/adf_test.go @@ -0,0 +1,20 @@ +package adf + +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.BidderAdf, config.Adapter{ + Endpoint: "https://adx.adform.net/adx/openrtb"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "adftest", bidder) +} diff --git a/adapters/adf/adftest/exemplary/multi-format.json b/adapters/adf/adftest/exemplary/multi-format.json new file mode 100644 index 00000000000..6b917658cdc --- /dev/null +++ b/adapters/adf/adftest/exemplary/multi-format.json @@ -0,0 +1,118 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + } + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "mid": "828783" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "tagid": "828782" + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "mid": "828783" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828783" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{video xml}", + "adomain": [], + "crid": "test-creative-id-1" + }] + }, { + "bid": [{ + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{banner html}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2" + }] + }], + "cur": "EUR" + } + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{video xml}", + "crid": "test-creative-id-1" + }, + "type": "video" + }, { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{banner html}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2" + }, + "type": "banner" + }] + }] +} diff --git a/adapters/adf/adftest/exemplary/multi-native.json b/adapters/adf/adftest/exemplary/multi-native.json new file mode 100644 index 00000000000..40bd7e15773 --- /dev/null +++ b/adapters/adf/adftest/exemplary/multi-native.json @@ -0,0 +1,108 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "native": { + "request": "{json string 1}", + "ver": "1.2" + } + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "mid": "828783" + } + }, + "native": { + "request": "{json string 2}", + "ver": "1.2" + } + }] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id-1", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "native": { + "request": "{json string 1}", + "ver": "1.2" + }, + "tagid": "828782" + }, { + "id": "test-imp-id-2", + "ext": { + "bidder": { + "mid": "828783" + } + }, + "native": { + "request": "{json string 2}", + "ver": "1.2" + }, + "tagid": "828783" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{json response string 1}", + "adomain": [], + "crid": "test-creative-id-1" + }, { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{json response string 2}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2" + }] + }], + "cur": "EUR" + } + } + }], + "expectedBidResponses": [{ + "currency": "EUR", + "bids": [{ + "bid": { + "id": "test-bid-id-1", + "impid": "test-imp-id-1", + "price": 10, + "adm": "{json response string 1}", + "crid": "test-creative-id-1" + }, + "type": "native" + }, { + "bid": { + "id": "test-bid-id-2", + "impid": "test-imp-id-2", + "price": 2, + "adm": "{json response string 2}", + "adomain": [ "ad-domain" ], + "crid": "test-creative-id-2" + }, + "type": "native" + }] + }] +} diff --git a/adapters/adf/adftest/exemplary/single-banner.json b/adapters/adf/adftest/exemplary/single-banner.json new file mode 100644 index 00000000000..5fdfe8af7c8 --- /dev/null +++ b/adapters/adf/adftest/exemplary/single-banner.json @@ -0,0 +1,95 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "tagid": "828782" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "adomain": [ "test.com" ], + "crid": "test-creative-id" + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{banner html}", + "crid": "test-creative-id", + "adomain": [ "test.com" ] + }, + "type": "banner" + } + ] + }] +} diff --git a/adapters/adf/adftest/exemplary/single-native.json b/adapters/adf/adftest/exemplary/single-native.json new file mode 100644 index 00000000000..909f8cec9a7 --- /dev/null +++ b/adapters/adf/adftest/exemplary/single-native.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "native": { + "request": "{json string}", + "ver": "1.2" + }, + "tagid": "828782" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "adomain": [], + "crid": "test-creative-id" + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{json response string}", + "crid": "test-creative-id" + }, + "type": "native" + } + ] + }] +} diff --git a/adapters/adf/adftest/exemplary/single-video.json b/adapters/adf/adftest/exemplary/single-video.json new file mode 100644 index 00000000000..ebe4c6f1b38 --- /dev/null +++ b/adapters/adf/adftest/exemplary/single-video.json @@ -0,0 +1,93 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + } + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "ext": { + "bidder": { + "mid": "828782" + } + }, + "video": { + "mimes": [ + "video/mp4" + ], + "placement": 1 + }, + "tagid": "828782" + }], + "site": { + "publisher": { + "id": "1" + }, + "page": "some-page-url" + }, + "device": { + "w": 1920, + "h": 800 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id" + }] + }], + "cur": "USD" + } + } + }], + "expectedBidResponses": [{ + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id" + }, + "type": "video" + } + ] + }] +} diff --git a/adapters/adf/adftest/params/race/native.json b/adapters/adf/adftest/params/race/native.json new file mode 100644 index 00000000000..79535d85da4 --- /dev/null +++ b/adapters/adf/adftest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "mid": "828782" +} diff --git a/adapters/adf/adftest/supplemental/bad-request.json b/adapters/adf/adftest/supplemental/bad-request.json new file mode 100644 index 00000000000..7424eae4656 --- /dev/null +++ b/adapters/adf/adftest/supplemental/bad-request.json @@ -0,0 +1,48 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher.", + "comparison": "literal" + } + ] +} diff --git a/adapters/adf/adftest/supplemental/empty-response.json b/adapters/adf/adftest/supplemental/empty-response.json new file mode 100644 index 00000000000..b2d6eab97fe --- /dev/null +++ b/adapters/adf/adftest/supplemental/empty-response.json @@ -0,0 +1,42 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} diff --git a/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json b/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json new file mode 100644 index 00000000000..2f01e7eaae9 --- /dev/null +++ b/adapters/adf/adftest/supplemental/invalid-imp-mediatype.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "audio": {}, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "audio": { + "mimes": null + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "bid": [{ + "id": "test-bid-id", + "impid": "test-imp-id", + "price": 10, + "adm": "{vast xml}", + "crid": "test-creative-id" + }] + }], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Failed to find supported impression \"test-imp-id\" mediatype", + "comparison": "literal" + } + ] +} diff --git a/adapters/adf/adftest/supplemental/nobid-response.json b/adapters/adf/adftest/supplemental/nobid-response.json new file mode 100644 index 00000000000..ec559aa7468 --- /dev/null +++ b/adapters/adf/adftest/supplemental/nobid-response.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": null, + "bidid": null, + "cur": null + } + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/adf/adftest/supplemental/server-error.json b/adapters/adf/adftest/supplemental/server-error.json new file mode 100644 index 00000000000..15604ad2189 --- /dev/null +++ b/adapters/adf/adftest/supplemental/server-error.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 500, + "body": "Server error" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500.", + "comparison": "literal" + } + ] +} diff --git a/adapters/adf/adftest/supplemental/unparsable-response.json b/adapters/adf/adftest/supplemental/unparsable-response.json new file mode 100644 index 00000000000..091f05cea22 --- /dev/null +++ b/adapters/adf/adftest/supplemental/unparsable-response.json @@ -0,0 +1,49 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "native": { + "request": "" + }, + "ext": { + "bidder": { + "mid": 12345 + } + } + }] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://adx.adform.net/adx/openrtb", + "body": { + "id": "test-request-id", + "imp": [{ + "ext": { + "bidder": { + "mid": 12345 + } + }, + "id": "test-imp-id", + "native": { + "request": "" + }, + "tagid": "12345" + }] + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/adf/params_test.go b/adapters/adf/params_test.go new file mode 100644 index 00000000000..779d3fb6f2d --- /dev/null +++ b/adapters/adf/params_test.go @@ -0,0 +1,57 @@ +package adf + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/adf.json +// +// These also validate the format of the external API: request.imp[i].ext.adf + +// TestValidParams makes sure that the adform 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.BidderAdf, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected adform params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the adform 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.BidderAdf, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"mid":123}`, + `{"mid":"123"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{"notmid":"123"}`, + `{"mid":"placementID"}`, +} diff --git a/adapters/adf/usersync.go b/adapters/adf/usersync.go new file mode 100644 index 00000000000..e3bd11422c4 --- /dev/null +++ b/adapters/adf/usersync.go @@ -0,0 +1,12 @@ +package adf + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewAdfSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("adf", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/adf/usersync_test.go b/adapters/adf/usersync_test.go new file mode 100644 index 00000000000..693e6418444 --- /dev/null +++ b/adapters/adf/usersync_test.go @@ -0,0 +1,31 @@ +package adf + +import ( + "testing" + "text/template" + + "github.com/stretchr/testify/assert" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" +) + +func TestAdfSyncer(t *testing.T) { + syncURL := "https://cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewAdfSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "A", + Consent: "B", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://cm.adform.net?return_url=localhost%2Fsetuid%3Fbidder%3Dadf%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/adform/adformtest/supplemental/user-nil.json b/adapters/adform/adformtest/supplemental/user-nil.json index 5f02fe85971..96ea1dbff71 100644 --- a/adapters/adform/adformtest/supplemental/user-nil.json +++ b/adapters/adform/adformtest/supplemental/user-nil.json @@ -31,12 +31,7 @@ }, "user": { "ext": { - "consent": "abc2", - "digitrust": { - "ID": "digitrustId", - "KeyV": 1, - "Pref": 0 - } + "consent": "abc2" } } }, diff --git a/adapters/admixer/admixer.go b/adapters/admixer/admixer.go index ec49950a17e..5008b0ce5c6 100644 --- a/adapters/admixer/admixer.go +++ b/adapters/admixer/admixer.go @@ -100,14 +100,17 @@ func preprocess(imp *openrtb2.Imp) error { } //don't use regexp due to possible performance reduce - if len(admixerExt.ZoneId) != 36 { + if len(admixerExt.ZoneId) < 32 || len(admixerExt.ZoneId) > 36 { return &errortypes.BadInput{ Message: "ZoneId must be UUID/GUID", } } imp.TagID = admixerExt.ZoneId - imp.BidFloor = admixerExt.CustomBidFloor + + if imp.BidFloor == 0 && admixerExt.CustomBidFloor > 0 { + imp.BidFloor = admixerExt.CustomBidFloor + } imp.Ext = nil diff --git a/adapters/admixer/admixertest/exemplary/optional-params.json b/adapters/admixer/admixertest/exemplary/optional-params.json index 8ef112bbdb5..b93aa9c8154 100644 --- a/adapters/admixer/admixertest/exemplary/optional-params.json +++ b/adapters/admixer/admixertest/exemplary/optional-params.json @@ -44,6 +44,76 @@ } } } + }, + { + "id": "test-imp-id", + "bidfloor": 0.5, + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + } + }, + { + "id": "test-imp-id", + "bidfloor": 0.5, + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + }, + "customFloor": 0.9 + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "customFloor": 0.9, + "zone": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + } } ] }, @@ -92,6 +162,69 @@ ] } } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "bidfloor": 0.5, + "ext": { + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "bidfloor": 0.5, + "ext": { + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } + }, + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "tagid": "2eb6bd58-865c-47ce-af7f-a918108c3fd2", + "bidfloor": 0.9, + "ext": { + "customParams": { + "foo": [ + "bar", + "baz" + ] + } + } } ] } diff --git a/adapters/admixer/params_test.go b/adapters/admixer/params_test.go index 71cccb6a3da..11f3feb0657 100644 --- a/adapters/admixer/params_test.go +++ b/adapters/admixer/params_test.go @@ -44,6 +44,8 @@ var validParams = []string{ `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customFloor": 0.1}`, `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customParams": {"foo": "bar"}}`, `{"zone": "9ff668a2-4122-462e-aaf8-36ea3a54ba21", "customFloor": 0.1, "customParams": {"foo": ["bar", "baz"]}}`, + `{"zone": "9FF668A24122462EAAF836EA3A54BA21"}`, + `{"zone": "9FF668A24122462EAAF836EA3A54BA212"}`, } var invalidParams = []string{ @@ -54,4 +56,6 @@ var invalidParams = []string{ `{"zone": "123", "customFloor": "0.1"}`, `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customFloor": -0.1}`, `{"zone": "9FF668A2-4122-462E-AAF8-36EA3A54BA21", "customParams": "foo: bar"}`, + `{"zone": "9FF668A24122462EAAF836EA3A54BA2"}`, + `{"zone": "9FF668A24122462EAAF836EA3A54BA2112336"}`, } diff --git a/adapters/adocean/adocean.go b/adapters/adocean/adocean.go index 635cba8c9bc..6a0eb892be4 100644 --- a/adapters/adocean/adocean.go +++ b/adapters/adocean/adocean.go @@ -21,7 +21,7 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const adapterVersion = "1.1.0" +const adapterVersion = "1.2.0" const maxUriLength = 8000 const measurementCode = ` ", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0 + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13" + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/banner.json b/adapters/e_volution/evolutiontest/exemplary/banner.json new file mode 100644 index 00000000000..68fda4907e2 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/banner.json @@ -0,0 +1,174 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "bidid": "880184da7ec02b1f42802acb46b63b3c", + "seatbid": [{ + "bid": [{ + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "banner" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "6ca51ef38eb42820d27503ee96b634b5", + "impid": "126365", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=880184da7ec02b1f42802acb46b63b3c&price=${AUCTION_PRICE}", + "adm": "\"\"\"\"\"\"", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/native.json b/adapters/e_volution/evolutiontest/exemplary/native.json new file mode 100644 index 00000000000..724f55f6b8b --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/native.json @@ -0,0 +1,181 @@ +{ + "mockBidRequest": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "tmax": 500, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "js": 1, + "ip": "79.26.58.249", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "os": "Mac OS", + "language": "en", + "geo": { + "country": "ITA" + } + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "site": { + "id": "57078628", + "domain": "www.affaritaliani.it", + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "name": "www.affaritaliani.it", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + }, + "cat": [ + "IAB12" + ] + }, + "publisher": {}, + "cur": [ + "USD" + ], + "bcat": [ + "IAB12-2", + "IAB9-7" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.1, + "bidfloorcur": "USD", + "instl": 0, + "secure": 1, + "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": { + "key": "test_native" + } + } + }], + "regs": { + "ext": { + "gdpr": 1 + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "imp": [{ + "id": "1", + "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" + }, + "bidfloor": 0.1, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "site": { + "id": "57078628", + "name": "www.affaritaliani.it", + "domain": "www.affaritaliani.it", + "cat": ["IAB12"], + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "ITA" + }, + "dnt": 0, + "ip": "79.26.58.249", + "devicetype": 2, + "os": "Mac OS", + "js": 1, + "language": "en" + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "bcat": ["IAB12-2", "IAB9-7"], + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [{ + "bid": [{ + "id": "1af875cae46410c18e4d8b1fcc909e6c", + "impid": "1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "adm": "{\"link\":{\"url\":\"https://e-volution.ai\"},\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"text\":\"Viralize test\"}},{\"id\":2,\"required\":1,\"img\":{}},{\"id\":4,\"data\":{\"value\":\"3$\"}},{\"id\":5,\"data\":{\"value\":\"2$\"}},{\"id\":6,\"data\":{\"value\":\"viralize\"}}],\"ver\":\"1.1\",\"imptrackers\":[\"https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=359da97d0384d8a14767029c18fd840d&cp=${AUCTION_PRICE}\",\"https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=1&gdpr_consent=CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA&ccpa_consent=\"]}", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "native" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1af875cae46410c18e4d8b1fcc909e6c", + "impid": "1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=359da97d0384d8a14767029c18fd840d&price=${AUCTION_PRICE}", + "adm": "{\"link\":{\"url\":\"https://e-volution.ai\"},\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"text\":\"Viralize test\"}},{\"id\":2,\"required\":1,\"img\":{}},{\"id\":4,\"data\":{\"value\":\"3$\"}},{\"id\":5,\"data\":{\"value\":\"2$\"}},{\"id\":6,\"data\":{\"value\":\"viralize\"}}],\"ver\":\"1.1\",\"imptrackers\":[\"https://us-east-edge1.e-volution.ai/?c=rtb&m=i&key=359da97d0384d8a14767029c18fd840d&cp=${AUCTION_PRICE}\",\"https://us-east-edge1.e-volution.ai/?c=rtb&m=sync&gdpr=1&gdpr_consent=CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA&ccpa_consent=\"]}", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/exemplary/video.json b/adapters/e_volution/evolutiontest/exemplary/video.json new file mode 100644 index 00000000000..f7a03146918 --- /dev/null +++ b/adapters/e_volution/evolutiontest/exemplary/video.json @@ -0,0 +1,200 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "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 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "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 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "bidid": "ccbd63285c0e7b69602d90319bda6be4", + "seatbid": [{ + "bid": [{ + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "w": 0, + "h": 0, + "ext": { + "mediaType": "video" + } + }], + "seat": "1" + }], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7f6c6d6ba432059a5305815a8d740283", + "impid": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "price": 0.1, + "nurl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=nurl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "burl": "http://us-east-edge1.e-volution.ai/?c=rtb&m=burl&auctionId=ccbd63285c0e7b69602d90319bda6be4&price=${AUCTION_PRICE}", + "adm": "846500:00:16", + "adomain": ["test.com"], + "cat": ["IAB24"], + "cid": "1IP31", + "crid": "1KS13", + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/params/race/banner.json b/adapters/e_volution/evolutiontest/params/race/banner.json new file mode 100644 index 00000000000..0a04c95b072 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "key": "test_banner" +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/params/race/native.json b/adapters/e_volution/evolutiontest/params/race/native.json new file mode 100644 index 00000000000..032b9dd56d8 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "key": "test_native" +} diff --git a/adapters/e_volution/evolutiontest/params/race/video.json b/adapters/e_volution/evolutiontest/params/race/video.json new file mode 100644 index 00000000000..87071003920 --- /dev/null +++ b/adapters/e_volution/evolutiontest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "key": "test_video" +} diff --git a/adapters/e_volution/evolutiontest/supplemental/bad-response.json b/adapters/e_volution/evolutiontest/supplemental/bad-response.json new file mode 100644 index 00000000000..75f3cb455af --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/bad-response.json @@ -0,0 +1,158 @@ +{ + "mockBidRequest": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "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 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "6d773e60ab245243a04217d5f7e9e786", + "imp": [{ + "id": "bfb2dd46a5e63a0a84711c20fd8ce9e1", + "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 + }, + "bidfloor": 0.40404, + "bidfloorcur": "USD", + "secure": 1, + "exp": 3600, + "ext": { + "bidder": { + "key": "test_video" + } + } + }], + "site": { + "id": "1c94d85ea1a55ebf3325a82935240614", + "domain": "kiwilimon.com", + "cat": ["IAB1", "IAB2", "IAB3", "IAB4", "IAB5", "IAB6", "IAB7", "IAB8", "IAB9", "IAB10", "IAB11", "IAB13", "IAB14", "IAB15", "IAB17", "IAB18"], + "page": "https://kiwilimon.com/", + "publisher": { + "id": "866d6b3a4a483ed4a454a1acaa588852" + }, + "ext": { + "amp": 0 + } + }, + "device": { + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36", + "geo": { + "lat": 20.6668, + "lon": -103.3918, + "accuracy": 5, + "country": "MEX", + "region": "JAL", + "zip": "45186" + }, + "dnt": 0, + "lmt": 0, + "ip": "187.152.119.145", + "devicetype": 2, + "os": "Windows", + "osv": "10", + "carrier": "Telmex" + }, + "test": 1, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "source": { + "tid": "42da74bc-88c5-40ea-8187-9070a34e8e10" + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad response, json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json b/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..c9a103aea39 --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/empty-seatbid.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "tmax": 500, + "at": 1, + "device": { + "dnt": 0, + "devicetype": 2, + "js": 1, + "ip": "79.26.58.249", + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "os": "Mac OS", + "language": "en", + "geo": { + "country": "ITA" + } + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "site": { + "id": "57078628", + "domain": "www.affaritaliani.it", + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "name": "www.affaritaliani.it", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + }, + "cat": [ + "IAB12" + ] + }, + "publisher": {}, + "cur": [ + "USD" + ], + "bcat": [ + "IAB12-2", + "IAB9-7" + ], + "imp": [{ + "id": "1", + "bidfloor": 0.1, + "bidfloorcur": "USD", + "instl": 0, + "secure": 1, + "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": { + "key": "test_native" + } + } + }], + "regs": { + "ext": { + "gdpr": 1 + } + } + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "imp": [{ + "id": "1", + "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" + }, + "bidfloor": 0.1, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_native" + } + } + }], + "site": { + "id": "57078628", + "name": "www.affaritaliani.it", + "domain": "www.affaritaliani.it", + "cat": ["IAB12"], + "page": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html", + "ref": "https://www.affaritaliani.it/cronache/gestione-covid-proteste-di-imprenditori-sanitari-ma-ignorate-da-ogni-media-742138.html?refresh_ce", + "publisher": { + "id": "439696", + "name": "www.affaritaliani.it" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "geo": { + "country": "ITA" + }, + "dnt": 0, + "ip": "79.26.58.249", + "devicetype": 2, + "os": "Mac OS", + "js": 1, + "language": "en" + }, + "user": { + "id": "l1eHS1lZE41a", + "ext": { + "consent": "CPCfkEbPCfkEbERACAENBbCsAP_AAH_AAAAAHetf_X_fb39j-_59_9t0eY1f9_7_v-wzjhfds-8NyPvX_L8X42M7PF36pq4KuR4Eu3LBIQFlHOHUTUmw6okVrTPsak2Mr7NKJ7LEinMbe2dYGHtfn91TuZKYr_7s_9fz__-v_v__79f3r-3_3_vp9X---_e_V399xKB3QBJhqXwAWYljgSTRpVCiBCFYSHQCgAooBhaJrCAhYFOyuAj1BAwAQGoCMCIEGIKMWAQAAAQBIREBIAeCARAEQCAAEAKkBCAAiYBBYAWBgEAAoBoWIEUAQgSEGRwVHKYEBEi0UE8lYAlF3sYYQhlFgBQKP6KjARKEAAAA.YAAAAAAAAAAA" + } + }, + "at": 1, + "tmax": 500, + "cur": ["USD"], + "bcat": ["IAB12-2", "IAB9-7"], + "regs": { + "ext": { + "gdpr": 1 + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "a1580f2f-be6d-11eb-a150-d094662c1c35", + "bidid": "359da97d0384d8a14767029c18fd840d", + "seatbid": [], + "cur": "USD" + } + } + }], + + "expectedMakeBidsErrors": [ + { + "value": "Empty seatbid", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-204.json b/adapters/e_volution/evolutiontest/supplemental/status-204.json new file mode 100644 index 00000000000..85e89873fd2 --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-204.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "test_banner" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-400.json b/adapters/e_volution/evolutiontest/supplemental/status-400.json new file mode 100644 index 00000000000..b26e827200e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-400.json @@ -0,0 +1,133 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 400, + "body": "The Key has a different ad format" + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Bad Request. \"The Key has a different ad format\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/status-503.json b/adapters/e_volution/evolutiontest/supplemental/status-503.json new file mode 100644 index 00000000000..0f289ea8d3e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/status-503.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 503 + } + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json b/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..5d0df32383e --- /dev/null +++ b/adapters/e_volution/evolutiontest/supplemental/unexpected-status.json @@ -0,0 +1,132 @@ +{ + "mockBidRequest": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "secure": 1, + "bidfloor": 0.20, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250, + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "country": "MEX", + "region": "CMX", + "city": "Mexico City", + "lat": 19.42, + "lon": -99.1663 + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "regs": { + "ext": { + "gdpr": 0 + } + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://service.e-volution.ai/pbserver", + "body": { + "id": "d910ceef496ff2d746176506e296f4b3_z53356_a126365", + "imp": [{ + "id": "126365", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "bidfloor": 0.2, + "bidfloorcur": "USD", + "secure": 1, + "ext": { + "bidder": { + "key": "45352" + } + } + }], + "site": { + "id": "12", + "domain": "milano.repubblica.it", + "cat": ["IAB12"], + "page": "https://milano.repubblica.it", + "publisher": { + "id": "45672" + } + }, + "device": { + "ua": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1", + "geo": { + "lat": 19.42, + "lon": -99.1663, + "country": "MEX", + "region": "CMX", + "city": "Mexico City" + }, + "ip": "189.146.118.247", + "devicetype": 4, + "language": "it" + }, + "user": { + "id": "460ecf1d41fa6656c4b892fd84e61ff8c2ab5ad5", + "ext": { + "consent": "" + } + }, + "regs": { + "ext": { + "gdpr": 0 + } + } + } + }, + "mockResponse": { + "status": 401 + } + } + ], + + "expectedMakeBidsErrors": [ + { + "value": "Something went wrong, please contact your Account Manager. Status Code: [ 401 ] ", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/e_volution/params_test.go b/adapters/e_volution/params_test.go new file mode 100644 index 00000000000..2d3602fd72b --- /dev/null +++ b/adapters/e_volution/params_test.go @@ -0,0 +1,49 @@ +package evolution + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "key": "24" }`, +} + +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.BidderEVolution, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected evolution params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, +} + +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.BidderEVolution, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/e_volution/usersync.go b/adapters/e_volution/usersync.go new file mode 100644 index 00000000000..f22784d018b --- /dev/null +++ b/adapters/e_volution/usersync.go @@ -0,0 +1,12 @@ +package evolution + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewEvolutionSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("e_volution", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/e_volution/usersync_test.go b/adapters/e_volution/usersync_test.go new file mode 100644 index 00000000000..d7a3eba5f0a --- /dev/null +++ b/adapters/e_volution/usersync_test.go @@ -0,0 +1,33 @@ +package evolution + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNewEvolutionSyncer(t *testing.T) { + syncURL := "https://sync.test.com/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewEvolutionSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "allGdpr", + }, + CCPA: ccpa.Policy{ + Consent: "1---", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://sync.test.com/pbserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/inmobi/inmobi.go b/adapters/inmobi/inmobi.go index a23472e8892..63baa8a4ba5 100644 --- a/adapters/inmobi/inmobi.go +++ b/adapters/inmobi/inmobi.go @@ -124,6 +124,9 @@ func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { if imp.Video != nil { mediaType = openrtb_ext.BidTypeVideo } + if imp.Native != nil { + mediaType = openrtb_ext.BidTypeNative + } break } } diff --git a/adapters/inmobi/inmobitest/exemplary/simple-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-app-banner.json similarity index 100% rename from adapters/inmobi/inmobitest/exemplary/simple-banner.json rename to adapters/inmobi/inmobitest/exemplary/simple-app-banner.json diff --git a/adapters/inmobi/inmobitest/exemplary/simple-app-native.json b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json new file mode 100644 index 00000000000..3a5bfd38412 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-app-native.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1618933962959" + } + }, + "native": { + "request": "{\"ver\":\"1.2\",\"context\":2,\"contextsubtype\":20,\"plcmttype\":11,\"plcmtcnt\":1,\"aurlsupport\":1,\"durlsupport\":1,\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"len\":140}},{\"id\":128,\"required\":0,\"img\":{\"wmin\":836,\"hmin\":627,\"type\":3}}]}" + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "app": { + "bundle": "com.example.app" + }, + "id": "req-id", + "device": { + "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d", + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1618933962959" + } + }, + "native": { + "request": "{\"ver\":\"1.2\",\"context\":2,\"contextsubtype\":20,\"plcmttype\":11,\"plcmtcnt\":1,\"aurlsupport\":1,\"durlsupport\":1,\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"len\":140}},{\"id\":128,\"required\":0,\"img\":{\"wmin\":836,\"hmin\":627,\"type\":3}}]}" + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": "native-json", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": "native-json", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "native" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/exemplary/simple-video.json b/adapters/inmobi/inmobitest/exemplary/simple-app-video.json similarity index 100% rename from adapters/inmobi/inmobitest/exemplary/simple-video.json rename to adapters/inmobi/inmobitest/exemplary/simple-app-video.json diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json new file mode 100644 index 00000000000..131249ba8a1 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-web-banner.json @@ -0,0 +1,105 @@ +{ + "mockBidRequest": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1617941157285" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1617941157285" + } + }, + "banner": { + "w": 320, + "h": 50 + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": "bannerhtml", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": "bannerhtml", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "banner" + }] + }] +} diff --git a/adapters/inmobi/inmobitest/exemplary/simple-web-video.json b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json new file mode 100644 index 00000000000..3aed605f416 --- /dev/null +++ b/adapters/inmobi/inmobitest/exemplary/simple-web-video.json @@ -0,0 +1,109 @@ +{ + "mockBidRequest": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1621323101291" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "https://api.w.inmobi.com/showad/openrtb/bidder/prebid", + "body": { + "site": { + "page": "https://www.inmobi.com" + }, + "id": "req-id", + "device": { + "ip": "1.1.1.1", + "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36" + }, + "imp": [ + { + "ext": { + "bidder": { + "plc": "1621323101291" + } + }, + "video": { + "w": 640, + "h": 360, + "mimes": ["video/mp4"] + }, + "id": "imp-id" + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "req-id", + "seatbid": [ + { + "bid": [ + { + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + }, + "nurl": "https://some.event.url/params", + "crid": "123456789", + "adomain": [], + "price": 2.0, + "id": "1234", + "adm": " ", + "impid": "imp-id" + } + ] + } + ] + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "1234", + "impid": "imp-id", + "price": 2.0, + "adm": " ", + "crid": "123456789", + "nurl": "https://some.event.url/params", + "ext": { + "prebid": { + "meta": { + "networkName": "inmobi" + } + } + } + }, + "type": "video" + }] + }] +} + + diff --git a/adapters/inmobi/usersync.go b/adapters/inmobi/usersync.go new file mode 100644 index 00000000000..7f022e3c5d0 --- /dev/null +++ b/adapters/inmobi/usersync.go @@ -0,0 +1,12 @@ +package inmobi + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewInmobiSyncer(template *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("inmobi", template, adapters.SyncTypeRedirect) +} diff --git a/adapters/interactiveoffers/interactiveoffers.go b/adapters/interactiveoffers/interactiveoffers.go new file mode 100644 index 00000000000..fd4cd5807f5 --- /dev/null +++ b/adapters/interactiveoffers/interactiveoffers.go @@ -0,0 +1,79 @@ +package interactiveoffers + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/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 +} + +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)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for _, bid := range seatBid.Bid { + bid := bid // pin https://github.com/kyoh86/scopelint#whats-this + b := &adapters.TypedBid{ + Bid: &bid, + BidType: openrtb_ext.BidTypeBanner, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +// Builder builds a new instance of the Interactiveoffers adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/interactiveoffers/interactiveoffers_test.go b/adapters/interactiveoffers/interactiveoffers_test.go new file mode 100644 index 00000000000..5746f123b41 --- /dev/null +++ b/adapters/interactiveoffers/interactiveoffers_test.go @@ -0,0 +1,20 @@ +package interactiveoffers + +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.BidderInteractiveoffers, config.Adapter{ + Endpoint: "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "interactiveofferstest", bidder) +} diff --git a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json new file mode 100644 index 00000000000..946289e5401 --- /dev/null +++ b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodmultiplebidrequest.json @@ -0,0 +1,136 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + },{ + "id": "test-imp-id-2", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + },{ + "id": "test-imp-id-2", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "interactiveoffers", + "bid": [{ + "id": "randomid", + "impid": "test-imp-id", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + },{ + "id": "randomid2", + "impid": "test-imp-id-2", + "price": 0.500000, + "adid": "12345678", + "adm": "some-test-ad", + "cid": "987", + "crid": "12345678", + "h": 250, + "w": 300 + }] + }], + "cur": "USD" + } + } + }], + + "expectedBidResponses": [{ + "currency": "USD", + "bids": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + },{ + "bid": { + "id": "randomid2", + "impid": "test-imp-id-2", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json new file mode 100644 index 00000000000..2f49d5451c8 --- /dev/null +++ b/adapters/interactiveoffers/interactiveofferstest/exemplary/goodsinglebidrequest.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [{ + "seat": "interactiveoffers", + "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": [{ + "bid": { + "id": "randomid", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "adid": "12345678", + "cid": "987", + "crid": "12345678", + "w": 300, + "h": 250 + }, + "type": "banner" + }] + }] +} \ No newline at end of file diff --git a/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json b/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json new file mode 100644 index 00000000000..d81c02a5dc3 --- /dev/null +++ b/adapters/interactiveoffers/interactiveofferstest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "pubid": 35 +} diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json new file mode 100644 index 00000000000..0d56311a188 --- /dev/null +++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/204.json @@ -0,0 +1,55 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + }], + + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json new file mode 100644 index 00000000000..9aaf12cb239 --- /dev/null +++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/400.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + } + }, + "mockResponse": { + "status": 400, + "body": "" + } + }], + + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json new file mode 100644 index 00000000000..222be912a92 --- /dev/null +++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/not200.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + } + }, + "mockResponse": { + "status": 202, + "body": "" + } + }], + + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 202. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json b/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json new file mode 100644 index 00000000000..7ae16d4a95a --- /dev/null +++ b/adapters/interactiveoffers/interactiveofferstest/supplemental/wrongjsonresponse.json @@ -0,0 +1,60 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04", + "body": { + "id": "test-request-id", + "site": { + "page": "https://good.site/url" + }, + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "pubid": 35 + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": "" + } + }], + + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "json: cannot unmarshal string into Go value of type openrtb2.BidResponse", + "comparison": "literal" + } + ] +} diff --git a/adapters/interactiveoffers/params_test.go b/adapters/interactiveoffers/params_test.go new file mode 100644 index 00000000000..754be842a80 --- /dev/null +++ b/adapters/interactiveoffers/params_test.go @@ -0,0 +1,44 @@ +package interactiveoffers + +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.BidderInteractiveoffers, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected interactiveoffers 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.BidderInteractiveoffers, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"pubid":35}`, +} + +var invalidParams = []string{ + `{"pubid":"35"}`, + `{"pubId":35}`, + `{"PubId":35}`, + `{}`, +} diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index f8903008328..b251ec0f736 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -7,7 +7,10 @@ import ( "fmt" "io/ioutil" "net/http" + "sort" + "github.com/mxmCherry/openrtb/v15/native1" + native1response "github.com/mxmCherry/openrtb/v15/native1/response" "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/config" @@ -400,7 +403,7 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque for _, bid := range seatBid.Bid { bidType, ok := impMediaType[bid.ImpID] if !ok { - errs = append(errs, fmt.Errorf("Unmatched impression id: %s.", bid.ImpID)) + errs = append(errs, fmt.Errorf("unmatched impression id: %s", bid.ImpID)) } var bidExtVideo *openrtb_ext.ExtBidPrebidVideo @@ -409,8 +412,32 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque unmarshalExtErr := json.Unmarshal(bid.Ext, &bidExt) if unmarshalExtErr == nil && bidExt.Prebid != nil && bidExt.Prebid.Video != nil { bidExtVideo = &openrtb_ext.ExtBidPrebidVideo{ - Duration: bidExt.Prebid.Video.Duration, - PrimaryCategory: bidExt.Prebid.Video.PrimaryCategory, + Duration: bidExt.Prebid.Video.Duration, + } + if len(bid.Cat) == 0 { + bid.Cat = []string{bidExt.Prebid.Video.PrimaryCategory} + } + } + } + + var bidNative1v1 *Native11Wrapper + if bidType == openrtb_ext.BidTypeNative { + err := json.Unmarshal([]byte(bid.AdM), &bidNative1v1) + if err == nil && len(bidNative1v1.Native.EventTrackers) > 0 { + mergeNativeImpTrackers(&bidNative1v1.Native) + if json, err := json.Marshal(bidNative1v1); err == nil { + bid.AdM = string(json) + } + } + } + + var bidNative1v2 *native1response.Response + if bidType == openrtb_ext.BidTypeNative { + err := json.Unmarshal([]byte(bid.AdM), &bidNative1v2) + if err == nil && len(bidNative1v2.EventTrackers) > 0 { + mergeNativeImpTrackers(bidNative1v2) + if json, err := json.Marshal(bidNative1v2); err == nil { + bid.AdM = string(json) } } } @@ -442,3 +469,33 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters } return bidder, nil } + +// native 1.2 to 1.1 tracker compatibility handling + +type Native11Wrapper struct { + Native native1response.Response `json:"native,omitempty"` +} + +func mergeNativeImpTrackers(bidNative *native1response.Response) { + + // create unique list of imp pixels urls from `imptrackers` and `eventtrackers` + uniqueImpPixels := map[string]struct{}{} + for _, v := range bidNative.ImpTrackers { + uniqueImpPixels[v] = struct{}{} + } + + for _, v := range bidNative.EventTrackers { + if v.Event == native1.EventTypeImpression && v.Method == native1.EventTrackingMethodImage { + uniqueImpPixels[v.URL] = struct{}{} + } + } + + // rewrite `imptrackers` with new deduped list of imp pixels + bidNative.ImpTrackers = make([]string, 0) + for k := range uniqueImpPixels { + bidNative.ImpTrackers = append(bidNative.ImpTrackers, k) + } + + // sort so tests pass correctly + sort.Strings(bidNative.ImpTrackers) +} diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index bc70f3999df..d292273a92c 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -792,8 +792,8 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) { if bidResponse.Bids[0].BidVideo.Duration != expectedBidDuration { t.Errorf("video duration should be set") } - if bidResponse.Bids[0].BidVideo.PrimaryCategory != expectedBidCategory { - t.Errorf("video category should be set") + if bidResponse.Bids[0].Bid.Cat[0] != expectedBidCategory { + t.Errorf("bid category should be set") } if len(errors) != expectedErrorCount { t.Errorf("should not have any errors, errors=%v", errors) diff --git a/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json new file mode 100644 index 00000000000..36a239987a6 --- /dev/null +++ b/adapters/ix/ixtest/exemplary/native-eventtrackers-compat-12.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"eventtrackers\":[{\"url\":\"https://example.com/imp-2.gif\",\"event\":1,\"method\":1},{\"url\":\"https://example.com/imp-3.gif\",\"event\":1,\"method\":1}],\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\",\"https://example.com/imp-3.gif\"],\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-2.gif\"},{\"event\":1,\"method\":1,\"url\":\"https://example.com/imp-3.gif\"}]}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/bad-imp-id.json b/adapters/ix/ixtest/supplemental/bad-imp-id.json index 0b852c85d2b..1ca053b674e 100644 --- a/adapters/ix/ixtest/supplemental/bad-imp-id.json +++ b/adapters/ix/ixtest/supplemental/bad-imp-id.json @@ -111,7 +111,7 @@ ], "expectedMakeBidsErrors": [ { - "value": "Unmatched impression id: bad-imp-id.", + "value": "unmatched impression id: bad-imp-id", "comparison": "literal" } ] diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json new file mode 100644 index 00000000000..4cf314e742f --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-empty.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"],\"eventtrackers\":[]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"],\"eventtrackers\":[]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json new file mode 100644 index 00000000000..d8c78a5cbca --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-eventtrackers-missing.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{\"assets\":[{\"id\":123,\"required\":1,\"title\":{\"text\":\"Learn about this awesome thing\"}},{\"id\":124,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/thumbnail1.png\"}},{\"id\":128,\"required\":1,\"img\":{\"url\":\"http://www.myads.com/largethumb1.png\"}},{\"id\":126,\"required\":1,\"data\":{\"value\":\"My Brand\"}},{\"id\":127,\"required\":1,\"data\":{\"value\":\"Learn all about this awesome story of someone using my product.\"}}],\"link\":{\"url\":\"http: //i.am.a/URL\"},\"imptrackers\":[\"https://example.com/imp-1.gif\",\"https://example.com/imp-2.gif\"]}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/ixtest/supplemental/native-missing.json b/adapters/ix/ixtest/supplemental/native-missing.json new file mode 100644 index 00000000000..ec2108ce5d1 --- /dev/null +++ b/adapters/ix/ixtest/supplemental/native-missing.json @@ -0,0 +1,104 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://host/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "native": { + "request": "{}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "siteId": "569749" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + } + ] + } + ], + "bidid": "5778926625248726496", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "7706636740145184841", + "impid": "test-imp-id", + "price": 0.5, + "adid": "29681110", + "adm": "{\"native\":{}}", + "adomain": [ + "https://advertiser.example.com" + ], + "cid": "958", + "crid": "29681110", + "cat": [ + "IAB3-1" + ], + "ext": { + "ix": {} + } + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/ix/params_test.go b/adapters/ix/params_test.go new file mode 100644 index 00000000000..9246a43a725 --- /dev/null +++ b/adapters/ix/params_test.go @@ -0,0 +1,59 @@ +package ix + +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.BidderIx, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected ix 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.BidderIx, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"siteid":"1234"}`, + `{"siteID":"12345"}`, + `{"siteId":"123456"}`, + `{"siteid":"1234567", "size": [640,480]}`, +} + +var invalidParams = []string{ + `{"siteid":""}`, + `{"siteID":""}`, + `{"siteId":""}`, + `{"siteid":"1234", "siteID":"12345"}`, + `{"siteid":"1234", "siteId":"123456"}`, + `{"siteid":123}`, + `{"siteids":"123"}`, + `{"notaparam":"123"}`, + `{"siteid":"123", "size": [1,2,3]}`, + `null`, + `true`, + `0`, + `abc`, + `[]`, + `{}`, +} diff --git a/adapters/kayzen/kayzen.go b/adapters/kayzen/kayzen.go new file mode 100644 index 00000000000..1fcb9877e47 --- /dev/null +++ b/adapters/kayzen/kayzen.go @@ -0,0 +1,154 @@ +package kayzen + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb/v15/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 Kayzen adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (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, reqInfo *adapters.ExtraRequestInfo) (requests []*adapters.RequestData, errors []error) { + var kayzenExt *openrtb_ext.ExtKayzen + var err error + + if len(request.Imp) > 0 { + kayzenExt, err = a.getImpressionExt(&(request.Imp[0])) + if err != nil { + errors = append(errors, err) + } + request.Imp[0].Ext = nil + } else { + errors = append(errors, &errortypes.BadInput{ + Message: "Missing Imp Object", + }) + } + + if len(errors) > 0 { + return nil, errors + } + + url, err := a.buildEndpointURL(kayzenExt) + if err != nil { + return nil, []error{err} + } + + requestJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: requestJSON, + Uri: url, + Headers: headers, + }}, nil +} + +func (a *adapter) getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtKayzen, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Bidder extension not provided or can't be unmarshalled", + } + } + var kayzenExt openrtb_ext.ExtKayzen + if err := json.Unmarshal(bidderExt.Bidder, &kayzenExt); err != nil { + return nil, &errortypes.BadInput{ + Message: "Error while unmarshaling bidder extension", + } + } + return &kayzenExt, nil +} + +func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtKayzen) (string, error) { + endpointParams := macros.EndpointTemplateParams{ + ZoneID: params.Zone, + AccountID: params.Exchange, + } + return macros.ResolveMacros(a.endpoint, endpointParams) +} + +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 { + 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 response.StatusCode != http.StatusOK { + err := &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", response.StatusCode), + } + return nil, []error{err} + } + + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Bad Server Response", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + var errs []error + for _, seatBid := range bidResp.SeatBid { + for _, bid := range seatBid.Bid { + b := &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, errs +} + +func getMediaTypeForImp(impID string, imps []openrtb2.Imp) openrtb_ext.BidType { + for _, imp := range imps { + if imp.ID == impID { + if imp.Banner != nil { + return openrtb_ext.BidTypeBanner + } else if imp.Video != nil { + return openrtb_ext.BidTypeVideo + } else if imp.Native != nil { + return openrtb_ext.BidTypeNative + } + } + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/kayzen/kayzen_test.go b/adapters/kayzen/kayzen_test.go new file mode 100644 index 00000000000..4cd07470ebb --- /dev/null +++ b/adapters/kayzen/kayzen_test.go @@ -0,0 +1,29 @@ +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/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderKayzen, config.Adapter{ + Endpoint: "https://example-{{.ZoneID}}.com/?exchange={{.AccountID}}", + }) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "kayzentest", bidder) +} + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderKayzen, config.Adapter{ + Endpoint: "{{Malformed}}"}) + + assert.Error(t, buildErr) +} diff --git a/adapters/kayzen/kayzentest/exemplary/banner-app.json b/adapters/kayzen/kayzentest/exemplary/banner-app.json new file mode 100644 index 00000000000..157b79165cc --- /dev/null +++ b/adapters/kayzen/kayzentest/exemplary/banner-app.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w": 320, + "h": 50 + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + } + ], + "type": "banner", + "seat": "kayzen-prebid" + } + ], + "cur": "USD", + "ext": { + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} + diff --git a/adapters/kayzen/kayzentest/exemplary/banner-web.json b/adapters/kayzen/kayzentest/exemplary/banner-web.json new file mode 100644 index 00000000000..0d25d4fd981 --- /dev/null +++ b/adapters/kayzen/kayzentest/exemplary/banner-web.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 320, + "h": 50 + } + ], + "type": "banner", + "seat": "kayzen-prebid" + } + ], + "cur": "USD", + "ext": { + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "1", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/kayzen/kayzentest/exemplary/native-app.json b/adapters/kayzen/kayzentest/exemplary/native-app.json new file mode 100644 index 00000000000..ef43dab798c --- /dev/null +++ b/adapters/kayzen/kayzentest/exemplary/native-app.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + } + ], + "type": "native", + "seat": "kayzen-prebid" + } + ], + "cur": "USD", + "ext": { + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ] + }, + "type": "native" + } + ] + } + ] + } diff --git a/adapters/kayzen/kayzentest/exemplary/native-web.json b/adapters/kayzen/kayzentest/exemplary/native-web.json new file mode 100644 index 00000000000..34050f989ad --- /dev/null +++ b/adapters/kayzen/kayzentest/exemplary/native-web.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "native": { + "ver":"1.1", + "request":"{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}" + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20" + } + ], + "seat": "kayzen-prebid" + } + ], + "cur": "USD", + "ext": { + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ] + }, + "type": "native" + } + ] + } + ] +} diff --git a/adapters/kayzen/kayzentest/exemplary/video-app.json b/adapters/kayzen/kayzentest/exemplary/video-app.json new file mode 100644 index 00000000000..f8744b638f2 --- /dev/null +++ b/adapters/kayzen/kayzentest/exemplary/video-app.json @@ -0,0 +1,150 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720 + } + ], + "seat": "kayzen-prebid" + } + ], + "cur": "USD", + "ext": { + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids":[ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "crid": "20", + "adomain": [ + "awesome.com" + ], + "w": 1280, + "h": 720 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/kayzen/kayzentest/exemplary/video-web.json b/adapters/kayzen/kayzentest/exemplary/video-web.json new file mode 100644 index 00000000000..7a507ca3cd9 --- /dev/null +++ b/adapters/kayzen/kayzentest/exemplary/video-web.json @@ -0,0 +1,149 @@ +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "awesome-resp-id", + "seatbid": [ + { + "bid": [ + { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "kayzen-prebid" + } + ], + "cur": "USD", + "ext": { + } + } + } + } + ], + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "a3ae1b4e2fc24a4fb45540082e98e161", + "impid": "some-impression-id", + "price": 3.5, + "adm": "awesome-markup", + "adomain": [ + "awesome.com" + ], + "crid": "20", + "w": 1280, + "h": 720, + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] +} + diff --git a/adapters/kayzen/kayzentest/params/race/banner.json b/adapters/kayzen/kayzentest/params/race/banner.json new file mode 100644 index 00000000000..bdb2c9de976 --- /dev/null +++ b/adapters/kayzen/kayzentest/params/race/banner.json @@ -0,0 +1,5 @@ +{ + "zone": "dc", + "exchange": "ex" +} + diff --git a/adapters/kayzen/kayzentest/params/race/native.json b/adapters/kayzen/kayzentest/params/race/native.json new file mode 100644 index 00000000000..bdb2c9de976 --- /dev/null +++ b/adapters/kayzen/kayzentest/params/race/native.json @@ -0,0 +1,5 @@ +{ + "zone": "dc", + "exchange": "ex" +} + diff --git a/adapters/kayzen/kayzentest/params/race/video.json b/adapters/kayzen/kayzentest/params/race/video.json new file mode 100644 index 00000000000..bdb2c9de976 --- /dev/null +++ b/adapters/kayzen/kayzentest/params/race/video.json @@ -0,0 +1,5 @@ +{ + "zone": "dc", + "exchange": "ex" +} + diff --git a/adapters/kayzen/kayzentest/supplemental/invalid-ext-object.json b/adapters/kayzen/kayzentest/supplemental/invalid-ext-object.json new file mode 100644 index 00000000000..2dda068b33e --- /dev/null +++ b/adapters/kayzen/kayzentest/supplemental/invalid-ext-object.json @@ -0,0 +1,29 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Bidder extension not provided or can't be unmarshalled", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "some-impression-id", + "tagid": "my-adcode", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": "" + } + ], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/kayzen/kayzentest/supplemental/invalid-response.json b/adapters/kayzen/kayzentest/supplemental/invalid-response.json new file mode 100644 index 00000000000..4963ed677f9 --- /dev/null +++ b/adapters/kayzen/kayzentest/supplemental/invalid-response.json @@ -0,0 +1,101 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "w":320, + "h":50 + } + } + ], + "app": { + "id": "123456789", + "name": "Awesome App", + "bundle": "com.app.awesome", + "domain": "awesomeapp.com", + "cat": [ + "IAB22-1" + ], + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 200, + "body": "invalid response" + } + }], + "expectedMakeBidsErrors": [ + { + "value": "Bad Server Response", + "comparison": "literal" + } + ] +} diff --git a/adapters/kayzen/kayzentest/supplemental/requires-imp-object.json b/adapters/kayzen/kayzentest/supplemental/requires-imp-object.json new file mode 100644 index 00000000000..1fcf1b48c5a --- /dev/null +++ b/adapters/kayzen/kayzentest/supplemental/requires-imp-object.json @@ -0,0 +1,16 @@ +{ + "expectedMakeRequestsErrors": [ + { + "value": "Missing Imp Object", + "comparison": "literal" + } + ], + "mockBidRequest": { + "id": "test-request-id", + "imp": [], + "site": { + "page": "test.com" + } + }, + "httpCalls": [] +} diff --git a/adapters/kayzen/kayzentest/supplemental/status-code-bad-request.json b/adapters/kayzen/kayzentest/supplemental/status-code-bad-request.json new file mode 100644 index 00000000000..8b9e4d06fbf --- /dev/null +++ b/adapters/kayzen/kayzentest/supplemental/status-code-bad-request.json @@ -0,0 +1,91 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "app": { + "publisher": { + "id": "123456789" + }, + "cat": [ + "IAB22-1" + ], + "bundle": "com.app.awesome", + "name": "Awesome App", + "domain": "awesomeapp.com", + "id": "123456789" + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 400 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/kayzen/kayzentest/supplemental/status-code-no-content.json b/adapters/kayzen/kayzentest/supplemental/status-code-no-content.json new file mode 100644 index 00000000000..69e64c37d52 --- /dev/null +++ b/adapters/kayzen/kayzentest/supplemental/status-code-no-content.json @@ -0,0 +1,72 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 204 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/kayzen/kayzentest/supplemental/status-code-other-error.json b/adapters/kayzen/kayzentest/supplemental/status-code-other-error.json new file mode 100644 index 00000000000..b487b91dfa2 --- /dev/null +++ b/adapters/kayzen/kayzentest/supplemental/status-code-other-error.json @@ -0,0 +1,77 @@ + +{ + "mockBidRequest": { + "id": "some-request-id", + "tmax": 1000, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": ["video/mp4"], + "w": 640, + "h": 480, + "minduration": 120, + "maxduration": 150 + }, + "ext": { + "bidder": { + "zone": "dc", + "exchange": "ex" + } + } + } + ] + }, + + "httpCalls": [{ + "expectedRequest": { + "uri": "https://example-dc.com/?exchange=ex", + "body": { + "id": "some-request-id", + "imp": [ + { + "id": "some-impression-id", + "video": { + "mimes": [ + "video/mp4" + ], + "minduration": 120, + "maxduration": 150, + "w": 640, + "h": 480 + } + } + ], + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "user": { + "buyeruid": "0000-000-000-0000" + }, + "tmax": 1000 + } + }, + "mockResponse": { + "status": 306 + } + }], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 306. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/kayzen/params_test.go b/adapters/kayzen/params_test.go new file mode 100644 index 00000000000..07bd0851a97 --- /dev/null +++ b/adapters/kayzen/params_test.go @@ -0,0 +1,55 @@ +package kayzen + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "zone": "dc", "exchange": "ex" }`, +} + +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.BidderKayzen, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Kayzen params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `4.2`, + `[]`, + `{}`, + `{ "key": 2 }`, + `{ "anyparam": "anyvalue" }`, + `{ "zone": "dc" }`, + `{ "exchange": "ex" }`, + `{ "exchange": "", "zone" : "" }`, + `{ "exchange": "ex", "zone" : "" }`, + `{ "exchange": "", "zone" : "dc" }`, +} + +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.BidderKayzen, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/lifestreet/lifestreet.go b/adapters/lifestreet/lifestreet.go deleted file mode 100644 index 14f6931751a..00000000000 --- a/adapters/lifestreet/lifestreet.go +++ /dev/null @@ -1,219 +0,0 @@ -package lifestreet - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "strings" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/openrtb_ext" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" -) - -type LifestreetAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// used for cookies and such -func (a *LifestreetAdapter) Name() string { - return "lifestreet" -} - -func (a *LifestreetAdapter) SkipNoCookies() bool { - return false -} - -// parameters for Lifestreet adapter. -type lifestreetParams struct { - SlotTag string `json:"slot_tag"` -} - -func (a *LifestreetAdapter) callOne(ctx context.Context, req *pbs.PBSRequest, reqJSON bytes.Buffer) (result adapters.CallOneResult, err error) { - httpReq, err := http.NewRequest("POST", a.URI, &reqJSON) - httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") - httpReq.Header.Add("Accept", "application/json") - - lsmResp, e := ctxhttp.Do(ctx, a.http.Client, httpReq) - if e != nil { - err = e - return - } - - defer lsmResp.Body.Close() - body, _ := ioutil.ReadAll(lsmResp.Body) - result.ResponseBody = string(body) - - result.StatusCode = lsmResp.StatusCode - - if lsmResp.StatusCode == 204 { - return - } - - if lsmResp.StatusCode != 200 { - err = fmt.Errorf("HTTP status %d; body: %s", lsmResp.StatusCode, result.ResponseBody) - return - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - return - } - if len(bidResp.SeatBid) == 0 || len(bidResp.SeatBid[0].Bid) == 0 { - return - } - bid := bidResp.SeatBid[0].Bid[0] - - t := openrtb_ext.BidTypeBanner - - if bid.Ext != nil { - var e openrtb_ext.ExtBid - err = json.Unmarshal(bid.Ext, &e) - if err != nil { - return - } - t = e.Prebid.Type - } - - result.Bid = &pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - NURL: bid.NURL, - CreativeMediaType: string(t), - } - return -} - -func (a *LifestreetAdapter) MakeOpenRtbBidRequest(req *pbs.PBSRequest, bidder *pbs.PBSBidder, slotTag string, mtype pbs.MediaType, unitInd int) (openrtb2.BidRequest, error) { - lsReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), []pbs.MediaType{mtype}) - - if err != nil { - return openrtb2.BidRequest{}, err - } - - if lsReq.Imp != nil && len(lsReq.Imp) > 0 { - lsReq.Imp = lsReq.Imp[unitInd : unitInd+1] - - if lsReq.Imp[0].Banner != nil { - lsReq.Imp[0].Banner.Format = nil - } - lsReq.Imp[0].TagID = slotTag - - return lsReq, nil - } else { - return lsReq, &errortypes.BadInput{ - Message: "No supported impressions", - } - } -} - -func (a *LifestreetAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - requests := make([]bytes.Buffer, len(bidder.AdUnits)*2) - reqIndex := 0 - for i, unit := range bidder.AdUnits { - var params lifestreetParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, err - } - if params.SlotTag == "" { - return nil, &errortypes.BadInput{ - Message: "Missing slot_tag param", - } - } - s := strings.Split(params.SlotTag, ".") - if len(s) != 2 { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("Invalid slot_tag param '%s'", params.SlotTag), - } - } - - // BANNER - lsReqB, err := a.MakeOpenRtbBidRequest(req, bidder, params.SlotTag, pbs.MEDIA_TYPE_BANNER, i) - if err == nil { - err = json.NewEncoder(&requests[reqIndex]).Encode(lsReqB) - reqIndex = reqIndex + 1 - if err != nil { - return nil, err - } - } - - // VIDEO - lsReqV, err := a.MakeOpenRtbBidRequest(req, bidder, params.SlotTag, pbs.MEDIA_TYPE_VIDEO, i) - if err == nil { - err = json.NewEncoder(&requests[reqIndex]).Encode(lsReqV) - reqIndex = reqIndex + 1 - if err != nil { - return nil, err - } - } - } - - ch := make(chan adapters.CallOneResult) - for i := range bidder.AdUnits { - go func(bidder *pbs.PBSBidder, reqJSON bytes.Buffer) { - result, err := a.callOne(ctx, req, reqJSON) - result.Error = err - if result.Bid != nil { - result.Bid.BidderCode = bidder.BidderCode - result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode) - if result.Bid.BidID == "" { - result.Error = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", result.Bid.AdUnitCode), - } - result.Bid = nil - } - } - ch <- result - }(bidder, requests[i]) - } - - var err error - - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(bidder.AdUnits); i++ { - result := <-ch - if result.Bid != nil { - bids = append(bids, result.Bid) - } - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: requests[i].String(), - StatusCode: result.StatusCode, - ResponseBody: result.ResponseBody, - } - bidder.Debug = append(bidder.Debug, debug) - } - if result.Error != nil { - err = result.Error - } - } - - if len(bids) == 0 { - return nil, err - } - return bids, nil -} - -func NewLifestreetLegacyAdapter(config *adapters.HTTPAdapterConfig, endpoint string) *LifestreetAdapter { - a := adapters.NewHTTPAdapter(config) - return &LifestreetAdapter{ - http: a, - URI: endpoint, - } -} diff --git a/adapters/lifestreet/lifestreet_test.go b/adapters/lifestreet/lifestreet_test.go deleted file mode 100644 index 5c4f47fdff9..00000000000 --- a/adapters/lifestreet/lifestreet_test.go +++ /dev/null @@ -1,291 +0,0 @@ -package lifestreet - -import ( - "bytes" - "context" - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/cache/dummycache" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" - - "fmt" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/config" -) - -type lsTagInfo struct { - code string - slotTag string - bid float64 - content string -} - -type lsBidInfo struct { - appBundle string - deviceIP string - deviceUA string - deviceMake string - deviceModel string - deviceConnectiontype int8 - deviceIfa string - tags []lsTagInfo - referrer string - width uint64 - height uint64 - delay time.Duration -} - -var lsdata lsBidInfo - -func DummyLifestreetServer(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var breq openrtb2.BidRequest - err = json.Unmarshal(body, &breq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if breq.App == nil { - http.Error(w, fmt.Sprintf("No app object sent"), http.StatusInternalServerError) - return - } - if breq.App.Bundle != lsdata.appBundle { - http.Error(w, fmt.Sprintf("Bundle '%s' doesn't match '%s", breq.App.Bundle, lsdata.appBundle), http.StatusInternalServerError) - return - } - if breq.Device.UA != lsdata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s", breq.Device.UA, lsdata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != lsdata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s", breq.Device.IP, lsdata.deviceIP), http.StatusInternalServerError) - return - } - if breq.Device.Make != lsdata.deviceMake { - http.Error(w, fmt.Sprintf("Make '%s' doesn't match '%s", breq.Device.Make, lsdata.deviceMake), http.StatusInternalServerError) - return - } - if breq.Device.Model != lsdata.deviceModel { - http.Error(w, fmt.Sprintf("Model '%s' doesn't match '%s", breq.Device.Model, lsdata.deviceModel), http.StatusInternalServerError) - return - } - if *breq.Device.ConnectionType != openrtb2.ConnectionType(lsdata.deviceConnectiontype) { - http.Error(w, fmt.Sprintf("Connectiontype '%d' doesn't match '%d", breq.Device.ConnectionType, lsdata.deviceConnectiontype), http.StatusInternalServerError) - return - } - if breq.Device.IFA != lsdata.deviceIfa { - http.Error(w, fmt.Sprintf("IFA '%s' doesn't match '%s", breq.Device.IFA, lsdata.deviceIfa), http.StatusInternalServerError) - return - } - if len(breq.Imp) != 1 { - http.Error(w, fmt.Sprintf("Wrong number of imp objects sent: %d", len(breq.Imp)), http.StatusInternalServerError) - return - } - var bid *openrtb2.Bid - for _, tag := range lsdata.tags { - if breq.Imp[0].Banner == nil { - http.Error(w, fmt.Sprintf("No banner object sent"), http.StatusInternalServerError) - return - } - if *breq.Imp[0].Banner.W != int64(lsdata.width) || *breq.Imp[0].Banner.H != int64(lsdata.height) { - http.Error(w, fmt.Sprintf("Size '%dx%d' doesn't match '%dx%d", breq.Imp[0].Banner.W, breq.Imp[0].Banner.H, lsdata.width, lsdata.height), http.StatusInternalServerError) - return - } - if breq.Imp[0].TagID == tag.slotTag { - bid = &openrtb2.Bid{ - ID: "random-id", - ImpID: breq.Imp[0].ID, - Price: tag.bid, - AdM: tag.content, - W: int64(lsdata.width), - H: int64(lsdata.height), - } - } - } - if bid == nil { - http.Error(w, fmt.Sprintf("Slot tag '%s' not found", breq.Imp[0].TagID), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: "2345676337", - BidID: "975537589956", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "LSM", - Bid: []openrtb2.Bid{*bid}, - }, - }, - } - - if lsdata.delay > 0 { - <-time.After(lsdata.delay) - } - - js, err := json.Marshal(resp) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(js) -} - -func TestLifestreetBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyLifestreetServer)) - defer server.Close() - - lsdata = lsBidInfo{ - appBundle: "AppNexus.PrebidMobileDemo", - deviceIP: "111.111.111.111", - deviceUA: "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Mobile/14E8301", - deviceMake: "Apple", - deviceModel: "x86_64", - deviceConnectiontype: 1, - deviceIfa: "6F3EA622-C2EE-4449-A97A-AE986D080C08", - tags: make([]lsTagInfo, 2), - referrer: "http://test.com", - width: 320, - height: 480, - } - lsdata.tags[0] = lsTagInfo{ - code: "first-tag", - slotTag: "slot123.123", - bid: 2.44, - } - lsdata.tags[1] = lsTagInfo{ - code: "second-tag", - slotTag: "slot122.122", - bid: 1.11, - } - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewLifestreetLegacyAdapter(&conf, "https://prebid.s2s.lfstmedia.com/adrequest") - an.URI = server.URL - - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 2), - App: &openrtb2.App{ - Bundle: lsdata.appBundle, - }, - Device: &openrtb2.Device{ - UA: lsdata.deviceUA, - IP: lsdata.deviceIP, - Make: lsdata.deviceMake, - Model: lsdata.deviceModel, - ConnectionType: openrtb2.ConnectionType(lsdata.deviceConnectiontype).Ptr(), - IFA: lsdata.deviceIfa, - }, - } - for i, tag := range lsdata.tags { - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - Sizes: []openrtb2.Format{ - { - W: int64(lsdata.width), - H: int64(lsdata.height), - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "lifestreet", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(fmt.Sprintf("{\"slot_tag\": \"%s\"}", tag.slotTag)), - }, - }, - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - - fmt.Println("body", body) - - req := httptest.NewRequest("POST", server.URL, body) - req.Header.Add("User-Agent", lsdata.deviceUA) - req.Header.Add("Referer", lsdata.referrer) - req.Header.Add("X-Real-IP", lsdata.deviceIP) - - pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{}) - fakewriter := httptest.NewRecorder() - - pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour) - req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie")) - - cacheClient, _ := dummycache.New() - hcc := config.HostCookie{} - pbReq, err := pbs.ParsePBSRequest(req, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cacheClient, &hcc) - if err != nil { - t.Fatalf("ParsePBSRequest failed: %v", err) - } - if len(pbReq.Bidders) != 1 { - t.Fatalf("ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - } - if pbReq.Bidders[0].BidderCode != "lifestreet" { - t.Fatalf("ParsePBSRequest returned invalid bidder") - } - - ctx := context.TODO() - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatalf("Should not have gotten an error: %v", err) - } - if len(bids) != 2 { - t.Fatalf("Received %d bids instead of 2", len(bids)) - } - for _, bid := range bids { - matched := false - for _, tag := range lsdata.tags { - if bid.AdUnitCode == tag.code { - matched = true - if bid.BidderCode != "lifestreet" { - t.Errorf("Incorrect BidderCode '%s'", bid.BidderCode) - } - if bid.Price != tag.bid { - t.Errorf("Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - } - if bid.Width != int64(lsdata.width) || bid.Height != int64(lsdata.height) { - t.Errorf("Incorrect bid size %dx%d, expected %dx%d", bid.Width, bid.Height, lsdata.width, lsdata.height) - } - if bid.Adm != tag.content { - t.Errorf("Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - } - } - } - if !matched { - t.Errorf("Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - } - - // same test but with request timing out - lsdata.delay = 5 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err == nil { - t.Fatalf("Should have gotten a timeout error: %v", err) - } -} diff --git a/adapters/lifestreet/lifestreettest/params/race/banner.json b/adapters/lifestreet/lifestreettest/params/race/banner.json deleted file mode 100644 index c746cc15630..00000000000 --- a/adapters/lifestreet/lifestreettest/params/race/banner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "slot_tag": "slot166704" -} diff --git a/adapters/lifestreet/lifestreettest/params/race/video.json b/adapters/lifestreet/lifestreettest/params/race/video.json deleted file mode 100644 index 7103cf63631..00000000000 --- a/adapters/lifestreet/lifestreettest/params/race/video.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "slot_tag": "slot1227631" -} diff --git a/adapters/lifestreet/usersync.go b/adapters/lifestreet/usersync.go deleted file mode 100644 index f5300ebaa90..00000000000 --- a/adapters/lifestreet/usersync.go +++ /dev/null @@ -1,12 +0,0 @@ -package lifestreet - -import ( - "text/template" - - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/usersync" -) - -func NewLifestreetSyncer(temp *template.Template) usersync.Usersyncer { - return adapters.NewSyncer("lifestreet", temp, adapters.SyncTypeRedirect) -} diff --git a/adapters/lifestreet/usersync_test.go b/adapters/lifestreet/usersync_test.go deleted file mode 100644 index e41217fe10f..00000000000 --- a/adapters/lifestreet/usersync_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package lifestreet - -import ( - "testing" - "text/template" - - "github.com/prebid/prebid-server/privacy" - "github.com/prebid/prebid-server/privacy/gdpr" - "github.com/stretchr/testify/assert" -) - -func TestLifestreetSyncer(t *testing.T) { - syncURL := "//ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl=localhost%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24" - syncURLTemplate := template.Must( - template.New("sync-template").Parse(syncURL), - ) - - syncer := NewLifestreetSyncer(syncURLTemplate) - syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ - GDPR: gdpr.Policy{ - Signal: "0", - }, - }) - - assert.NoError(t, err) - assert.Equal(t, "//ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl=localhost%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%24%24visitor_cookie%24%24", syncInfo.URL) - assert.Equal(t, "redirect", syncInfo.Type) - assert.Equal(t, false, syncInfo.SupportCORS) -} diff --git a/adapters/madvertise/madvertise.go b/adapters/madvertise/madvertise.go new file mode 100644 index 00000000000..dfc996d2497 --- /dev/null +++ b/adapters/madvertise/madvertise.go @@ -0,0 +1,163 @@ +package madvertise + +import ( + "encoding/json" + "fmt" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb/v15/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 +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (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 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.Device != nil { + if len(request.Device.UA) > 0 { + headers.Add("User-Agent", request.Device.UA) + } + + if len(request.Device.IP) > 0 { + headers.Add("X-Forwarded-For", request.Device.IP) + } + } + + return headers +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + zoneID := "" + for _, imp := range request.Imp { + madvertiseExt, err := getImpressionExt(imp) + if err != nil { + return nil, []error{err} + } + if len(madvertiseExt.ZoneID) < 7 { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("The minLength of zone ID is 7; ImpID=%s", imp.ID), + }} + } + if zoneID == "" { + zoneID = madvertiseExt.ZoneID + } else if zoneID != madvertiseExt.ZoneID { + return nil, []error{&errortypes.BadInput{ + Message: "There must be only one zone ID", + }} + } + } + url, err := a.buildEndpointURL(zoneID) + 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: url, + Body: requestJSON, + Headers: getHeaders(request), + } + + return []*adapters.RequestData{requestData}, nil +} + +func getImpressionExt(imp openrtb2.Imp) (*openrtb_ext.ExtImpMadvertise, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("%s; ImpID=%s", err.Error(), imp.ID), + } + } + var madvertiseExt openrtb_ext.ExtImpMadvertise + if err := json.Unmarshal(bidderExt.Bidder, &madvertiseExt); err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("%s; ImpID=%s", err.Error(), imp.ID), + } + } + if madvertiseExt.ZoneID == "" { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("ext.bidder.zoneId not provided; ImpID=%s", imp.ID), + } + } + + return &madvertiseExt, nil +} + +func (a *adapter) buildEndpointURL(zoneID string) (string, error) { + endpointParams := macros.EndpointTemplateParams{ZoneID: zoneID} + return macros.ResolveMacros(a.endpointTemplate, endpointParams) +} + +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.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)) + bidResponse.Currency = response.Cur + for _, seatBid := range response.SeatBid { + for i := range seatBid.Bid { + bid := seatBid.Bid[i] + bidMediaType := getMediaTypeForBid(bid.Attr) + b := &adapters.TypedBid{ + Bid: &bid, + BidType: bidMediaType, + } + bidResponse.Bids = append(bidResponse.Bids, b) + } + } + return bidResponse, nil +} + +func getMediaTypeForBid(attr []openrtb2.CreativeAttribute) openrtb_ext.BidType { + for i := 0; i < len(attr); i++ { + if attr[i] == openrtb2.CreativeAttribute(16) { + return openrtb_ext.BidTypeVideo + } else if attr[i] == openrtb2.CreativeAttribute(6) { + return openrtb_ext.BidTypeVideo + } else if attr[i] == openrtb2.CreativeAttribute(7) { + return openrtb_ext.BidTypeVideo + } + } + return openrtb_ext.BidTypeBanner +} diff --git a/adapters/madvertise/madvertise_test.go b/adapters/madvertise/madvertise_test.go new file mode 100644 index 00000000000..924f25c1f8e --- /dev/null +++ b/adapters/madvertise/madvertise_test.go @@ -0,0 +1,26 @@ +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/stretchr/testify/assert" +) + +func TestEndpointTemplateMalformed(t *testing.T) { + _, buildErr := Builder(openrtb_ext.BidderMadvertise, config.Adapter{ + Endpoint: "{{Malformed}}"}) + + assert.Error(t, buildErr) +} + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderMadvertise, config.Adapter{ + Endpoint: "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}"}) + + assert.NoError(t, buildErr, "Builder returned unexpected error %v", buildErr) + + adapterstest.RunJSONBidderTest(t, "madvertisetest", bidder) +} diff --git a/adapters/madvertise/madvertisetest/exemplary/simple-banner.json b/adapters/madvertise/madvertisetest/exemplary/simple-banner.json new file mode 100644 index 00000000000..0c277a06b62 --- /dev/null +++ b/adapters/madvertise/madvertisetest/exemplary/simple-banner.json @@ -0,0 +1,203 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "tmax": 500, + "at": 1, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack" + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "instl": 0, + "banner": { + "h": 50, + "w": 320, + "pos": 1 + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "body": { + "id": "test-request-id", + "tmax": 500, + "at": 1, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack" + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "banner": { + "h": 50, + "w": 320, + "pos": 1 + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + } + }, + "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", + "burl": "https://burl.com", + "adomain": [ + "madvertise.com" + ], + "w": 320, + "h": 50 + } + ], + "seat": "adman" + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "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", + "burl": "https://burl.com", + "adomain": [ + "madvertise.com" + ], + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/exemplary/simple-video.json b/adapters/madvertise/madvertisetest/exemplary/simple-video.json new file mode 100644 index 00000000000..a387c06eeac --- /dev/null +++ b/adapters/madvertise/madvertisetest/exemplary/simple-video.json @@ -0,0 +1,256 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "tmax": 500, + "at": 2, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack", + "cat": [ + "IAB6", + "IAB9", + "IAB10", + "IAB12", + "IAB14", + "IAB20" + ] + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "instl": 0, + "video": { + "mimes": [ + "video/mp4" + ], + "w": 320, + "h": 480, + "minduration": 2, + "maxduration": 30, + "playbackmethod": [ + 1, + 3 + ], + "boxingallowed": 0, + "protocols": [ + 1, + 2, + 3, + 4, + 5, + 6 + ], + "placement": 5 + }, + "ext": { + "bidder": { + "zoneId": "/1111111/video" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mobile.mng-ads.com/bidrequest/1111111/video", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "body": { + "id": "test-request-id", + "tmax": 500, + "at": 2, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack", + "cat": [ + "IAB6", + "IAB9", + "IAB10", + "IAB12", + "IAB14", + "IAB20" + ] + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "video": { + "mimes": [ + "video/mp4" + ], + "w": 320, + "h": 480, + "minduration": 2, + "maxduration": 30, + "playbackmethod": [ + 1, + 3 + ], + "protocols": [ + 1, + 2, + 3, + 4, + 5, + 6 + ], + "placement": 5 + }, + "ext": { + "bidder": { + "zoneId": "/1111111/video" + } + } + } + ] + } + }, + "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", + "burl": "https://burl.com", + "attr": [6], + "adomain": [ + "madvertise.com" + ], + "w": 320, + "h": 480 + } + ], + "seat": "adman" + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "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", + "burl": "https://burl.com", + "attr": [6], + "adomain": [ + "madvertise.com" + ], + "w": 320, + "h": 480 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/params/race/banner.json b/adapters/madvertise/madvertisetest/params/race/banner.json new file mode 100644 index 00000000000..45243896f71 --- /dev/null +++ b/adapters/madvertise/madvertisetest/params/race/banner.json @@ -0,0 +1,4 @@ +{ + "zoneId": "/1111111/banner" +} + \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/params/race/video.json b/adapters/madvertise/madvertisetest/params/race/video.json new file mode 100644 index 00000000000..8d462a8e8d7 --- /dev/null +++ b/adapters/madvertise/madvertisetest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "zoneId": "/1111111/video" +} \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/supplemental/display-site-test.json b/adapters/madvertise/madvertisetest/supplemental/display-site-test.json new file mode 100644 index 00000000000..e111a5d9d71 --- /dev/null +++ b/adapters/madvertise/madvertisetest/supplemental/display-site-test.json @@ -0,0 +1,203 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "tmax": 500, + "at": 1, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "instl": 0, + "banner": { + "h": 50, + "w": 320, + "pos": 1 + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "body": { + "id": "test-request-id", + "tmax": 500, + "at": 1, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "site": { + "page": "test.com", + "publisher": { + "id": "123456789" + } + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "banner": { + "h": 50, + "w": 320, + "pos": 1 + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + } + }, + "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", + "burl": "https://burl.com", + "adomain": [ + "madvertise.com" + ], + "w": 320, + "h": 50 + } + ], + "seat": "adman" + } + ], + "cur": "EUR" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "EUR", + "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", + "burl": "https://burl.com", + "adomain": [ + "madvertise.com" + ], + "w": 320, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/supplemental/length-zoneid.json b/adapters/madvertise/madvertisetest/supplemental/length-zoneid.json new file mode 100644 index 00000000000..9d78bc855d9 --- /dev/null +++ b/adapters/madvertise/madvertisetest/supplemental/length-zoneid.json @@ -0,0 +1,29 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "/22222" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "The minLength of zone ID is 7; ImpID=test-imp-id-1", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/supplemental/required-ext.json b/adapters/madvertise/madvertisetest/supplemental/required-ext.json new file mode 100644 index 00000000000..8989a8dc4e1 --- /dev/null +++ b/adapters/madvertise/madvertisetest/supplemental/required-ext.json @@ -0,0 +1,19 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [{"w": 300, "h": 250}] + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "unexpected end of JSON input; ImpID=test-imp-id-1", + "comparison": "literal" + } + ] +} diff --git a/adapters/madvertise/madvertisetest/supplemental/required-zoneid-.json b/adapters/madvertise/madvertisetest/supplemental/required-zoneid-.json new file mode 100644 index 00000000000..bfff193d76d --- /dev/null +++ b/adapters/madvertise/madvertisetest/supplemental/required-zoneid-.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "ext.bidder.zoneId not provided; ImpID=test-imp-id-1", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/supplemental/response-204.json b/adapters/madvertise/madvertisetest/supplemental/response-204.json new file mode 100644 index 00000000000..f0162291d12 --- /dev/null +++ b/adapters/madvertise/madvertisetest/supplemental/response-204.json @@ -0,0 +1,189 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "tmax": 500, + "at": 2, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack", + "cat": [ + "IAB6", + "IAB9", + "IAB10", + "IAB12", + "IAB14", + "IAB20" + ] + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "instl": 0, + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ], + "w": 320, + "h": 50, + "api": [ + 3, + 5, + 6 + ] + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "body": { + "id": "test-request-id", + "tmax": 500, + "at": 2, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack", + "cat": [ + "IAB6", + "IAB9", + "IAB10", + "IAB12", + "IAB14", + "IAB20" + ] + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ], + "w": 320, + "h": 50, + "api": [ + 3, + 5, + 6 + ] + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/madvertise/madvertisetest/supplemental/response-400.json b/adapters/madvertise/madvertisetest/supplemental/response-400.json new file mode 100644 index 00000000000..a5af414d7a0 --- /dev/null +++ b/adapters/madvertise/madvertisetest/supplemental/response-400.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "tmax": 500, + "at": 2, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack", + "cat": [ + "IAB6", + "IAB9", + "IAB10", + "IAB12", + "IAB14", + "IAB20" + ] + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "instl": 0, + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ], + "w": 320, + "h": 50, + "api": [ + 3, + 5, + 6 + ] + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "body": { + "id": "test-request-id", + "tmax": 500, + "at": 2, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack", + "cat": [ + "IAB6", + "IAB9", + "IAB10", + "IAB12", + "IAB14", + "IAB20" + ] + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ], + "w": 320, + "h": 50, + "api": [ + 3, + 5, + 6 + ] + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "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/madvertise/madvertisetest/supplemental/response-500.json b/adapters/madvertise/madvertisetest/supplemental/response-500.json new file mode 100644 index 00000000000..8a4b0da4323 --- /dev/null +++ b/adapters/madvertise/madvertisetest/supplemental/response-500.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "tmax": 500, + "at": 2, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack", + "cat": [ + "IAB6", + "IAB9", + "IAB10", + "IAB12", + "IAB14", + "IAB20" + ] + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "instl": 0, + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ], + "w": 320, + "h": 50, + "api": [ + 3, + 5, + 6 + ] + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://mobile.mng-ads.com/bidrequest/1111111/banner", + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ], + "X-Openrtb-Version": [ + "2.5" + ], + "User-Agent": [ + "test-user-agent" + ], + "X-Forwarded-For": [ + "123.123.123.123" + ] + }, + "body": { + "id": "test-request-id", + "tmax": 500, + "at": 2, + "cur": [ + "EUR" + ], + "regs": { + "ext": { + "gdpr": 1 + } + }, + "user": { + "ext": { + "consent": "CO-X2XiO_eyUoAsAxBFRBECsAP_AAH_AAAqIGMgB7CpERSNAYWApAOMAKYhfQAACAGAABAYIASgBQQBAMJQEkGAIMAjAAAAKAAAEACQAAAAgCAGAAAAAAAAAAQCMAAAAABAAACAAAAASAAAACAAACACSCAAQjAAAAAEAgAAAAAIF5wJwAFgAPAAqABcADIAHAAQAAqABiADQAHkARABFACeAFKALgAugBiADaAG8AOYAfwBCAClAGqANcAdQA_QCBgEIAIsARwAq4BgADZAHyAP-Aj0BUwC6gF5gCAoAYBEAC4AKpCQAwCIAFwAVSIgAgABFQAQAAg" + } + }, + "app": { + "id": "9561622", + "bundle": "com.madvertise.bluestack", + "name": "FR_BlueStack_App_Android_MNG", + "storeurl": "https://play.google.com/store/apps/details?id=com.madvertise.bluestack", + "cat": [ + "IAB6", + "IAB9", + "IAB10", + "IAB12", + "IAB14", + "IAB20" + ] + }, + "device": { + "ip": "123.123.123.123", + "ua": "test-user-agent", + "dnt": 0, + "lmt": 0, + "ifa": "b879a967-cccc-bbbb-aaaa-1aa111111111", + "make": "Sony", + "model": "G8441", + "os": "Android", + "osv": "9.0", + "devicetype": 4, + "connectiontype": 6, + "js": 1, + "carrier": "Free", + "geo": { + "type": 2, + "country": "FRA" + } + }, + "imp": [ + { + "id": "test-imp-id", + "secure": 1, + "bidfloor": 3, + "bidfloorcur": "EUR", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ], + "w": 320, + "h": 50, + "api": [ + 3, + 5, + 6 + ] + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + } + ] + } + }, + "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/madvertise/madvertisetest/supplemental/unique-zone-id.json b/adapters/madvertise/madvertisetest/supplemental/unique-zone-id.json new file mode 100644 index 00000000000..68a56652dd7 --- /dev/null +++ b/adapters/madvertise/madvertisetest/supplemental/unique-zone-id.json @@ -0,0 +1,45 @@ +{ + "mockBidRequest": { + "id": "test-no-placement-id", + "imp": [ + { + "id": "test-imp-id-unique-id-1", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "/1111111/banner" + } + } + }, + { + "id": "test-imp-id-unique-id-2", + "banner": { + "format": [ + { + "w": 728, + "h": 90 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "/2222222/banner" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "There must be only one zone ID", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/madvertise/params_test.go b/adapters/madvertise/params_test.go new file mode 100644 index 00000000000..7c138bf48ac --- /dev/null +++ b/adapters/madvertise/params_test.go @@ -0,0 +1,55 @@ +package madvertise + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/Madvertise.json +// +// These also validate the format of the external API: request.imp[i].ext.Madvertise + +// TestValidParams makes sure that the Madvertise 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.BidderMadvertise, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected Madvertise params: %s \n Error: %s", validParam, err) + } + } +} + +// TestInvalidParams makes sure that the Madvertise 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.BidderMadvertise, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"zoneId":"/1111111/banner"}`, +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `[]`, + `{}`, + `{"zoneId":""}`, + `{"zoneId":/1111111}`, + `{"zoneId":/1111"}`, +} diff --git a/adapters/operaads/operaads.go b/adapters/operaads/operaads.go new file mode 100644 index 00000000000..890a15ddb5f --- /dev/null +++ b/adapters/operaads/operaads.go @@ -0,0 +1,215 @@ +package operaads + +import ( + "encoding/json" + "fmt" + "github.com/prebid/prebid-server/macros" + "net/http" + "text/template" + + "github.com/mxmCherry/openrtb/v15/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 { + epTemplate *template.Template +} + +// Builder builds a new instance of the operaads adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + epTemplate, err := template.New("endpoint").Parse(config.Endpoint) + if err != nil { + return nil, err + } + bidder := &adapter{ + epTemplate: epTemplate, + } + return bidder, nil +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + impCount := len(request.Imp) + requestData := make([]*adapters.RequestData, 0, impCount) + errs := []error{} + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + err := checkRequest(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + for _, imp := range request.Imp { + requestCopy := *request + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + var operaadsExt openrtb_ext.ImpExtOperaads + if err := json.Unmarshal(bidderExt.Bidder, &operaadsExt); err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + err := convertImpression(&imp) + if err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: err.Error(), + }) + continue + } + + imp.TagID = operaadsExt.PlacementID + + requestCopy.Imp = []openrtb2.Imp{imp} + reqJSON, err := json.Marshal(&requestCopy) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + macro := macros.EndpointTemplateParams{PublisherID: operaadsExt.PublisherID, AccountID: operaadsExt.EndpointID} + endpoint, err := macros.ResolveMacros(*a.epTemplate, ¯o) + if err != nil { + errs = append(errs, err) + continue + } + reqData := &adapters.RequestData{ + Method: http.MethodPost, + Uri: endpoint, + Body: reqJSON, + Headers: headers, + } + requestData = append(requestData, reqData) + } + return requestData, errs +} + +func checkRequest(request *openrtb2.BidRequest) error { + if request.Device == nil || len(request.Device.OS) == 0 { + return &errortypes.BadInput{ + Message: "Impression is missing device OS information", + } + } + + return nil +} + +func convertImpression(imp *openrtb2.Imp) error { + if imp.Banner != nil { + bannerCopy, err := convertBanner(imp.Banner) + if err != nil { + return err + } + imp.Banner = bannerCopy + } + if imp.Native != nil && imp.Native.Request != "" { + v := make(map[string]interface{}) + err := json.Unmarshal([]byte(imp.Native.Request), &v) + if err != nil { + return err + } + _, ok := v["native"] + if !ok { + body, err := json.Marshal(struct { + Native interface{} `json:"native"` + }{ + Native: v, + }) + if err != nil { + return err + } + native := *imp.Native + native.Request = string(body) + imp.Native = &native + } + } + return nil +} + +// make sure that banner has openrtb 2.3-compatible size information +func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) { + if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 { + if len(banner.Format) > 0 { + f := banner.Format[0] + + bannerCopy := *banner + + bannerCopy.W = openrtb2.Int64Ptr(f.W) + bannerCopy.H = openrtb2.Int64Ptr(f.H) + + return &bannerCopy, nil + } else { + return nil, &errortypes.BadInput{ + Message: "Size information missing for banner", + } + } + } + return banner, nil +} + +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 parsedResponse openrtb2.BidResponse + if err := json.Unmarshal(response.Body, &parsedResponse); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: err.Error(), + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + for _, sb := range parsedResponse.SeatBid { + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + if bid.Price != 0 { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.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 +} diff --git a/adapters/operaads/operaads_test.go b/adapters/operaads/operaads_test.go new file mode 100644 index 00000000000..eb4280b68e9 --- /dev/null +++ b/adapters/operaads/operaads_test.go @@ -0,0 +1,20 @@ +package operaads + +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.BidderOperaads, config.Adapter{ + Endpoint: "http://example.com/operaads/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "operaadstest", bidder) +} diff --git a/adapters/operaads/operaadstest/exemplary/native.json b/adapters/operaads/operaadstest/exemplary/native.json new file mode 100644 index 00000000000..4491bd150e4 --- /dev/null +++ b/adapters/operaads/operaadstest/exemplary/native.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + }, + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + }, + "httpcalls":[ + { + "expectedRequest":{ + "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "tagid":"s123456", + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + }, + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + } + }, + "mockResponse":{ + "status":200, + "body":{ + "id":"some-req-id", + "seatbid":[ + { + "bid":[ + { + "id":"928185755156387460", + "impid":"some-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\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/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=${AUCTION_PRICE}\"]}", + "adomain":[ + "example.com" + ], + "iurl":"http://example.com/cr?id=69595837", + "cid":"958", + "crid":"69595837", + "cat":[ + "IAB3-1" + ], + "ext":{ + + } + } + ], + "seat":"958" + } + ], + "bidid":"8141327771600527856", + "cur":"USD" + } + } + } + ], + "expectedBidResponses":[ + { + "currency":"USD", + "bids":[ + { + "bid":{ + "id":"928185755156387460", + "impid":"some-imp-id", + "price":1, + "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/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=${AUCTION_PRICE}\"]}", + "adid":"69595837", + "adomain":[ + "example.com" + ], + "iurl":"http://example.com/cr?id=69595837", + "cid":"958", + "crid":"69595837", + "cat":[ + "IAB3-1" + ], + "ext":{ + + } + }, + "type":"native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/operaads/operaadstest/exemplary/simple-banner.json b/adapters/operaads/operaadstest/exemplary/simple-banner.json new file mode 100644 index 00000000000..53b19c82c2a --- /dev/null +++ b/adapters/operaads/operaadstest/exemplary/simple-banner.json @@ -0,0 +1,156 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "secure": 1, + "ext": { + "bidder": { + "placementId": "s17890", + "endpointId": "ep19979", + "publisherId": "pub456" + } + } + } + ], + "device": { + "os": "android", + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375 + }, + "at": 1, + "tmax": 200, + "test": 1, + "source": { + "tid": "283746293874293" + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/operaads/ortb/v2/pub456?ep=ep19979", + "body": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "tagid": "s17890", + "banner": { + "h": 250, + "w": 300, + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "secure": 1, + "ext": { + "bidder": { + "placementId": "s17890", + "endpointId": "ep19979", + "publisherId": "pub456" + } + } + } + ], + "device": { + "os": "android", + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375 + }, + "at": 1, + "tmax": 200, + "test": 1, + "source": { + "tid": "283746293874293" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "1519967420713_259406708_583019428", + "seatbid": [ + { + "bid": [ + { + "price": 0.5, + "adm": "some-test-ad", + "impid": "1", + "auid": 46, + "id": "1", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "crid": "11_222222", + "w": 300 + } + ], + "seat": "51" + } + ], + "bidid":"8141327771600527856", + "cur":"USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency":"USD", + "bids": [ + { + "bid": { + "price": 0.5, + "adm": "some-test-ad", + "impid": "1", + "id": "1", + "h": 250, + "adomain": [ + "goodadvertiser.com" + ], + "crid": "11_222222", + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/operaads/operaadstest/exemplary/video.json b/adapters/operaads/operaadstest/exemplary/video.json new file mode 100644 index 00000000000..a76bbe5ccf8 --- /dev/null +++ b/adapters/operaads/operaadstest/exemplary/video.json @@ -0,0 +1,173 @@ +{ + "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": { + "bidder": { + "placementId":"s00000", + "endpointId":"ep00000", + "publisherId":"pub00000" + } + } + } + ], + "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", + "os": "android" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/operaads/ortb/v2/pub00000?ep=ep00000", + "body": { + "id": "test-video-request", + "imp": [ + { + "id": "test-video-imp", + "tagid": "s00000", + "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": { + "bidder": { + "placementId":"s00000", + "endpointId":"ep00000", + "publisherId":"pub00000" + } + } + } + ], + "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", + "os": "android" + }, + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-video-request", + "seatbid": [ + { + "seat": "958", + "bid": [ + { + "id": "7706636740145184841", + "impid": "test-video-imp", + "price": 0.5, + "adid": "29681110", + "adm": "some-test-ad", + "adomain": [ + "example.com" + ], + "crid": "29681110", + "h": 250, + "w": 300 + } + ] + } + ], + "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": [ + "example.com" + ], + "crid": "29681110", + "w": 300, + "h": 250 + }, + "type": "video" + } + ] + } + ] +} diff --git a/adapters/operaads/operaadstest/supplemental/badrequest.json b/adapters/operaads/operaadstest/supplemental/badrequest.json new file mode 100644 index 00000000000..ff3fe071c4a --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/badrequest.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + }, + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + }, + "httpcalls":[ + { + "expectedRequest":{ + "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "tagid":"s123456", + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + }, + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + } + }, + "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/operaads/operaadstest/supplemental/banner-size-miss.json b/adapters/operaads/operaadstest/supplemental/banner-size-miss.json new file mode 100644 index 00000000000..70ac350c2f0 --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/banner-size-miss.json @@ -0,0 +1,50 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "site": { + "domain": "good.site", + "page": "https://good.site/url", + "publisher": { + "id": "test-publisher-id" + }, + "ext": { + "amp": 0 + } + }, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [] + }, + "secure": 1, + "ext": { + "bidder": { + "placementId": "s17890", + "endpointId": "ep19979", + "publisherId": "pub456" + } + } + } + ], + "device": { + "os": "android", + "ua": "test-user-agent", + "ip": "123.123.123.123", + "h": 700, + "w": 375 + }, + "at": 1, + "tmax": 200, + "test": 1, + "source": { + "tid": "283746293874293" + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Size information missing for banner", + "comparison": "literal" + } + ] +} diff --git a/adapters/operaads/operaadstest/supplemental/miss-native.json b/adapters/operaads/operaadstest/supplemental/miss-native.json new file mode 100644 index 00000000000..918bbc4ded5 --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/miss-native.json @@ -0,0 +1,137 @@ +{ + "mockBidRequest":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "native":{ + "request":"{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}", + "ver":"1.1" + }, + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + }, + "httpcalls":[ + { + "expectedRequest":{ + "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "tagid":"s123456", + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + }, + "native":{ + "request":"{\"native\":{\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"img\":{\"hmin\":194,\"type\":3,\"wmin\":344},\"required\":1},{\"id\":3,\"img\":{\"h\":128,\"hmin\":80,\"type\":1,\"w\":128,\"wmin\":80},\"required\":1},{\"data\":{\"len\":90,\"type\":2},\"id\":4,\"required\":1},{\"data\":{\"len\":15,\"type\":12},\"id\":6}],\"layout\":3,\"ver\":\"1.1\"}}", + "ver":"1.1" + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + } + }, + "mockResponse":{ + "status":200, + "body":{ + "id":"some-req-id", + "seatbid":[ + { + "bid":[ + { + "id":"928185755156387460", + "impid":"some-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\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/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=${AUCTION_PRICE}\"]}", + "adomain":[ + "example.com" + ], + "iurl":"http://example.com/cr?id=69595837", + "cid":"958", + "crid":"69595837", + "cat":[ + "IAB3-1" + ], + "ext":{ + + } + } + ], + "seat":"958" + } + ], + "bidid":"8141327771600527856", + "cur":"USD" + } + } + } + ], + "expectedBidResponses":[ + { + "currency":"USD", + "bids":[ + { + "bid":{ + "id":"928185755156387460", + "impid":"some-imp-id", + "price":1, + "adm":"{\"assets\":[{\"id\": 2,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 344,\"h\": 194}},{\"id\": 3,\"img\":{\"url\":\"http://example.com/p/creative-image/5e/b6/de/c3/5eb6dec3-4854-4dcd-980a-347f36ab502e.jpg\",\"w\": 100,\"h\": 100}} ,{\"id\": 1,\"title\":{\"text\":\"This is an example Prebid Native creative\"}},{\"id\": 4,\"data\":{\"value\":\"This is a Prebid Native Creative. There are many like it, but this one is mine.\"}},{\"id\": 6,\"data\":{\"value\":\"Please.\"}} ],\"link\":{\"url\":\"http://example.com/click?AAAAAAAA8D8AAAAAAADwPwAAAAAAAAAAAAAAAAAA8D8AAAAAAADwPwhdYz3ZyNFNG3fXpZUyLXNZ0o5aAAAAACrElgC-AwAAvgMAAAIAAAC98iUEeP4QAAAAAABVU0QAVVNEAAEAAQARIAAAAAABAgQCAAAAAAEAhBaSXgAAAAA./pp=${AUCTION_PRICE}/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=${AUCTION_PRICE}\"]}", + "adid":"69595837", + "adomain":[ + "example.com" + ], + "iurl":"http://example.com/cr?id=69595837", + "cid":"958", + "crid":"69595837", + "cat":[ + "IAB3-1" + ], + "ext":{ + + } + }, + "type":"native" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/operaads/operaadstest/supplemental/missing-device.json b/adapters/operaads/operaadstest/supplemental/missing-device.json new file mode 100644 index 00000000000..3ba01d81632 --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/missing-device.json @@ -0,0 +1,33 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id-1", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": {} + } + } + ], + "site": { + "id": "siteID", + "publisher": { + "id": "1234" + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Impression is missing device OS information", + "comparison": "literal" + } + ] +} diff --git a/adapters/operaads/operaadstest/supplemental/nocontent.json b/adapters/operaads/operaadstest/supplemental/nocontent.json new file mode 100644 index 00000000000..d6baf35d4a6 --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/nocontent.json @@ -0,0 +1,82 @@ +{ + "mockBidRequest": { + "id": "some-req-id", + "imp": [ + { + "id": "some-imp-id", + "native": { + "request": "{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver": "1.1" + }, + "ext": { + "bidder": { + "placementId": "s123456", + "endpointId": "ep19978", + "publisherId": "pub123" + } + } + } + ], + "site": { + "domain": "example.com", + "page": "example.com" + }, + "device": { + "ip": "152.193.6.74", + "os": "android" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "8299345306627569435" + }, + "tmax": 500 + }, + "httpcalls": [ + { + "expectedRequest": { + "uri": "http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body": { + "id": "some-req-id", + "imp": [ + { + "id": "some-imp-id", + "tagid": "s123456", + "ext": { + "bidder": { + "placementId": "s123456", + "endpointId": "ep19978", + "publisherId": "pub123" + } + }, + "native": { + "request": "{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver": "1.1" + } + } + ], + "site": { + "domain": "example.com", + "page": "example.com" + }, + "device": { + "ip": "152.193.6.74", + "os": "android" + }, + "user": { + "id": "db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid": "8299345306627569435" + }, + "tmax": 500 + } + }, + "mockResponse": { + "status": 204, + "body": {} + } + } + ], + "expectedBidResponses": [ + ], + "expectedMakeBidsErrors": [ + ] +} \ No newline at end of file diff --git a/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json b/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json new file mode 100644 index 00000000000..a6c8c5052ae --- /dev/null +++ b/adapters/operaads/operaadstest/supplemental/unexcept-statuscode.json @@ -0,0 +1,84 @@ +{ + "mockBidRequest":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + }, + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + }, + "httpcalls":[ + { + "expectedRequest":{ + "uri":"http://example.com/operaads/ortb/v2/pub123?ep=ep19978", + "body":{ + "id":"some-req-id", + "imp":[ + { + "id":"some-imp-id", + "tagid":"s123456", + "ext":{ + "bidder":{ + "placementId":"s123456", + "endpointId":"ep19978", + "publisherId":"pub123" + } + }, + "native":{ + "request":"{\"native\":{\"ver\":\"1.1\",\"layout\":3,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":90}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":344,\"hmin\":194}},{\"id\":3,\"required\":1,\"img\":{\"type\":1,\"w\":128,\"wmin\":80,\"h\":128,\"hmin\":80}},{\"id\":4,\"required\":1,\"data\":{\"type\":2,\"len\":90}},{\"id\":6,\"data\":{\"type\":12,\"len\":15}}]}}", + "ver":"1.1" + } + } + ], + "site":{ + "domain":"example.com", + "page":"example.com" + }, + "device":{ + "ip":"152.193.6.74", + "os":"android" + }, + "user":{ + "id":"db089de9-a62e-4861-a881-0ff15e052516", + "buyeruid":"8299345306627569435" + }, + "tmax":500 + } + }, + "mockResponse":{ + "status":205, + "body":{} + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 205. Run with request.debug = 1 for more info", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/operaads/params_test.go b/adapters/operaads/params_test.go new file mode 100644 index 00000000000..e998127b001 --- /dev/null +++ b/adapters/operaads/params_test.go @@ -0,0 +1,54 @@ +package operaads + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/operaads.json +// +// These also validate the format of the external API: request.imp[i].ext.operaads + +// TestValidParams makes sure that the operaads 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.BidderOperaads, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected operaads params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the operaads 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.BidderOpenx, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"placementId": "s123", "endpointId": "ep12345", "publisherId": "pub12345"}`, +} + +var invalidParams = []string{ + `{"placementId": "s123"}`, + `{"endpointId": "ep12345"}`, + `{"publisherId": "pub12345"}`, + `{"placementId": "s123", "endpointId": "ep12345"}`, + `{"placementId": "s123", "publisherId": "pub12345"}`, + `{"endpointId": "ep12345", "publisherId": "pub12345"}`, + `{"placementId": "", "endpointId": "", "publisherId": ""}`, +} diff --git a/adapters/operaads/usersync.go b/adapters/operaads/usersync.go new file mode 100644 index 00000000000..aae6fb600e3 --- /dev/null +++ b/adapters/operaads/usersync.go @@ -0,0 +1,11 @@ +package operaads + +import ( + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" + "text/template" +) + +func NewOperaadsSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("operaads", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/operaads/usersync_test.go b/adapters/operaads/usersync_test.go new file mode 100644 index 00000000000..e9b402ac465 --- /dev/null +++ b/adapters/operaads/usersync_test.go @@ -0,0 +1,33 @@ +package operaads + +import ( + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/stretchr/testify/assert" +) + +func TestOperaadsSyncer(t *testing.T) { + syncURL := "https://t-odx.op-mobile.opera.com/pbs/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r=localhost%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewOperaadsSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "A", + Consent: "B", + }, + CCPA: ccpa.Policy{ + Consent: "C", + }}) + + assert.NoError(t, err) + assert.Equal(t, "https://t-odx.op-mobile.opera.com/pbs/sync?gdpr=A&gdpr_consent=B&r=localhost%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3DA%26gdpr_consent%3DB%26uid%3D%24%7BUID%7D", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/outbrain/outbrain.go b/adapters/outbrain/outbrain.go index 282a6d53aa0..6b121cb4732 100644 --- a/adapters/outbrain/outbrain.go +++ b/adapters/outbrain/outbrain.go @@ -43,8 +43,10 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte errs = append(errs, err) continue } - imp.TagID = outbrainExt.TagId - reqCopy.Imp[i] = imp + if outbrainExt.TagId != "" { + imp.TagID = outbrainExt.TagId + reqCopy.Imp[i] = imp + } } publisher := &openrtb2.Publisher{ diff --git a/adapters/outbrain/outbraintest/supplemental/general_params.json b/adapters/outbrain/outbraintest/supplemental/general_params.json new file mode 100644 index 00000000000..b2a547c8b4e --- /dev/null +++ b/adapters/outbrain/outbraintest/supplemental/general_params.json @@ -0,0 +1,139 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com" + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com/bid", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "tagid": "tag-id", + "ext": { + "bidder": { + "publisher": { + "id": "publisher-id" + } + } + } + } + ], + "bcat": ["bad-category"], + "badv": ["bad-advertiser"], + "site": { + "page": "http://example.com", + "publisher": { + "id": "publisher-id" + } + }, + "device": { + "ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36", + "h": 500, + "w": 1000 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5095a742-1c27-402b-ab6f-66b1bd53383b", + "seatbid": [ + { + "bid": [ + { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + } + ], + "seat": "acc-1876" + } + ], + "bidid": "43ccadf2-8b2e-11eb-b294-de4c49e99ff6", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test-request-id", + "impid": "test-imp-id", + "price": 1000, + "nurl": "http://example.com/win/1000", + "adm": "
ad
", + "adomain": [ + "example.com" + ], + "cid": "test-cid", + "crid": "test-crid", + "cat": [ + "IAB13-4" + ], + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/outbrain/outbraintest/supplemental/optional_params.json b/adapters/outbrain/outbraintest/supplemental/optional_params.json index a69ceaa0c85..d75875e0e70 100644 --- a/adapters/outbrain/outbraintest/supplemental/optional_params.json +++ b/adapters/outbrain/outbraintest/supplemental/optional_params.json @@ -12,6 +12,7 @@ } ] }, + "tagid": "should-be-overwritten-tagid", "ext": { "bidder": { "publisher": { @@ -26,6 +27,8 @@ } } ], + "bcat": ["should-be-overwritten-bcat"], + "badv": ["should-be-overwritten-badv"], "site": { "page": "http://example.com" }, diff --git a/adapters/pangle/pangle.go b/adapters/pangle/pangle.go index a4694c71559..da86a904e5c 100644 --- a/adapters/pangle/pangle.go +++ b/adapters/pangle/pangle.go @@ -16,9 +16,16 @@ type adapter struct { Endpoint string } +type NetworkIDs struct { + AppID string `json:"appid,omitempty"` + PlacementID string `json:"placementid,omitempty"` +} + type wrappedExtImpBidder struct { *adapters.ExtImpBidder - AdType int `json:"adtype,omitempty"` + AdType int `json:"adtype,omitempty"` + IsPrebid bool `json:"is_prebid,omitempty"` + NetworkIDs *NetworkIDs `json:"networkids,omitempty"` } type pangleBidExt struct { @@ -78,23 +85,34 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte errs = append(errs, fmt.Errorf("failed unmarshalling imp ext (err)%s", err.Error())) continue } + // get token & networkIDs + var bidderImpExt openrtb_ext.ImpExtPangle + if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil { + errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error())) + continue + } // detect and fill adtype - if adType := getAdType(imp, &impExt); adType == -1 { + adType := getAdType(imp, &impExt) + if adType == -1 { errs = append(errs, &errortypes.BadInput{Message: "not a supported adtype"}) continue - } else { - impExt.AdType = adType - if newImpExt, err := json.Marshal(impExt); err == nil { - imp.Ext = newImpExt - } else { - errs = append(errs, fmt.Errorf("failed re-marshalling imp ext with adtype")) - continue + } + // remarshal imp.ext + impExt.AdType = adType + impExt.IsPrebid = true + if len(bidderImpExt.AppID) > 0 && len(bidderImpExt.PlacementID) > 0 { + impExt.NetworkIDs = &NetworkIDs{ + AppID: bidderImpExt.AppID, + PlacementID: bidderImpExt.PlacementID, } + } else if len(bidderImpExt.AppID) > 0 || len(bidderImpExt.PlacementID) > 0 { + errs = append(errs, &errortypes.BadInput{Message: "only one of appid or placementid is provided"}) + continue } - // for setting token - var bidderImpExt openrtb_ext.ImpExtPangle - if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil { - errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error())) + if newImpExt, err := json.Marshal(impExt); err == nil { + imp.Ext = newImpExt + } else { + errs = append(errs, fmt.Errorf("failed re-marshalling imp ext")) continue } diff --git a/adapters/pangle/pangletest/exemplary/app_banner.json b/adapters/pangle/pangletest/exemplary/app_banner.json index 3fa410e5b7f..95694eb3552 100644 --- a/adapters/pangle/pangletest/exemplary/app_banner.json +++ b/adapters/pangle/pangletest/exemplary/app_banner.json @@ -62,6 +62,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/pangletest/exemplary/app_banner_instl.json b/adapters/pangle/pangletest/exemplary/app_banner_instl.json index 585d155a057..1f11229c01f 100644 --- a/adapters/pangle/pangletest/exemplary/app_banner_instl.json +++ b/adapters/pangle/pangletest/exemplary/app_banner_instl.json @@ -64,6 +64,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/pangletest/exemplary/app_native.json b/adapters/pangle/pangletest/exemplary/app_native.json index 2502baa4f9f..44d3f98d0b4 100644 --- a/adapters/pangle/pangletest/exemplary/app_native.json +++ b/adapters/pangle/pangletest/exemplary/app_native.json @@ -52,6 +52,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/pangletest/exemplary/app_video_instl.json b/adapters/pangle/pangletest/exemplary/app_video_instl.json index d5af392fd91..778baca5996 100644 --- a/adapters/pangle/pangletest/exemplary/app_video_instl.json +++ b/adapters/pangle/pangletest/exemplary/app_video_instl.json @@ -76,6 +76,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/pangletest/exemplary/app_video_rewarded.json b/adapters/pangle/pangletest/exemplary/app_video_rewarded.json index 2dbf08b944e..d23dff1e516 100644 --- a/adapters/pangle/pangletest/exemplary/app_video_rewarded.json +++ b/adapters/pangle/pangletest/exemplary/app_video_rewarded.json @@ -79,6 +79,7 @@ "is_rewarded_inventory": 1, "storedrequest": null }, + "is_prebid": true, "bidder": { "token": "123" } diff --git a/adapters/pangle/pangletest/params/race/banner.json b/adapters/pangle/pangletest/params/race/banner.json index afd1684b04e..4a9da04cf8e 100644 --- a/adapters/pangle/pangletest/params/race/banner.json +++ b/adapters/pangle/pangletest/params/race/banner.json @@ -1,3 +1,5 @@ { - "token": "123" + "token": "123", + "appid": "123", + "placementid": "123" } \ No newline at end of file diff --git a/adapters/pangle/pangletest/params/race/native.json b/adapters/pangle/pangletest/params/race/native.json index afd1684b04e..4a9da04cf8e 100644 --- a/adapters/pangle/pangletest/params/race/native.json +++ b/adapters/pangle/pangletest/params/race/native.json @@ -1,3 +1,5 @@ { - "token": "123" + "token": "123", + "appid": "123", + "placementid": "123" } \ No newline at end of file diff --git a/adapters/pangle/pangletest/params/race/video.json b/adapters/pangle/pangletest/params/race/video.json index afd1684b04e..4a9da04cf8e 100644 --- a/adapters/pangle/pangletest/params/race/video.json +++ b/adapters/pangle/pangletest/params/race/video.json @@ -1,3 +1,5 @@ { - "token": "123" + "token": "123", + "appid": "123", + "placementid": "123" } \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/appid_placementid_check.json b/adapters/pangle/pangletest/supplemental/appid_placementid_check.json new file mode 100644 index 00000000000..4808cb2bbb9 --- /dev/null +++ b/adapters/pangle/pangletest/supplemental/appid_placementid_check.json @@ -0,0 +1,155 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "token": "123", + "appid": "123456", + "placementid": "78910" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://pangle.io/api/get_ads", + "headers": { + "Content-Type": [ + "application/json" + ], + "TOKEN": [ + "123" + ] + }, + "body": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "adtype": 7, + "prebid": { + "bidder": null, + "is_rewarded_inventory": 1, + "storedrequest": null + }, + "is_prebid": true, + "networkids": { + "appid": "123456", + "placementid": "78910" + }, + "bidder": { + "token": "123", + "appid": "123456", + "placementid": "78910" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "seat-id", + "bid": [ + { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.500000, + "adm": "some-test-ad", + "crid": "test-crid", + "h": 250, + "w": 300, + "ext": { + "pangle": { + "adtype": 7 + } + } + } + ] + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "1", + "impid": "test-imp-id", + "adid": "11110126", + "price": 0.5, + "adm": "some-test-ad", + "crid": "test-crid", + "w": 300, + "h": 250, + "ext": { + "pangle": { + "adtype": 7 + } + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/missing_appid_or_placementid.json b/adapters/pangle/pangletest/supplemental/missing_appid_or_placementid.json new file mode 100644 index 00000000000..929b3c6ea88 --- /dev/null +++ b/adapters/pangle/pangletest/supplemental/missing_appid_or_placementid.json @@ -0,0 +1,47 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "app": { + "bundle": "com.prebid" + }, + "device": { + "ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab" + }, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576, + "ext": { + "foo": "bar" + } + }, + "ext": { + "prebid": { + "is_rewarded_inventory": 1 + }, + "bidder": { + "token": "123", + "placementid": "78910" + } + } + } + ] + }, + "httpCalls": [], + "expectedBidResponses": [], + "expectedMakeRequestsErrors": [ + { + "value": "only one of appid or placementid is provided", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/pangle/pangletest/supplemental/pangle_ext_check.json b/adapters/pangle/pangletest/supplemental/pangle_ext_check.json index c9dc5c28c6a..e80c199a180 100644 --- a/adapters/pangle/pangletest/supplemental/pangle_ext_check.json +++ b/adapters/pangle/pangletest/supplemental/pangle_ext_check.json @@ -62,6 +62,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/pangletest/supplemental/response_code_204.json b/adapters/pangle/pangletest/supplemental/response_code_204.json index 16c13bdf18f..a1e940a027e 100644 --- a/adapters/pangle/pangletest/supplemental/response_code_204.json +++ b/adapters/pangle/pangletest/supplemental/response_code_204.json @@ -62,6 +62,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/pangletest/supplemental/response_code_400.json b/adapters/pangle/pangletest/supplemental/response_code_400.json index 0a5810325e2..841dba8d009 100644 --- a/adapters/pangle/pangletest/supplemental/response_code_400.json +++ b/adapters/pangle/pangletest/supplemental/response_code_400.json @@ -62,6 +62,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/pangletest/supplemental/response_code_non_200.json b/adapters/pangle/pangletest/supplemental/response_code_non_200.json index 0d1447db1fe..b87e0f51c9e 100644 --- a/adapters/pangle/pangletest/supplemental/response_code_non_200.json +++ b/adapters/pangle/pangletest/supplemental/response_code_non_200.json @@ -62,6 +62,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json b/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json index 451c0ed1909..613694a98b1 100644 --- a/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json +++ b/adapters/pangle/pangletest/supplemental/unrecognized_adtype.json @@ -62,6 +62,7 @@ "bidder": { "token": "123" }, + "is_prebid": true, "prebid": null } } diff --git a/adapters/pangle/param_test.go b/adapters/pangle/param_test.go index 7b037bd52d6..5e1d30b3c7b 100644 --- a/adapters/pangle/param_test.go +++ b/adapters/pangle/param_test.go @@ -9,6 +9,7 @@ import ( var validParams = []string{ `{"token": "SomeAccessToken"}`, + `{"token": "SomeAccessToken", "appid": "12345", "placementid": "12345"}`, } var invalidParams = []string{ @@ -16,6 +17,12 @@ var invalidParams = []string{ `{"token": 42}`, `{"token": null}`, `{}`, + // appid & placementid + `{"appid": "12345", "placementid": "12345"}`, + `{"token": "SomeAccessToken", "appid": "12345"}`, + `{"token": "SomeAccessToken", "placementid": "12345"}`, + `{"token": "SomeAccessToken", "appid": 12345, "placementid": 12345}`, + `{"token": "SomeAccessToken", "appid": null, "placementid": null}`, } func TestValidParams(t *testing.T) { diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go index d787846c76b..f88a35bef8d 100644 --- a/adapters/pubmatic/pubmatic.go +++ b/adapters/pubmatic/pubmatic.go @@ -196,8 +196,8 @@ func (a *PubmaticAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder } pbReq.Imp[i].TagID = strings.TrimSpace(adSlot[0]) - pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) pbReq.Imp[i].Banner.W = openrtb2.Int64Ptr(int64(width)) + pbReq.Imp[i].Banner.H = openrtb2.Int64Ptr(int64(height)) if len(params.Keywords) != 0 { kvstr := prepareImpressionExt(params.Keywords) @@ -564,9 +564,8 @@ func validateAdSlot(adslot string, imp *openrtb2.Imp) error { } //In case of video, size could be derived from the player size - if imp.Banner != nil && height != 0 && width != 0 { - imp.Banner.H = openrtb2.Int64Ptr(int64(height)) - imp.Banner.W = openrtb2.Int64Ptr(int64(width)) + if imp.Banner != nil { + imp.Banner = assignBannerWidthAndHeight(imp.Banner, int64(width), int64(height)) } } else { return errors.New(fmt.Sprintf("Invalid adSlot %v", adSlotStr)) @@ -575,25 +574,23 @@ func validateAdSlot(adslot string, imp *openrtb2.Imp) error { return nil } -func assignBannerSize(banner *openrtb2.Banner) error { - if banner == nil { - return nil - } - +func assignBannerSize(banner *openrtb2.Banner) (*openrtb2.Banner, error) { if banner.W != nil && banner.H != nil { - return nil + return banner, nil } if len(banner.Format) == 0 { - return errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) + return nil, errors.New(fmt.Sprintf("No sizes provided for Banner %v", banner.Format)) } - banner.W = new(int64) - *banner.W = banner.Format[0].W - banner.H = new(int64) - *banner.H = banner.Format[0].H + return assignBannerWidthAndHeight(banner, banner.Format[0].W, banner.Format[0].H), nil +} - return nil +func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.Banner { + bannerCopy := *banner + bannerCopy.W = openrtb2.Int64Ptr(w) + bannerCopy.H = openrtb2.Int64Ptr(h) + return &bannerCopy } // parseImpressionObject parse the imp to get it ready to send to pubmatic @@ -635,14 +632,14 @@ func parseImpressionObject(imp *openrtb2.Imp, wrapExt *pubmaticWrapperExt, pubID } if imp.Banner != nil { - if err := assignBannerSize(imp.Banner); err != nil { + bannerCopy, err := assignBannerSize(imp.Banner) + if err != nil { return err } + imp.Banner = bannerCopy } - imp.Ext = nil - - impExtMap := make(map[string]interface{}) + impExtMap := make(map[string]interface{}, 0) if pubmaticExt.Keywords != nil && len(pubmaticExt.Keywords) != 0 { addKeywordsToExt(pubmaticExt.Keywords, impExtMap) } diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go index b0df1903a42..1a15f221130 100644 --- a/adapters/pubmatic/pubmatic_test.go +++ b/adapters/pubmatic/pubmatic_test.go @@ -73,7 +73,7 @@ func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) { var bids []openrtb2.Bid for i, imp := range breq.Imp { - bid := openrtb2.Bid{ + bids = append(bids, openrtb2.Bid{ ID: fmt.Sprintf("SeatID_%d", i), ImpID: imp.ID, Price: float64(int(rand.Float64()*1000)) / 100, @@ -83,9 +83,7 @@ func DummyPubMaticServer(w http.ResponseWriter, r *http.Request) { W: *imp.Banner.W, H: *imp.Banner.H, DealID: fmt.Sprintf("DealID_%d", i), - } - - bids = append(bids, bid) + }) } resp.SeatBid[0].Bid = bids diff --git a/adapters/pubmatic/pubmatictest/exemplary/simple-banner.json b/adapters/pubmatic/pubmatictest/exemplary/banner.json similarity index 100% rename from adapters/pubmatic/pubmatictest/exemplary/simple-banner.json rename to adapters/pubmatic/pubmatictest/exemplary/banner.json diff --git a/adapters/pubmatic/pubmatictest/params/race/video.json b/adapters/pubmatic/pubmatictest/params/race/video.json index 770a3e1d4ab..86317f86e4c 100644 --- a/adapters/pubmatic/pubmatictest/params/race/video.json +++ b/adapters/pubmatic/pubmatictest/params/race/video.json @@ -1,6 +1,8 @@ { "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", diff --git a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json index ce4e4f854b2..ee763ce1c35 100644 --- a/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json +++ b/adapters/pubmatic/pubmatictest/supplemental/gptSlotNameInImpExt.json @@ -42,7 +42,8 @@ "adserver": { "name": "gam", "adslot": "/1111/home" - } + }, + "pbadslot": "/2222/home" } } } @@ -172,4 +173,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/adapters/pubmatic/pubmatictest/supplemental/noAdSlot.json b/adapters/pubmatic/pubmatictest/supplemental/noAdSlot.json new file mode 100644 index 00000000000..a277ef530e4 --- /dev/null +++ b/adapters/pubmatic/pubmatictest/supplemental/noAdSlot.json @@ -0,0 +1,129 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }] + }, + "ext": { + "bidder": { + "publisherId": "999", + "keywords": [{ + "key": "pmZoneID", + "value": ["Zone1", "Zone2"] + }, + { + "key": "preference", + "value": ["sports", "movies"] + } + ], + "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", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }], + "w": 300, + "h": 250 + }, + "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" + } + }, + "ext": { + "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", + "w": 300, + "h": 250, + "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/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 89d69522fe8..e9627916cc6 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -91,21 +91,21 @@ type rubiconExtUserTpID struct { UID string `json:"uid"` } -type rubiconUserDataExt struct { - TaxonomyName string `json:"taxonomyname"` +type rubiconDataExt struct { + SegTax int `json:"segtax"` } type rubiconUserExt struct { - Consent string `json:"consent,omitempty"` - DigiTrust *openrtb_ext.ExtUserDigiTrust `json:"digitrust"` - Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` - TpID []rubiconExtUserTpID `json:"tpid,omitempty"` - RP rubiconUserExtRP `json:"rp"` - LiverampIdl string `json:"liveramp_idl,omitempty"` + Consent string `json:"consent,omitempty"` + Eids []openrtb_ext.ExtUserEid `json:"eids,omitempty"` + TpID []rubiconExtUserTpID `json:"tpid,omitempty"` + RP rubiconUserExtRP `json:"rp"` + LiverampIdl string `json:"liveramp_idl,omitempty"` } type rubiconSiteExtRP struct { - SiteID int `json:"site_id"` + SiteID int `json:"site_id"` + Target json.RawMessage `json:"target,omitempty"` } type rubiconSiteExt struct { @@ -676,7 +676,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) var err error - requestData := make([]*adapters.RequestData, 0, numRequests) headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") @@ -750,14 +749,29 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } + resolvedBidFloor, err := resolveBidFloor(thisImp.BidFloor, thisImp.BidFloorCur, reqInfo) + if err != nil { + errs = append(errs, &errortypes.BadInput{ + Message: fmt.Sprintf("Unable to convert provided bid floor currency from %s to USD", + thisImp.BidFloorCur), + }) + continue + } + + if resolvedBidFloor > 0 { + thisImp.BidFloorCur = "USD" + thisImp.BidFloor = resolvedBidFloor + } + if request.User != nil { userCopy := *request.User - userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: rubiconExt.Visitor}} - if err := updateUserExtWithIabAttribute(&userExtRP, userCopy.Data); err != nil { + target, err := updateExtWithIabAttribute(rubiconExt.Visitor, userCopy.Data, []int{4}) + if err != nil { errs = append(errs, err) continue } + userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: target}} if request.User.Ext != nil { var userExt *openrtb_ext.ExtUser @@ -768,9 +782,6 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } userExtRP.Consent = userExt.Consent - if userExt.DigiTrust != nil { - userExtRP.DigiTrust = userExt.DigiTrust - } userExtRP.Eids = userExt.Eids // set user.ext.tpid @@ -845,19 +856,32 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada thisImp.Video = nil } - siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: rubiconExt.AccountId}} if request.Site != nil { siteCopy := *request.Site - siteCopy.Ext, err = json.Marshal(&siteExt) + siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} + if siteCopy.Content != nil { + target, err := updateExtWithIabAttribute(nil, siteCopy.Content.Data, []int{1, 2}) + if err != nil { + errs = append(errs, err) + continue + } + siteExtRP.RP.Target = target + } + + siteCopy.Ext, err = json.Marshal(&siteExtRP) + if err != nil { + errs = append(errs, err) + continue + } + siteCopy.Publisher = &openrtb2.Publisher{} siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.Site = &siteCopy - } - if request.App != nil { + } else { appCopy := *request.App - appCopy.Ext, err = json.Marshal(&siteExt) + appCopy.Ext, err = json.Marshal(rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}}) appCopy.Publisher = &openrtb2.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.App = &appCopy @@ -893,41 +917,64 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada return requestData, errs } -func updateUserExtWithIabAttribute(userExtRP *rubiconUserExt, data []openrtb2.Data) error { - var segmentIdsToCopy = make([]string, 0) +func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.ExtraRequestInfo) (float64, error) { + if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != "USD" { + return reqInfo.ConvertCurrency(bidFloor, bidFloorCur, "USD") + } + + return bidFloor, nil +} + +func updateExtWithIabAttribute(target json.RawMessage, data []openrtb2.Data, segTaxes []int) (json.RawMessage, error) { + var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) + if len(segmentIdsToCopy) == 0 { + return target, nil + } + + extRPTarget := make(map[string]interface{}) + + if target != nil { + if err := json.Unmarshal(target, &extRPTarget); err != nil { + return nil, &errortypes.BadInput{Message: err.Error()} + } + } + + extRPTarget["iab"] = segmentIdsToCopy + + jsonTarget, err := json.Marshal(&extRPTarget) + if err != nil { + return nil, &errortypes.BadInput{Message: err.Error()} + } + return jsonTarget, nil +} + +func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { + var segmentIdsToCopy = make([]string, 0, len(data)) for _, dataRecord := range data { if dataRecord.Ext != nil { - var dataExtObject rubiconUserDataExt + var dataExtObject rubiconDataExt err := json.Unmarshal(dataRecord.Ext, &dataExtObject) if err != nil { continue } - if strings.EqualFold(dataExtObject.TaxonomyName, "iab") { + if contains(segTaxValues, dataExtObject.SegTax) { for _, segment := range dataRecord.Segment { segmentIdsToCopy = append(segmentIdsToCopy, segment.ID) } } } } + return segmentIdsToCopy +} - 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()} +func contains(s []int, e int) bool { + for _, a := range s { + if a == e { + return true } } - - userExtRPTarget["iab"] = segmentIdsToCopy - - if target, err := json.Marshal(&userExtRPTarget); err != nil { - return &errortypes.BadInput{Message: err.Error()} - } else { - userExtRP.RP.Target = target - } - - return nil + return false } func getTpIdsAndSegments(eids []openrtb_ext.ExtUserEid) (mappedRubiconUidsParam, []error) { diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index dc5b3a90423..28ddebbf5e3 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "encoding/json" + "errors" + "github.com/stretchr/testify/mock" "io/ioutil" "net/http" "net/http/httptest" @@ -572,6 +574,125 @@ func TestResolveVideoSizeId(t *testing.T) { } } +func TestOpenRTBRequestWithDifferentBidFloorAttributes(t *testing.T) { + testScenarios := []struct { + bidFloor float64 + bidFloorCur string + setMock func(m *mock.Mock) + expectedBidFloor float64 + expectedBidCur string + expectedErrors []error + }{ + { + bidFloor: 1, + bidFloorCur: "WRONG", + setMock: func(m *mock.Mock) { m.On("GetRate", "WRONG", "USD").Return(2.5, errors.New("some error")) }, + expectedBidFloor: 0, + expectedBidCur: "", + expectedErrors: []error{ + &errortypes.BadInput{Message: "Unable to convert provided bid floor currency from WRONG to USD"}, + }, + }, + { + bidFloor: 1, + bidFloorCur: "USD", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: 1, + expectedBidCur: "USD", + expectedErrors: nil, + }, + { + bidFloor: 1, + bidFloorCur: "EUR", + setMock: func(m *mock.Mock) { m.On("GetRate", "EUR", "USD").Return(1.2, nil) }, + expectedBidFloor: 1.2, + expectedBidCur: "USD", + expectedErrors: nil, + }, + { + bidFloor: 0, + bidFloorCur: "", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: 0, + expectedBidCur: "", + expectedErrors: nil, + }, + { + bidFloor: -1, + bidFloorCur: "CZK", + setMock: func(m *mock.Mock) {}, + expectedBidFloor: -1, + expectedBidCur: "CZK", + expectedErrors: nil, + }, + } + + for _, scenario := range testScenarios { + mockConversions := &mockCurrencyConversion{} + scenario.setMock(&mockConversions.Mock) + + extraRequestInfo := adapters.ExtraRequestInfo{ + CurrencyConversions: mockConversions, + } + + SIZE_ID := getTestSizes() + bidder := new(RubiconAdapter) + + request := &openrtb2.BidRequest{ + ID: "test-request-id", + Imp: []openrtb2.Imp{{ + ID: "test-imp-id", + BidFloorCur: scenario.bidFloorCur, + BidFloor: scenario.bidFloor, + Banner: &openrtb2.Banner{ + Format: []openrtb2.Format{ + SIZE_ID[15], + SIZE_ID[10], + }, + }, + Ext: json.RawMessage(`{"bidder": { + "zoneId": 8394, + "siteId": 283282, + "accountId": 7891 + }}`), + }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, + } + + reqs, errs := bidder.MakeRequests(request, &extraRequestInfo) + + mockConversions.AssertExpectations(t) + + if scenario.expectedErrors == nil { + rubiconReq := &openrtb2.BidRequest{} + if err := json.Unmarshal(reqs[0].Body, rubiconReq); err != nil { + t.Fatalf("Unexpected error while decoding request: %s", err) + } + assert.Equal(t, scenario.expectedBidFloor, rubiconReq.Imp[0].BidFloor) + assert.Equal(t, scenario.expectedBidCur, rubiconReq.Imp[0].BidFloorCur) + } else { + assert.Equal(t, scenario.expectedErrors, errs) + } + } +} + +type mockCurrencyConversion struct { + mock.Mock +} + +func (m mockCurrencyConversion) GetRate(from string, to string) (float64, error) { + args := m.Called(from, to) + return args.Get(0).(float64), args.Error(1) +} + +func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 { + args := m.Called() + return args.Get(0).(*map[string]map[string]float64) +} + func TestNoContentResponse(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) @@ -989,15 +1110,15 @@ func TestOpenRTBRequest(t *testing.T) { } }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, Device: &openrtb2.Device{ PxRatio: rubidata.devicePxRatio, }, User: &openrtb2.User{ - Ext: json.RawMessage(`{"digitrust": { - "id": "some-digitrust-id", - "keyv": 1, - "pref": 0 - }, + Ext: json.RawMessage(`{ "eids": [{ "source": "pubcid", "id": "2402fc76-7b39-4f0e-bfc2-060ef7693648" @@ -1071,10 +1192,6 @@ func TestOpenRTBRequest(t *testing.T) { t.Fatal("Error unmarshalling request.user.ext object.") } - assert.Equal(t, "some-digitrust-id", userExt.DigiTrust.ID, "DigiTrust ID id not as expected!") - assert.Equal(t, 1, userExt.DigiTrust.KeyV, "DigiTrust KeyV id not as expected!") - assert.Equal(t, 0, userExt.DigiTrust.Pref, "DigiTrust Pref id not as expected!") - assert.NotNil(t, userExt.Eids) assert.Equal(t, 1, len(userExt.Eids), "Eids values are not as expected!") assert.Contains(t, userExt.Eids, openrtb_ext.ExtUserEid{Source: "pubcid", ID: "2402fc76-7b39-4f0e-bfc2-060ef7693648"}) @@ -1108,6 +1225,10 @@ func TestOpenRTBRequestWithBannerImpEvenIfImpHasVideo(t *testing.T) { "visitor": {"key2" : "val2"} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1157,6 +1278,10 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { } }`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1212,6 +1337,10 @@ func TestOpenRTBRequestWithBadvOverflowed(t *testing.T) { } }`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1245,6 +1374,10 @@ func TestOpenRTBRequestWithSpecificExtUserEids(t *testing.T) { "accountId": 7891 }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, User: &openrtb2.User{ Ext: json.RawMessage(`{"eids": [ { @@ -1353,6 +1486,10 @@ func TestOpenRTBRequestWithVideoImpEvenIfImpHasBannerButAllRequiredVideoFields(t "video": {"size_id": 1} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, errs := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) @@ -1398,6 +1535,10 @@ func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) "video": {"size_id": 1} }}`), }}, + App: &openrtb2.App{ + ID: "com.test", + Name: "testApp", + }, } reqs, _ := bidder.MakeRequests(request, &adapters.ExtraRequestInfo{}) diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index b85c28def44..1daffe9b386 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -9,11 +9,50 @@ "id": "1", "bundle": "com.wls.testwlsapplication" }, + "site": { + "content": { + "data": [ + { + "ext": { + "segtax": 1 + }, + "segment": [ + { + "id": "segmentId1" + }, + { + "id": "segmentId2" + } + ] + }, + { + "ext": { + "segtax": "1" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": 2 + }, + "segment": [ + { + "id": "segmentId3" + } + ] + } + ] + } + }, "user": { "data": [ { "ext": { - "taxonomyname": "iab" + "segtax": 4 }, "segment": [ { @@ -23,7 +62,7 @@ }, { "ext": { - "taxonomyname": "someValue" + "segtax": "someValue" }, "segment": [ { @@ -33,7 +72,17 @@ }, { "ext": { - "taxonomyname": "IaB" + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 }, "segment": [ { @@ -43,13 +92,13 @@ }, { "ext": { - "taxonomyname": [ - "wrong iab type" + "segtax": [ + 4 ] }, "segment": [ { - "id": "shouldNotBeCopied2" + "id": "shouldNotBeCopied3" } ] } @@ -98,11 +147,69 @@ "ip": "123.123.123.123", "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" }, + "site": { + "content": { + "data": [ + { + "ext": { + "segtax": 1 + }, + "segment": [ + { + "id": "segmentId1" + }, + { + "id": "segmentId2" + } + ] + }, + { + "ext": { + "segtax": "1" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": 2 + }, + "segment": [ + { + "id": "segmentId3" + } + ] + } + ] + }, + "ext": { + "rp": { + "site_id": 113932, + "target": { + "iab": [ + "segmentId1", + "segmentId2", + "segmentId3" + ] + } + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, "user": { "data": [ { "ext": { - "taxonomyname": "iab" + "segtax": 4 }, "segment": [ { @@ -112,7 +219,7 @@ }, { "ext": { - "taxonomyname": "someValue" + "segtax": "someValue" }, "segment": [ { @@ -122,7 +229,17 @@ }, { "ext": { - "taxonomyname": "IaB" + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 }, "segment": [ { @@ -132,19 +249,18 @@ }, { "ext": { - "taxonomyname": [ - "wrong iab type" + "segtax": [ + 4 ] }, "segment": [ { - "id": "shouldNotBeCopied2" + "id": "shouldNotBeCopied3" } ] } ], "ext": { - "digitrust": null, "rp": { "target": { "iab": [ @@ -157,18 +273,6 @@ }, "app": { "id": "1", - "ext": { - "rp": { - "site_id": 113932 - } - }, - "publisher": { - "ext": { - "rp": { - "account_id": 1001 - } - } - }, "bundle": "com.wls.testwlsapplication" }, "imp": [ diff --git a/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json new file mode 100644 index 00000000000..0be214da4bc --- /dev/null +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content-data.json @@ -0,0 +1,289 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "site": { + "content": { + } + }, + "user": { + "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" + } + ] + } + ] + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "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": { + "content": { + }, + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "user": { + "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": { + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "ext": { + "rp": { + "size_id": 203 + } + }, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "rp": { + "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/supplemental/no-site-content.json b/adapters/rubicon/rubicontest/supplemental/no-site-content.json new file mode 100644 index 00000000000..2e830a2dd00 --- /dev/null +++ b/adapters/rubicon/rubicontest/supplemental/no-site-content.json @@ -0,0 +1,285 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "site": { + }, + "user": { + "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" + } + ] + } + ] + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "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": { + "ext": { + "rp": { + "site_id": 113932 + } + }, + "publisher": { + "ext": { + "rp": { + "account_id": 1001 + } + } + } + }, + "user": { + "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": { + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, + "app": { + "id": "1", + "bundle": "com.wls.testwlsapplication" + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "ext": { + "rp": { + "size_id": 203 + } + }, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "rp": { + "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/sa_lunamedia/params_test.go b/adapters/sa_lunamedia/params_test.go new file mode 100644 index 00000000000..bf7a1f493e6 --- /dev/null +++ b/adapters/sa_lunamedia/params_test.go @@ -0,0 +1,52 @@ +package salunamedia + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +var validParams = []string{ + `{ "key": "2", "type": "network"}`, + `{ "key": "1"}`, + `{ "key": "33232", "type": "publisher"}`, +} + +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.BidderSaLunaMedia, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected sa_lunamedia params: %s", validParam) + } + } +} + +var invalidParams = []string{ + ``, + `null`, + `true`, + `5`, + `[]`, + `{}`, + `{ "anyparam": "anyvalue" }`, + `{ "type": "network" }`, + `{ "key": "asddsfd", "type": "any"}`, +} + +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.BidderSaLunaMedia, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} diff --git a/adapters/sa_lunamedia/salunamedia.go b/adapters/sa_lunamedia/salunamedia.go new file mode 100644 index 00000000000..ea6e12b01d6 --- /dev/null +++ b/adapters/sa_lunamedia/salunamedia.go @@ -0,0 +1,132 @@ +package salunamedia + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb/v15/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" +) + +type adapter struct { + endpoint string +} + +type bidExt struct { + MediaType string `json:"mediaType"` +} + +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + endpoint: config.Endpoint, + } + return bidder, nil +} + +func (a *adapter) MakeRequests( + openRTBRequest *openrtb2.BidRequest, + reqInfo *adapters.ExtraRequestInfo, +) ( + requestsToBidder []*adapters.RequestData, + errs []error, +) { + + reqJSON, err := json.Marshal(openRTBRequest) + if err != nil { + return nil, []error{err} + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: http.MethodPost, + Body: reqJSON, + Uri: a.endpoint, + Headers: headers, + }}, nil +} + +func (a *adapter) MakeBids( + openRTBRequest *openrtb2.BidRequest, + requestToBidder *adapters.RequestData, + bidderRawResponse *adapters.ResponseData, +) ( + bidderResponse *adapters.BidderResponse, + errs []error, +) { + if bidderRawResponse.StatusCode == http.StatusNoContent { + return nil, nil + } + + if bidderRawResponse.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad Request. %s", string(bidderRawResponse.Body)), + }} + } + + if bidderRawResponse.StatusCode == http.StatusServiceUnavailable { + return nil, []error{&errortypes.BadInput{ + Message: "Bidder unavailable. Please contact the bidder support.", + }} + } + + if bidderRawResponse.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Status Code: [ %d ] %s", bidderRawResponse.StatusCode, string(bidderRawResponse.Body)), + }} + } + + responseBody := bidderRawResponse.Body + var bidResp openrtb2.BidResponse + if err := json.Unmarshal(responseBody, &bidResp); err != nil { + return nil, []error{err} + } + + if len(bidResp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid", + }} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(1) + + bids := bidResp.SeatBid[0].Bid + + if len(bids) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Empty SeatBid.Bids", + }} + } + + bid := bids[0] + + var bidExt bidExt + var bidType openrtb_ext.BidType + + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: "Missing BidExt", + }} + } + + bidType, err := getBidType(bidExt) + + if err != nil { + return nil, []error{err} + } + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + }) + return bidResponse, nil +} + +func getBidType(ext bidExt) (openrtb_ext.BidType, error) { + return openrtb_ext.ParseBidType(ext.MediaType) +} diff --git a/adapters/sa_lunamedia/salunamedia_test.go b/adapters/sa_lunamedia/salunamedia_test.go new file mode 100644 index 00000000000..f5d2058208e --- /dev/null +++ b/adapters/sa_lunamedia/salunamedia_test.go @@ -0,0 +1,18 @@ +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/stretchr/testify/assert" +) + +func TestJsonSamples(t *testing.T) { + bidder, buildErr := Builder(openrtb_ext.BidderSaLunaMedia, config.Adapter{ + Endpoint: "http://test.com/pserver"}) + + assert.NoError(t, buildErr) + adapterstest.RunJSONBidderTest(t, "salunamediatest", bidder) +} diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json b/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json new file mode 100644 index 00000000000..2ce4ad81106 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/banner.json @@ -0,0 +1,142 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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": "Test", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }], + "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": "Test", + "adomain": ["test.com"], + "cat": ["IAB1"], + "cid": "cid", + "crid": "crid", + "w": 300, + "h": 250, + "ext": { + "mediaType": "banner" + } + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/native.json b/adapters/sa_lunamedia/salunamediatest/exemplary/native.json new file mode 100644 index 00000000000..74d8940f0a1 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/native.json @@ -0,0 +1,132 @@ +{ + "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": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "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": { + "key": "test" + } + } + }], + "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", + "ext": { + "mediaType": "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", + "ext": { + "mediaType": "native" + } + }, + "type": "native" + }] + }] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/exemplary/video.json b/adapters/sa_lunamedia/salunamediatest/exemplary/video.json new file mode 100644 index 00000000000..9a042d726d9 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/exemplary/video.json @@ -0,0 +1,169 @@ +{ + "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": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "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": { + "key": "test" + } + } + } + ], + "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", + "ext": { + "mediaType": "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", + "ext": { + "mediaType": "video" + } + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/banner.json b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/native.json b/adapters/sa_lunamedia/salunamediatest/params/race/native.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/native.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/params/race/video.json b/adapters/sa_lunamedia/salunamediatest/params/race/video.json new file mode 100644 index 00000000000..c02a5de97a2 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "key": "test" +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json b/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json new file mode 100644 index 00000000000..6373207d481 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/bad-response.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json b/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json new file mode 100644 index 00000000000..8942b3be65a --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/empty-seatbid.json @@ -0,0 +1,102 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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": "Empty SeatBid", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json new file mode 100644 index 00000000000..042b96bde65 --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-204.json @@ -0,0 +1,92 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json new file mode 100644 index 00000000000..1ecdc46e5fa --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-400.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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": "Bad Request. \"The Key has a different ad format\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json b/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json new file mode 100644 index 00000000000..2590418a75f --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/status-503.json @@ -0,0 +1,98 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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 unavailable. Please contact the bidder support.", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json b/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json new file mode 100644 index 00000000000..a54737cafdb --- /dev/null +++ b/adapters/sa_lunamedia/salunamediatest/supplemental/unexpected-status.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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://test.com/pserver", + "body": { + "id": "id", + "imp": [{ + "id": "id", + "secure": 1, + "bidfloor": 0.01, + "bidfloorcur": "USD", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "key": "test" + } + } + }], + "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": "Status Code: [ 403 ] \"Access is denied\"", + "comparison": "literal" + } + ], + + "expectedBidResponses": [] +} \ No newline at end of file diff --git a/adapters/sa_lunamedia/usersync.go b/adapters/sa_lunamedia/usersync.go new file mode 100644 index 00000000000..f78b7944cb2 --- /dev/null +++ b/adapters/sa_lunamedia/usersync.go @@ -0,0 +1,12 @@ +package salunamedia + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSaLunamediaSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("salunamedia", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/sa_lunamedia/usersync_test.go b/adapters/sa_lunamedia/usersync_test.go new file mode 100644 index 00000000000..e3820fbc1af --- /dev/null +++ b/adapters/sa_lunamedia/usersync_test.go @@ -0,0 +1,33 @@ +package salunamedia + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestNewSaLunamediaSyncer(t *testing.T) { + syncURL := "https://test.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + syncer := NewSaLunamediaSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + Consent: "allGdpr", + }, + CCPA: ccpa.Policy{ + Consent: "1---", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "https://test.com/pserver?gdpr=0&gdpr_consent=allGdpr&ccpa=1---", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/sharethrough/butler.go b/adapters/sharethrough/butler.go index b34ae0844ab..b7dd5003e6a 100644 --- a/adapters/sharethrough/butler.go +++ b/adapters/sharethrough/butler.go @@ -21,6 +21,7 @@ const defaultTmax = 10000 // 10 sec type StrAdSeverParams struct { Pkey string BidID string + GPID string ConsentRequired bool ConsentString string USPrivacySignal string @@ -97,6 +98,11 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb2.Imp, request *open return nil, err } + var gpid string + if strImpParams.Data != nil && strImpParams.Data.PBAdSlot != "" { + gpid = strImpParams.Data.PBAdSlot + } + usPolicySignal := "" if usPolicy, err := ccpa.ReadFromRequest(request); err == nil { usPolicySignal = usPolicy.Consent @@ -107,6 +113,7 @@ func (s StrOpenRTBTranslator) requestFromOpenRTB(imp openrtb2.Imp, request *open Uri: s.UriHelper.buildUri(StrAdSeverParams{ Pkey: pKey, BidID: imp.ID, + GPID: gpid, ConsentRequired: s.Util.gdprApplies(request), ConsentString: userInfo.Consent, USPrivacySignal: usPolicySignal, @@ -191,6 +198,9 @@ func (h StrUriHelper) buildUri(params StrAdSeverParams) string { v := url.Values{} v.Set("placement_key", params.Pkey) v.Set("bidId", params.BidID) + if params.GPID != "" { + v.Set("gpid", params.GPID) + } v.Set("consent_required", fmt.Sprintf("%t", params.ConsentRequired)) v.Set("consent_string", params.ConsentString) if params.USPrivacySignal != "" { diff --git a/adapters/sharethrough/butler_test.go b/adapters/sharethrough/butler_test.go index fbef417e530..3d19eea9171 100644 --- a/adapters/sharethrough/butler_test.go +++ b/adapters/sharethrough/butler_test.go @@ -83,7 +83,7 @@ func TestSuccessRequestFromOpenRTB(t *testing.T) { "Generates the correct AdServer request from Imp (no user provided)": { inputImp: openrtb2.Imp{ ID: "abc", - Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true, "iframeSize": [10, 20], "bidfloor": 1.0} }`), + Ext: []byte(`{ "bidder": {"pkey": "pkey", "iframe": true, "iframeSize": [10, 20], "bidfloor": 1.0, "data": { "pbadslot": "adslot" } } }`), Banner: &openrtb2.Banner{ Format: []openrtb2.Format{{H: 30, W: 40}}, }, @@ -435,6 +435,7 @@ func TestBuildUri(t *testing.T) { inputParams: StrAdSeverParams{ Pkey: "pkey", BidID: "bid", + GPID: "gpid", ConsentRequired: true, ConsentString: "consent", USPrivacySignal: "ccpa", @@ -449,6 +450,7 @@ func TestBuildUri(t *testing.T) { "http://abc.com?", "placement_key=pkey", "bidId=bid", + "gpid=gpid", "consent_required=true", "consent_string=consent", "us_privacy=ccpa", diff --git a/adapters/smaato/image.go b/adapters/smaato/image.go index 582206ccb0c..a4dad157bd1 100644 --- a/adapters/smaato/image.go +++ b/adapters/smaato/image.go @@ -3,6 +3,7 @@ package smaato import ( "encoding/json" "fmt" + "github.com/prebid/prebid-server/errortypes" "net/url" "strings" ) @@ -22,32 +23,27 @@ type img struct { Ctaurl string `json:"ctaurl"` } -func extractAdmImage(adapterResponseAdm string) (string, error) { - var imgMarkup string - var err error - +func extractAdmImage(adMarkup string) (string, error) { var imageAd imageAd - err = json.Unmarshal([]byte(adapterResponseAdm), &imageAd) - var image = imageAd.Image - - if err == nil { - var clickEvent strings.Builder - var impressionTracker strings.Builder - - for _, clicktracker := range image.Clicktrackers { - clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'.replace(/\\+/g, ' ')), " + - "{cache: 'no-cache'});") + if err := json.Unmarshal([]byte(adMarkup), &imageAd); err != nil { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup), } + } - for _, impression := range image.Impressiontrackers { - - impressionTracker.WriteString(fmt.Sprintf(``, impression)) - } + var clickEvent strings.Builder + for _, clicktracker := range imageAd.Image.Clicktrackers { + clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'.replace(/\\+/g, ' ')), " + + "{cache: 'no-cache'});") + } - imgMarkup = fmt.Sprintf(`
%s
`, - &clickEvent, url.QueryEscape(image.Img.Ctaurl), image. - Img.URL, image.Img.W, image.Img. - H, &impressionTracker) + var impressionTracker strings.Builder + for _, impression := range imageAd.Image.Impressiontrackers { + impressionTracker.WriteString(fmt.Sprintf(``, impression)) } - return imgMarkup, err + + imageAdMarkup := fmt.Sprintf(`
%s
`, + &clickEvent, url.QueryEscape(imageAd.Image.Img.Ctaurl), imageAd.Image.Img.URL, imageAd.Image.Img.W, imageAd.Image.Img.H, &impressionTracker) + + return imageAdMarkup, nil } diff --git a/adapters/smaato/image_test.go b/adapters/smaato/image_test.go index 5f39c857201..1ba99ddd7c5 100644 --- a/adapters/smaato/image_test.go +++ b/adapters/smaato/image_test.go @@ -1,44 +1,51 @@ package smaato import ( + "github.com/stretchr/testify/assert" "testing" ) -func TestRenderAdMarkup(t *testing.T) { - type args struct { - adType adMarkupType - adapterResponseAdm string - } - expectedResult := `
` + - `` + - `` + - `
` - +func TestExtractAdmImage(t *testing.T) { tests := []struct { - testName string - args args - result string + testName string + adMarkup string + expectedAdMarkup string + expectedError string }{ - {"imageTest", args{"Img", - "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\"," + + { + testName: "extract image", + adMarkup: "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\"," + "\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"}," + "\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," + - "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}"}, - expectedResult, + "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + expectedAdMarkup: `
` + + `` + + `` + + `
`, + expectedError: "", + }, + { + testName: "invalid adMarkup", + adMarkup: "{", + expectedAdMarkup: "", + expectedError: "Invalid ad markup {.", }, } + for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - got, err := renderAdMarkup(tt.args.adType, tt.args.adapterResponseAdm) - if err != nil { - t.Errorf("error rendering ad markup: %v", err) - } - if got != tt.result { - t.Errorf("renderAdMarkup() got = %v, result %v", got, tt.result) + adMarkup, err := extractAdmImage(tt.adMarkup) + + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + } else { + assert.NoError(t, err) } + + assert.Equal(t, tt.expectedAdMarkup, adMarkup) }) } } diff --git a/adapters/smaato/params_test.go b/adapters/smaato/params_test.go index 6c71cbe75c6..2e29550a394 100644 --- a/adapters/smaato/params_test.go +++ b/adapters/smaato/params_test.go @@ -41,6 +41,8 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"publisherId":"test-id-1234-smaato","adspaceId": "1123581321"}`, + `{"publisherId":"test-id-1234-smaato","adbreakId": "4123581321"}`, + `{"publisherId":"test-id-1234-smaato","adspaceId": "1123581321","adbreakId": "4123581321"}`, } var invalidParams = []string{ diff --git a/adapters/smaato/richmedia.go b/adapters/smaato/richmedia.go index 1c94a3555c1..a8865361d38 100644 --- a/adapters/smaato/richmedia.go +++ b/adapters/smaato/richmedia.go @@ -3,6 +3,7 @@ package smaato import ( "encoding/json" "fmt" + "github.com/prebid/prebid-server/errortypes" "net/url" "strings" ) @@ -22,31 +23,27 @@ type richmedia struct { Clicktrackers []string `json:"clicktrackers"` } -func extractAdmRichMedia(adapterResponseAdm string) (string, error) { - var richMediaMarkup string - var err error - +func extractAdmRichMedia(adMarkup string) (string, error) { var richMediaAd richMediaAd - err = json.Unmarshal([]byte(adapterResponseAdm), &richMediaAd) - var richMedia = richMediaAd.RichMedia - - if err == nil { - var clickEvent strings.Builder - var impressionTracker strings.Builder - - for _, clicktracker := range richMedia.Clicktrackers { - clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'), " + - "{cache: 'no-cache'});") + if err := json.Unmarshal([]byte(adMarkup), &richMediaAd); err != nil { + return "", &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Invalid ad markup %s.", adMarkup), } - for _, impression := range richMedia.Impressiontrackers { + } - impressionTracker.WriteString(fmt.Sprintf(``, impression)) - } + var clickEvent strings.Builder + var impressionTracker strings.Builder - richMediaMarkup = fmt.Sprintf(`
%s%s
`, - &clickEvent, - richMedia.MediaData.Content, - &impressionTracker) + for _, clicktracker := range richMediaAd.RichMedia.Clicktrackers { + clickEvent.WriteString("fetch(decodeURIComponent('" + url.QueryEscape(clicktracker) + "'), " + + "{cache: 'no-cache'});") + } + for _, impression := range richMediaAd.RichMedia.Impressiontrackers { + impressionTracker.WriteString(fmt.Sprintf(``, impression)) } - return richMediaMarkup, err + + richmediaAdMarkup := fmt.Sprintf(`
%s%s
`, + &clickEvent, richMediaAd.RichMedia.MediaData.Content, &impressionTracker) + + return richmediaAdMarkup, nil } diff --git a/adapters/smaato/richmedia_test.go b/adapters/smaato/richmedia_test.go index 20fa1ba353c..eff559852be 100644 --- a/adapters/smaato/richmedia_test.go +++ b/adapters/smaato/richmedia_test.go @@ -1,39 +1,48 @@ package smaato import ( + "github.com/stretchr/testify/assert" "testing" ) func TestExtractAdmRichMedia(t *testing.T) { - type args struct { - adType adMarkupType - adapterResponseAdm string - } - expectedResult := `
hello
` + - `
` tests := []struct { - testName string - args args - result string + testName string + adMarkup string + expectedAdMarkup string + expectedError string }{ - {"richmediaTest", args{"Richmedia", "{\"richmedia\":{\"mediadata\":{\"content\":\"
hello
\"," + - "" + "\"w\":350," + - "\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," + - "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}"}, - expectedResult, + { + testName: "extract richmedia", + adMarkup: "{\"richmedia\":{\"mediadata\":{\"content\":\"
hello
\"," + + "" + "\"w\":350," + + "\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"]," + + "\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + expectedAdMarkup: `
hello
` + + `
`, + expectedError: "", + }, + { + testName: "invalid adMarkup", + adMarkup: "{", + expectedAdMarkup: "", + expectedError: "Invalid ad markup {.", }, } + for _, tt := range tests { t.Run(tt.testName, func(t *testing.T) { - got, err := renderAdMarkup(tt.args.adType, tt.args.adapterResponseAdm) - if err != nil { - t.Errorf("error rendering ad markup: %v", err) - } - if got != tt.result { - t.Errorf("renderAdMarkup() got = %v, result %v", got, tt.result) + adMarkup, err := extractAdmRichMedia(tt.adMarkup) + + if tt.expectedError != "" { + assert.EqualError(t, err, tt.expectedError) + } else { + assert.NoError(t, err) } + + assert.Equal(t, tt.expectedAdMarkup, adMarkup) }) } } diff --git a/adapters/smaato/smaato.go b/adapters/smaato/smaato.go index 9aea2e1e614..c84dd356a59 100644 --- a/adapters/smaato/smaato.go +++ b/adapters/smaato/smaato.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" "github.com/buger/jsonparser" @@ -11,10 +12,12 @@ import ( "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" ) -const clientVersion = "prebid_server_0.2" +const clientVersion = "prebid_server_0.4" type adMarkupType string @@ -24,23 +27,20 @@ const ( smtAdTypeVideo adMarkupType = "Video" ) -// SmaatoAdapter describes a Smaato prebid server adapter. -type SmaatoAdapter struct { - URI string -} - -//userExt defines User.Ext object for Smaato -type userExt struct { - Data userExtData `json:"data"` +// adapter describes a Smaato prebid server adapter. +type adapter struct { + clock timeutil.Time + endpoint string } +// userExtData defines User.Ext.Data object for Smaato type userExtData struct { Keywords string `json:"keywords"` Gender string `json:"gender"` Yob int64 `json:"yob"` } -//userExt defines Site.Ext object for Smaato +// siteExt defines Site.Ext object for Smaato type siteExt struct { Data siteExtData `json:"data"` } @@ -49,189 +49,253 @@ type siteExtData struct { Keywords string `json:"keywords"` } +// bidRequestExt defines BidRequest.Ext object for Smaato +type bidRequestExt struct { + Client string `json:"client"` +} + +// bidExt defines Bid.Ext object for Smaato +type bidExt struct { + Duration int `json:"duration"` +} + +// videoExt defines Video.Ext object for Smaato +type videoExt struct { + Context string `json:"context,omitempty"` +} + // Builder builds a new instance of the Smaato adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { - bidder := &SmaatoAdapter{ - URI: config.Endpoint, + bidder := &adapter{ + clock: &timeutil.RealTime{}, + endpoint: config.Endpoint, } return bidder, nil } // MakeRequests makes the HTTP requests which should be made to fetch bids. -func (a *SmaatoAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - errs := make([]error, 0, len(request.Imp)) +func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { if len(request.Imp) == 0 { - errs = append(errs, &errortypes.BadInput{Message: "no impressions in bid request"}) - return nil, errs + return nil, []error{&errortypes.BadInput{Message: "No impressions in bid request."}} } - // Use bidRequestExt of first imp to retrieve params which are valid for all imps, e.g. publisherId - publisherID, err := jsonparser.GetString(request.Imp[0].Ext, "bidder", "publisherId") - if err != nil { - errs = append(errs, err) - return nil, errs + // set data in request that is common for all requests + if err := prepareCommonRequest(request); err != nil { + return nil, []error{err} } - for i := 0; i < len(request.Imp); i++ { - err := parseImpressionObject(&request.Imp[i]) - // If the parsing is failed, remove imp and add the error. - if err != nil { - errs = append(errs, err) - request.Imp = append(request.Imp[:i], request.Imp[i+1:]...) - i-- + isVideoEntryPoint := reqInfo.PbsEntryPoint == metrics.ReqTypeVideo + + if isVideoEntryPoint { + return adapter.makePodRequests(request) + } else { + return adapter.makeIndividualRequests(request) + } +} + +// MakeBids unpacks the server's response into Bids. +func (adapter *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.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) + + var errors []error + for _, seatBid := range bidResp.SeatBid { + for i := 0; i < len(seatBid.Bid); i++ { + bid := seatBid.Bid[i] + + adMarkupType, err := getAdMarkupType(response, bid.AdM) + if err != nil { + errors = append(errors, err) + continue + } + + bid.AdM, err = renderAdMarkup(adMarkupType, bid.AdM) + if err != nil { + errors = append(errors, err) + continue + } + + bidType, err := convertAdMarkupTypeToMediaType(adMarkupType) + if err != nil { + errors = append(errors, err) + continue + } + + bidVideo, err := buildBidVideo(&bid, bidType) + if err != nil { + errors = append(errors, err) + continue + } + + bid.Exp = adapter.getTTLFromHeaderOrDefault(response) + + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: bidType, + BidVideo: bidVideo, + }) } } + return bidResponse, errors +} - if request.Site != nil { - siteCopy := *request.Site - siteCopy.Publisher = &openrtb2.Publisher{ID: publisherID} +func (adapter *adapter) makeIndividualRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { + imps := request.Imp - if request.Site.Ext != nil { - var siteExt siteExt - err := json.Unmarshal([]byte(request.Site.Ext), &siteExt) + requests := make([]*adapters.RequestData, 0, len(imps)) + errors := make([]error, 0, len(imps)) + + for _, imp := range imps { + impsByMediaType, err := splitImpressionsByMediaType(&imp) + if err != nil { + errors = append(errors, err) + continue + } + + for _, impByMediaType := range impsByMediaType { + request.Imp = []openrtb2.Imp{impByMediaType} + if err := prepareIndividualRequest(request); err != nil { + errors = append(errors, err) + continue + } + + requestData, err := adapter.makeRequest(request) if err != nil { - errs = append(errs, err) - return nil, errs + errors = append(errors, err) + continue } - siteCopy.Keywords = siteExt.Data.Keywords - siteCopy.Ext = nil + + requests = append(requests, requestData) } - request.Site = &siteCopy } - if request.App != nil { - appCopy := *request.App - appCopy.Publisher = &openrtb2.Publisher{ID: publisherID} + return requests, errors +} - request.App = &appCopy +func splitImpressionsByMediaType(imp *openrtb2.Imp) ([]openrtb2.Imp, error) { + if imp.Banner == nil && imp.Video == nil { + return nil, &errortypes.BadInput{Message: "Invalid MediaType. Smaato only supports Banner and Video."} } - if request.User != nil && request.User.Ext != nil { - var userExt userExt - var userExtRaw map[string]json.RawMessage + imps := make([]openrtb2.Imp, 0, 2) - rawExtErr := json.Unmarshal(request.User.Ext, &userExtRaw) - if rawExtErr != nil { - errs = append(errs, rawExtErr) - return nil, errs - } + if imp.Banner != nil { + impCopy := *imp + impCopy.Video = nil + imps = append(imps, impCopy) + } + + if imp.Video != nil { + imp.Banner = nil + imps = append(imps, *imp) + } + + return imps, nil +} + +func (adapter *adapter) makePodRequests(request *openrtb2.BidRequest) ([]*adapters.RequestData, []error) { + pods, orderedKeys, errors := groupImpressionsByPod(request.Imp) + requests := make([]*adapters.RequestData, 0, len(pods)) - userExtErr := json.Unmarshal([]byte(request.User.Ext), &userExt) - if userExtErr != nil { - errs = append(errs, userExtErr) - return nil, errs + for _, key := range orderedKeys { + request.Imp = pods[key] + + if err := preparePodRequest(request); err != nil { + errors = append(errors, err) + continue } - userCopy := *request.User - extractUserExtAttributes(userExt, &userCopy) - delete(userExtRaw, "data") - userCopy.Ext, err = json.Marshal(userExtRaw) + requestData, err := adapter.makeRequest(request) if err != nil { - errs = append(errs, err) - return nil, errs + errors = append(errors, err) + continue } - request.User = &userCopy - } - // Setting ext client info - type bidRequestExt struct { - Client string `json:"client"` - } - request.Ext, err = json.Marshal(bidRequestExt{Client: clientVersion}) - if err != nil { - errs = append(errs, err) - return nil, errs + requests = append(requests, requestData) } + + return requests, errors +} + +func (adapter *adapter) makeRequest(request *openrtb2.BidRequest) (*adapters.RequestData, error) { reqJSON, err := json.Marshal(request) if err != nil { - errs = append(errs, err) - return nil, errs + return nil, err } - uri := a.URI - headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") - return []*adapters.RequestData{{ + return &adapters.RequestData{ Method: "POST", - Uri: uri, + Uri: adapter.endpoint, Body: reqJSON, Headers: headers, - }}, errs + }, nil } -// MakeBids unpacks the server's response into Bids. -func (a *SmaatoAdapter) 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{fmt.Errorf("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} +func getAdMarkupType(response *adapters.ResponseData, adMarkup string) (adMarkupType, error) { + if admType := adMarkupType(response.Headers.Get("X-Smt-Adtype")); admType != "" { + return admType, nil + } else if strings.HasPrefix(adMarkup, `{"image":`) { + return smtAdTypeImg, nil + } else if strings.HasPrefix(adMarkup, `{"richmedia":`) { + return smtAdTypeRichmedia, nil + } else if strings.HasPrefix(adMarkup, ` 0 { + primaryCategory = bid.Cat[0] } + + var bidExt bidExt + if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { + return nil, &errortypes.BadServerResponse{Message: "Invalid bid.ext."} + } + + return &openrtb_ext.ExtBidPrebidVideo{ + Duration: bidExt.Duration, + PrimaryCategory: primaryCategory, + }, nil } diff --git a/adapters/smaato/smaato_test.go b/adapters/smaato/smaato_test.go index c7c4a65017f..0012bd6158d 100644 --- a/adapters/smaato/smaato_test.go +++ b/adapters/smaato/smaato_test.go @@ -1,7 +1,12 @@ package smaato import ( + "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/adapters" + "github.com/stretchr/testify/assert" "testing" + "time" "github.com/prebid/prebid-server/adapters/adapterstest" "github.com/prebid/prebid-server/config" @@ -16,5 +21,93 @@ func TestJsonSamples(t *testing.T) { t.Fatalf("Builder returned unexpected error %v", buildErr) } + adapter, _ := bidder.(*adapter) + assert.NotNil(t, adapter.clock) + adapter.clock = &mockTime{time: time.Date(2021, 6, 25, 10, 00, 0, 0, time.UTC)} + adapterstest.RunJSONBidderTest(t, "smaatotest", bidder) } + +func TestVideoWithCategoryAndDuration(t *testing.T) { + bidder := &adapter{} + + mockedReq := &openrtb2.BidRequest{ + Imp: []openrtb2.Imp{{ + ID: "1_1", + Video: &openrtb2.Video{ + W: 640, + H: 360, + MIMEs: []string{"video/mp4"}, + MaxDuration: 60, + Protocols: []openrtb2.Protocol{2, 3, 5, 6}, + }, + Ext: json.RawMessage( + `{ + "bidder": { + "publisherId": "12345" + "adbreakId": "4123456" + } + }`, + )}, + }, + } + mockedExtReq := &adapters.RequestData{} + mockedBidResponse := &openrtb2.BidResponse{ + ID: "some-id", + SeatBid: []openrtb2.SeatBid{{ + Seat: "some-seat", + Bid: []openrtb2.Bid{{ + ID: "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + ImpID: "1_1", + Price: 0.01, + AdM: "", + Cat: []string{"IAB1"}, + Ext: json.RawMessage( + `{ + "duration": 5 + }`, + ), + }}, + }}, + } + body, _ := json.Marshal(mockedBidResponse) + mockedRes := &adapters.ResponseData{ + StatusCode: 200, + Body: body, + } + + expectedBidCount := 1 + expectedBidType := openrtb_ext.BidTypeVideo + expectedBidDuration := 5 + expectedBidCategory := "IAB1" + expectedErrorCount := 0 + + bidResponse, errors := bidder.MakeBids(mockedReq, mockedExtReq, mockedRes) + + if len(bidResponse.Bids) != expectedBidCount { + t.Errorf("should have 1 bid, bids=%v", bidResponse.Bids) + } + if bidResponse.Bids[0].BidType != expectedBidType { + t.Errorf("bid type should be video, bidType=%s", bidResponse.Bids[0].BidType) + } + if bidResponse.Bids[0].BidVideo.Duration != expectedBidDuration { + t.Errorf("video duration should be set") + } + if bidResponse.Bids[0].BidVideo.PrimaryCategory != expectedBidCategory { + t.Errorf("bid category should be set") + } + if bidResponse.Bids[0].Bid.Cat[0] != expectedBidCategory { + t.Errorf("bid category should be set") + } + if len(errors) != expectedErrorCount { + t.Errorf("should not have any errors, errors=%v", errors) + } +} + +type mockTime struct { + time time.Time +} + +func (mt *mockTime) Now() time.Time { + return mt.time +} diff --git a/adapters/smaato/smaatotest/exemplary/multiple-impressions.json b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json new file mode 100644 index 00000000000..c30a9a6a39e --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/multiple-impressions.json @@ -0,0 +1,358 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "bidfloor": 0.00123, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + }, + { + "id": "postbid_iframe", + "video": { + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ], + "ext": { + "rewarded": 0 + } + }, + "bidfloor": 0.00456, + "ext": { + "bidder": { + "publisherId": "1100042526", + "adspaceId": "130563104" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "bidfloor": 0.00123, + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "postbid_iframe", + "tagid": "130563104", + "bidfloor": 0.00456, + "video": { + "w": 1024, + "h": 768, + "ext": { + "rewarded": 0 + }, + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042526" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6524", + "crid": "CR69382", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "postbid_iframe", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6524", + "crid": "CR69382", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "postbid_iframe", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/multiple-media-types.json b/adapters/smaato/smaatotest/exemplary/multiple-media-types.json new file mode 100644 index 00000000000..a7d97666778 --- /dev/null +++ b/adapters/smaato/smaatotest/exemplary/multiple-media-types.json @@ -0,0 +1,348 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "video": { + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ], + "ext": { + "rewarded": 0 + } + }, + "bidfloor": 0.00123, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "bidfloor": 0.00123, + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "bidfloor": 0.00123, + "video": { + "w": 1024, + "h": 768, + "ext": { + "rewarded": 0 + }, + "mimes": [ + "video/mp4", + "video/quicktime", + "video/3gpp", + "video/x-m4v" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json index 8194f568c28..cd29d93a34d 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-app.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-app.json @@ -160,7 +160,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -215,7 +215,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json index 46722c4ff71..8ddc9a7273f 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia-app.json @@ -164,7 +164,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -219,7 +219,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json index 1018dbc39ac..f0fe35ff206 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner-richMedia.json @@ -129,7 +129,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -184,7 +184,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/exemplary/simple-banner.json b/adapters/smaato/smaatotest/exemplary/simple-banner.json index 0ba4050a143..babce4f892d 100644 --- a/adapters/smaato/smaatotest/exemplary/simple-banner.json +++ b/adapters/smaato/smaatotest/exemplary/simple-banner.json @@ -31,6 +31,7 @@ } ] }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -80,6 +81,7 @@ { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "tagid": "130563103", + "bidfloor": 0.00123, "banner": { "h": 50, "w": 320, @@ -125,7 +127,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -180,7 +182,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/exemplary/video-app.json b/adapters/smaato/smaatotest/exemplary/video-app.json index bf939eb078a..fc94c2d43cb 100644 --- a/adapters/smaato/smaatotest/exemplary/video-app.json +++ b/adapters/smaato/smaatotest/exemplary/video-app.json @@ -165,7 +165,7 @@ "keywords": "keywords" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -220,7 +220,8 @@ "nurl": "https://nurl", "price": 0.01, "w": 1024, - "h": 768 + "h": 768, + "exp": 300 }, "type": "video" } diff --git a/adapters/smaato/smaatotest/exemplary/video.json b/adapters/smaato/smaatotest/exemplary/video.json index bad3825bb62..205c02ad84c 100644 --- a/adapters/smaato/smaatotest/exemplary/video.json +++ b/adapters/smaato/smaatotest/exemplary/video.json @@ -29,6 +29,7 @@ "rewarded": 0 } }, + "bidfloor": 0.00123, "ext": { "bidder": { "publisherId": "1100042525", @@ -82,6 +83,7 @@ { "id": "postbid_iframe", "tagid": "130563103", + "bidfloor": 0.00123, "video": { "w": 1024, "h": 768, @@ -122,7 +124,7 @@ } }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -177,7 +179,8 @@ "nurl": "https://nurl", "price": 0.01, "w": 1024, - "h": 768 + "h": 768, + "exp": 300 }, "type": "video" } diff --git a/adapters/smaato/smaatotest/params/banner.json b/adapters/smaato/smaatotest/params/race/banner.json similarity index 100% rename from adapters/smaato/smaatotest/params/banner.json rename to adapters/smaato/smaatotest/params/race/banner.json diff --git a/adapters/smaato/smaatotest/params/race/video.json b/adapters/smaato/smaatotest/params/race/video.json new file mode 100644 index 00000000000..a84c44d4d8e --- /dev/null +++ b/adapters/smaato/smaatotest/params/race/video.json @@ -0,0 +1,4 @@ +{ + "publisherId": "1100042525", + "adspaceId": "130563103" +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json new file mode 100644 index 00000000000..14b966f9bdd --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/adtype-header-response.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Adtype": ["Img"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json index db724565d52..efeba9ed6ae 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-adm-response.json +++ b/adapters/smaato/smaatotest/supplemental/bad-adm-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -162,10 +162,9 @@ } } ], - "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "value": "Invalid ad markup {\"badmedia\":{\"mediadata\":{\"content\":\"
hello
\", \"w\":350,\"h\":50},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json new file mode 100644 index 00000000000..b066dc1b6cb --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-adtype-header-response.json @@ -0,0 +1,174 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Adtype": ["something"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Unknown markup type something.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json new file mode 100644 index 00000000000..065b639509e --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-expires-header-response.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Expires": ["something"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-ext-req.json b/adapters/smaato/smaatotest/supplemental/bad-ext-req.json deleted file mode 100644 index 0c970fc5bad..00000000000 --- a/adapters/smaato/smaatotest/supplemental/bad-ext-req.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "mockBidRequest": { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "site": { - "page": "prebid.org", - "publisher": { - "id": "1" - } - }, - "imp": [ - { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "banner": { - "format": [ - { - "w": 320, - "h": 50 - }, - { - "w": 320, - "h": 250 - } - ] - }, - "ext": { - } - } - ], - "device": { - "ua": "test-user-agent", - "ip": "123.123.123.123", - "language": "en", - "dnt": 0 - }, - "user": { - "ext": { - "consent": "gdprConsentString" - } - }, - "regs": { - "coppa": 1, - "ext": { - "gdpr": 1, - "us_privacy": "uspConsentString" - } - } - }, - "expectedMakeRequestsErrors": [ - { - "value": "Key path not found", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json deleted file mode 100644 index 768b4ef9d2c..00000000000 --- a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-req.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "mockBidRequest": { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "imp": [ - { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "banner": { - "format": [] - }, - "ext": { - "bidder": { - "publisherId": "1100042525", - "adspaceId": "130563103" - } - } - } - ], - "site": { - "page": "prebid.org", - "publisher": { - "id": "1" - } - } - }, - "httpCalls": [ - { - "expectedRequest": { - "headers": { - "Content-Type": ["application/json;charset=utf-8"], - "Accept": ["application/json"] - }, - "uri": "https://prebid/bidder", - "body": { - "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", - "imp": [], - "site": { - "page": "prebid.org", - "publisher": { - "id": "1100042525" - } - }, - "ext": { - "client": "prebid_server_0.2" - } - } - } - } - ], - "expectedMakeRequestsErrors": [ - { - "value": "No sizes provided for Banner []", - "comparison": "literal" - } - ], - "expectedMakeBidsErrors": [ - { - "value": "unexpected status code: 0. Run with request.debug = 1 for more info", - "comparison": "literal" - } - ] -} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json new file mode 100644 index 00000000000..0c3661bdf69 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-imp-banner-format-request.json @@ -0,0 +1,28 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "No sizes provided for Banner.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json b/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json new file mode 100644 index 00000000000..2b59495c829 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-media-type-request.json @@ -0,0 +1,27 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "native": { + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid MediaType. Smaato only supports Banner and Video.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json b/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json new file mode 100644 index 00000000000..0df53e6c0bc --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-site-ext-request.json @@ -0,0 +1,34 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org", + "ext": "" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid site.ext.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/status-code-400.json b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json similarity index 97% rename from adapters/smaato/smaatotest/supplemental/status-code-400.json rename to adapters/smaato/smaatotest/supplemental/bad-status-code-response.json index fc84c93e269..bfe3dbe2a2d 100644 --- a/adapters/smaato/smaatotest/supplemental/status-code-400.json +++ b/adapters/smaato/smaatotest/supplemental/bad-status-code-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -137,7 +137,7 @@ "expectedBidResponses": [], "expectedMakeBidsErrors": [ { - "value": "Unexpected status code: 400. Run with request.debug = 1 for more info", + "value": "Unexpected status code: 400. Run with request.debug = 1 for more info.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json b/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json similarity index 81% rename from adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json rename to adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json index 7f05b2dff14..10f5ad474c6 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-user-ext-req.json +++ b/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-request.json @@ -2,10 +2,7 @@ "mockBidRequest": { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "site": { - "page": "prebid.org", - "publisher": { - "id": "1" - } + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder" }, "imp": [ { @@ -37,8 +34,11 @@ "dnt": 0 }, "user": { - "gender": "M", - "ext": 99 + "ext": { + "data": { + "yob": "" + } + } }, "regs": { "coppa": 1, @@ -50,7 +50,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal number into Go value of type map[string]json.RawMessage", + "value": "Invalid user.ext.data.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json b/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json new file mode 100644 index 00000000000..26df372e14f --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/bad-user-ext-request.json @@ -0,0 +1,36 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "user": { + "ext": 99 + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid user.ext.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json new file mode 100644 index 00000000000..1d59e96d634 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/banner-w-and-h.json @@ -0,0 +1,173 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "w": 320, + "h": 50 + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320 + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 320, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 320, + "h": 50, + "exp": 300 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/expires-header-response.json b/adapters/smaato/smaatotest/supplemental/expires-header-response.json new file mode 100644 index 00000000000..be057419177 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/expires-header-response.json @@ -0,0 +1,194 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Expires": ["1624618800000"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50, + "exp": 3600 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json b/adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json similarity index 65% rename from adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json rename to adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json index 9e65fce1c3e..88b0a4080e6 100644 --- a/adapters/smaato/smaatotest/supplemental/bad-user-ext-data-req.json +++ b/adapters/smaato/smaatotest/supplemental/no-adspace-id-request.json @@ -2,9 +2,18 @@ "mockBidRequest": { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "site": { - "page": "prebid.org", "publisher": { - "id": "1" + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } } }, "imp": [ @@ -24,8 +33,7 @@ }, "ext": { "bidder": { - "publisherId": "1100042525", - "adspaceId": "130563103" + "publisherId": "1100042525" } } } @@ -37,17 +45,16 @@ "dnt": 0 }, "user": { - "gender": "M", "ext": { + "consent": "gdprConsentString", "data": { - "keywords":"a,b", + "keywords": "a,b", "gender": "M", - "yob": "", + "yob": 1984, "geo": { "country": "ca" } - }, - "consent":"yes" + } } }, "regs": { @@ -60,7 +67,7 @@ }, "expectedMakeRequestsErrors": [ { - "value": "json: cannot unmarshal string into Go struct field userExtData.data.yob of type int64", + "value": "Missing adspaceId parameter.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/no-app-site-request.json b/adapters/smaato/smaatotest/supplemental/no-app-site-request.json new file mode 100644 index 00000000000..04a73b4f40d --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/no-app-site-request.json @@ -0,0 +1,30 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing Site/App.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/status-code-204.json b/adapters/smaato/smaatotest/supplemental/no-bid-response.json similarity index 96% rename from adapters/smaato/smaatotest/supplemental/status-code-204.json rename to adapters/smaato/smaatotest/supplemental/no-bid-response.json index b409597f986..4f674f2a34d 100644 --- a/adapters/smaato/smaatotest/supplemental/status-code-204.json +++ b/adapters/smaato/smaatotest/supplemental/no-bid-response.json @@ -125,7 +125,7 @@ "keywords": "power tools" }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -133,7 +133,5 @@ "status": 204 } } - ], - "expectedBidResponses": [], - "expectedMakeBidsErrors": [] + ] } \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/no-consent-info.json b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json similarity index 98% rename from adapters/smaato/smaatotest/supplemental/no-consent-info.json rename to adapters/smaato/smaatotest/supplemental/no-consent-info-request.json index b9a4294b00b..b33fea6e7e1 100644 --- a/adapters/smaato/smaatotest/supplemental/no-consent-info.json +++ b/adapters/smaato/smaatotest/supplemental/no-consent-info-request.json @@ -72,7 +72,7 @@ } }, "ext": { - "client": "prebid_server_0.2" + "client": "prebid_server_0.4" } } }, @@ -127,7 +127,8 @@ "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", "price": 0.01, "w": 350, - "h": 50 + "h": 50, + "exp": 300 }, "type": "banner" } diff --git a/adapters/smaato/smaatotest/supplemental/no-imp-req.json b/adapters/smaato/smaatotest/supplemental/no-imp-request.json similarity index 59% rename from adapters/smaato/smaatotest/supplemental/no-imp-req.json rename to adapters/smaato/smaatotest/supplemental/no-imp-request.json index bfaf51e6ea8..eab4f2a0697 100644 --- a/adapters/smaato/smaatotest/supplemental/no-imp-req.json +++ b/adapters/smaato/smaatotest/supplemental/no-imp-request.json @@ -2,15 +2,12 @@ "mockBidRequest": { "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", "site": { - "page": "prebid.org", - "publisher": { - "id": "1" - } + "page": "prebid.org" } }, "expectedMakeRequestsErrors": [ { - "value": "no impressions in bid request", + "value": "No impressions in bid request.", "comparison": "literal" } ] diff --git a/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json b/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json new file mode 100644 index 00000000000..60a0af594a8 --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/no-publisher-id-request.json @@ -0,0 +1,29 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder" + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + } + ] + }, + "ext": { + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing publisherId parameter.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json new file mode 100644 index 00000000000..abab70facbd --- /dev/null +++ b/adapters/smaato/smaatotest/supplemental/outdated-expires-header-response.json @@ -0,0 +1,193 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "ext": { + "data": { + "keywords": "power tools", + "search": "drill", + "content": { + "userrating": 4 + } + } + } + }, + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adspaceId": "130563103" + } + } + } + ], + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "user": { + "ext": { + "consent": "gdprConsentString", + "data": { + "keywords": "a,b", + "gender": "M", + "yob": 1984, + "geo": { + "country": "ca" + } + } + } + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json;charset=utf-8"], + "Accept": ["application/json"] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "imp": [ + { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "tagid": "130563103", + "banner": { + "h": 50, + "w": 320, + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + } + } + ], + "user": { + "ext": { + "consent": "gdprConsentString" + }, + "gender": "M", + "keywords": "a,b", + "yob": 1984 + }, + "device": { + "ua": "test-user-agent", + "ip": "123.123.123.123", + "language": "en", + "dnt": 0 + }, + "regs": { + "coppa": 1, + "ext": { + "gdpr": 1, + "us_privacy": "uspConsentString" + } + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "page": "http://localhost:3000/server.html?pbjs_debug=true&endpoint=http://localhost:3000/bidder", + "keywords": "power tools" + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "headers": { + "X-Smt-Expires": ["1524618800000"] + }, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "{\"image\":{\"img\":{\"url\":\"//prebid-test.smaatolabs.net/img/320x50.jpg\",\"w\":350,\"h\":50,\"ctaurl\":\"//prebid-test.smaatolabs.net/track/ctaurl/1\"},\"impressiontrackers\":[\"//prebid-test.smaatolabs.net/track/imp/1\",\"//prebid-test.smaatolabs.net/track/imp/2\"],\"clicktrackers\":[\"//prebid-test.smaatolabs.net/track/click/1\",\"//prebid-test.smaatolabs.net/track/click/2\"]}}", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "
\"\"\"\"
", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "iurl": "https://bidstalkcreatives.s3.amazonaws.com/1x1.png", + "nurl": "https://ets-eu-west-1.track.smaato.net/v1/view?sessionId=e4e17adb-9599-42b1-bb5f-a1f1b3bee572&adSourceId=6906aae8-7f74-4edd-9a4f-f49379a3cadd&originalRequestTime=1552310449698&expires=1552311350698&winurl=ama8JbpJVpFWxvEja5viE3cLXFu58qRI8dGUh23xtsOn3N2-5UU0IwkgNEmR82pI37fcMXejL5IWTNAoW6Cnsjf-Dxl_vx2dUqMrVEevX-Vdx2VVnf-D5f73gZhvi4t36iPL8Dsw4aACekoLvVOV7-eXDjz7GHy60QFqcwKf5g2AlKPOInyZ6vJg_fn4qA9argvCRgwVybXE9Ndm2W0v8La4uFYWpJBOUveDDUrSQfzal7RsYvLb_OyaMlPHdrd_bwA9qqZWuyJXd-L9lxr7RQ%3D%3D%7CMw3kt91KJR0Uy5L-oNztAg%3D%3D&dpid=4XVofb_lH-__hr2JNGhKfg%3D%3D%7Cr9ciCU1cx3zmHXihItKO0g%3D%3D", + "price": 0.01, + "w": 350, + "h": 50 + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/video/multiple-adpods.json b/adapters/smaato/smaatotest/video/multiple-adpods.json new file mode 100644 index 00000000000..e5023d87b28 --- /dev/null +++ b/adapters/smaato/smaatotest/video/multiple-adpods.json @@ -0,0 +1,555 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "2_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "2_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "1_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + } + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + }, + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "2_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "2_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "2_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "2_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + } + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + + }, + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + }, + "exp": 300 + }, + "type": "video" + } + ] + }, + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "2_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + }, + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "2_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + }, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/video/single-adpod.json b/adapters/smaato/smaatotest/video/single-adpod.json new file mode 100644 index 00000000000..a5f0c0590f5 --- /dev/null +++ b/adapters/smaato/smaatotest/video/single-adpod.json @@ -0,0 +1,297 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "bidfloor": 0.00123, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "bidfloor": 0.00123, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "tagid": "400000001", + "bidfloor": 0.00123, + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "1_2", + "tagid": "400000001", + "bidfloor": 0.00123, + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + } + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + }, + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + }, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json new file mode 100644 index 00000000000..308648d3f64 --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-adbreak-id-request.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1234567" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1234567" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing adbreakId parameter.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json new file mode 100644 index 00000000000..08803d1894e --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-adm-response.json @@ -0,0 +1,276 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "1_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": { + "duration": 30 + } + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid ad markup .", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json new file mode 100644 index 00000000000..75e288362c0 --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-bid-ext-response.json @@ -0,0 +1,274 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": [ + "application/json;charset=utf-8" + ], + "Accept": [ + "application/json" + ] + }, + "uri": "https://prebid/bidder", + "body": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 1, + "ext": { + "context": "adpod" + } + } + }, + { + "id": "1_2", + "tagid": "400000001", + "video": { + "w": 1024, + "h": 768, + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "startdelay": 0, + "linearity": 1, + "maxduration": 30, + "skip": 1, + "protocols": [ + 7 + ], + "skipmin": 5, + "api": [ + 7 + ], + "sequence": 2, + "ext": { + "context": "adpod" + } + } + } + ], + "user": { + "ext": { + } + }, + "device": { + "ua": "test-user-agent" + }, + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "ext": { + "client": "prebid_server_0.4" + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "5ebea288-f13a-4754-be6d-4ade66c68877", + "seatbid": [ + { + "seat": "CM6523", + "bid": [ + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + } + }, + { + "adm": "", + "adomain": [ + "smaato.com" + ], + "bidderName": "smaato", + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_2", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB2"], + "ext": "" + } + ] + } + ], + "bidid": "04db8629-179d-4bcd-acce-e54722969006", + "cur": "USD" + } + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Invalid bid.ext.", + "comparison": "literal" + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "adm": "", + "adomain": [ + "smaato.com" + ], + "cid": "CM6523", + "crid": "CR69381", + "id": "6906aae8-7f74-4edd-9a4f-f49379a3cadd", + "impid": "1_1", + "iurl": "https://iurl", + "nurl": "https://nurl", + "price": 0.01, + "w": 1024, + "h": 768, + "cat": ["IAB1"], + "ext": { + "duration": 5 + }, + "exp": 300 + }, + "type": "video" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json new file mode 100644 index 00000000000..1c345de029d --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-media-type-request.json @@ -0,0 +1,37 @@ +{ + "mockBidRequest": { + "id": "1C86242D-9535-47D6-9576-7B1FE87F282C", + "site": { + "page": "prebid.org" + }, + "imp": [ + { + "id": "1_1", + "banner": { + "format": [ + { + "w": 320, + "h": 50 + }, + { + "w": 320, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "publisherId": "1100042525", + "adbreakId": "400000001" + } + } + } + ] + }, + "expectedMakeRequestsErrors": [ + { + "value": "Invalid MediaType. Smaato only supports Video for AdPod.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json b/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json new file mode 100644 index 00000000000..1615b670e9b --- /dev/null +++ b/adapters/smaato/smaatotest/videosupplemental/bad-publisher-id-request.json @@ -0,0 +1,90 @@ +{ + "mockBidRequest": { + "id": "447a0a1d-389d-4730-a418-3777e95de7bd", + "imp": [ + { + "id": "1_1", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "adbreakId": "400000001" + } + } + }, + { + "id": "1_2", + "video": { + "mimes": [ + "video/mp4", + "video/3gpp" + ], + "minduration": 5, + "maxduration": 30, + "protocols": [ + 7 + ], + "w": 1024, + "h": 768, + "startdelay": 0, + "linearity": 1, + "skip": 1, + "skipmin": 5, + "api": [ + 7 + ] + }, + "ext": { + "bidder": { + "adbreakId": "400000001" + } + } + } + ], + "site": { + "publisher": { + "id": "1100042525" + }, + "content": { + "title": "a-title", + "season": "a-season", + "series": "a-series", + "episode": 1, + "len": 900, + "livestream": 1 + } + }, + "device": { + "ua": "test-user-agent" + }, + "user": { + "ext": { + "data": {} + } + } + }, + "expectedMakeRequestsErrors": [ + { + "value": "Missing publisherId parameter.", + "comparison": "literal" + } + ] +} \ No newline at end of file diff --git a/adapters/smilewanted/params_test.go b/adapters/smilewanted/params_test.go new file mode 100644 index 00000000000..2ea032d6ff3 --- /dev/null +++ b/adapters/smilewanted/params_test.go @@ -0,0 +1,58 @@ +package smilewanted + +import ( + "encoding/json" + "testing" + + "github.com/prebid/prebid-server/openrtb_ext" +) + +// This file actually intends to test static/bidder-params/smilewanted.json +// +// These also validate the format of the external API: request.imp[i].ext.smilewanted + +// TestValidParams makes sure that the smilewanted 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.BidderSmileWanted, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected SmileWanted params: %s", validParam) + } + } +} + +// TestInvalidParams makes sure that the SmileWanted 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.BidderSmileWanted, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"zoneId": "zone_code"}`, +} + +var invalidParams = []string{ + `{"zoneId": 100}`, + `{"zoneId": true}`, + `{"zoneId": 123}`, + `{"zoneID": "1"}`, + ``, + `null`, + `true`, + `9`, + `1.2`, + `[]`, + `{}`, +} diff --git a/adapters/smilewanted/smilewanted.go b/adapters/smilewanted/smilewanted.go new file mode 100644 index 00000000000..376389df787 --- /dev/null +++ b/adapters/smilewanted/smilewanted.go @@ -0,0 +1,106 @@ +package smilewanted + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/mxmCherry/openrtb/v15/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 { + URI string +} + +func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + + request.AT = 1 //Defaulting to first price auction for all prebid requests + + reqJSON, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Json not encoded. err: %s", 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") + headers.Add("sw-integration-type", "prebid_server") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: a.URI, + Body: reqJSON, + Headers: headers, + }}, []error{} +} + +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. 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: %s.", err), + }} + } + + var bidReq openrtb2.BidRequest + if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { + return nil, []error{err} + } + + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid)) + sb := bidResp.SeatBid[0] + for i := 0; i < len(sb.Bid); i++ { + bid := sb.Bid[i] + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bid, + BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp), + }) + } + return bidResponse, nil +} + +// getMediaTypeForImp figures out which media type this bid is for. +func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType { + mediaType := openrtb_ext.BidTypeBanner //default type + for _, imp := range imps { + if imp.ID == impId { + if imp.Video != nil { + mediaType = openrtb_ext.BidTypeVideo + } + return mediaType + } + } + return mediaType +} + +// Builder builds a new instance of the SmileWanted adapter for the given bidder with the given config. +func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { + bidder := &adapter{ + URI: config.Endpoint, + } + return bidder, nil +} diff --git a/adapters/smilewanted/smilewanted_test.go b/adapters/smilewanted/smilewanted_test.go new file mode 100644 index 00000000000..75e7849e750 --- /dev/null +++ b/adapters/smilewanted/smilewanted_test.go @@ -0,0 +1,20 @@ +package smilewanted + +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.BidderSmileWanted, config.Adapter{ + Endpoint: "http://example.com"}) + + if buildErr != nil { + t.Fatalf("Builder returned unexpected error %v", buildErr) + } + + adapterstest.RunJSONBidderTest(t, "smilewantedtest", bidder) +} diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json new file mode 100644 index 00000000000..0c68d74c588 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-banner.json @@ -0,0 +1,94 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "seat": "smilewanted", + "bid": [{ + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }] + } + ], + "cur": "USD" + } + } + } + ], + + "expectedBidResponses": [ + { + "bids": [ + { + "bid": { + "id": "8ee514f1-b2b8-4abb-89fd-084437d1e800", + "impid": "test-imp-id", + "price": 0.5, + "adm": "some-test-ad", + "crid": "crid_10", + "h": 250, + "w": 300 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json new file mode 100644 index 00000000000..b3ff9ba9edd --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/exemplary/simple-video.json @@ -0,0 +1,87 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext":{ + "bidder":{ + "zoneId": "zone_code_test_video" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at": 1, + "imp": [ + { + "id": "test-imp-id", + "video": { + "mimes": ["video/mp4"], + "protocols": [2, 5], + "w": 1024, + "h": 576 + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_video" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "cur": "USD", + "seatbid": [ + { + "seat": "smilewanted", + "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 + }] + } + ] + } + } + } + ], + + "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" + }] + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/params/race/banner.json b/adapters/smilewanted/smilewantedtest/params/race/banner.json new file mode 100644 index 00000000000..42dddd702a0 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "zoneId": "zone_code_test_display" +} diff --git a/adapters/smilewanted/smilewantedtest/params/race/video.json b/adapters/smilewanted/smilewantedtest/params/race/video.json new file mode 100644 index 00000000000..64ac780ecde --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/params/race/video.json @@ -0,0 +1,3 @@ +{ + "zoneId": "zone_code_test_video" +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json new file mode 100644 index 00000000000..461ad9327a9 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/bad-server-response.json @@ -0,0 +1,63 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": "bad_json" + } + } + ], + "expectedMakeBidsErrors": [ + { + "value": "Bad server response: json: cannot unmarshal string into Go value of type openrtb2.BidResponse.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json new file mode 100644 index 00000000000..0d8a432e26d --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-204.json @@ -0,0 +1,59 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 204 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json new file mode 100644 index 00000000000..bdf2caa3c01 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/status-code-400.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 400 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 400.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json new file mode 100644 index 00000000000..49a11e3ead3 --- /dev/null +++ b/adapters/smilewanted/smilewantedtest/supplemental/unexpected-status-code.json @@ -0,0 +1,64 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "at" : 1, + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://example.com", + "body": { + "id": "test-request-id", + "at" : 1, + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + } + ] + }, + "ext": { + "bidder": { + "zoneId": "zone_code_test_display" + } + } + } + ] + } + }, + "mockResponse": { + "status": 500 + } + } + ], + "expectedBidResponses": [], + "expectedMakeBidsErrors": [ + { + "value": "Unexpected status code: 500. Run with request.debug = 1 for more info.", + "comparison": "literal" + } + ] +} diff --git a/adapters/smilewanted/usersync.go b/adapters/smilewanted/usersync.go new file mode 100644 index 00000000000..8f29cb845d8 --- /dev/null +++ b/adapters/smilewanted/usersync.go @@ -0,0 +1,12 @@ +package smilewanted + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewSmileWantedSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("smilewanted", temp, adapters.SyncTypeRedirect) +} diff --git a/adapters/smilewanted/usersync_test.go b/adapters/smilewanted/usersync_test.go new file mode 100644 index 00000000000..497e5061554 --- /dev/null +++ b/adapters/smilewanted/usersync_test.go @@ -0,0 +1,34 @@ +package smilewanted + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/ccpa" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestSmileWantedSyncer(t *testing.T) { + syncURL := "//csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewSmileWantedSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "1", + Consent: "COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA", + }, + CCPA: ccpa.Policy{ + Consent: "1YNN", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//csync.smilewanted.com/getuid?source=prebid-server&gdpr=1&gdpr_consent=COyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA&us_privacy=1YNN&redirect=https%3A%2F%2Flocalhost%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D1%26gdpr_consent%3DCOyASAoOyASAoAfAAAENAfCAAAAAAAAAAAAAAAAAAAAA%26uid%3D%24UID", syncInfo.URL) + assert.Equal(t, "redirect", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go index be1c2221ae5..40969d3638e 100644 --- a/adapters/sovrn/sovrn.go +++ b/adapters/sovrn/sovrn.go @@ -287,7 +287,10 @@ func preprocess(imp *openrtb2.Imp) (string, error) { } imp.TagID = getTagid(sovrnExt) - imp.BidFloor = sovrnExt.BidFloor + + if imp.BidFloor == 0 && sovrnExt.BidFloor > 0 { + imp.BidFloor = sovrnExt.BidFloor + } return imp.TagID, nil } diff --git a/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json new file mode 100644 index 00000000000..4b997b68266 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/both-custom-default-bidfloor.json @@ -0,0 +1,126 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json new file mode 100644 index 00000000000..0aa3ad74e62 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/no-bidfloor.json @@ -0,0 +1,122 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json new file mode 100644 index 00000000000..3cd6539f988 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/only-custom-bidfloor.json @@ -0,0 +1,125 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 4.20, + "ext": { + "bidder": { + "tagid": "123456", + "bidfloor": 4.20 + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json new file mode 100644 index 00000000000..cb74e5643b6 --- /dev/null +++ b/adapters/sovrn/sovrntest/supplemental/only-default-bidfloor.json @@ -0,0 +1,124 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "device": { }, + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "headers": { + "Content-Type": ["application/json"], + "Cookie": ["ljt_reader=test_reader_id"] + }, + "uri": "http://sovrn.com/test/endpoint", + "body": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "tagid": "123456", + "bidfloor": 1.69, + "ext": { + "bidder": { + "tagid": "123456" + } + } + } + ], + "site": { + "domain": "www.publisher.com", + "page": "http://www.publisher.com/awesome/site" + }, + "user": { + "buyeruid": "test_reader_id" + }, + "device": { } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "adm": "some-test-ad", + "w": 300, + "h": 250 + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "a_449642_554a13d3b9f348fba707cf83f0f63800", + "impid": "test-imp-id", + "price": 3.5, + "adm": "some-test-ad", + "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800", + "w": 300, + "h": 250 + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/tappx/params_test.go b/adapters/tappx/params_test.go index 3a73d4dab53..9457924875f 100644 --- a/adapters/tappx/params_test.go +++ b/adapters/tappx/params_test.go @@ -35,6 +35,11 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com"}`, `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "mktag":"txmk-xxxxx-xxx-xxxx"}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcid":["123"]}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcrid":["245"]}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcrid":["245", "321"]}`, + `{"tappxkey":"pub-12345-android-9876", "endpoint":"ZZ1INTERNALTEST149147915", "host":"test.tappx.com", "bidfloor":0.5, "bcid":["123", "654"], "bcrid":["245", "321"]}`, } var invalidParams = []string{ @@ -60,4 +65,14 @@ var invalidParams = []string{ `{"tappxkey": 1, "endpoint":"ZZ1INTERNALTEST149147915", "host":""}`, `{"tappxkey":"pub-12345-android-9876", "endpoint": 1, "host":""}`, `{"tappxkey": 1, "endpoint": 1, "host": 123}`, + `{"tappxkey": "1", "endpoint": 1}`, + `{"tappxkey": "1", "endpoint": "ZZ1INTERNALTEST149147915", "host":[]]}`, + `{"tappxkey": "1", "endpoint": 1, "host":"host"}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "mktag":1}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "mktag":[1,2]}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":""}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":"123", bcrid: ["123"]}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":["123"], bcrid: 123}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":["123"], bcrid: [123]}`, + `{"tappxkey": "1", "endpoint": "1", "host":"host", "bcid":[123], bcrid: ["123"]}`, } diff --git a/adapters/tappx/tappx.go b/adapters/tappx/tappx.go index 5970ccb6cfe..5f0710cf08a 100644 --- a/adapters/tappx/tappx.go +++ b/adapters/tappx/tappx.go @@ -18,13 +18,24 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -const TAPPX_BIDDER_VERSION = "1.2" +const TAPPX_BIDDER_VERSION = "1.3" const TYPE_CNN = "prebid" type TappxAdapter struct { endpointTemplate template.Template } +type Bidder struct { + Tappxkey string `json:"tappxkey"` + Mktag string `json:"mktag,omitempty"` + Bcid []string `json:"bcid,omitempty"` + Bcrid []string `json:"bcrid,omitempty"` +} + +type Ext struct { + Bidder `json:"bidder"` +} + // Builder builds a new instance of the Tappx adapter for the given bidder with the given config. func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { template, err := template.New("endpointTemplate").Parse(config.Endpoint) @@ -51,7 +62,6 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt Message: "Error parsing bidderExt object", }} } - var tappxExt openrtb_ext.ExtImpTappx if err := json.Unmarshal(bidderExt.Bidder, &tappxExt); err != nil { return nil, []error{&errortypes.BadInput{ @@ -59,6 +69,23 @@ func (a *TappxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt }} } + ext := Ext{ + Bidder: Bidder{ + Tappxkey: tappxExt.TappxKey, + Mktag: tappxExt.Mktag, + Bcid: tappxExt.Bcid, + Bcrid: tappxExt.Bcrid, + }, + } + + if jsonext, err := json.Marshal(ext); err == nil { + request.Ext = jsonext + } else { + return nil, []error{&errortypes.FailedToRequestBids{ + Message: "Error marshaling tappxExt parameters", + }} + } + var test int test = int(request.Test) diff --git a/adapters/tappx/tappx_test.go b/adapters/tappx/tappx_test.go index 10e57d12132..ea7011a7bdc 100644 --- a/adapters/tappx/tappx_test.go +++ b/adapters/tappx/tappx_test.go @@ -47,7 +47,7 @@ func TestTsValue(t *testing.T) { url, err := bidderTappx.buildEndpointURL(&tappxExt, test) - match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.2`, url) + match, err := regexp.MatchString(`http://example\.host\.tappx\.com/DUMMYENDPOINT\?tappxkey=dummy-tappx-key&ts=[0-9]{13}&type_cnn=prebid&v=1\.3`, url) if err != nil { t.Errorf("Error while running regex validation: %s", err.Error()) return diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json new file mode 100644 index 00000000000..a6ddf2848e2 --- /dev/null +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-extra.json @@ -0,0 +1,130 @@ +{ + "mockBidRequest": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", + "mktag": "mktag-12345-android-9876", + "bcid": ["1","2","3"], + "bcrid": ["4","5","6"] + } + } + } + ], + "app": { + "id": "app_001", + "bundle": "com.rovio.angrybirds", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + } + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", + "body": { + "id": "0000000000001", + "test": 1, + "imp": [ + { + "id": "adunit-1", + "banner": { + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "endpoint": "ZZ123456PS", + "host": "testing.ssp.tappx.com/rtb/v2/", + "mktag": "mktag-12345-android-9876", + "bcid": ["1","2","3"], + "bcrid": ["4","5","6"] + } + } + } + ], + "app": { + "bundle": "com.rovio.angrybirds", + "id": "app_001", + "publisher": { + "id": "2" + } + }, + "user": { + "buyeruid": "A-38327932832" + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876", + "mktag": "mktag-12345-android-9876", + "bcid": ["1","2","3"], + "bcrid": ["4","5","6"] + } + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "75472df2-1cb3-4f8e-9a28-10cb95fe05a4", + "seatbid": [{ + "bid": [{ + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "cid": "3706", + "crid": "19005", + "adid": "19005", + "adm": "", + "cat": ["IAB2"], + "adomain": ["test.com"], + "h": 250, + "w": 300 + }] + }], + "bidid": "wehM-93KGr0" + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "wehM-93KGr0_0_0", + "impid": "adunit-1", + "price": 0.5, + "adm": "", + "adid": "19005", + "adomain": ["test.com"], + "cid": "3706", + "crid": "19005", + "w": 300, + "h": 250, + "cat": ["IAB2"] + }, + "type": "banner" + } + ] + } + ] +} \ No newline at end of file diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json index 3c3037afefb..259d51cb34f 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression-future-feature.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://ZZ123456PS.ssp.tappx.com/rtb/?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -48,7 +48,7 @@ "bidder": { "tappxkey": "pub-12345-android-9876", "endpoint": "ZZ123456PS", - "host": "ZZ123456PS.ssp.tappx.com/rtb/" + "host": "ZZ123456PS.ssp.tappx.com/rtb/" } } } @@ -62,6 +62,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json index 54f472d9fff..532e2b1f4a1 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-impression.json @@ -33,7 +33,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -62,6 +62,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext":{ + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/exemplary/single-banner-site.json b/adapters/tappx/tappxtest/exemplary/single-banner-site.json index 58490233ede..e8858bd6ea6 100644 --- a/adapters/tappx/tappxtest/exemplary/single-banner-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-banner-site.json @@ -37,7 +37,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -70,6 +70,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext":{ + "bidder": { + "tappxkey": "pub-12345-site-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-impression.json b/adapters/tappx/tappxtest/exemplary/single-video-impression.json index d6ce0554c5f..23e079258e7 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-impression.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-impression.json @@ -35,7 +35,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -67,6 +67,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext":{ + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/exemplary/single-video-site.json b/adapters/tappx/tappxtest/exemplary/single-video-site.json index f151151e776..85872b6a29e 100644 --- a/adapters/tappx/tappxtest/exemplary/single-video-site.json +++ b/adapters/tappx/tappxtest/exemplary/single-video-site.json @@ -39,7 +39,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/VZ123456PS?tappxkey=pub-12345-site-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -75,6 +75,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext":{ + "bidder": { + "tappxkey": "pub-12345-site-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/supplemental/204status.json b/adapters/tappx/tappxtest/supplemental/204status.json index 1c72cc90f24..918b278e6dc 100644 --- a/adapters/tappx/tappxtest/supplemental/204status.json +++ b/adapters/tappx/tappxtest/supplemental/204status.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -56,6 +56,11 @@ "publisher": { "id": "2" } + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/supplemental/bidfloor.json b/adapters/tappx/tappxtest/supplemental/bidfloor.json index 093f77adfc6..3d3ced65e25 100644 --- a/adapters/tappx/tappxtest/supplemental/bidfloor.json +++ b/adapters/tappx/tappxtest/supplemental/bidfloor.json @@ -34,7 +34,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -65,6 +65,11 @@ }, "user": { "buyeruid": "A-38327932832" + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status.json b/adapters/tappx/tappxtest/supplemental/http-err-status.json index a80a5eaa675..f1783b3f77a 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -56,6 +56,11 @@ "publisher": { "id": "2" } + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/tappx/tappxtest/supplemental/http-err-status2.json b/adapters/tappx/tappxtest/supplemental/http-err-status2.json index 41dcc26d653..4b855c57404 100644 --- a/adapters/tappx/tappxtest/supplemental/http-err-status2.json +++ b/adapters/tappx/tappxtest/supplemental/http-err-status2.json @@ -30,7 +30,7 @@ "httpCalls": [ { "expectedRequest": { - "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.2", + "uri": "http://testing.ssp.tappx.com/rtb/v2/ZZ123456PS?tappxkey=pub-12345-android-9876&type_cnn=prebid&v=1.3", "body": { "id": "0000000000001", "test": 1, @@ -56,6 +56,11 @@ "publisher": { "id": "2" } + }, + "ext": { + "bidder": { + "tappxkey": "pub-12345-android-9876" + } } } }, diff --git a/adapters/viewdeos/usersync.go b/adapters/viewdeos/usersync.go new file mode 100644 index 00000000000..05cb80c54a1 --- /dev/null +++ b/adapters/viewdeos/usersync.go @@ -0,0 +1,12 @@ +package viewdeos + +import ( + "text/template" + + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/usersync" +) + +func NewViewdeosSyncer(temp *template.Template) usersync.Usersyncer { + return adapters.NewSyncer("viewdeos", temp, adapters.SyncTypeIframe) +} diff --git a/adapters/viewdeos/usersync_test.go b/adapters/viewdeos/usersync_test.go new file mode 100644 index 00000000000..8b8908a44e6 --- /dev/null +++ b/adapters/viewdeos/usersync_test.go @@ -0,0 +1,29 @@ +package viewdeos + +import ( + "testing" + "text/template" + + "github.com/prebid/prebid-server/privacy" + "github.com/prebid/prebid-server/privacy/gdpr" + "github.com/stretchr/testify/assert" +) + +func TestViewdeosSyncer(t *testing.T) { + syncURL := "//sync.sync.viewdeos.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Buid%7D" + syncURLTemplate := template.Must( + template.New("sync-template").Parse(syncURL), + ) + + syncer := NewViewdeosSyncer(syncURLTemplate) + syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{ + GDPR: gdpr.Policy{ + Signal: "0", + }, + }) + + assert.NoError(t, err) + assert.Equal(t, "//sync.sync.viewdeos.com/csync?t=p&ep=0&redir=localhost%2Fsetuid%3Fbidder%3Dmediafuse%26gdpr%3D0%26gdpr_consent%3D%26uid%3D%7Buid%7D", syncInfo.URL) + assert.Equal(t, "iframe", syncInfo.Type) + assert.Equal(t, false, syncInfo.SupportCORS) +} diff --git a/analytics/config/config_test.go b/analytics/config/config_test.go index 310dbe1a481..fe95a66ed6f 100644 --- a/analytics/config/config_test.go +++ b/analytics/config/config_test.go @@ -1,13 +1,12 @@ package config import ( + "github.com/stretchr/testify/assert" "net/http" "os" "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/stretchr/testify/assert" - "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" ) diff --git a/analytics/filesystem/file_module_test.go b/analytics/filesystem/file_module_test.go index 0c3d3c9e6ac..45a72266569 100644 --- a/analytics/filesystem/file_module_test.go +++ b/analytics/filesystem/file_module_test.go @@ -1,14 +1,13 @@ package filesystem import ( + "github.com/prebid/prebid-server/config" "net/http" "os" "strings" "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/usersync" ) diff --git a/config/adapter.go b/config/adapter.go index ff262b186fd..8cdad538dbf 100644 --- a/config/adapter.go +++ b/config/adapter.go @@ -64,6 +64,7 @@ const ( dummyGDPR string = "0" dummyGDPRConsent string = "someGDPRConsentString" dummyCCPA string = "1NYN" + dummyZoneID string = "zone" ) // validateAdapterEndpoint makes sure that an adapter has a valid endpoint @@ -84,6 +85,7 @@ func validateAdapterEndpoint(endpoint string, adapterName string, errs []error) Host: dummyHost, PublisherID: dummyPublisherID, AccountID: dummyAccountID, + ZoneID: dummyZoneID, }) if err != nil { return append(errs, fmt.Errorf("Unable to resolve endpoint: %s for adapter: %s. %v", endpoint, adapterName, err)) diff --git a/config/config.go b/config/config.go old mode 100755 new mode 100644 index 83001b10f3c..efb71adcc31 --- a/config/config.go +++ b/config/config.go @@ -98,7 +98,7 @@ type HTTPClient struct { DialKeepAlive int `mapstructure:"dial_keepalive"` } -func (cfg *Configuration) validate() []error { +func (cfg *Configuration) validate(v *viper.Viper) []error { var errs []error errs = cfg.AuctionTimeouts.validate(errs) errs = cfg.StoredRequests.validate(errs) @@ -110,7 +110,7 @@ func (cfg *Configuration) validate() []error { if cfg.MaxRequestSize < 0 { errs = append(errs, fmt.Errorf("cfg.max_request_size must be >= 0. Got %d", cfg.MaxRequestSize)) } - errs = cfg.GDPR.validate(errs) + errs = cfg.GDPR.validate(v, errs) errs = cfg.CurrencyConverter.validate(errs) errs = validateAdapters(cfg.Adapters, errs) errs = cfg.Debug.validate(errs) @@ -198,22 +198,26 @@ type Privacy struct { type GDPR struct { Enabled bool `mapstructure:"enabled"` HostVendorID int `mapstructure:"host_vendor_id"` - UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"` + DefaultValue string `mapstructure:"default_value"` Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"` NonStandardPublisherMap map[string]struct{} - TCF1 TCF1 `mapstructure:"tcf1"` TCF2 TCF2 `mapstructure:"tcf2"` AMPException bool `mapstructure:"amp_exception"` // Deprecated: Use account-level GDPR settings (gdpr.integration_enabled.amp) instead // EEACountries (EEA = European Economic Area) are a list of countries where we should assume GDPR applies. // If the gdpr flag is unset in a request, but geo.country is set, we will assume GDPR applies if and only // if the country matches one on this list. If both the GDPR flag and country are not set, we default - // to UsersyncIfAmbiguous + // to DefaultValue EEACountries []string `mapstructure:"eea_countries"` EEACountriesMap map[string]struct{} } -func (cfg *GDPR) validate(errs []error) []error { +func (cfg *GDPR) validate(v *viper.Viper, errs []error) []error { + if !v.IsSet("gdpr.default_value") { + errs = append(errs, fmt.Errorf("gdpr.default_value is required and must be specified")) + } else if cfg.DefaultValue != "0" && cfg.DefaultValue != "1" { + errs = append(errs, fmt.Errorf("gdpr.default_value must be 0 or 1")) + } if cfg.HostVendorID < 0 || cfg.HostVendorID > 0xffff { errs = append(errs, fmt.Errorf("gdpr.host_vendor_id must be in the range [0, %d]. Got %d", 0xffff, cfg.HostVendorID)) } @@ -223,9 +227,6 @@ func (cfg *GDPR) validate(errs []error) []error { if cfg.AMPException == true { errs = append(errs, fmt.Errorf("gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)")) } - if cfg.TCF1.FetchGVL == true { - errs = append(errs, fmt.Errorf("gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward")) - } return errs } @@ -242,28 +243,33 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration { return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond } -// TCF1 defines the TCF1 specific configurations for GDPR -type TCF1 struct { - FetchGVL bool `mapstructure:"fetch_gvl"` // Deprecated: In a future version TCF1 will always use the fallback GVL - FallbackGVLPath string `mapstructure:"fallback_gvl_path"` -} - // TCF2 defines the TCF2 specific configurations for GDPR type TCF2 struct { - Enabled bool `mapstructure:"enabled"` - Purpose1 PurposeDetail `mapstructure:"purpose1"` - Purpose2 PurposeDetail `mapstructure:"purpose2"` - Purpose7 PurposeDetail `mapstructure:"purpose7"` - SpecialPurpose1 PurposeDetail `mapstructure:"special_purpose1"` - PurposeOneTreatment PurposeOneTreatement `mapstructure:"purpose_one_treatement"` + Enabled bool `mapstructure:"enabled"` + Purpose1 TCF2Purpose `mapstructure:"purpose1"` + Purpose2 TCF2Purpose `mapstructure:"purpose2"` + Purpose3 TCF2Purpose `mapstructure:"purpose3"` + Purpose4 TCF2Purpose `mapstructure:"purpose4"` + Purpose5 TCF2Purpose `mapstructure:"purpose5"` + Purpose6 TCF2Purpose `mapstructure:"purpose6"` + Purpose7 TCF2Purpose `mapstructure:"purpose7"` + Purpose8 TCF2Purpose `mapstructure:"purpose8"` + Purpose9 TCF2Purpose `mapstructure:"purpose9"` + Purpose10 TCF2Purpose `mapstructure:"purpose10"` + SpecialPurpose1 TCF2Purpose `mapstructure:"special_purpose1"` + PurposeOneTreatment TCF2PurposeOneTreatment `mapstructure:"purpose_one_treatment"` } // Making a purpose struct so purpose specific details can be added later. -type PurposeDetail struct { - Enabled bool `mapstructure:"enabled"` +type TCF2Purpose struct { + Enabled bool `mapstructure:"enabled"` + EnforceVendors bool `mapstructure:"enforce_vendors"` + // Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed + VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions"` + VendorExceptionMap map[openrtb_ext.BidderName]struct{} } -type PurposeOneTreatement struct { +type TCF2PurposeOneTreatment struct { Enabled bool `mapstructure:"enabled"` AccessAllowed bool `mapstructure:"access_allowed"` } @@ -358,6 +364,9 @@ type DisabledMetrics struct { // server establishes with bidder servers such as the number of connections // that were created or reused. AdapterConnectionMetrics bool `mapstructure:"adapter_connections_metrics"` + + // True if we don't want to collect the per adapter GDPR request blocked metric + AdapterGDPRRequestBlocked bool `mapstructure:"adapter_gdpr_request_blocked"` } func (cfg *Metrics) validate(errs []error) []error { @@ -451,6 +460,7 @@ type DefReqFiles struct { type Debug struct { TimeoutNotification TimeoutNotification `mapstructure:"timeout_notification"` + OverrideToken string `mapstructure:"override_token"` } func (cfg *Debug) validate(errs []error) []error { @@ -509,6 +519,30 @@ func New(v *viper.Viper) (*Configuration, error) { c.GDPR.NonStandardPublisherMap[c.GDPR.EEACountries[i]] = s } + // To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table located in the + // VendorExceptions field of the GDPR.TCF2.PurposeX struct defined in this file + purposeConfigs := []*TCF2Purpose{ + &c.GDPR.TCF2.Purpose1, + &c.GDPR.TCF2.Purpose2, + &c.GDPR.TCF2.Purpose3, + &c.GDPR.TCF2.Purpose4, + &c.GDPR.TCF2.Purpose5, + &c.GDPR.TCF2.Purpose6, + &c.GDPR.TCF2.Purpose7, + &c.GDPR.TCF2.Purpose8, + &c.GDPR.TCF2.Purpose9, + &c.GDPR.TCF2.Purpose10, + &c.GDPR.TCF2.SpecialPurpose1, + } + for c := 0; c < len(purposeConfigs); c++ { + purposeConfigs[c].VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{}) + + for v := 0; v < len(purposeConfigs[c].VendorExceptions); v++ { + bidderName := purposeConfigs[c].VendorExceptions[v] + purposeConfigs[c].VendorExceptionMap[bidderName] = struct{}{} + } + } + // To look for a request's app_id in O(1) time, we fill this hash table located in the // the BlacklistedApps field of the Configuration struct defined in this file c.BlacklistedAppMap = make(map[string]bool) @@ -528,7 +562,7 @@ func New(v *viper.Viper) (*Configuration, error) { glog.Info("Logging the resolved configuration:") logGeneral(reflect.ValueOf(c), " \t") - if errs := c.validate(); len(errs) > 0 { + if errs := c.validate(v); len(errs) > 0 { return &c, errortypes.NewAggregateError("validation errors", errs) } @@ -573,6 +607,8 @@ func (cfg *Configuration) setDerivedDefaults() { externalURL := cfg.ExternalURL setDefaultUsersync(cfg.Adapters, openrtb_ext.Bidder33Across, "https://ic.tynt.com/r/d?m=xch&rt=html&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3D33across%26uid%3D33XUSERID33X&id=zzz000000000002zzz") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAcuityAds, "https://cs.admanmedia.com/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dacuityads%26uid%3D%5BUID%5D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdagio, "https://mp.4dex.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadagio%26uid%3D%7B%7BUID%7D%7D%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26us_privacy%3D{{.USPrivacy}}") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdf, "https://cm.adform.net/cookie?redirect_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadf%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdform, "https://cm.adform.net/cookie?redirect_url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadform%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderAdgeneration doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAdkernel, "https://sync.adkernel.com/user-sync?t=image&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dadkernel%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BUID%7D") @@ -592,6 +628,9 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderAvocet, "https://ads.avct.cloud/getuid?&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Davocet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7BUUID%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeachfront, "https://sync.bfmio.com/sync_s2s?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeachfront%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bio_cid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBeintoo, "https://ib.beintoo.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbeintoo%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBidmyadz, "https://cookie-sync.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&red="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbidmyadz%26uid%3D%5BUID%5D%26us_privacy%3D{{.USPrivacy}}%26gdpr_consent%3D{{.GDPRConsent}}%26gdpr%3D{{.GDPR}}") + // openrtb_ext.BidderBidsCube doesn't have a good default. + // openrtb_ext.BidderBmtm doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderBrightroll, "https://pr-bh.ybp.yahoo.com/sync/appnexusprebidserver/?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&url="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dbrightroll%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderColossus, "https://sync.colossusssp.com/pbs.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dcolossus%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderConnectAd, "https://cdn.connectad.io/connectmyusers.php?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dconnectad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") @@ -603,6 +642,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDmx, "https://dmx.districtm.io/s/v1/img/s/10007?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddatablocks%26gdpr%3D%24%7Bgdpr%7D%26gdpr_consent%3D%24%7Bgdpr_consent%7D%26uid%3D%24%7Buid%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderDeepintent, "https://match.deepintent.com/usersync/136?id=unk&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Ddeepintent%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEmxDigital, "https://cs.emxdgt.com/um?ssp=pbs&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Demx_digital%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEVolution, "https://sync.e-volution.ai/pbserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3De_volution%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEngageBDR, "https://match.bnmla.com/usersync/s2s_sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dengagebdr%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderEPlanning, "https://ads.us.e-planning.net/uspd/1/?du="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Deplanning%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") // openrtb_ext.BidderEpom doesn't have a good default. @@ -612,14 +652,16 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGrid, "https://x.bidswitch.net/check_uuid/"+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgrid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BBSW_UUID%7D?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderGumGum, "https://rtb.gumgum.com/usync/prbds2s?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderImprovedigital, "https://ad.360yield.com/server_match?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dimprovedigital%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BPUB_USER_ID%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderInMobi, "https://id5-sync.com/i/495/0.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&callback="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dinmobi%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7BID5UID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderIx, "https://ssum.casalemedia.com/usermatchredir?s=186523&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dix%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") // openrtb_ext.BidderInvibes doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderJixie, "https://id.jixie.io/api/sync?pid=&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Djixie%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%25%25JXUID%25%25") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderKrushmedia, "https://cs.krushmedia.com/4e4abdd5ecc661643458a730b1aa927d.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dkrushmedia%26uid%3D%5BUID%5D") - setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLifestreet, "https://ads.lfstmedia.com/idsync/137062?synced=1&ttl=1s&rurl="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlifestreet%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%24visitor_cookie%24%24") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLockerDome, "https://lockerdome.com/usync/prebidserver?pid="+cfg.Adapters["lockerdome"].PlatformID+"&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlockerdome%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7B%7Buid%7D%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLogicad, "https://cr-p31.ladsp.jp/cookiesender/31?r=true&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlogicad%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderLunaMedia, "https://api.lunamedia.io/xp/user-sync?redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dlunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSaLunaMedia, "https://cookie.lmgssp.com/pserver?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&ccpa={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsa_lunamedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") + // openrtb_ext.BidderMadvertise doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMarsmedia, "https://dmp.rtbsrv.com/dmp/profiles/cm?p_id=179&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmarsmedia%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUUID%7D") // openrtb_ext.BidderMediafuse doesn't have a good default. setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderMgid, "https://cm.mgid.com/m?cdsp=363893&adu="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dmgid%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%7Bmuidn%7D") @@ -627,6 +669,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNinthDecimal, "https://rtb.ninthdecimal.com/xp/user-sync?acctid={aid}&&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dninthdecimal%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderNoBid, "https://ads.servenobid.com/getsync?tek=pbs&ver=1&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dnobid%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOpenx, "https://rtb.openx.net/sync/prebid?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dopenx%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOperaads, "https://t.adx.opera.com/pbs/sync?gdpr={{.GDPR}}&us_privacy={{.USPrivacy}}&gdpr_consent={{.GDPRConsent}}&r="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Doperaads%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOneTag, "https://onetag-sys.com/usync/?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Donetag%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUSER_TOKEN%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderOutbrain, "https://prebidtest.zemanta.com/usersync/prebidtest?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Doutbrain%26uid%3D__ZUID__") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderPubmatic, "https://ads.pubmatic.com/AdServer/js/user_sync.html?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&predirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dpubmatic%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D") @@ -638,6 +681,7 @@ func (cfg *Configuration) setDerivedDefaults() { setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartAdserver, "https://ssbsync-global.smartadserver.com/api/sync?callerId=5&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirectUri="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartadserver%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%5Bssb_sync_pid%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartRTB, "https://market-global.smrtb.com/sync/all?nid=smartrtb&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&rr="+url.QueryEscape(externalURL)+"%252Fsetuid%253Fbidder%253Dsmartrtb%2526gdpr%253D{{.GDPR}}%2526gdpr_consent%253D{{.GDPRConsent}}%2526uid%253D%257BXID%257D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmartyAds, "https://as.ck-ie.com/prebid.gif?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmartyads%26uid%3D%5BUID%5D") + setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSmileWanted, "https://csync.smilewanted.com/getuid?source=prebid-server&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsmilewanted%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSomoaudience, "https://publisher-east.mobileadtrading.com/usersync?ru="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsomoaudience%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7BUID%7D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSonobi, "https://sync.go.sonobi.com/us.gif?loc="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsonobi%26consent_string%3D{{.GDPR}}%26gdpr%3D{{.GDPRConsent}}%26uid%3D%5BUID%5D") setDefaultUsersync(cfg.Adapters, openrtb_ext.BidderSovrn, "https://ap.lijit.com/pixel?redir="+url.QueryEscape(externalURL)+"%2Fsetuid%3Fbidder%3Dsovrn%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24UID") @@ -714,6 +758,10 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("http_client.max_idle_connections", 400) v.SetDefault("http_client.max_idle_connections_per_host", 10) v.SetDefault("http_client.idle_connection_timeout_seconds", 60) + v.SetDefault("http_client_cache.max_connections_per_host", 0) // unlimited + v.SetDefault("http_client_cache.max_idle_connections", 10) + v.SetDefault("http_client_cache.max_idle_connections_per_host", 2) + v.SetDefault("http_client_cache.idle_connection_timeout_seconds", 60) v.SetDefault("http_client.tls_handshake_timeout", 0) //no timeout v.SetDefault("http_client.response_header_timeout", 0) //unlimited v.SetDefault("http_client.dial_timeout", 0) //no timeout @@ -725,6 +773,8 @@ func SetupViper(v *viper.Viper, filename string) { // no metrics configured by default (metrics{host|database|username|password}) v.SetDefault("metrics.disabled_metrics.account_adapter_details", false) v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true) + v.SetDefault("metrics.disabled_metrics.adapter_gdpr_request_blocked", false) + v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true) v.SetDefault("metrics.influxdb.host", "") v.SetDefault("metrics.influxdb.database", "") v.SetDefault("metrics.influxdb.username", "") @@ -815,6 +865,8 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.33across.endpoint", "https://ssc.33across.com/api/v1/s2s") v.SetDefault("adapters.33across.partner_id", "") v.SetDefault("adapters.acuityads.endpoint", "http://{{.Host}}.admanmedia.com/bid?token={{.AccountID}}") + v.SetDefault("adapters.adagio.endpoint", "https://mp.4dex.io/ortb2") + v.SetDefault("adapters.adf.endpoint", "https://adx.adform.net/adx/openrtb") v.SetDefault("adapters.adform.endpoint", "https://adx.adform.net/adx") v.SetDefault("adapters.adgeneration.endpoint", "https://d.socdm.com/adsv/v1") v.SetDefault("adapters.adhese.endpoint", "https://ads-{{.AccountID}}.adhese.com/json") @@ -833,6 +885,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.adxcg.disabled", true) v.SetDefault("adapters.adyoulike.endpoint", "https://broker.omnitagjs.com/broker/bid?partnerId=19340f4f097d16f41f34fc0274981ca4") v.SetDefault("adapters.aja.endpoint", "https://ad.as.amanad.adtdp.com/v1/bid/4") + v.SetDefault("adapters.algorix.endpoint", "https://xyz.svr-algorix.com/rtb/sa?sid={{.SourceId}}&token={{.AccountID}}") v.SetDefault("adapters.amx.endpoint", "http://pbs.amxrtb.com/auction/openrtb") v.SetDefault("adapters.applogy.endpoint", "http://rtb.applogy.com/v1/prebid") v.SetDefault("adapters.appnexus.endpoint", "http://ib.adnxs.com/openrtb2") // Docs: https://wiki.appnexus.com/display/supply/Incoming+Bid+Request+from+SSPs @@ -840,11 +893,15 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.audiencenetwork.disabled", true) v.SetDefault("adapters.audiencenetwork.endpoint", "https://an.facebook.com/placementbid.ortb") v.SetDefault("adapters.avocet.disabled", true) + v.SetDefault("adapters.axonix.disabled", true) v.SetDefault("adapters.beachfront.endpoint", "https://display.bfmio.com/prebid_display") v.SetDefault("adapters.beachfront.extra_info", "{\"video_endpoint\":\"https://reachms.bfmio.com/bid.json?exchange_id\"}") v.SetDefault("adapters.beintoo.endpoint", "https://ib.beintoo.com/um") v.SetDefault("adapters.between.endpoint", "http://{{.Host}}.betweendigital.com/openrtb_bid?sspId={{.PublisherID}}") v.SetDefault("adapters.bidmachine.endpoint", "https://{{.Host}}.bidmachine.io") + v.SetDefault("adapters.bidmyadz.endpoint", "http://endpoint.bidmyadz.com/c0f68227d14ed938c6c49f3967cbe9bc") + v.SetDefault("adapters.bidscube.endpoint", "http://supply.bidscube.com/?c=o&m=rtb") + v.SetDefault("adapters.bmtm.endpoint", "https://one.elitebidder.com/api/pbs") v.SetDefault("adapters.brightroll.endpoint", "http://east-bid.ybp.yahoo.com/bid/appnexuspbs") v.SetDefault("adapters.colossus.endpoint", "http://colossusssp.com/?c=o&m=rtb") v.SetDefault("adapters.connectad.endpoint", "http://bidder.connectad.io/API?src=pbs") @@ -861,23 +918,27 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.eplanning.endpoint", "http://rtb.e-planning.net/pbs/1") v.SetDefault("adapters.epom.endpoint", "https://an.epom.com/ortb") v.SetDefault("adapters.epom.disabled", true) + v.SetDefault("adapters.e_volution.endpoint", "http://service.e-volution.ai/pbserver") v.SetDefault("adapters.gamma.endpoint", "https://hb.gammaplatform.com/adx/request/") v.SetDefault("adapters.gamoshi.endpoint", "https://rtb.gamoshi.io") v.SetDefault("adapters.grid.endpoint", "https://grid.bidswitch.net/sp_bid?sp=prebid") v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") v.SetDefault("adapters.improvedigital.endpoint", "http://ad.360yield.com/pbs") v.SetDefault("adapters.inmobi.endpoint", "https://api.w.inmobi.com/showad/openrtb/bidder/prebid") + v.SetDefault("adapters.interactiveoffers.endpoint", "https://rtb.ioadx.com/bidRequest/?partnerId=d9e56d418c4825d466ee96c7a31bf1da6b62fa04") v.SetDefault("adapters.ix.disabled", false) v.SetDefault("adapters.ix.endpoint", "http://exchange.indexww.com/pbs?p=192919") v.SetDefault("adapters.jixie.endpoint", "https://hb.jixie.io/v2/hbsvrpost") + v.SetDefault("adapters.kayzen.endpoint", "https://bids-{{.ZoneID}}.bidder.kayzen.io/?exchange={{.AccountID}}") v.SetDefault("adapters.krushmedia.endpoint", "http://ads4.krushmedia.com/?c=rtb&m=req&key={{.AccountID}}") v.SetDefault("adapters.invibes.endpoint", "https://{{.Host}}/bid/ServerBidAdContent") v.SetDefault("adapters.kidoz.endpoint", "http://prebid-adapter.kidoz.net/openrtb2/auction?src=prebid-server") v.SetDefault("adapters.kubient.endpoint", "https://kssp.kbntx.ch/prebid") - v.SetDefault("adapters.lifestreet.endpoint", "https://prebid.s2s.lfstmedia.com/adrequest") v.SetDefault("adapters.lockerdome.endpoint", "https://lockerdome.com/ladbid/prebidserver/openrtb2") v.SetDefault("adapters.logicad.endpoint", "https://pbs.ladsp.com/adrequest/prebidserver") v.SetDefault("adapters.lunamedia.endpoint", "http://api.lunamedia.io/xp/get?pubid={{.PublisherID}}") + v.SetDefault("adapters.sa_lunamedia.endpoint", "http://balancer.lmgssp.com/pserver") + v.SetDefault("adapters.madvertise.endpoint", "https://mobile.mng-ads.com/bidrequest{{.ZoneID}}") v.SetDefault("adapters.marsmedia.endpoint", "https://bid306.rtbsrv.com/bidder/?bid=f3xtet") v.SetDefault("adapters.mediafuse.endpoint", "http://ghb.hbmp.mediafuse.com/pbs/ortb") v.SetDefault("adapters.mgid.endpoint", "https://prebid.mgid.com/prebid/") @@ -888,6 +949,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.nobid.endpoint", "https://ads.servenobid.com/ortb_adreq?tek=pbs&ver=1") v.SetDefault("adapters.onetag.endpoint", "https://prebid-server.onetag-sys.com/prebid-server/{{.PublisherID}}") v.SetDefault("adapters.openx.endpoint", "http://rtb.openx.net/prebid") + v.SetDefault("adapters.operaads.endpoint", "https://s.adx.opera.com/ortb/v2/{{.PublisherID}}?ep={{.AccountID}}") v.SetDefault("adapters.orbidder.endpoint", "https://orbidder.otto.de/openrtb2") v.SetDefault("adapters.outbrain.endpoint", "https://prebidtest.zemanta.com/api/bidder/prebidtest/bid/") v.SetDefault("adapters.pangle.disabled", true) @@ -906,6 +968,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.smartadserver.endpoint", "https://ssb-global.smartadserver.com") v.SetDefault("adapters.smartrtb.endpoint", "http://market-east.smrtb.com/json/publisher/rtb?pubid={{.PublisherID}}") v.SetDefault("adapters.smartyads.endpoint", "http://{{.Host}}.smartyads.com/bid?rtb_seat_id={{.SourceId}}&secret_key={{.AccountID}}") + v.SetDefault("adapters.smilewanted.endpoint", "http://prebid-server.smilewanted.com") v.SetDefault("adapters.somoaudience.endpoint", "http://publisher-east.mobileadtrading.com/rtb/bid") v.SetDefault("adapters.sonobi.endpoint", "https://apex.go.sonobi.com/prebid?partnerid=71d9d3d8af") v.SetDefault("adapters.sovrn.endpoint", "http://ap.lijit.com/rtb/bid?src=prebid_server") @@ -923,6 +986,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.valueimpression.endpoint", "https://rtb.valueimpression.com/endpoint") v.SetDefault("adapters.vastbidder.endpoint", "https://test.com") v.SetDefault("adapters.verizonmedia.disabled", true) + v.SetDefault("adapters.viewdeos.endpoint", "http://ghb.sync.viewdeos.com/pbs/ortb") v.SetDefault("adapters.visx.endpoint", "https://t.visx.net/s2s_bid?wrapperType=s2s_prebid_standard") v.SetDefault("adapters.vrtcal.endpoint", "http://rtb.vrtcal.com/bidder_prebid.vap?ssp=1812") v.SetDefault("adapters.yeahmobi.endpoint", "https://{{.Host}}/prebid/bid") @@ -941,22 +1005,47 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("analytics.pubstack.buffers.count", 100) v.SetDefault("analytics.pubstack.buffers.timeout", "900s") v.SetDefault("amp_timeout_adjustment_ms", 0) + v.BindEnv("gdpr.default_value") v.SetDefault("gdpr.enabled", true) v.SetDefault("gdpr.host_vendor_id", 0) + v.SetDefault("gdpr.default_value", "0") v.SetDefault("gdpr.usersync_if_ambiguous", true) v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) v.SetDefault("gdpr.non_standard_publishers", []string{""}) - v.SetDefault("gdpr.tcf1.fetch_gvl", false) - v.SetDefault("gdpr.tcf1.fallback_gvl_path", "/home/http/GO_SERVER/dmhbserver/static/tcf1/fallback_gvl.json") v.SetDefault("gdpr.tcf2.enabled", true) 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_vendors", true) + v.SetDefault("gdpr.tcf2.purpose2.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose3.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose4.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose5.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose6.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose7.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose8.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose9.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose10.enforce_vendors", true) + v.SetDefault("gdpr.tcf2.purpose1.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose2.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose3.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose4.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose5.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose6.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose7.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose8.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose9.vendor_exceptions", []openrtb_ext.BidderName{}) + v.SetDefault("gdpr.tcf2.purpose10.vendor_exceptions", []openrtb_ext.BidderName{}) v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true) - v.SetDefault("gdpr.tcf2.purpose_one_treatement.enabled", true) - v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true) + v.SetDefault("gdpr.tcf2.special_purpose1.vendor_exceptions", []openrtb_ext.BidderName{}) v.SetDefault("gdpr.amp_exception", false) v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST", "FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA", @@ -985,6 +1074,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("debug.timeout_notification.log", false) v.SetDefault("debug.timeout_notification.sampling_rate", 0.0) v.SetDefault("debug.timeout_notification.fail_only", false) + v.SetDefault("debug.override_token", "") /* IPv4 /* Site Local: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 @@ -1010,6 +1100,10 @@ func SetupViper(v *viper.Viper, filename string) { // Migrate config settings to maintain compatibility with old configs migrateConfig(v) + migrateConfigPurposeOneTreatment(v) + + v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true) + v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true) } func migrateConfig(v *viper.Viper) { @@ -1025,6 +1119,18 @@ func migrateConfig(v *viper.Viper) { } } +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 setBidderDefaults(v *viper.Viper, bidder string) { adapterCfgPrefix := "adapters." v.SetDefault(adapterCfgPrefix+bidder+".endpoint", "") diff --git a/config/config_test.go b/config/config_test.go index 43ee8fa21df..f5fbbfa341f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -115,11 +115,8 @@ func TestExternalCacheURLValidate(t *testing.T) { } } -func TestDefaults(t *testing.T) { - v := viper.New() - SetupViper(v, "") - cfg, err := New(v) - assert.NoError(t, err, "Setting up config should work but it doesn't") + func TestDefaults(t *testing.T) { + cfg, _ := newDefaultConfig(t) cmpInts(t, "port", cfg.Port, 8000) cmpInts(t, "admin_port", cfg.AdminPort, 6060) @@ -135,18 +132,128 @@ func TestDefaults(t *testing.T) { cmpInts(t, "metrics.influxdb.collection_rate_seconds", cfg.Metrics.Influxdb.MetricSendInterval, 20) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, false) cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) + cmpBools(t, "adapter_gdpr_request_blocked", cfg.Metrics.Disabled.AdapterGDPRRequestBlocked, false) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "") cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path) cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, true) cmpBools(t, "generate_bid_id", cfg.GenerateBidID, false) + + //Assert purpose VendorExceptionMap hash tables were built correctly + expectedTCF2 := TCF2{ + Enabled: true, + Purpose1: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose2: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose3: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose4: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose5: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose6: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose7: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose8: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose9: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + Purpose10: TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + SpecialPurpose1: TCF2Purpose{ + Enabled: true, + VendorExceptions: []openrtb_ext.BidderName{}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + }, + PurposeOneTreatment: TCF2PurposeOneTreatment{ + Enabled: true, + AccessAllowed: true, + }, + } + assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2") } var fullConfig = []byte(` gdpr: host_vendor_id: 15 - usersync_if_ambiguous: true + default_value: "1" non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] + tcf2: + purpose1: + enforce_vendors: false + vendor_exceptions: ["foo1a", "foo1b"] + purpose2: + enabled: false + enforce_vendors: false + vendor_exceptions: ["foo2"] + purpose3: + enforce_vendors: false + vendor_exceptions: ["foo3"] + purpose4: + enforce_vendors: false + vendor_exceptions: ["foo4"] + purpose5: + enforce_vendors: false + vendor_exceptions: ["foo5"] + purpose6: + enforce_vendors: false + vendor_exceptions: ["foo6"] + purpose7: + enforce_vendors: false + vendor_exceptions: ["foo7"] + purpose8: + enforce_vendors: false + vendor_exceptions: ["foo8"] + purpose9: + enforce_vendors: false + vendor_exceptions: ["foo9"] + purpose10: + enforce_vendors: false + vendor_exceptions: ["foo10"] + special_purpose1: + vendor_exceptions: ["fooSP1"] ccpa: enforce: true lmt: @@ -197,6 +304,7 @@ metrics: disabled_metrics: account_adapter_details: true adapter_connections_metrics: true + adapter_gdpr_request_blocked: true datacache: type: postgres filename: /usr/db/db.db @@ -348,7 +456,7 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "http_client_cache.max_idle_connections_per_host", cfg.CacheClient.MaxIdleConnsPerHost, 2) cmpInts(t, "http_client_cache.idle_connection_timeout_seconds", cfg.CacheClient.IdleConnTimeout, 3) cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15) - cmpBools(t, "gdpr.usersync_if_ambiguous", cfg.GDPR.UsersyncIfAmbiguous, true) + cmpStrings(t, "gdpr.default_value", cfg.GDPR.DefaultValue, "1") //Assert the NonStandardPublishers was correctly unmarshalled cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[0], "siteID") @@ -377,6 +485,81 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "cfg.BlacklistedAppMap", cfg.BlacklistedAppMap[cfg.BlacklistedApps[i]], true) } + //Assert purpose VendorExceptionMap hash tables were built correctly + expectedTCF2 := TCF2{ + Enabled: true, + Purpose1: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo1a"), openrtb_ext.BidderName("foo1b")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo1a"): {}, openrtb_ext.BidderName("foo1b"): {}}, + }, + Purpose2: TCF2Purpose{ + Enabled: false, + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo2")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo2"): {}}, + }, + Purpose3: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo3")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo3"): {}}, + }, + Purpose4: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo4")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo4"): {}}, + }, + Purpose5: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo5")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo5"): {}}, + }, + Purpose6: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo6")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo6"): {}}, + }, + Purpose7: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo7")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo7"): {}}, + }, + Purpose8: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo8")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo8"): {}}, + }, + Purpose9: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo9")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo9"): {}}, + }, + Purpose10: TCF2Purpose{ + Enabled: true, // true by default + EnforceVendors: false, + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}}, + }, + SpecialPurpose1: TCF2Purpose{ + Enabled: true, // true by default + VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("fooSP1")}, + VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("fooSP1"): {}}, + }, + PurposeOneTreatment: TCF2PurposeOneTreatment{ + Enabled: true, // true by default + AccessAllowed: true, // true by default + }, + } + assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2") + cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://currency.prebid.org") cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800) cmpStrings(t, "recaptcha_secret", cfg.RecaptchaSecret, "asdfasdfasdfasdf") @@ -413,16 +596,19 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, false) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true) cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) + cmpBools(t, "adapter_gdpr_request_blocked", cfg.Metrics.Disabled.AdapterGDPRRequestBlocked, true) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem") cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24") cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16") cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[1], "2222::/16") cmpBools(t, "generate_bid_id", cfg.GenerateBidID, true) + cmpStrings(t, "debug.override_token", cfg.Debug.OverrideToken, "") } func TestUnmarshalAdapterExtraInfo(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(adapterExtraInfoConfig)) cfg, err := New(v) @@ -454,6 +640,9 @@ func TestUnmarshalAdapterExtraInfo(t *testing.T) { func TestValidConfig(t *testing.T) { cfg := Configuration{ + GDPR: GDPR{ + DefaultValue: "1", + }, StoredRequests: StoredRequests{ Files: FileFetcherConfig{Enabled: true}, InMemoryCache: InMemoryCache{ @@ -475,14 +664,18 @@ func TestValidConfig(t *testing.T) { }, } + v := viper.New() + v.Set("gdpr.default_value", "0") + resolvedStoredRequestsConfig(&cfg) - err := cfg.validate() + err := cfg.validate(v) assert.Nil(t, err, "OpenRTB filesystem config should work. %v", err) } func TestMigrateConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(oldStoredRequestsConfig)) migrateConfig(v) @@ -499,16 +692,87 @@ func TestMigrateConfigFromEnv(t *testing.T) { defer os.Unsetenv("PBS_STORED_REQUESTS_FILESYSTEM") } os.Setenv("PBS_STORED_REQUESTS_FILESYSTEM", "true") - v := viper.New() - SetupViper(v, "") - cfg, err := New(v) - assert.NoError(t, err, "Setting up config should work but it doesn't") + cfg, _ := newDefaultConfig(t) cmpBools(t, "stored_requests.filesystem.enabled", true, cfg.StoredRequests.Files.Enabled) } +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 TestInvalidAdapterEndpointConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(invalidAdapterEndpointConfig)) _, err := New(v) @@ -518,6 +782,7 @@ func TestInvalidAdapterEndpointConfig(t *testing.T) { func TestInvalidAdapterUserSyncURLConfig(t *testing.T) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer(invalidUserSyncURLConfig)) _, err := New(v) @@ -525,16 +790,16 @@ func TestInvalidAdapterUserSyncURLConfig(t *testing.T) { } func TestNegativeRequestSize(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.MaxRequestSize = -1 - assertOneError(t, cfg.validate(), "cfg.max_request_size must be >= 0. Got -1") + assertOneError(t, cfg.validate(v), "cfg.max_request_size must be >= 0. Got -1") } func TestNegativePrometheusTimeout(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Metrics.Prometheus.Port = 8001 cfg.Metrics.Prometheus.TimeoutMillisRaw = 0 - assertOneError(t, cfg.validate(), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001") + assertOneError(t, cfg.validate(v), "metrics.prometheus.timeout_ms must be positive if metrics.prometheus.port is defined. Got timeout=0 and port=8001") } func TestInvalidHostVendorID(t *testing.T) { @@ -556,44 +821,57 @@ func TestInvalidHostVendorID(t *testing.T) { } for _, tt := range tests { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.HostVendorID = tt.vendorID - errs := cfg.validate() + errs := cfg.validate(v) assert.Equal(t, 1, len(errs), tt.description) assert.EqualError(t, errs[0], tt.wantErrorMsg, tt.description) } } -func TestInvalidFetchGVL(t *testing.T) { - cfg := newDefaultConfig(t) - cfg.GDPR.TCF1.FetchGVL = true - assertOneError(t, cfg.validate(), "gdpr.tcf1.fetch_gvl has been discontinued and must be removed from your config. TCF1 will always use the fallback GVL going forward") -} - func TestInvalidAMPException(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.GDPR.AMPException = true - assertOneError(t, cfg.validate(), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") + assertOneError(t, cfg.validate(v), "gdpr.amp_exception has been discontinued and must be removed from your config. If you need to disable GDPR for AMP, you may do so per-account (gdpr.integration_enabled.amp) or at the host level for the default account (account_defaults.gdpr.integration_enabled.amp)") +} + +func TestInvalidGDPRDefaultValue(t *testing.T) { + cfg, v := newDefaultConfig(t) + cfg.GDPR.DefaultValue = "2" + assertOneError(t, cfg.validate(v), "gdpr.default_value must be 0 or 1") +} + +func TestMissingGDPRDefaultValue(t *testing.T) { + v := viper.New() + + cfg, _ := newDefaultConfig(t) + assertOneError(t, cfg.validate(v), "gdpr.default_value is required and must be specified") } func TestNegativeCurrencyConverterFetchInterval(t *testing.T) { + v := viper.New() + v.Set("gdpr.default_value", "0") + cfg := Configuration{ CurrencyConverter: CurrencyConverter{ FetchIntervalSeconds: -1, }, } - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds should prevent negative values, but it doesn't") } func TestOverflowedCurrencyConverterFetchInterval(t *testing.T) { + v := viper.New() + v.Set("gdpr.default_value", "0") + cfg := Configuration{ CurrencyConverter: CurrencyConverter{ FetchIntervalSeconds: (0xffff) + 1, }, } - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.currency_converter.fetch_interval_seconds prevent values over %d, but it doesn't", 0xffff) } @@ -651,6 +929,7 @@ func TestNewCallsRequestValidation(t *testing.T) { for _, test := range testCases { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") v.ReadConfig(bytes.NewBuffer([]byte( `request_validation: @@ -668,31 +947,32 @@ func TestNewCallsRequestValidation(t *testing.T) { } func TestValidateDebug(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Debug.TimeoutNotification.SamplingRate = 1.1 - err := cfg.validate() + err := cfg.validate(v) assert.NotNil(t, err, "cfg.debug.timeout_notification.sampling_rate should not be allowed to be greater than 1.0, but it was allowed") } func TestValidateAccountsConfigRestrictions(t *testing.T) { - cfg := newDefaultConfig(t) + cfg, v := newDefaultConfig(t) cfg.Accounts.Files.Enabled = true cfg.Accounts.HTTP.Endpoint = "http://localhost" cfg.Accounts.Postgres.ConnectionInfo.Database = "accounts" - errs := cfg.validate() + errs := cfg.validate(v) assert.Len(t, errs, 1) assert.Contains(t, errs, errors.New("accounts.postgres: retrieving accounts via postgres not available, use accounts.files")) } -func newDefaultConfig(t *testing.T) *Configuration { +func newDefaultConfig(t *testing.T) (*Configuration, *viper.Viper) { v := viper.New() SetupViper(v, "") + v.Set("gdpr.default_value", "0") v.SetConfigType("yaml") cfg, err := New(v) - assert.NoError(t, err) - return cfg + assert.NoError(t, err, "Setting up config should work but it doesn't") + return cfg, v } func assertOneError(t *testing.T, errs []error, message string) { diff --git a/currency/aggregate_conversions.go b/currency/aggregate_conversions.go new file mode 100644 index 00000000000..a15404fe501 --- /dev/null +++ b/currency/aggregate_conversions.go @@ -0,0 +1,41 @@ +package currency + +// AggregateConversions contains both the request-defined currency rate +// map found in request.ext.prebid.currency and the currencies conversion +// rates fetched with the RateConverter object defined in rate_converter.go +// It implements the Conversions interface. +type AggregateConversions struct { + customRates, serverRates Conversions +} + +// NewAggregateConversions expects both customRates and pbsRates to not be nil +func NewAggregateConversions(customRates, pbsRates Conversions) *AggregateConversions { + return &AggregateConversions{ + customRates: customRates, + serverRates: pbsRates, + } +} + +// GetRate returns the conversion rate between two currencies prioritizing +// the customRates currency rate over that of the PBS currency rate service +// returns an error if both Conversions objects return error. +func (re *AggregateConversions) GetRate(from string, to string) (float64, error) { + rate, err := re.customRates.GetRate(from, to) + if err == nil { + return rate, nil + } else if _, isMissingRateErr := err.(ConversionNotFoundError); !isMissingRateErr { + // other error, return the error + return 0, err + } + + // because the custom rates' GetRate() call returned an error other than "conversion + // rate not found", there's nothing wrong with the 3 letter currency code so let's + // try the PBS rates instead + return re.serverRates.GetRate(from, to) +} + +// GetRates is not implemented for AggregateConversions . There is no need to call +// this function for this scenario. +func (r *AggregateConversions) GetRates() *map[string]map[string]float64 { + return nil +} diff --git a/currency/aggregate_conversions_test.go b/currency/aggregate_conversions_test.go new file mode 100644 index 00000000000..773a596c28c --- /dev/null +++ b/currency/aggregate_conversions_test.go @@ -0,0 +1,89 @@ +package currency + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGroupedGetRate(t *testing.T) { + + // Setup: + customRates := NewRates(time.Now(), map[string]map[string]float64{ + "USD": { + "GBP": 3.00, + "EUR": 2.00, + }, + }) + + pbsRates := NewRates(time.Now(), map[string]map[string]float64{ + "USD": { + "GBP": 4.00, + "MXN": 10.00, + }, + }) + aggregateConversions := NewAggregateConversions(customRates, pbsRates) + + // Test cases: + type aTest struct { + desc string + from string + to string + expectedRate float64 + } + + testGroups := []struct { + expectedError error + testCases []aTest + }{ + { + expectedError: nil, + testCases: []aTest{ + {"Found in both, return custom rate", "USD", "GBP", 3.00}, + {"Found in both, return inverse custom rate", "GBP", "USD", 1 / 3.00}, + {"Found in custom rates only", "USD", "EUR", 2.00}, + {"Found in PBS rates only", "USD", "MXN", 10.00}, + {"Found in PBS rates only, return inverse", "MXN", "USD", 1 / 10.00}, + {"Same currency, return unitary rate", "USD", "USD", 1}, + }, + }, + { + expectedError: errors.New("currency: tag is not well-formed"), + testCases: []aTest{ + {"From-currency three-digit code malformed", "XX", "EUR", 0}, + {"To-currency three-digit code malformed", "GBP", "", 0}, + {"Both currencies malformed", "", "", 0}, + }, + }, + { + expectedError: errors.New("currency: tag is not a recognized currency"), + testCases: []aTest{ + {"From-currency three-digit code not found", "FOO", "EUR", 0}, + {"To-currency three-digit code not found", "GBP", "BAR", 0}, + }, + }, + { + expectedError: ConversionNotFoundError{FromCur: "GBP", ToCur: "EUR"}, + testCases: []aTest{ + {"Valid three-digit currency codes, but conversion rate not found", "GBP", "EUR", 0}, + }, + }, + } + + for _, group := range testGroups { + for _, tc := range group.testCases { + // Execute: + rate, err := aggregateConversions.GetRate(tc.from, tc.to) + + // Verify: + assert.Equal(t, tc.expectedRate, rate, "conversion rate doesn't match the expected rate: %s\n", tc.desc) + if group.expectedError != nil { + assert.Error(t, err, "error doesn't match expected: %s\n", tc.desc) + } else { + assert.NoError(t, err, "err should be nil: %s\n", tc.desc) + } + } + } +} diff --git a/currency/constant_rates.go b/currency/constant_rates.go index 26471a966a5..ccc5c24d3fc 100644 --- a/currency/constant_rates.go +++ b/currency/constant_rates.go @@ -1,8 +1,6 @@ package currency import ( - "fmt" - "golang.org/x/text/currency" ) @@ -29,7 +27,7 @@ func (r *ConstantRates) GetRate(from string, to string) (float64, error) { } if fromUnit.String() != toUnit.String() { - return 0, fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert '%s' => '%s'", fromUnit.String(), toUnit.String()) + return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()} } return 1, nil diff --git a/currency/errors.go b/currency/errors.go new file mode 100644 index 00000000000..bb4c42aa90a --- /dev/null +++ b/currency/errors.go @@ -0,0 +1,13 @@ +package currency + +import "fmt" + +// ConversionNotFoundError is thrown by the currency.Conversions GetRate(from string, to string) method +// when the conversion rate between the two currencies, nor its reciprocal, can be found. +type ConversionNotFoundError struct { + FromCur, ToCur string +} + +func (err ConversionNotFoundError) Error() string { + return fmt.Sprintf("Currency conversion rate not found: '%s' => '%s'", err.FromCur, err.ToCur) +} diff --git a/currency/rates.go b/currency/rates.go index a3ae5f30fd5..b9cb0201b38 100644 --- a/currency/rates.go +++ b/currency/rates.go @@ -3,7 +3,6 @@ package currency import ( "encoding/json" "errors" - "fmt" "time" "golang.org/x/text/currency" @@ -45,9 +44,12 @@ func (r *Rates) UnmarshalJSON(b []byte) error { return nil } -// GetRate returns the conversion rate between two currencies -// returns an error in case the conversion rate between the two given currencies is not in the currencies rates map -func (r *Rates) GetRate(from string, to string) (float64, error) { +// GetRate returns the conversion rate between two currencies or: +// - An error if one of the currency strings is not well-formed +// - An error if any of the currency strings is not a recognized currency code. +// - A ConversionNotFoundError in case the conversion rate between the two +// given currencies is not in the currencies rates map +func (r *Rates) GetRate(from, to string) (float64, error) { var err error fromUnit, err := currency.ParseISO(from) if err != nil { @@ -63,12 +65,12 @@ func (r *Rates) GetRate(from string, to string) (float64, error) { if r.Conversions != nil { if conversion, present := r.Conversions[fromUnit.String()][toUnit.String()]; present { // In case we have an entry FROM -> TO - return conversion, err + return conversion, nil } else if conversion, present := r.Conversions[toUnit.String()][fromUnit.String()]; present { // In case we have an entry TO -> FROM - return 1 / conversion, err + return 1 / conversion, nil } - return 0, fmt.Errorf("Currency conversion rate not found: '%s' => '%s'", fromUnit.String(), toUnit.String()) + return 0, ConversionNotFoundError{FromCur: fromUnit.String(), ToCur: toUnit.String()} } return 0, errors.New("rates are nil") } diff --git a/docs/developers/images/img_grafana.png b/docs/developers/images/img_grafana.png new file mode 100644 index 0000000000000000000000000000000000000000..59d6773c94ab9ff0f9e4f717b5886dcf525db508 GIT binary patch literal 271788 zcmeGEc|4T;_Xm!rRks^?D=Itpoyt~;vD1P|s1(LFl`Up$x0$hwG1>@Ox`ix*QdE}7 zU}!KZG4{zeV=%;&!C2B@FqZE%N}pfv&*$;@{rCOr%U|X(*LA(F^E&5w&Uv2aOY|ix zbKyS@{;_4t7UA>f&R*HF<&VZKTmC#LxD)tfVwdTkTed`OIe+%l)nJ!N2Kcd4Pnz;_ z^)&_>g$P1$CtiC8B@1YtYrc3s2YkA8PwtcY>wosjKTO+oyY=Cz{hGb4n%cQOt*~Qv z)0DRn4qQB7(9dvYI2RO5Ij~6#M+A?AEztO~=MgIV!NXSIS7+I9#rCKYsfH3?YMuG1 z&bW4r7i+zOIEC=3=sF23=*Le)(FMrCm(LwKg?DX!wB@Cq06oS6B>n4yoQdsjD(VdX zUmrxg7Edgygx3lD`sB5h31Q^_yUG6_n-Ggm$fKGvePYd1SFRm*&+x&sULX3+uKg2R z@d@gyXD>_l+U@0W_hD+ewTEqIDxGa@{vMCq_RHQ^8MgZp(Xerd-)=HBT(;Y8WeB>K za3RXA>;HEniA9Ixt9+LU8Fx$O{b8#^87tli#w`;CioZYe=vKT9fu2V_LMRD@^_@qq zwt6EUS^nk#gqVoQ1~6t)HUl2-^JmlA>frh_iRYH&1>$dBQ2bqh?z*c&Wi31#(^VD- zTSjenmvdf@9%)VM_u1WAj3qcvm8_RjY6q_v`&A7JcI8Ql{CcNLh~z&T&ECutm$FsY z0xUa^XirJ&x6T}2vqPpOl=xJ?OXUL#yrR%xg;@N92qTOcm` zt#b*o&qK=7#}PI8Vjl*)(nZ2_!oyb9*oN!Q1gh_2U!7lTGwDH~O|(xJ6zRPs@Ob(v z>(%zPEy()EK7^+Dj@Aj83Kdz$<>^naI_Z{Zg!&i`f&qLI85);a2)O_Ejd3uCD) zOi&MmI(m*J6D`sKSMlJ73#SyiWg z-$8K!`nSbq>%H}2Xw%toQk${pI+GGPv_AfcbhBBz-_4cWm4fimTLYI?v}9$&e&0^Y zb~hHrJ7XkUaWAS{PKNPq#(0es-rl(NwP!n{IM@qktg&`{YjIQ9%rwul-cV)DrX=(N zTURNO#~V|u@>*%h7`>4Ff84arkG59uY|#mFS-L-Ub){|?4MuB5{poABV)TJ9Zh&ZF z&d7Z8i!JX0?BJD&GeR~K#QWbPb^iD2{Aw*PhmmlvEP~p|kKdvytleDu_qld0O$-_@ z4t0Ags%990(th8Utlf>pkT=ekGe6nwh4g;;XuJknG~8M?{-nF63iE7y5s6u!A6_pk zvzS!S)W*#$Cu5Qg|Briw2i2}$+N(Ujuf7RQrwmE=r1s>;XOuc?tA{sKd#VlO>9~y| zwWLzl5VM`{*VPN4tl(NmMF?qbw&{0Ap1)|a0Lq?HiRl9AYORON7_A>KuwGrOe99cu zAF+-J(I3TLXOFy16T(}^IUXCAbzjbhw|t5*${kWURq*n-1X+tr%zyI(`M;knXtlBNNdA(x$cL6ej-9u*?5 zw5bVYD46s18(qu>ioK^pzvb_nS^OuknIymmUg3PATD@dL8R%t#UCaV(iww8KGzt-}5KFIgL`UlC%W-OUb1HS~Tc)vBXFV+gLEpev2GxKLX3-ybpB>vZxau6;hcBGH=tTOLZMlB_~&)~V9 zdzDYjx(vew)U!G~Lh76GvechmZLL)^T!NH1nUYU~%`=&tu?c>T08w zR+rDLpV5ZoGE>n1SfHg{Pb$zApirGPcit+wkl^@VaJMf}Sj=gaaUa;ruEv!vt-2@| z$^cvZ%*4#nWcEI&B+RjuJw#a046j{#V@zr2p(V?fb|?rDOJiZbdnQ7FPDCXvrJ4qy zAeRTIpgC4i(~Ph&#HdswL%BSZQmVnuP z5it9j7VY{LPay8F8DTXw*=+7o0W9l8hZd62T&wP@GcvRFZDlGo zYUa9r$M4n!09!kK`IS%X`pg2bNkK`xf3p!8;gH%HOmjoLS*wjUW}ISq6Fn}SbmaG! zcpCxh4eo{Ptq1X9MjX{(p=;CP>+p-Wdn}kbFUamRGMqhdcjCXX$v{uSk$F6EX()b-$HyI zW};n~RbhGAfND2g^>>2SVjYba6NDR>tN?wXR2;;t2CRD$QhbwtzY1=$(0X?be>iJ0 z<5EI)e`--~#_vICD3e$-EVN#IIBPoNSVH!Y<8W`{|GLGh)N;%S-I8RyW{lGxw#GBL zcYa5a{B8IdMz`Hwjg^jA;q~cnGc$v|D6A{{zxGa~(J1^psesAjd@b1F_CFPR zsHycXH=HpL=`McLAnyFW0?BavA#ccv17wzG&LIvv{WDP88qZMqU-v9_UbQowy$ftk zsb3?7J3Xe-ZZKl(Ht#;#HjuZNFBut+TP^IL<5}s$GUC|#eh+!zp>+s0F9r&V&Sr}& zeth|%us{-xU(8PhUO5Ugw?JMe@jj3WNvXd_si9or)MG)a5%L=3@=9=0koNk@YF7=z zQ2Y0O6RM3@mOB$F65L3sbqXsNy|LIu~kHmgHqFGwoH8fxd zc+(kM-|QXNs@`W<|KV*uZ!?j?B$OZce)i@&6=aHqtMB6oNi+?{PmJA$zKs#lCH;^{cTv@$bCyR~XGxv09qiQMMR-SlHIeI3p*LO#l_9uzA__ z!#`W!-cVs>N< z1I0)johmC}VWck^-fFnMuo4{mZDzc5S1C>O=h|TCpY8N@_CMY2+v*cit(!hP$1V&3 zJEA(Zu9K-3)b|mY=mY0${k5>(zTH2(;2o9O`Bt1(KTo*7N}pXPH&2xkqrVUQ>s3G4 zWjuXr(`-O(@uOwG&i1eS5G_QGL|^#uGI!jGm;2SGlx)VXP(i9%rN7A7+;&*p&kJ&v zNu|I$NXS^r74;r*JX47nZlunNR{21ZjErnq?PCA#`0*YQMR5_ZdOq4+?bk5tC`S}# zK~88nn)gDNO>Fy{|K>E{*i-vB_ck%;I#luJR`$$S6d+sgJQW|FzQ2v`Ffr(&tyQ2A zNO`t)lesc0)EJrMvAgM~!@Q%O{>d!79&dXgKBP-1z7|B~u>O<_U45TdPMMZFoaJx= zA77dq(ED(lesI$*Cw{tR#Kcnjh!=QInRu3C=U}zi9ZTBPitFC&PwaK4x9Ae&^62b3 zy8a{l9Rp*=ncwu%9{J6s{xiQq73ePEdxAGP7})#;r(7G4is|O%Pa~sDKghQL`}qp- zy@{*Wmmc2?sZHFp=hd`L`~f|Eyu2qv*F&eU$|MaBcGJwnmf*j&K zjLR#E?QV^B#-)nB^)y^q6bHAR^I2(5rq}rE7zcBv`f9%CW%#LC88ZuSRv6NPvpXaN zeuVLu@J|H$#G8hk3^>&}ZeuJ`BC8u1y09XeV%0>o8+~(NsW&vJk9gPfPY*5TlzB^@ z#N953pm$S;jCo5McoVBCqh&fJR?4r&*|Ssq4QF6SeUNNe<;gw$7rG(gJ$9u_Ikb5oFcr+JogTW@{jO%iZJ*DfB&Oq1CLux1+GK_O1rAeuYj! z4%HG%3bgHtC50^|D~UAZh0igAV(L|pEu)bx{@op9H`?3*&+Me0xU2bF@rc^RuecbF zMx~CvNY8(=)#9?XX6Z@P$6|>a0UR;7>EksjdRQ}Z*U8ViF3lfs)~eHTR|5MA$HA7G zK^iNSoT=QZlpr6pThpf{+E;$<)%*D49n+FaDRE zW@4=Jz1{>Gpl{g99%LLtjU61df*NYxo}uU*)D z`pEMn>jFe?^|xmhF;&x~psqDib`NcJ5gQaM!|NA)Rp%zv8-k>sTDl!2*EV66t12(W zZEfJWPJFdVIs&0>J9L;7ZnSVjMyj}a0tz;S z7sNr{o~VlCI+bE0bjoumHO1BWN)Ru(s1x}#^v8QTAcvjDuShvkv4jpAi$ergU+0RTM=};B+Nuu3XMC^4>wXyb z%uMdFPI-L@V#mZ;6YaAg zIzFB7!i9Z9s717`*- z=+-COp^JKh>0is4tuAk%w*6kBMsw#DLc_nmA8Y4ogafT&@uNsQ0VqHwNo1pP+~b5* z;$%#Lx4`%|s;q54rHquS>O#Qle0?}yZWvEPwz8~m@wDp8{l~=2_pu=U0kc5Z79gBm zs&7^l`l{|kW;siT_KMZtmvp|O-w?urT$QexUcBj_stcaF)#hCGT-&o+t`7vdE z4w=fT{fAqF@3V@g?dNhVNGr)B1(2#@iPi+JDV$~rwb5j?Y8VB6@r)H_IomtVL~3N& zP1X$Eu8&{sD(<dAk4(Gzl1uY-4>H(eNomkDg7PDkI1+4vBD3I=1VLh^7nGN51c<*Pfp39P3FJVud4Y}dPP8`dv zT1a?}g7xZP-UNGjtpy)&b_>J`Hb&}C6O}WRCkd?dLPA=dQ6PM_wUn~2c6r_pA2Ol1 zMN$T;B>ynHk6$x0)%$lEj`71%B=^Hf}{Y=48Euoy0nquTY7!ON#LhQmBR!c0N zC;!Rn7d5bFFQ>MHBtV0qC}gJKwik%0%F#Y4?^93B!~`wa2TcX%>Qh~ssK+vuv%+3K z(L=Lfq^t1si)6Dz^&;q@t)1hGg4gpA3VJg6Zq|2a;a`4rzSru?Nc#6Kf7GPvFWpoKn)5i?;@si}AadY?_Hl zGX_DYZ0!yaujacBh4Re7&PUgDo=&bS7M%qx34X~7+^Bp^G&e!pQP#mgO~^k!fBo%* zP5r-KMcdxwPRUA%V2|4ml)hZuG6A(vUYMuJoT!=CeWL1qs zd3L&q8csfd6@My3dBgvsrXTJMzs>vX7w!KA}&HakcC11Y|yw zN;R!Kyw#@;aS;Z3(;Yw7o6!!b7_Js7UABsze&+r(-z=cm0gKxsK!s16)kq|+e*bFc zq4jFG-O-6HcFB@vSJ9sZb;zP2+j&VtD7}n|4nBOvd1V?G&klcpcbwP8R&H)=Y3$gkdbMM$vv1F-+~KVjN@6nNU9Kldgw+K~ ztw>m5R`B;%llJp8355>#f`#5IQV{cSwoi~-If9jmI;D+q1_3~nvpVKFq~=@8WY;@q z6x00%Q0s~6uY^)n{SGR|Mtps0E?xd2ocC&5IaL2N(#t1!@evt6-=oD2Jpe(-qMU09 ziU#O~w=57Ecar_@enw_j-yZG*PJ8XG#&Rk0gU@cIG{ZjBxvFPffuXIvQDwe`dIu8Y zu3y))JB-SXpHF|7gXn(FJbiDAwPWqmCt7|tinP}v6s8m0aatc`HJsn-IWkkq34)Cz z-XO1@F4fNXkKV)V*{Hp^nl&?9$GkQ0p7$&J<*@k)`omuwo){Df&$^tW(Z)!;+T^(1 zVu#|G-Rn=32J-`IZ!PqZ;7#uIfw*QP&3K*VCxz--P9_U&6@$e)%-QTrpr!~IZy3#v zolQ%V&Fu&tvN?1@-Vs??iDYUXrb2MBHF1|hOZ^vb4GnDs(J#g$C#bpvcJuC2fWCP3 zr1QPWbYmk3w=>}Dz6?pL=85zkvt!CzRlR(i!-UcF z%0=(5WrPmE89EHLWh52hNmOkj&>-NC->4LcQW8^m<)KhX3>rk&Rsz`#dg?WN<(v#> zNgTg)=3dK9Nar#!H(s|GPEq{SU0CH0+V1Asri=}9u+0vg?V;gIhGTFpNj+54h}rv+ zkoJaqc%16`1g*Gb;)51w;BgPNp4OAPVjZ10JpxIJJv)Dp^8oU|uY426Gw zUr0zANrxH?`oC}NI4TquuWiB|vC!}gypR91VvlPQ)W~-|v)x9j?#?TmvrRa*X0cNR z?Bh(=l8_Bmotb2ejXL_aG@K-|26?5&5drPZrwL-8FGAVrj20$(Kc+X48YL)$S$`{u zKi&~t*2C%!-tOjzI2X@D(Rts-J%Rp+YWdOdg=(3etN`6(c+Hh21ANq$sfn9gHx_HQ zf^Hzc{3cORb7QtFTJ-*4?aPpe=Sd3(rRf3fS(;CZ5Q<@FfLPKMYu>8py1OyuzGpMB z*VOz&nfh7DzB27=5^JGr_fzpivs~duGrMlM6I|mWyZQ0`lNwbcuYnSEVQ}VM=k=>; z!8NIn`5rZ_lKIio55_aaavtVfcF2gqeYYY<>Sx>|9nzPVV!=cAloFTBcdNPSVteS9!WrZ8lVXE%z zfK7A!alpJB_2nzJQVe7lv0DHt+R{*u%D=XFV8h+-Y?iD5S!Nk_c&=LaE?tlK{@Hm! zp(w81!V%xu1+Ih#E1<(vzol3bn- z^I#p&WT+LW*hqYlIp0|7+|e`BLvx|T_gvUs*0CzfJ9n&iF3Qb8l7@7k zwU_g;!-J`~Kb4AT-rxu9JG4&fea}N+k5MBgJ>yqkMX`Z;=FVN8#`hODby0Lwq@pn$90;Hp(tGc_nLQ+FJi|htD!>4gn3yDd0~%ZN4o&P-b@-c>U-GPX6M)_&w~q z(cpfJS9A0%2h&~B9p&`We? zR$}q4gg>su_0U;a5Fw#FGVJdKW8#;T`&Xsnv;+@IXgH>|i6MtvvkQ?xbpjR9(J4?7 zzg+~ML~}7J1yGN*Vhg~$FJXnzpMxDZ01MbY>Yb;0kB8kQ*FKRIx|~)x&EO8o5$Zhb zfsW1LWnnQRhjFfcK4qtjT*%yPVX;l~R(uEID1q7jaK$f7-RA2c>73R@ObDkgCmcOk zF9(0lbWP|a5&`hU)eesq7GH!LT9PBn)Gu}e#m!c{qznAKtyK1XKBLf+HzGR_JQG8C zRmRu9ZP{{Vvlf)%v+fd9X}|G<#HKEbJa@pVp6%1cxaldliT2i<@i44NquF&0L$lIv zwhXuJZq~-oGsxE2KN}-;K(mOh*?nKfOB_S^1eB?Tog1gekl$4tpF*zq%rzH;$bB4caIN+e8*^#Kks$^b(BTl3ESaf7zW($NUmN-U0)(vy5Y=S*fc zz&d94SV!^+6UU)Jp3IXT#09VkLHwD@tpZ$tT#MELfkF6WnpAt!M5GPU$C^*Yfl`1< z6lF;^JFE{>^{27pi}@3b{4=WxLyqQ?J2Ztpfl&s0ks8a}l+XcOYun;n)IRQqrb82qkSohn8{!N(p#Rb-6C6bO&<{l=Jf82d+eJt&q_H!ZtPwt-@Re8qRn!ds~_BOU7}ms zZ(~wn4tfji_!t#g$4woDrdaQUD6QRnzHmQH=9PXT?Ocxv^GuIg+5d*OqfqE0?nA-zQnk|Z^Oo}JG1sT*J)#)@+)^$v3U$)kfa5wLrRK4B5U2{7>fvkngTA3*>D0Co{fevcM) z%IFbh-4nNN8jTjI_Hx$P?iSP^XsN9}Xzj(YKbudT+mHQMw!jylQje_uv0!Von2{~v z>8qohQIMqgus^sp*w{?|WD)y0J_1%P>4Kh`&wJ+Rb?*j_w6Hw#*oN}bSy_LT`St7h6JLKWm47j$X6$zf~vChebQY{QF(Sf@2cz#gnye`ML| z6(`#><0=M?M!1JJEa3>+EcL@_mZ$g6m8-KKrxTIy0WV;8mS4=nfBwX;>F4{$ot}be zAIH4yCALHN4i_&ToqQSl>GEMA<9JU1aeV9UyDB}I>!5_YRCA_zj#Mzxw+dKF(BP7! zJG;QK8um4@4<9kv%-}YUN?XCjtys}-d>v>)0`z?a03U4Hs}HyZP|`QXcN`fnIp|}I z$f;bkkb=%Uf;*}3E92w*CZ1FhGyk0Sxm2SXfEEX!uXY4RAhtBG+?qozr~83A$LQO) zT)LAxt_JWo_fu_FIsjsoy59E{X|(!c_+qj;!PA7muUKC*VmoJx&Zf>G2VFPpuxFF+ z;2DyD*tMM!h|<>1vg5LBVd0|E(O220_OGxh>uc&ghUKnLMhv7h}M#0B!;q~Wls zbT&R@!~n<|>m6<40f7Nm{ogmkr4NbqMiH|~xo zY>fw+q^8jWv3PE02|J!Lp;tuPiWmFKihRGUSjL^psubxYx(GHl$Z0%B1gu*RNh&3; zDV%ct5gL4-B?cFX&0!XJl+@zC_pxvcs3hMA5k(olpcIs1*>pOcIc1C+P~d}UFdr9w zc*Hl>E#PS%qNmfwohXLur){Ou5hbW^PtEat-Dh{Yfx^Ew!<>4*)pPKDSHUlF;y`BH zIS5o5XCD5ox|k&T{n((mwTN)85oa31zMS zn8$8Ta~LxOJ@!nJ8-;)@>#G0=Xt zcEIH2%}oc|Mer7H1HQjjBFTh;LMS^xIWx1zvP`hgG^hbTFU$>>j^@vaMr%vIq#qEi z*`J7JPsw?>CM>P&1~dF`igty>>y1_x@ zLd`J#W8y~Ex4UESpQ)m0{6GMWUkG3fDY230%oNW&8d zvsG2drryq~JWl+J+)<$Gf7B+a zP}PvG3Cc|B`B>fs!?c_|_FDA`qWJ*3w6Bz6Xk1`%rm6_Y>)Ip-0~!5)O&lNK4kg6b zAo$>~KuNtnHO=-v7YKA8wA0gZ{FViHq!*jRHfYV%)F_aNbM&iH%0Qizxon;*2~c=k z>SD}NS#dHRW%oqaGu*ZxKo~q~^jy5=755ps?;njRMYt~R<8Q;}#x3!IV>is*5bk}# zjr{3FRZtP{!+Bt`Gv)?aNQ1Y(m2@lLu+XP-Kj-JfAq=43;9CgLPBc%CQ6*#*SuClfkG(Pkae;U!o=X9I?SGiy}g!lyyBY7y=KZ#H9@7y5jfdNEHk6G{0B50m!@4)2B z!@nN}1JgfGSEj>2xHpPR;ui*S3I2YrZ8AX&z|JAD{&sp06+TMH7Vs9fiF%M9eDmWw zS|e;|8_ffe`v@6wv96!_3NTq4F$8B{<7wO93NXO(QtP9RUj^m?^pHi&9S1x2`VZa* z`>5y(w;ry6ylCF~5&$#o1Js4X?18G`Se83b`L7)YhI**ZrhIoq0OcGz{YZa-lrL;O zbAw2JgV^i^rnynq^xYKNqQ8xH9-%uB-#Y_}l5(r(aKj`oU~VNSkg}uk^!B|<7azyJ zLh%XuXu_b|Ym9o@G_DpU^T=lVJm^O{Zq+Q@t8 z<_79i1%`dh4$Nx%^t#1myGt_(uY2~Aa*RC+;?FHB;SFU;qseS15HL*p2&fw2W zJ3qPI%7^;slLBT|=>rNMyr;tNBj6f7g!UIIcAoPpK|?A9LNnF8@Z+AtBZVaO(?xB? z$zwY+skG$#HCgE4`oiLlSNT}_oK~EX4g!7$!X0z*YcsBaI z;QFWS8%8ezoMuC{1Fx-h)(W}(DL>_;DDz@u0n6H#+nZSIbs~9(Dg_n{8sR?*`*@Y#?F`5Dq1|8 z)#U&ch}!Lv{n?Sx={qhfC5k(r4$7*^v6Xz9C74=IOUy5icK#@Nyk9*}HA;#*lM(uZRTd8MK zu6meW&o%)|iYwgVA=f5!)jVgVgG|cki3&D;L4E)|R3y|PT&h|rt1uC~{(SyMrF@|N zNOc}g*TvcalohHaUlkHBiq}1TZ%#>iC>R&Z1?rojG=RFRQNvuGBN^5t@}TpbpeBbCM3Et|7<>^O#Nd(t_9S! z*RUP2-MeMY`U(v|CVo>^t}wbmKnCu~rUkgPxM0FkUT`)5$^__gH;!EqhxmN<>GWJg z3!u+S{<|xt*6}TR!AX9 zSp0S}9`{r>7GInn$B(R!M799=WZ40ME{|by$C%sVp*ty4g=#Jk=?c|9AIB_tX!L67 z+d6NLjnwymFZm#g*(1c*cMf`(+%bcUc<9xk3Z#qOexRi6T})Of)je3R$ILAIL(9BU zZQ&%gsu*nd$t+-+sZ!@ygJZr;3mo<2%%DE^rLa!8oXISZtL03;;q|}P^sj1e_bNPI zBxPMAooG1sAQunZ$;JFSrjpC?cfN-Lno0hQxdm!)7SvtYkbzJUJ#JoL#o!OLwe!>m zS@T*SSIYthfFxfB@W&tgF_oXDJ#B6Qm5cGpErfN<5w_8{seAQW2db>cWm!VR9A+T5pduhup2_N?5CecPR!h_jP*Jm%VB`b5Q&%TgWje zAx?f)$znM+jOaG=zkV6pDNJSiQZ8oFJ5%T@Wrp$L+<_-`w6XXZf+ zmu7EsZ>}@MA!Mb>TEg0xvpP9D3%sxgggkfwUI0^m;uN%ez%%Zs-~zyjNdQWmrcnzS zpt!KCjUgr5+rXb+WJ$&Gi^q(0{;YMP!4U^2o{1Wn-M03c2+mZHM_}@JIncq*n|{^< zhRTbDdSjogJvqs`GtV^KnKq0)iNZ{@weIlaF7uO^?yS(hG&-|rD_-F=z9bJm;6IYJ z+U94N*!E+x)%Gi6qCA?)RG;fguv&d(4_QuWp>rhC0X^o*#q4ZBsCLotn(4Ok_BS2# z$d0l{Ma;WjvBLZFIo5e&E9sG@>CvB>fkX`9HBaf+hSMILMi-rZ$026;dL2-G7(qKD zYU}{?gkUMo=Q9azS|tSd^wqEzh(co-M*xwe_{E69teb43_g;a4hAl8~Vo)`)yA51f zxd`OMp3^Pk$6G$?#-T6Pj-vj0mUJ_d3!m-=n%>^;Dos&=eapgX+iU}-&R){E<8pcZ zn^J^~2ChfZG95o^}lZw`!~*kx9bq=2F7d4hhV zun{>Ve#^IWM5k0|B@4Ua_KD_?s1q{01yM|9kCu4$?(xONyK!a3X)o0rF%zrVVV9hO zR^!UK&U)r5{0UAA&m`1x2u(o)O2oCCXR3l&>Pbx?3u9U8FVP~;1GQpMD*&Bsr>_xU zd_FtD*x)=%Ef8oGKs{?YU)PVEo(jc$d)nvesrO?q%Qoe(jG2`CtKf5ifX+HyF86hO z|BF(F>zU9$jR-gAv&s|sjE9R5`|P{JceDTw#xz3ZbZWpu!FZp=k&tqcxcM!yz7P(>HWW7!9ErHDX0t8wOl|W)5kh9P{(wUWw{n|QlbOp zpz+3MAE>h(+t>3biM=%6)q#y}D2KPHZsYbJtKuk75T$)Hiv0z77soXQo%-n0CAHuZ zSETe7ZT;0kVQ@Yf0ns^9u#s!GL^STJyz%G<9AFtS7160DdClLufe048b z;CkuU_x%vG3zy%gd2c*-%XR#((9K{BE#-Jt-gH4!HOlI^zFDk$ErX{S*aoRfH^%=I zS8stEZ{{FjIrX6k`z-$76(aS)3hTroXVP_jMJ}=#w)5^5oswpYp~nITtp0Cy;8EcDVE9x^kgI6`J14 zle#*WR=B_gxFVZpv3{Yg(TC)PF8=jXpS$Jt*`HNQC|Rl{&+TcORbNoUmt7|0o7EI+ zc)n%A51x0+mbsmqMv#G|fu7vgPTODWT;h7%)d55Jbw0xH=NX5H!4mRk6Nv%0n_;e( z{el1y^~O&`5q8rODfuaaO;(Y)K@vaonE+|za_Fa^y5{So?p<(j((F86AdmiW?UpT} zvA-^+uWqZKv7Ju5ucJRav9!%l4Q+`1PhdW8^Q&_bn~OE1sqF_WUL(ET5YTo`&ci=Y zY$va_VyRWE&F$^IxM>1P43H@KCnoOek}sHO2aONBpS1sZPm`I=ygCR64mhZ`>e_C_ zpQ)(`yKvmc8-v``n&)tT?OmTIF#?u5E7+K&Z_eGk&C5SFSLO`T*xz~@>#88nE0IjG zYKz|()&ftPQ~&i9PQJ)qIW@rLg5pymCdo!sfiqc~#|nNOtEic(*2S_FRphJa3?UTP z;GG+|h9Xce3l%wQ8H71B1( zKP50#-elFurbbiq!MPP=M(Cb^lkF=bru49DS^(WXIiLF0pLwkw%nE;HkN<}Y6X~-v z&i*MY-C|O)+2VWa_}F^tz4UFHcI=jtfpeV<6Zasct*SbD2X&@cIypYwD#E~qhNORUu-^rbB6kca)DN71H z8b}u1-6S$;pj<#t|0(p{vgO()p7m%B0!h|}SLzy{&y$Gr^Be#U+jx*CO@T+|UmSYN zC!;J^^wA{st$6SLZv%#tkgxA{Y=;E|TfE;2_SQ0jHrS>0+mSa{y?ery_b0z#*$;_!J$=DQJ z^XTgYFewywv+Z`&J{9Cp)dI?)>sJ2zG{^Xoy0SJJ(`uV0AbGcy1cs(%$m(UIlQHCx z>W=}>RHi?x%gtD-Ol7SfCk)?&@e!sD&=}?R)4FboqPLx1U-0ZdSneIjJ=ZV54)u-&l{(Nhg-D0VpCeOa8>@yWXB1` za|aTzi0M@CT1e&W>znN#{;q#3i4L)_Wb8F{nc5xRuoJ&Q zz~3~8jclRNwN?=9M8n-MLNIbN->HP6duVho`yazOlca|6q>H7m8v(c`%QJy%<% z!bNVq*m!V}XcH$uAN!~8HDUUbQbJn-dU7||S<@V+?{)5QS?lh6ELKM8;g=QjUHx-! zufoYi6vpDBKh4}#a)cPHx|QnXlUv$e^%whjf3SKwBFu2UkWqG`0i=>bk02MnUp77-X{}uCpzwZleHY$N$pJ)5I5x* zP`ixGIX3fVR;b|j#}dwdHcG;c+DP>{nhnMP%@7-TxWyYT!$4>B4P3ILdbDPqdybhuY7uSh;5PQCeTr$=Qg?+1d++4LRM}`6Uny6rKJE$+w2q0 zMG|6|ZG@SwPfd;7Ep_*q*i}n?Z;|N}`P2Y{3!rFiU@1L!!1&IriZ;cgK0h`04O!dp z@r3xiao3`Ue*wH>OQ_HOfv-%F{5YJx%TU>YDmSK5YoER58_s0m=mUO)T5i8Cl@0x~ zF-1m+n5R8$@xb3R^kGAKL!#gQmZgM5@71?T2}`H7wNDnSefTiWX$cxawQx3&nxzPL z#HbfD;gi+%Ed&F(_n#ZCPup+BXVd2#CKAk*>2Rm*VQfljjopvn_#a<#j2c@`-)Tx7 zN&9>0xZV_T2X;ml)l@wzx?q)p+=dq$ETr8Z8w6u~7w$M$7=%xoo;mX(?o`LvWLmq_ z!Mo2o9+j^xKKoigFvyBjVk`PP&g?yC+{H3~eOz9OqXwy=?J3|6;v_~4IhhIi{YyUsMi3-AOX37pXR}w`AbD`~hlpNMj zbln&gw5)awp|N7*myI&ZN=gd6`7hZus`HP-hvnrTFQ13DdpmZ?Hp*T?wH~|ieU6%`v%9=HeB>3>k!EeoyL0pV-0IG2 z!j=WY(}b5h@z@x9La3hC{&BQj9c7OKOfMhae8ACKwJ7pUj|qV&cTMb8O5n}x&e=Ec z`jR4DF+>Wjg3FmXlvp&4I%ZN>pGC;Z5t4;UD{CN57p8S!Nz}L4Y7sA(E4>YPf z&c5giOn4re&R*D#7t_Smj(zWAlGzX0u_-bg%LjhQd$u6Mx6)hr$3f?^>+-CQWXaZd zxnaUSJ-#`7c*jdnyKIfl!n(_er6NBu`C3R<52%Ow zaBobCVxlU`vMuedE}WOOd%!!^)= zORIkHc^}~)5&DKB*66|iH&;0rXHC?cRnlt9NR8C-iOKW*$Y+z$JS!F11NFDK(L0i2 z_jM7b$|nnnyWP&re`ytzFL*lAoWvFwJ@@q1tAm3nw00-VWp*E!<$Oh2``fbFm4@>E z*BMF9QJS5+K~yuTXts^UaFD{i{(sne&#z{6}JrwMd^x)fPjVGf(6j6fb>pO zKsrdT30P5(VgZz@poAW3s39m)BmyEOBnS~=z$8EjkOm3)Rus>7&a=<+{rdjA@3r{> zSF%>ZTw{)Tk1_5!<`~dUk8GscHA#v=STu-NwbL!5ToVzhq)%B6-FXwGB~Uk#VkG03 zaBJm_8YKa(4#s4H9a?4|;$nV{_}a|j-`e7+>mEcah_v*+O+u`oc4JFfjIZ(?*fYlZ zjiKxz`Hs38m55&kEZ6;gK%F9gFco#)NwK}WWfM-F#~mTkt)aXDS5 zQ^#ZbM)pUjAb}M7%qLcxv9I&Vz|t#Wu^(Y9E1DO+^ww$p8u!1J?S}X4RTHHN{fHY% zn&VPD>QlXh43R4eQaPYvR_E7`9mwE95m2_qTUJL~oo1kV4hktH7BQ=}fAviAuJNta z(X(e7mF#>C6Kq{{Jwduw=8IN=xM41DEf8984(nZ$KVmXgcq|;oqoiYGm5=%QcM4Ni>~s#W#&Mj<<|s+(-wQpMnp9x0_Img>>-0zUOMWG|YbSNiA6Itj);-+?E=dk4!aSFTvLDLqMs4ioDDX|on>q~dt8=Ov& zGtC-^ntg4$B5?(niai6jv{Y{N5X`j8X}Pk>$RCT0D%DrOT2S|;%t%k4aB_;|hO3)y zcd6zSb$U4U$#>NsP0gvLLj7QrPS+3pHZ6#4<)xr88ix-7<<3o0qX=f*&GE=2ADWm- zLrCzL9RS^>rdkDafwF5!{h$fQ4~W1fUoBuBGKMDBkKXjo+*EP4Hdb3R?&RL11| z`y|_OyIGJ2g_L_&>)P78yPb%=WTUbM<`BtmiSP$@@kqaBojRoD^_3hgABrm&sNL0K zNy{b@9t+KM7MGTqftu&O4_IV>j`}?HwoYyG`I$biUU3EgWJ?#cR-iVpy3!?}Za#e^ z59h1mg5%J&w-Iy0ZE8EEx_3*;X5?MI_nu_;@FcTkm$*1#c;-=PpxEqHkmKr3&f_xF z!Xhp@;S~eG=z7D-;X#kffewE6-qGpd>Qltmrq;hrjqxmKBcYG^-Zu5x^-20G$PlZf#gI!WBT5jE-&2m0=$O@P!x1PDh^$CWFXh>_m z)%2MjnyM_eO!#wkNcvfonkysQW;tr@fuc9T`tFur=VLc)sI1xZ%Y1EEvCKx;=SqnK zTcR#mmPdb+UDrI0{jwGtmNU}i@?4yPI@HQX)qGpXow%0g20!=Y0ZJ_A5=K3)6dV0 zh>AKxt=m6%o$)6862yi|+5PKdTz@0(>+kPbf5TvY9dg!RZ;%OJpX=Xmr0U%Eo&NVC zHQhPu1O9!-chg@^;qTWQg5=g8{P#PJ{|saDw=6bn_(vN5Uf1g%Y5XIN^|kT8;>N3J zaBht)4VA&WY#u|hGz|O%(`VxenY^qeP&^^a)YC!j+lP&%0@^n zH~0BF;6q%NV*CBp*mnwdWU23LaBF*OlA+<}iyI0d#`SJSs5%dZ_zlucG2j9u^9>iqM2SmLhWtj-7L7GWACV0OU=%CZVRcg5Uh&)ynJI&B?~&doV5|YGop?}-w59o zQ9$Y~#H%C6msbnJGbdK9Xea@fe#wlOrZDMs?$bbJ9jre$BDd;RU&EN?4_K%jE?*1q zC%oS2W~*)}BOd5`uj@i?e7oJX4 zB8)0#9)6mn$shx-kQMTqun%2jAMe`x@d5C%ym+S@)f>2fn`=AL#9byD419zBsMXeL zy!T(5u~aqwznV<}zD)i7=$2^<8c{!0!t`2e4bXdw2L9-;1WHNd}CU%{hnH z_j%e_>#oGB;O3aXdR(~T=f;NkFRT30*8HESv+Jx&A7p+<^GK`X?Y<7Z=M(Ep*M`qK ze#=jp>$0UO(;Ag{tFFaNrz&u8dISfQ0IFBl&j#3~ z;auros{RVR?U(QVxp6rT{M#CmG}DQe(FvVi>%i4uM8U0;+^JO%%e*QQJ=`%Hx#Y7H zrrJE)6kSD1=_8BXgtUi?v*JO8@X3oQc#2O$q z_THGp2Q$Nh^l2r~K>cbnYZ&!M%|q8WS<4 z>V4gOL0J=K4$;<45n_{#_d&vizu3D7OzVrRID;RIPALtqY9R* ztlaYB=>zeArqFNUJ%fHs2ZbL`X&gnIVx~)a-Wh#m zCMjdNl^mY4wTzgZb1HQgWh4)uDQY7T>FZ}Y+;Tf_$;8%I-XFPC(TU?j!Sv3Xw9#y=9sgQr~u@msa?dfoRVMAL!D;Dh{Z)MG3?B?| z*%mRm@lsH?^SVqvkL-9!=_~FP1hBG%Hfrk(aju%J6i)KV*;*|iRa>b^ z`MM)PXTGI*^z=fr*YKlK0jRVoymvi&sp`l&SIl2PE{O}(JD(_z-twBm0U7LL0OM;A z#ze{?I*P8NtuaH>NkEySmQuQe?3U`i$j(a@lT}@LWl3FLwba;!>thsU?(#raqI1|d zQcVLATP(<1=t`b`o)W&+kx^U0o1N*eLsl?Bqbs%_&9c>zN;j1|VtI2G>}Z+L zy^PJ%)A+HP>_dCGQv_!Nj&5lsuY}`QU-`{v4vy?@l9T4KRXWr#6Q5UR-fhA*ZeA6p z9+I>pbOH+q#-@muV_H*|iAUA)%-@Df2Bnpxd7dEi6=#-|^NoVtf02WLlhzO2*A zdHBqdN1^hHkkciC`keE!B1!B(1%GW9;k>hQ!96qZt=0Xy%tQpnW|v!h!uVgdPK|FF z6H^zyzB^p|`VA!yID3N29s-g(@!CmYEpGt(R=Tw;=YT@$+=mE(YLDDt7a#u*X~R|9 z^h#6Io;OGnY6q9Az)|dsy2j#dAZE7aTR-Q}n9A;2^0%Y$b>-zog|d&H-5SJ5Ox0pT z{1x)YiN+GnfaG9N39PHRG31vx+@wpiTI$?)dfFF*FrJpcE-?uhRP(}#^-YxmlYh6& zucH*avSS>QoMh9>`y`c9_56oFmRC_Z-{O+26B*xunZ3UE@j&L~31W9=P(wU34cd2`ezt8U`CH-(MMG@D+FjZ|n(UrzF= zLOjzV`utE$-9>Xiu|L-*KEq_Y#8AfYN^6T4R9(_rDz&a2Iwls7ts2eSoCLvzbWBg# zqSj2tT;s{OP)hxS_({Hr4V=0JQ$6=xlZ4P_q^(L7>4mHCod$|}D&$)h-n%>r1S7N` zq#CH-`55HSd2HJ?B{WPPO@VT51-}0X6MU>UdOFFDKCz0Li0HL- z4tE`UR87;QtyVOx)t>41cjSFl$qi_ke|SBhk)Dje8&g}E&@tRqy_afG*oR;hovET5 z?Zhl^)ES%I9Q_0HbzQ++?{DGW*_z_8_np~~25;6PAFw3D_=hx0ZQx!pMkLoaw`XNj zKv=Ov7VkDZwjl7Y09`1Z>HM(k3Vz)$daVBM9zcBErK{En-D){HPBPJBbK!{2DOGHP z$dlQtOUCW*E~nQSmc7*+Uan|HS?2x2YZJ$Rjvt2b`oykd~4k_;12q zMG72#p#(Q}vUf@Orsm8XwKS{m1jiD}5mm<0(fMj3avuwQWM8Os#jY}_xXvERT|NH> zR<%hvZQHd{*I@(IO~a~jNwF11)q)V_YI&9BM}%0O(Thxd=G5Ex`rb!_PUtGlz<5fqno-<=jK+0FTw1x4|)}uEE>?nOGNK4N2PWim;3th;~F~XbMzI5K_TkT5q z!&0C-UxQl2puZrb&F=TUWH5_VUP{(Z=vF2h9@yCqQF(bfclpk zJ>%Oa$_iW5$;r2ovGU-d@nER2Ajjo6#<0v@GJ3Jt!@(6`r-2+1`P?;j@Gh^1_7#bE zm7n;kbA7QEp*39PEs>e~*`bL{rZ@yoh0HHT(TB#iSrHI}_IV;hf7T+pfX=X8w96s*;MkM7uk^3W;{!m4o)iV^xZ_YVVu8mmSh_A4Y#E zhShZ*#wB1tFT9UOk=(OzQm$`UbTEc2wV+#3$6(|e_s?cl2!t{ zWCMpg#=20VB3M;ji9};bOGlLKMXF+$Kd@Ul99 zw}_DrSF0Ht*TDqYtSp4A8UJ-W9JCIXBFdlp84| zOtRAC>ZbXc=7wiEOUr$cYWZG?bNwYJ1NeXVl70a);)i8Q*Mz?wni}0^GT7*P)bs(8 zRh^V%pqk_^9XnM#(xT~HsXg*&`OVNzy?SgdvpOk_==26Oxysjf+2zC_+_B+SEya>G?a_IDe7 zu;;R`nE3)*Epby>f{^CDcM3shYcCnT$HcEQnucp(u~R6bV1!rqeg|}8dk1W6fA<6O zs{HvufBky$%C?)OuJI=vU38ab`+ZO#OJEkNG>EKQdA+x8&%QsOE+P%#%;0?3XfSU? zlO8@yR;&9}LU2Ref4ECDRWV>IHTTF^;xpIy{eey`54uim|=+$+59|5r@ zQ^urXe`<-VaJLN)$1Xm@gqJJs67Ei)2SS9giSyPM{7Y@}92^IM8luD0WPTJ2f3Vc1 zff{xYk2URz-`}66&ts5}EkE8NJmPI9pmLT`9f2{BkB+!}EYR^{77F+iuKH z#&NtfqAa9E)d`r}S_s3Vf7gn3yMXlC>nbKm`b3(v0r_eps7KiS_P%1QHbf zm>xWJYOreHbTH#~N14`=*j``l^e94a`Q#`>5Lys8m!Mc*TVeRHiCE%eRQtV>=$E2n z!4G??w!7hhXlR7qaapjmTro+gJKYp;$ETX{tMA%;Rw~s{A^cS|%|{q|v;Su7{~Ja3 z0j>*XA|e=wAxuk>t?v$OJE!3^0VVrz8YQF^QkBX9m8Z2!bKmR*7v`PS^$+!rgq+xn zC9i1CsaIGt=R33f%gYaXbC>M})GIm&R&W$>n5W|rEPDju-+nZszO#9k8-a$VP3mVo z*@U&o&TaDj&g*E_q&IOmR>Hd1+|6zQ@lv9B!neh5f ze&}#s@toL!%QG3SfWklU_6()Di~A6)nG-w%AOC~F&@~X#(|HGpRB65cm|3~=4%rTP z&ZmQ=KkHujQ=&dy!rXUeer=wE%d5bRZ%O0@JjA3{>`+W*TlhHZ8vQin!&L2Ec4#df z9~p7ECY#u;Gs<>DVmd1g`yE`*cfjMNTGgJy`yOqm)TojLxcYbTds-$MUQel!ELL#xPv_afLiQdQ%w}db97q$RJv1&Q>=3la ziNIl}e!AIeVe|LJz2Vo{dyVTmyA)EB=rrw80$@vUj3&TOPwt`;f@Xoz(-nFF_u7Y# zLjg9cz7ydT>Nggh2_Q3~j_6)~2@I6+s|T-T>$fMnGux zZ&G5JdQ8N03u7g#E^r-e{QiUH;yW}4!T`Eum-|C{jx?uI9Tk%eq(#9@^ROZo_ciGY zK=nGG>|4!&OV%CjDS8tkcMZF>B3B-lr<@Arb=C#xN>P(_s0yY!OJ!3l;{FOQF(l%M zoo?TC#bod3U3&x42(+1pUj2Z?Dy9K*B|b&#sagPND-n!SG$asz7k?mPR&cEIT&N%Y z4_pM5ri*cobqxecr-%xI7Cjw9IA0T0hFxrb1p%tplxAkTZ_o1AoTfZu6$EYATWeef zM#9cVJ@qOUgLP$!y2La_e52q!@8tkq^%DxSmDi4_0&u#ZfjFWghO(79*pN~J;*pjz zF!dU#O472XmyxtW$He$ndJbYH2}IR%$0O*l+^PycNDOrEJC}%oqa7Ms=Sk#MK07lu zVsy*FSsW!O4Vit^Nxg$_Vtkw&VTy&u*pr>ZKkD@=GKqjE}^S{ zSvh~N>6iT!yK=9xtHQ}T`td{S(kHXmZp08T0lB{c&D=iRR}_aH;M@x=f-oRTtIPRA z=P4Q2&zq6z=+BF8RhJV!JN5}HF!DcTda-q-PQaUT+3Q5+U$WS{84@E7;*+E$l@=?< zX9;;cwt(~(MhkJ20k~BM)P>=C`IzOv^Vx-2eP)IW;#J;FjCTRLR#{p40qmIw>UkvN z^(JiPN^MggSpx^nLw08gcQ8volfk27;MJ%1d;+&GZ~PEHAx z5$Cg-9p&NB#t1@h{nlMQhy9Z*{pGIYIygGw!g<(^0GG%*5x))szu0rf7ozdVh-z*$ zW?URtf*2sMx)?kHuB5}2VyCfr_2m$t)~KT^KxLNd#YV6-;I-2dfJMQv{Q0!ihzZ^? zyCQNib8u|xTZkWP(V4kZ4BJW2TO8~XlVcb9L@6QRE6y;!od76Fa11G$5=c zS6HoIFldg2Z) zGssjd)g}#9`sJAqKAQIovmYa<>pA&KuD+9^6-Ini*Hz{aIz`5L)e;Gvm&*BsWV!r! z$WnP7{%w`Asb^AuOO&3AD-fgsuGI)&8B^WSW78Q>USD%k9T5bB4LyJqng(#PJE;R9 zke2z;nq@cVbsYHDmK(53AZZ5Rz+3XA>bp4-jtVo&hf_^Dii`I`r66B>ZsZjYdJ|+{ zp5cAFLHF_ys+sA9C+#|FdNpa#k#G-(JZ4gy6t3-Y!ti3t)C(0g)AvfQldPB?WWoy- zXG>wZo=y)j>LLAth?bDP2tMm+yUR{xJcQWRnsc&fosSkuvF|&WXb^VdLU+^6Ir|QI zSgyXWg!c1o%C_Y$mf4N-*+yR*XFkZ+X}KQFRh(izguv!Bnt^=-O+z~1UkmeDma3_4 z*XHfU1fkJ7k32^S`rEX-x#~J%$ISAun&#^IWBx9VUMppr6Vx_S%ygD!Z_M8@{1+tp z{0AiF{80_iebs7mYuU*eoo7_doc&uMkV;;YP@!Y8}5x$d>E{Vt&sow1YMBC+c{IWoP zF`B{Ixb^dU%cuUGBdFID<}-{V6}E;4betprrtE>v_4KNGN^Z0$pR#cL#2V;|RR)TY z*G;9wt`yC!Q1OxV2&l)?3EL~)_eukseB{2=kyuwhRQ=K!1JrsJ&nqAVxgtrGWG9Us=$uLdppgQhO`b_R9GyHf^WfT8ZzeaNK4(d50)KG^qJx5 z`N#ilvlFoJ2F!mh{QLgzKO*_1i~qJI9&^23&xfxmQ!al|f0akru;IY(Mg91S#Eyc? zN7gsm4d6gL!HnggJrZ~{QmoP%aWKz|wanvu;BhdqKCERAeMgQ@d#n&|C4lD=bF&Qv zWq`!i=4zT%W2W&Sjc-dG9__KSr)AcdOC6~Ae#lf!-s(AY>^BLndIa{!QVxBpk~kDqsOjsCfTFNlBS@KYWBk;5-B{9i5y17;X< zk++`c_2bbp%68L9?O(_M*u2<7>*x0lD3W5~e_RUzm_oVKEphX2dFR%Xf==3e;ko?T z_5*M=Oo46m@yopXLcb(;t?UgCxi4&@Bze;pi)hixA8&?(XBWwta*4p?h6P1%e; zY^q2`d_R3)<35?&e>{v#eKB)(+wCKJHvO^h?&BkSWKIcg`f%ddZG!`qF9lprKHD`K zw=u(bwj??E$B&1}MGx2H7RlHl+{zr@;iXP6^;0n8QmcyoM(xI+J}0d*5BMJ&L>5JW z>c4RtE?qclrP@(%yJ-XYtO0_!4FKV*Ik7UoYXktd6tWn9Z7|RS&Y?^2izk#U3BFm^ zM&Qq9A-v0<8;S%rV$)>-i5rBj-CmbC|DMdLF~yA==rS^6LHzz&MSY;ubpZkw4J=~- zxxbPEO1mfX&zBFg1=a=6|EOm|05Hm-yK%l~ezUMw+B}2bnP0$F6WUzbhPbFbseW4g zL-bZN(ihj&2Dtc2z7@G|!&Sje)k?;|T*r|)o7d->|Hc12EME%zpMPfdmmGlK|EH+& zv5HGN^VyQn59ZiK;S7)PfEJZzcKTTLQBjv{$y?of&3|QM(NiwmKIHG{}Byv3sDS$4mEa9+QyR& zpfysz-puG+_*dbygN9%zeJqyv{W3HnY^V7#}RF z#r<2}))F(|pQfcEebpmj@&mP>_2Z=@uUG|0#V&f8YdvlahpzeeLx+%eRW@I{6}z^g z0*n>O)Y}7$6}8Hhj!IwsSPn|)`8n3OY4+bht>AlJ_=H+Bqlr?Go4d$CQ=xgiAdf)R z90ZQ{t-TtxMnM^lz3!$2a8xsQFZeA!6Lm@GOROH+zcfZ|$&w5i&Gu~a6xdWfch*3} z^LHP}lRN*0k0Dl9vQC{@U9Rabcx7=gfIBs1|7IM6JjP%MnSd0`R%32&j~u z+Z5e@s>tQiZ*9(kXJMj0JIj$MfH-MF1W5JHUvFZ>7uOWu>E@0FbDP=OVULML1 zdlK{&QKYKg92)23x3M=Bab-WUv$P>_PpJcvpeP1A7Xsu;fDPSG01tdhVWa(c$|w(AQgK@0FN( z%eN2(Y@q*=_5{|CXA18{iL3rbQr>DFd$LU)>*im3Ur0rm$vz{qHrp5uCMvo zeA8;~Y~xJqQ{bpAyyoXz5gwh3fC8iK=dteRa_PeXyrT-PdsF>V%>V2rzo z8SR{Tr=$}4zQX-{^&tsGF^piI=ikiJ7aC)EMA~rjNF$ItR!}{XpjdHb>!^s5%f2+f zyS-+R3iKy()EjCovs%?WH}2E9Q%e(Rr_<3%p)If-BgWt`NQq@&Gq(|*EH1^ za4JU}-_Bl`{F|XnhPj&Q9^HOSe!1z|K%_bLc~$^^PZE#hk!MWAeJjsHj@RV!*C6oU zMqh2jeoJ?xJ|rKcN6n$HtL{nVGJr`?O>dE15t zSu*GfSG(0=9^`o&xN$W9`u?21`5fB0 zG3T4#mp9L!l2x!hGTcnaKPq~GsJlgv-(q$)F_!J@(R$rU>Dl{R{6*o5?NH38S*R*) z3DjTvZYwFhHdQcRX_%M+w2xSIf_XUV zB6QaL!Y9VhT%nL>o&=FwYP2c-8MZtw9}rYpSV8<{krM>VX?*3ek|i}JVM6)VKErs< z{#KB3dCR2=D82x->v9xd6D~ z&m>3v$^F(2c1zJ)yP$OYp;8U2NdXjNIQ;K9hd2)MI`~t+Y*921;HJ%@(VueRDwMATTuj*?2C0~rG9UmL5I>+*MkQ4iwEMyNpk8c9Nh zAu)W4GaC%*>&ph&op*7jXm}bRDkxQO*^!E7eFXx^!KZ6Li-fk@UbPzCQ9ZMlzX-0y zytz|ZjAGM{o&GQQoL#r@HMi6I zLp@j_Gd(SbxIlPB76u7=-QclFN{tIsrkbD3RERlwD9qgW^7Av(#)E$iutR$5C4CJERZ@{eCK)Vm4rL}VY>GgwtA+(R6#qE%UNUlZ%;N(8-`^r(95QnJF{6|KV&dvQ_ooTI#32mEMl2nZ;NSiX!XsRd3$`+HZ<=tR!VnuAgCDT4&o{#Y^p9v5wHi* zg~;{x!-S*4sl%$=(B`sg4%!d{0UEL!Py;e43m$^MXor>PF zLVKPEHHRD{)3E6((aL-wZ|DRo92x7pRrYshonV*;qbH&6#<(E1PM z8rc0N@t%(5Mn$vhJMUlQdfeFDi&0OfoFQ(Xy7w6_X#WGRE_9+xD%>JGMmk7-dpCK1 z9W-(_Nd6UhhME**Fd{IzTPjS1ThXe--CXdy17awMBB2uOH)pITvFa7?9x=le)Mj4Qw-Y-a zcOWW@vCJHz38gz9vKudWGh!l?zC5_o=%`F7y@sH$C%azX!DyJIov}!sX<2e%{-Hm6 z@JGaOWbulwS^(Ds$y)KUd17mZb-zk2G9>yp(9%7d&RSAtVD>_(N+r=-ky6y~ws+=@ z`7ZA*!4jWWh&itL+5_nrL43^5j%jV4eAoB@Q0nYoY)ME<%)+Ef!2>Y3(A2ifNmxxi zQQu>B^SQ3N`cUpvZ3bJf+Kx^sXbV`cw2|U_rT;Gg(A|bH)5$~Sfzt* zS05T9M{z-k=}w>j3}#p3uf&FIs>X$HKT!@?U6}0+x|>(K&yIdav7`b574QY33gLuv zdYUWM+dWwB@|WKo2kFs;rqNsY?X#?0DDG$pAe{VI*{uVL)yO*S*Pja%I#Da#f0E`d zJpXZ3wMoU%V^6?mh@GM2MywSzZ&fBLE=@#vEJ1QOBHq z^B&qHdU0G<{wf)r0KeN_Yq{}kd9Zi%3|&UwWuv`hFqss(rj@9}e7t7KD0a12TE^_! zBBd4pwpqP_(Ytv8{k$puYEHR(C9$yg#Y@hO_dlbRviH)mx97D&CB~{(>Y*1V(caQ; z$&y~)+@7s&SuIkl?h!ydmw)=~L>L2;bdB$}x`*<9pnq~%MrH~UW7DZ+fA>jUVpM#{ zX&*C+6UC-1lX}s%PEu*R3j1*?4n~(C>bfe|=$%;shVGm1e~*cTnBp>aWNz=C2xaOG z1RW1a6*aOlFwi@{63Ey34R-`jMXa6a-+rETD>0PWxth=Z@ocHp=^)v<&+PIcU9j?k zo>^hk9!sE3S*DqPq;{swNx<92qTwI(94409&YRoQh;AW?h)%)jk=1~D*!?Z z-c$T+Bec!>#kiTR-m#S+_qTT*Z7+Iz&Ldy@R4B4IrvB!V(@w{NuHvPsB*PAs$9_T#pIh9U997MYlC zliPyeiWAb`E%qE2w%CtKUinZbSs^veSvkIK3P$kC;ph)B24TYxdk|OqZUmIuGuvLW zbyv5-^8yze$hIp`&NI=1#p1ZKa|Ny0Y9g!s0=+#F&G43$s9L9$ER`9y`xgY$JyTzR zvhC;OHC8~ILUS|pzmX`CDxu|{D5T1rcpZ{PSJ=uR>2}ofWxdtHdBs{VR|~@Jtv4ca zG@;yO!ahK?#^7Lsk$7FpjWK2$K{}?`C?zw6Z&1FGC!UZLMZ}cMTSR;@tN&E>gxnDk zoF9G_HJ7=&y97!D083 zU*#^;lZaMpgt~94HUX!Ip3qjwgqFAr2F|0gq(VeJ`GDj6-4y-gU%ZgfW%E4<&(RAr ze-Fn+rBkg{uC`uro>;M$uvy4!>?PbS&hNbCcucrbo4tIdz$>yfqcvY91DxE_=5zI! zOzDG2)K^2~F$ZW?@;L*W$?f3IrR$gO@8Yaop&^^o{xtc1i7wIr#2y7f+7Dv1@CDiW zI&)|zB%{gGLB4o{g|}ABOJIKP0<*)>7o~rBneT$Pa(SnX^1V$~#wWsAab@~Qz~^8#sj*p7IiAuLIYXX$V<;0w(G((PKd!Ma~@h%@17~p%D5?HGLzt%g~ri?dIgRDfT``3_Sd@07Z|#R1TU;tkU+TL9t97aT}MGFt`mPv;6=ieV+D2}d#^+CXBCYHQ$-4rJ-mCcJoNYPFxNfcgMsueh zoX?u}o(JaQQx&jm0#f73?u|Ahhj}@dZl0RK*-Vrhm3L zH-M@0?VIPsFA%$Wl;!Z>*(y=I<@2bxMR)R)c)8^+LS)!ickiKvveYL^XFS`;19FJv z$Cg5~fdA7B0H}XC~O@$9x&vw;fwQmt^B@`v8E>l{WgSZ>@-byc@R9tbwLF(W! z8AW&t?WSt4!5DGUSc>w;3(Kx6J)B?>I`7PxwS)n&Q;vjeM%Lf5ECf)_G&;E1;*=`79}tyyY>@-YXyC%HE%cMm{i~J~JC> zQ_-rOIROw~A@Pb2N%63#fMvZ?sHG)l{%OOQfScQ=*{7jefD>Rn9)I@$fO|3tQQ6AD z`hxoa)#7UW#cxI{V0=_&Y`JM=5-=3`E-DbLkRO;HrUg(b1FBoA0Ge5E5FX@gVb_;D zrXo`+H5dwA4OJ*OzO3zOV54`o1Y-qONmuhV2%9Nx_Q->*8LB#)h4$=FY0b{jrj~h% z*^rGjx67^uMGh5|NaG(UxDRcrF2%TrD+NoQU33?PKP=QKI!$@fC7tCC#}-Rxk@S!6 z1O(gzD9lt;S=1L5>WN@52Rnlz9aM*mKyy%vul#xb(ZdWqD2O{7JP`BKAuAx<5%&2j z-u3RDr^5}<(F=tCVkGddvb;Z6w11UNNfImYD~LJRdVM^0UtMur+VBMf2CdhO0W<~@ zJa)st=FlG~-S&%HY|s{VADEk1J*DvQcs$@y>9yBZ6w74cw}y$i7sa2`Y29>qUE!hZ z)w%YwfM2!!6wzETe&ti3cVyyDK;0?$4{PFY<7Vx$?-uM!^^0=mE@=0o4Gxt7RGosD zTuQR>eO+!y$wF6c-(K)Bv$VL`2T4pLG}zGmM`&u?ta7tiO4JD4do&IZ<#zd1ilr2{ zf%NsQsNvvV$c>2%c=cnl(nt!GiZ@dF*$l{93ih~;Qjvn_ILTQ?FJj6RFutgr+5+vB zg_wSP7toy=I-c1el}Pmpzk@RefwRv!!ghE-;NT4_Z>k1#E1dab6|WJ_7uu}#(FKQZa%seKtA&+Oe?U7Xwe zAaLZ){Q=#Qh3n`1czbM?kSzJ{q%KiB0SiQqVrlYI=X16@uuemflwX>T7;5F->D) zc6qAgb7tS>`=Abtwc%?F;FZn+PQ}_02PyN5{!0i~6XunsaPv4Ydg*&Qtf1QZJ&ZeB zOR4qkRikJludT4(6p3C3C0Ut~wr>Ko*kYzI`MTpxK#7Y(D_l_k$wS&*@(DPHI&&85 z!(hT@MU=Ye8hD)K#PUiSfAn-aPGjk6G?&{X?oVbnlV6|g1A?IE*@y}>gX+LM9s287 z4dK60Ie_cj05i9p&a2T>uze6v$wcb2<3=G&&;b+K{_i=37trjsyJO9jZttn92-YAYDJ-@UBtcreM*c+VMk z04c)nG;V()LJx}W=&{x#NJ2Q5%wO_J-Arii5G4OS4#7uc>u{F4K2VGzKggOS)n>;0 ztYvIW%vArOwhD0TM(BX(4*%akaRTamUIi;1!$Rkxe6r}KsQMXu?w)rT619bQR`plD zy3wQm60QkVS0y#&JQ&YIiKbCze)5Zrl!?{o9+r_iWb3e*oLPxEFVhFZeC~Dnny@M$ zVLzQYQLwW6=eV+{cZhch$ao6cAR~j1r`ce0nJq=wRW$@Yx+$RRGc;QpU9&ULY z@}TEh6Q-JIfqdXn*+c#Y*W$zQ5^f?u9m)60T?6%Yb3)mGJ)AE#wp?O>Aq~+4$N)F!C57*HY}i2f z`1g>>Y`YNlfwMgMlu}2$tXtne7yUWyx+$c(u?W@x9ke}eNWCZgc=~<`VQ3xxLSvcHFUu@PxwKmvAINZ&vUPpSL>}(Wg8|tCr+0C zh*&0ChyX!@SU5qCX-JG=^)g>&Tc|(O=+-aue!jVy?|tv_XxUtCl6SX2DAg~>IZIn* z*rz~0T*?VJCNU7AyJZ76L?d8KABd{gET{4(ocZHGT5hzb%TQr(8JNn9^`A%^dBrss z&m~L8g3~VJ8?l#dU9dHHzUNi+_-%9%Fw?pYTVr)eQ1osL@)1BK4@-xAwHk-?Zx*CK zm*0OQt#DKI6x>HJpgCpP-6AA_Jv7)K!X`|Z*<#c|Nri0p>j6un zp5<+Yx%cKYLxCQ$w`b}oD@>^aL$qQyO1G=o!}Km4M#Hmw+8s-?EYUPf)fD<|46vu0 zA84t^S-E!UrOQdnyq#u}w*0ur2xvzO1gLoget+@b+Kn5EEdKUFvf}Esm(tH!ZT{xe zM_iT{cb*yhxaSS4?WTeZ;sp!b<8&+sHXU{al9s?M2{N5al$nO7qnf$_m-98NLa)hRhV8GGjo!w9dTU3ug z8pQ@?6q{@HgG=>!y`p;UkZX25zLy8Mi)B~=qgZ%PKz%(vQbU1I4D*ULg+;CS1#x2_ zFM++t4!WitKw1^GymhA$9)bnY%x^!>ceNe=FWa_R?^9Zzgb%i-7!fU?D>eZtg^M^! zzy+ev(DJ0n7N1pm?f1=BtB(%UI6eb>J0O^kVlSbgKjZH*t9GcuHaUGE+Piqv!ydb( zetQX%=DxVuck%s6>6f}5L=Dp2mA0h3*S}i%{{La`Js+A{oVC$y0|f!yp!8w|1OcfM zNzrZ~Vrj^aP=kDb9>T@~u%Ra$XJL#- zmY~0SDm>I%$|z{Ui7{%fh=K$PpH(O#_jfz^$UsrI^gTWsrkUO(>!M^EuF_Ivl$Ang z98IxBKJ-!4Fu<%6ZKa-Cy+xy$L34xbPtU20I^z3JAVvFq>d zDX^!1z3!mUt|J7Er{DoXvLrK!jTg(G;Lt)Rc+ig&&$&cbX>g&JiI#uaA)Zhd zZibs&HOM+-JF*nVR9CF5KbtL>bmmwE>~68sv(h(X;++weufAjiZ2ONqor(*^tDd=- zX!-jVkI<$YB6Lsu+m?mKPx??KYw2bL33WwJ14z~PwsL!b8+3OPmvXxN;ZwSYii?Qk zqKU@REpwy@T^=MtnLh@e(b9m@{r}NFNEv_~Z#%GWzK9RS7Fq(d>a#$kg7N__Mh_iEY6L;vJwE2RVm?x=ed{6uQ+xw;|qH~aw+mVK03v}3YyJWTRsa8Zq zDmKF1fB^0ke{~M?*mO+r>6KF=zbK%8^zol0{*)T(YnO#6o8>oXrd33FA9LZkzy6uq z>vP^#o~M0jHb{vpQssb^#VC|5zPlnWKq{6rVy*bXYPfm0S{wJQq5wkVhFzu~Fty%- z6uX5ZEJ%zA>yHVia2HGKS?E5>xcWi!OlXMvUg_gorXgC^NX$ay*V)khh*hA+=LQge z$(1dH^i?!C^cvftpkC%K(xWvZ9nfcZ(4Rw+wiYbRpZgwY0@QMaC}l-CcL)lfHVIz- zLw(O*0E~&m^`e@0n)oV{Z;YV~sOIXWf4{ku5vkMcWRG$vXPJtQll8pRmr|3ig9f?A zLPk|5u$=qyP@m;pbDrx~TooYAs=F;^+&3*gj-^O3Ztt8A#a?pYa(ue6Fc`G*?Wzf{ zV~RO|Z^wzmEUl#)0{gL8i*o~T3}-f8xkI*kZF{>3%1>#904JDfAFu!q{q}INoro|m z-7Eo|g5);QxL}uD$D_54;T{M8AW@A5K42x5(yP8#$cl%|K*JRs%$7kx_sX$$!d~eI z=i5e0(dG=j`71XG#exMLmvj8Lb)$#!oJcY6leKL+4qKp+}NoI-fI_9cWWBB2$x2-l~%2>EnbyY_U z4<&GFjJ^XoFAJorj`5Y2Cf#-0FWk&*rGhjh2|@q4C~Wqp=r!)}F;8=i606*rLPS+@ zc8a-kw!Ha|*EuWCk`(z9tm1vxQ1qaZ^RmZ_*ORy|TUr4h9k@?ZBPVTf6MqLevE#|9 zgE&py8gXxhwWzbn78RLAy|~}D3Z$|{g*gqP)n%5a{8C|eCvG0^QEu7a_16SC!=3~o z%b}O0a@lV2Uu#PaK48(H0YxAk8}NwV&IvqFHrCcc&;qYr*}PFfn3+hayhHmN-uirE zE~VKV+n_xZZt&ayJaeV$ZsXwJ_hLR1dWPck=F(U-#)WS zJY;KDbE#U~$pk+gF3==#yf}NTH|mPzAS;K3^Z5--g+Nti?a*qM$ZLzh)ou2WlQ%wJ z2Ft?k;8TeDNHh8hk6FAoh_)NpzEV#$fXjp8Ay2o%4UTO@%|yEyzI^6A{AF+Jb3Y&& zyJNBR9gs5e#Ja}@0aeT0gBNP1MFBvNKS?(4aSFs{8zb=H#pk{D-hARqh+SiNAA8Ro z$LdN+(9o{ZT~3g0{b(%}Qv zLtSq1_T%BY=N^@lDCK`AZ~#R(6b>y5dFa$_Krx<{z4_1Ty#KD|)*mp_7o9tCoaY!D zkGZK!v*tG>?e#l6h@__w{GboEAIed4daqmQ08 zyS;q@{uu!fd!Dy^LjSI43Q4hCE?oucn5@Vg3Ws4|Pu|M{HO)k=( zxqTDi-n8J`wnZv2o{vfUtGG4B0q*9rX7DYrd;3R!-dHn~uvu^p?+^chE18D*wS z04m#%IDK$70_}pY5_Y@sK4Q4I2N0owIuQFC>{<8INhen200zo#A~zt86sA05qA;xK z0Az+)b@YNZ(GKsgZX=2jk{D`C6-xVxnA8+6T|I2JrFyL*r>ZLP3K{xkD8tt(9iZU$ zFP0%RnClM@aAf0cSa`25&cX!DeeCSj0ULCn*>!c@B_Ff<_t3rb8HiPH_hYBmY6eVv zVv9m%I8=1!8S-w+c2(<7MyO+|oxi%?=KH_(u)ngL7UKIVj(-N4bb^`k1N#HWua}SF zoWdxL%XZw$=p$vKyAAZx0OqdE-+@BE6G51 z_gqdC;zo0YExu{(Qc7jN@@#-paj{CIMp;yx`B=bDK!&+0BubZVe(|v5WE6I6PtYwW zJ7UV3;Cv6U+5t#4)i9euxTj?S4u#*g%nwOlMa1bCIcWqF<`}|{A6qs_P9fL|AMLdU zuL6yWl72CshZ;e%wpr_SBqg5Ua-xV9UjLlS)q0YGv8oHaR4(HM-p0jo3)%RgbAE$y%il7B`8qd#~{bf!IO>ZJ?HJAkTQ`voJ`Es3*>~YoVPaC%ELsuII z&@Ym8f<~j%RDzTJR*v<7kgSX+R-X32-QD)VYZdgHOPy7`bnQf0{Kau60Fp+KAr(Z3 z5*v}`(=PH)Vri=KheC^|bN0D6Bszq88LL}!+ukO}-2WpxIQWjwUibX*9gmGE zq-aKVP}1DQ6DjFt8s0H199M;hSje&PH%gA|G^#I@Z!vCU*2Qw5>4WJ|7)hi z(DDxek?fc`z#qm8dH+|hm{{=lR-?O&aWn_1f#L62PjU(fd!ciJB< z9Jc;U*!_R>&dvjHp2Y(=@qqNu<`M0DKIrd5PWy%Rvb+D^f8i)FvTQ&_gPE#qz7V!2 zjnQ zj{z=C9^a1PK6pa)=5sPL{93LuP@?<)`E?AsZ1})I9f~W879BX?WGw$Zu>g$8aQso7 z{LlQ&{|p=bS?>JLWYzzSk^K>}|9=}0TwnA5z209jhN)yS~1jpZ9}%M3Yry!l77$I;$d}IkMJBeva!ZOnF$2 zoq6W~V`kxgvdhNz8cz8yt-izz4a(St`UI~H(17b`9hw=ip6L=HmXP&KmsT{6D;TdK zzYfVeDBkN7S zKl?&m-DO%O+I|u;K!-?gt3MXiUxl@lQ8kudk@ixXuocq>*YHTH)oCbEzdVtLzA03JCDpV&Tg(zAm^K? zUl33h2p;T!_P<}<*9h04BCItsm?&ME+gt`LFmL*c(oTX!UHH8~7iu{3u@^N&O61#R z;Q&WVmO0-)P(4QKnWxXp9zT5Xc1Jj3&$TBSwD}V z;lRL|w(`0bG(vzw&?Kd*qod9E5zhunylzX@cHE))`-muV)gqe%F$qN0e40S7w+!@pk;KAi_(G8rCIzElZV=P~vR`z6Ui3fjp9EMDc() zQBy*i-QQU%!>7J~v{($mG6{mDH&I-VCf#H60_gZ#&wtctkUo-Mdps3!FzI><9n2Kw z(C@fRj6`R{@)c&jSpOHAZt^0&)Zjzv+_b3;C`u@hONL9~BnZdRWT+g#r6|3SmW z3M%Wm;ctM$xX9+gGcMI0Pc5R5(Pc*%aokIHI2R5!kWpVdum znqk$1ir+Jd4^I!VvJ5_-z8x`B*V#gd^k4>mTudsF?wI)l^hY;}W!R`6Xuy7fIdrFEVJ*ty5=r~G8zG^3HXTVu79Pl>YB4{eOx;`}F1dUwB$ zC|>(`Suk0Dq&<>X4{iBI;HzH;@rQSJ2df@NOZBKLW-P$qxWh6;?Om5##02l7Q|$9= zs$Q!T&99+HB0qJ_jVGpv`klux9j+@QK?3b>y8@58jtu2cOaxCQWbqyEi9=o{9y?se zXJRy$Xru_a+#FO`>|R!XR#p%r`m^xfCW@Hn#!fba;hH{@lA&!xQ1~;UF^k#y_#I&T!yB)H&3%RKXZy<~{` zZ_5xQu+l1BIC%TD89(g(ph&=5+4it|5jeVHHZ@V8?aW0QV9>xz%aHHilMqa39Mg$K zaMH2P2X8CFbW(<{SR<<0%Tg%|Xa$;pT6rjnwz{ zJVh78-og(o^4YW1FY7++JtXy@ArdS{bDtC)9|@QqKn@X@^4`q~gQ{Qw&RZCoEBEUv zSr8YBDj|RCS&au_>+`n0iImAmsS zz#?Xa^+;-b9@+Q%9EAHdW4C>>b?vSxpK`_AT-Ee@qglrAN1=5XFnBo3^MA}=uRH|~ zpe!u5iLA7LPlxrK#+?98VPgKeLciE>qY)08cS}9hUt8QyY`=vSpE8%_$#?>U+Q1ZB zgbx*!5%x|s+neiaw0Ku--S_FYZEjjJYd%V$$&16g-tGU<;vtrw238l zZts2w@*u`5hTYY52ITF_?!)wAj^jPXC+C`;t&*T?7za{pyrv+BD7(=FyU{UMO57#s ztpzXNS5Zesw>5A#4^XKMue}`^9l8W?-~r#Avl2Q6q5CocxCO8Lm6&5e=T9YTQ@Ri6 z`y|f`hwBFDK==Z0}#s|Flg<9`lp3mA`fktaaT$3vsHYOA3BcRrh$4_+?Qq?{^z?KDJGil}B6L z6&E%L&CN5gsU#<>`Qds!e%~e1Mnlhg)j79FqE@*C6Rcd~L`48$0!afY3|{~B)-27h z4IadI2$u@6Ie~YbI23&or9YEH`FitY57mVj?P8qH%Yk?`CS`V4oT=5zC2DP!3e57; z`5X2;j~uS!K5_{6(GZhf!F#;tfw~gRMA>Y5?^7}_ecEChAD?fAc!L z?-xtRU4FEC(rx_iFYs289&&}TiZH@^Suf40t$`}JGwmMFUcxytMEH!Vdw>#?s2G?9 zr@kM2s{(riBI|~vor)3IXz-SIo64T)lzKI?!>E{Lxm{^5QC*X{SL7JbC`9j1a(ouu znc`MlI(ua8?H%TT@|z+@*GFLk`xQQ0He&9;Mzyz#@A7yHr;?}QrlJ*S;EO7mc9GQT zCS+5z0J$+1#&1UEw*%wLqae#C@#iaOWBdwg`_}W}63u}-1mBi+meZAe&w;y}wkpTO z+(qS8Kmb>cE{kiFbHZnoM6LKmumHo{h&17F0O7ljL=L_!%Ac2#)>*E#B^JVLDSX44 z*B{__8>%_ep!b^f6F~yWjhuGk!cK&NOYNy#OA{hcvS6%>XQybuX1npwXS?pPP)F=R zC^{o7m|HQqg`C@%FTORC>pK_wIW=IFIz`ba#tWo3P3D-i*YDqU(64n&SM zIt6)mGK9-}JAkp6O8J?w`lvyl@k>)8Q-iwryyeWMC{>X+9yreHt0jZ2*?{sti!OJD z^Mv_Q$6$!?yq&U9`D$pTd-`MeOX5v{%v)oH;j|dqjM`OXCbgN6HW(C+M+S}T(RwwZ z(Zu{JZs!(q_|Ibjuv_5j*HmOvTAYeTThh!d5T448bOpp{L;*tveG?l$W&>9?t!CUN zifd89QXp(#>?r(nrF*CQEBV0bvRQLxq2pG07+VLy{~=<2liD&N`C+8iBiaf^8dVPr zi*m7D3Lb&Iv|0?6C|upanBbuD(9Tn&<_6I~MBdWOSbKSgLhaUZ45Mf^3$Gd-5Bjk^ zO>>5b=g89qtY#Iwz@R0RasnKk_JW>UK^k9`V`TEgMW)qrtw`rrG2wRRR7e@!7Sl-P z-_8_TOi^)63cZyz1TnWkn!9T?^-}q$BHaQ-nkQR_;_$y_=3j291;O78F60GhOtV{t zsw7Vtr!R_;lOnEQ>*c0dT&=1(pOCEbJgn1uG$x{MAG&~<`D+C%(^|TvvO|)= zw7H3!fOEE*zd^(5VV8|?7c$gonX78<6}^|qNhR2(HQH^~>{KJr%IIpFxML>FFBv|a zpn3K@D#pujDoD>gZJ$`0;>Xof1+8y`Cl)U_VJ?$l9@H6^0^kZAH|Ge3)v6>BX8SIrbVE4VKQd4 zgVvYJlHI<+bG`?!l503y3t3QV*2C13pxZ4T>AKsSxQ_Ow1}+QP{k3a3dQU1lMO9)D zAtycB92?;}igD6&P1%H*i@R-)P<4)`_*tLB!n`1oH`iPEy%BaeW9OE^=fiDAgeg(0 z!*vgctdT~jIn>-{oTyqXlS@OsCF>U{I(u;KYQOSksc%!?-zC{&P1d1GH;Z~b!b{xO z+oJ>|p_(?Os4y+-*!WV2171&?yVFp69AwCGDK~~1FDmkqGA_U445E6Q&W(mv=%wYY zrR5QCrAF<|aWwi{Va8{47dNdJ?bIk`EyomxbdsZkQhhcqf|@s7(DA& zl@DbJ&N}qE`veaQW@iP>eJuFxpk&|vDKXtc$CJie`1YCzt@wP;*MzsG-Lz~go+5)| zt}4D$Q%w6=EQhz45hGthNL)aq*Ngt>sL8m&R8y}1?k@#iiV*7gUXh+1IR1VD(~D@7 zg_&9`T_D={ahgXTWkVEpj{`hZ)`|?ODQJEE&NxWTqipY+f}uvW3u<=$E~u&@YxnwR zF-DQ0R&c_~6fuf0B1n2FAz&lZzFp>VPZCrdp&cK6zIDF=yxyQFd#rBv`ro}031zK1 z=mt?Iyy;S|sLF6{O4-36cGdBH5k;}`g;t>E%3twna z!KnpRdph`2j7HQg=N4wz%flRJW?Ydu0bP#nhkaNbTo}h(%f~j@Oz862;njJ}JTISF zWIWXtx1Lfcdg#}<6HRk3Og!)x`UH!{pmlXyzP&LJnt2_Tbolp=rLEKChaA|v3*#2n z)jxz(+{P`PD<|w&6!dDOA)SU*^BDK+2gz@>XVWBJ3iTc&e@k}X(Pzfd&SA*zbt-d}hV!E~4qrwjMeH zqHt0+dF=X9G_V8Y@9vSes%rh66?I|ry4i5qRNY*8{6{mM7iJe?gt47sMv?MgltJ71 zp1&yB&bYNK@iPf)`myKXjt>NtHY@Hy0~8&>RK6bW8_=IRw~?pgJURbVEAQ6wRz)*o1|?p4rrTX(79Bt)IZY`J_+$#^?L z1nkRq_OK;f2;j^gr$KY>4?pwnTrfY5jj#4XWa(wUm1zSB*pz8PY3w~!?X4k*NA^^_ zJ6pnzsdXadMYgQD_7cw;HyAz+a?HH|o~|D{Oc)r_jQBO8x|~w^3c|G(67#w%1U~H? zrvOx;VCD1%Iz?IZsaU3D4d}Ned>dy_Dp3tJtqoXMRm{?lvkFNXTl(enx5!f2iD_U5OkY z>SMWc;j$^nv)DmxKd$$#$zHV|q{eDT5*oP3zo-$qR65cw&b4Y72BHm#P>E9EDK61Jsn!9B8hJ`z=qPfD#ziHz zpNc_b`_&{`O6Y{;M>sB)+MHHW_Lx*s@mb}ct=88vL%oM9$xHRt=fmX(QJ1=e{_AUI5Q;!ui72dEVyf){!9ei&9FY6`Jw%=&TCUj-Jz%(WLJ(bi8(1PAnUQ3{PhGM#aofr zS3mxYw-RP|?g+MgHyalYpx>jR}o1d62yi;X_z(u7F$O$6~={wp6#vLX3r6j;AR@qJ&pWinSqA@M$gxxFoJHcvr zlq4$Xx=CD$7fmv&PE@{`&n%46Gt!R@Q~s+QMg$|gqTgzcm&{Hybc7=RD%G@*xVhBY zRJv9vHGay15>{m%y#umr7&v2SN%mEl-nc&`2m5l&3Ao;|4b*QtzbyUqjvJCx zi;gE+#(w6K=T;Y&+#4G^4n|NY4O>EW(()~JYUP%LrC|- zDvf(gZC!}^``iMcaO*qMlp9xVIll%p>VhDrv9-mJNfAt0#+rH*t`jwpff_qEHAJPC zEu2JXj&n(e$@R|JbJ)2XBvMRzb13ai?~uyG-H0BCW=c)btFw#`XX`G`qe1oR0Y3QPIOLl_k*3cV;EBG_=<@7A0JLSV#foDkWI^}qqU{bL*uf+ z8ARkuhbEvU2hAp4A8$FG?Xs$HhtC%++z^MQ=-|Q@ci58%*!AAzXHPMne<&K(DObooCEunrOz6|#R}_{hmUz3qkii|PR@6B6(@A$K&V4wvU?P`L?`T2k zl{D%vDT!uEflSE9* zYO-vhy)PqkFwDdPH#_tns#UBzhKc-*8ZC3Kw&SD?yXcN6ARpIxt4KXWKZE4maxOla2##1jSIhHle zDT(5F;SX^^p!#IC{PvAGE|c0=XS#H*@I$At(jVXA@$o*ZcbL6S9NRN1b8SdkyybB{ zS-v%Pt7CS|?Tcr6Pt7fV2V00MXXf)lJ7}%vn~Gl#bH7?}axFUCO}$i3em<%^So#hf z@1>*fMoV(c!MmAXXqI&W2d#jQmFXem4EvVQ9IO6v-?8mw!o(_ z{(YQ^nju4R*MTFmL!2MhG!WIkoj1oR`Y;?=3sqLkhG&lhJFc}Z2!or<^qkG-6q2d$ zm@(P=JOwk&rJ*G$C)Kae_W^M?D=cneK%2e}4su~JEfX-rS-NHXy?4W6h7VtL=`nK-f=5NTx z?nife+5cwn3OfZ0;>0H97?;n)mAJH0TXX5>&uEQ`hv@KAPI*M1$-+d4aOpP8p{`F2 zcI;s^O!;KsuvNLvt2dvg%T0%;eUhmrIWU>YU@$N~{-|vyz$7RS>5E%3!eg2Ud8gjg zdz(y_d94KtZsY8|g)`|dKTdwPBy`|4ElNa=FIfWl$qQfxe?RjawS5CS>P&cAi~Pi> z&VQB7d*lACEh;)>HS~9(r)!-}7-4e# zTf=foCSWG%TOK3w3$wq5Y#64cO!58!-5*=R!}uAf%{p)|Z=1=PCK{?-Vk=XzpJKVH z0V(Sc3>I;lI7havE`{huo#>fp)F_)powN`=dxBikpiZ3`0@`xWMe&YeM?vNB0KMPP z5sjQ}sETsGKgOh!m^K;8fJx}m98n!A(wnqC5}D;3&>L8pM~X|g$n1@T7ss4PEh^fI zcdzct>Xw_Q=9_u|yNHH#QEt&*PIHcn^ky}UYG6$kvSF6~);F!wexz+5a=0*lKi+%O zcSDW4(x$n;sJhvxzIniP^n)jk(`0wZmp?K}4Haki5!UY0w^Gbdu|WU2HXpwVTp zX*uukhT|y1R4*q5og2f29y^oJ7|Zpjpby{N6zw|qR_8Y~&82b9VCg*Q@|fVeg6_sc za&uB+a_DD1!=sywQ`+}Y`fh^BK0{tjA?!m`7Uz9mo!n5_?>@)Bs=Vi{2YavI{vqNc zZV-E#5c*|MXV;%^FFR!Sx1)$fY~=%@#kg%S7#GF1&rkB@w+LT=6-_D!<60k1ZnZcG zKO_#f=e&Y&o^bPy_$K#H2GM5ZGjEx*b@5(w*t%xCKwg*PTY(<@U8f!ZD4|zPhsNMf zbG*VNibYUea~U61YR0xqB=Wb$xrXvZ2D;$Z5v8J}BOJkdP46tvb&qY9b7D41q3WXj zvZJyw*F*s6^Kx2qtj~S%@p;oX96j{=UOC+prh4WGN+Ts^a!tK|Pq=7yH3ocB_yMi+ zh6emzt@>}L_1TB%&tF31uA&GW$mG@Tuwpb9<}1= zgI1z#S1aM}HtH+&$qF%TJ!C+ZlCNhE{_R#rbKT39tNGm5zNPY&#dRyr2d`Geafp)H zyyPepE4~*x9_7LUqb9?Fy(UuB^b9uF*QB>M(oXq$);ZRIfju3yuQ?KNom>`ZgHEk? z-B^k>W~khKRiUbydsE^*U-!E)=A;sZS{r1uyZ%X&J5r=0 zI#fhpad)M7PQ!MHdE=*-s{E=NH&mo7CSfC0D(wL#CGCcJhFa0>fz0X=u42h|#zdu>P{CYR!xy@-UTN^NQow6z5rQa#wEl zHd!agNw{~C==Oj2&6htSrlA~+*_vCxEDFRVt4?1rZ1Y^Pw`>5}X5?{N!@Nw^1}c!i z*BsMv2F_fxC8T65c9w9jORH2MWk?+IzH?<)Gf{_mY}@|ky8O4$c_U`4{P zHh51V=uttB*I$j^0CbLc=YMeAz-feGy*_!b#C0EK~%N|!FLs3Q(EGhyJvm7rG|U~`U1_^h&+nqk&VATv?k zLqn9B(dm14D%crZ3{hY&Tg`y~c%@xzzu0a z8ZG>O{Dql)uW5jVmDdjW#@++5B0QDkmZ;m_t?*pd-2ZJcL@=C-N;Ci5uTIUx1lcV4 zbOuz%%~w=C^PX+-YAzMUj&c=(7|RXEX54$6v!kO_!|5v_Cf18^=gE-E%3>KInYi_u zeeEarp)?tY@>bXN_iW$rT1`u2?LJ2`Fqv&3$(=wc>(B28{n8+Y22Dtzj=CaSo1=ft zpiY{LQuBTIi#)3x2uSO2Og(BR#k-n#W8Iqa=qYs_i-FUgF=u0waK>)&g4O*L>61g{ zotMb7QoBYhp?-G@%z=sa{nH$Y;dh@voBP9Cwnw}-8({B)KN7drat9oWxRgk94x6S; zD4p;->FOa2N~4oj@aAcEA5l13eB^Pt_;nETQ)Z{=saUKe_hPO*v_Ed5dj8{9o7>)& z8$R5yYVGg6y}3B2DQ4S*!Y;#xtzN;S`!^FO*opp<@%_QGkeyHch_j6Eus|DeBK|rvDbe#e>=b~??K{< zK8xqwrq2U;vx?@jt<0s>-gOOM0I>l2Wn7|aXAxF(^+TT;d^L=&lu!yk;pXE2Y=`lR z5DR6xzg?hk_;dyl@kNi$$e#fy3uSe~d`L0@!6N^1m^ zxN9>me8`ELmnPeO5diu{59y&%{~J)!7?I9%X&-GmJALK|6{=$WP>rJIyZNCdJMu>;rMZBFBs>L79@r@f=p075d!*-~_50ye zk~$%|u7jJ|)@tMKww!EidfIH3_TBlq5ND#;W44cWrQCBr% zU}1iGpWm&Jmm&9pefFKrW7yJCxDqwh`qk7=CvGK3rnM{DT#NM{G`M(g5Ri| zQ2mNpegaYTdG8W5t+QnI;7i)^d%y6PfEnZ-V8iW>a@qplEww9oM^q5iibm)sphvSc zSc&8)jpaBqwO-G}Uj6%7Zs7%=$B)b2>^Xm?8lC0(E8z)$5Za_Yy{aOy<3VU@CAqEH zNkupkGJ6PTJGA8QGkMX+!(rGfS=oEkc(V1G_bWNW7%lkD*9LyX;%8LS+#1 z+jqkOGi+ji2)7+C_ds|@y>Lx9L6~Blat~DI02+mqt{_ABcoU4ABsEGm+4w6at*}q31m6;`6-Yz8vf|Uprnc6l>RA)^0kLFH=n8YVmLxHG6=+K1d+xn%5t7 zC5Qh02G2U4zOazr4*~NOsfKs+hZre;qjXga83qSxDKmWpsYTCzquxx;6-rgX{5s)q z4$wF9jC5zQ^ZRG3(!|tXYGX6R$k0Y9;Lj}~HEzr(8G1~K7@voszIL$sE<1=O?Z+_+ zN-VPdHuTSKkVEDI-8+rdUtJ%fg~->qfkJvw!b3tpIz2?@s%{YH3r%9Cvsw zLKd9Da^b?gEZGYogUN4M77x5cBx0#^zV1%w71(4ti)f!`qEPv~2q^x2u;JI*vuK~z z7>A+qma}f^meB)FPJ_l2TBMGgm9-omk!lRq(?oRY_TY1|#Q!yIXj-Z}hKF+5E#y zKz03+?(Hq$xsrEA|I0DRTiVg%tnV?L4S|TE`6;n8h|&toMY)u?zXZ`v-dpOa;BPyk zj?nFmuWHq;wb--2vSv5R#@hEJ=DYaRu80XTBs8La))%fV){JNpV?fd7L7z)39khaz znQ8`Y{6#)Gqr`d-{AN({e|brFT0@qJ{?+X5Y)PfHjuZJ^r|YFd-p|D*L@8YO%hI@7 zt=L^vMDKP49rAi?^W2Q=P=wsVcB@yj-0GB4+`HS0Ge9H=v}`@jx3E<(T0l@Lh{Sm@6ot>Uo&z_Rl`3jQbMI{3*WxX##&AXubS+2tLOz?b6+vSX35a$W^ z%ZWnnTPJ&pv{4_Uzx4&IWK?Z>4c~^7AMR*yBp!8jw?$u^%OyGbWv}&0AHs$D#hZq3 zB&IiLWZrCWT4?u&?c}m*;u!mZN9i8oy`T+yJ547+`!v~B zqK`O527YaM>{3*I!|4QOiy&eRyTh;t+pof?N-kF`=YkPxeeH=rjF@3;_px9RDdMAY zT?O1Gje;Hw8z9&xw2}dj4hruo83eGx=8wFI?3X*Rs?6hN|||MQaK48;C-R&!s-q z;`{N2Z$*;nyHj3vCnz`OHb}Uu@^-YzOY-I>Xuiv>UcJXqjr2hAMOcStAy-Xw?9Xe) zK&Wfjsuc3{NNrHKOrDl5HhEGJ2VK4=&SCL|zGWj9Z7WL03Ln`3jXr^hxSAMYYBPgh z#1K{msX1zfpewFfo;3YQ$3r;98kH)GOFj{!FuAe-q@saV@WfW=JwfH$f_A*y>4h8y zLkzPENTa_K2EHmA#}h*y3jYpz@G}2N_T*e>=<*}z=^~DvqH{fF`Rql{w-rAq0)SvC z!1ik7HSp*wb;J(r#iei=y)FaPDSvP5v1ic$8r(Wk7j$89=z^GF9;Q(`9-E!aczo}! zxC%k&&iJu>^B@40sATE2#R{KWbi4qTvD2O_Bc^7Mp+Tk=mzu5ZUPOPDt0!f&KK@eC zm-Q>;LIIk18l5?ea11dXdZHflzN(r;4;MLvOJI3O&`ZPv#+?A4-E3S&6?AfpL@h3$ zVp?gWr1%p(Mfo!5C!3pQi#VAtf{%Mc^9Ba`41$&z(mr8)yb^bYXsys!o)O+bKkd#P z{;(jAx>gM?bkZdHC(phei#@Im*tJiKgJYgWV z;^rjwHYh_C6#i_uxW$eGj^GlR(U^3J!t`IOYw}TGxV4%W-#+q?(25rS73Pw;!XSaB7ONFtO0<+|Aza( z>4{^&7&odnbjwsnvctqyoTH~(^!0~MouF!;c+UMNvkZ|^Q4Zy#;FDZ}{Feh8k;gGC zt}d3;Cml~=zK)Kq`?W*cMBPt$nVJV)Fm+nf^SeX=mIDP0R?K38Wh%KZoJv7^(CM_m z*}$D0FOWXq46jVBZ5>tqegd+4B4S*^j*0Zgs{wU6R++B_97FIh`G0ss&&3OkU%m4B zK%M|7C_`u0Onj4rH8u1JjyA%y!){p_q>sfaJ`*qt-ni7DmKCyI%){g%!A+a#BCKT4 z(zY}jCE=OiGs)DxMfSoUMBe7(ev4a?4YAWeEZ7dvy(Nc`{i0+tC6koTGWg^l5Wu_( z(NP7h%I)~e4F#V=HodCxb`%+9r0zn8M!Tc^pXVvA+J;pBO*3)Ja(-Us{Ij)sdM+qG zNAFYZJyIcddK#hG=WkurmMP*l_ic&e8UYqQ!*Qg}v!kwI*#Ie<^}&F)y|uzf`FlgObo+e|AV= zw-b-v!GNkk;lIh@L#?PsM=DQ$U%NWLgWzEw+8b!v5C7Gc1gWp`dr!t#g@#}HYwc9R z+I8SIn1@W~xf!#cl=cOk;!_uDPYS9M)>1m4IkWgE^_=uyKd=&>TIa)`@r@3pN2L>P z$en}T7v!4dRR7i3NUUH7mZfJCeOt<%M7SWvzr?X7_Pkt$R?P&|3MM4y-Un|W1uXAU(rZL8?yPrc; zVeeg(^m3(%`f`_KtLyi*qdC2+)nA?w?pryswp$N)*_S3`Fs;ToLUCQ>^4eWvwJ$n* zWvzEZ1Rfms3=K#w&tGT%b4ai-X<&bu%5I6gMPasoUHU8<3Sw;Mt-q#7fC| zXCMs!TWtt8AEZ_Inr*&ssJ0Pf{Utx!?7ifIC2vE~rWwp!ms#$>JvuCkY5lK<<-4e_ zDaK$e7lR&LtlJJKJof?ssDi4GR3sCG8bp9RYvf;mJZ*&-r!|c_G0xwV4UTx$4sE&V z%Zbm-ZrG<{OBG~dPUCZ>kqjXx__K@UMY|3%5Ai z&R%soCNe6vT7kB0;9>@Gov=V;9#z%B0A01kUq61I3KudsH_PtYRmNa0o*2|J5^ZBzmRVPN8m3Z|*TzHSN8(!h~1w*Y50W z+Z6ck=whO#e>zTYvMoYnZtAltag2ji%xNTGK1;Elw;fWm($UZ zAA?`(t9{&NZ^y9rM9s`Hf8WVs?XZ#-k$W0@_VaXP^nQWU(vlxvdsEg$(^hP#N>#;t zizu%b5E`$G6@689{74i0I;uU+s5{;Ya6J~)PZ{gr_gQYR#4tovFGK2(NAxpq^ogFs zS^+y8D{(v*LO*b2-%x+mzE;Hwer+`oGwF&#M5Kq)~{Q7I7%oq(f)qJWJiCDKGBfOJAhbSxkpHK0O> ziii#n2uNswpb&yo1tEk`6bu0Zgb+vwY3~np{?GHy%yX`DKAm&D=kbFdB$BrBB+Q5!VFI=Jvvu4SXdQu?@Rb-_c`X!bdMyaP-!9(u|+ z_qU-H#!TI`@Ub`pjE5nsa;ksixQq$poIGIGo7p!YX#L-QmAw)FNeP6QD?@oRIQd~C z(PY04&#asH76t2JcR)#1X(>9f@wrZUljaH?seKYTP|E9T{n}#Xm zcs5yf?S22lu*T=3dj2z=St(z(kC?>6M{An!QS(na7g{}>_|?lXcHM+tq9irk43R%t zmM^Fhx=j$m+^Y|M;C2+-StkGL;1dmC04lKzpH=pDCtOXtpQvc3(|?bdNy|L)a!c3E z^J41phkDT&Tn$s5Negb9HLZiJXO-Hss2U^!71L9=8lu<_okS;Y`|BHxTCMF3d(Nv8 z7uaYITK>ECH0c>jba$DuOxVZ{&nr(_FE3DrsU!3?KH(P(C`qD+1Zj~=Tu?Y+@YFBLxCsN_I2cr+8Px>RozkZ21))Pvc2#s zDbR99VS;yFpv&VtS4Bq36lCvjk+d1lA?K>p51m;~{fk>Lhh4)9591^FHA)oWgfIw3 zstr;OgfHrSP9D!mG<$ZM=kQh~2(*Rbg0tqB1+=A-oKngb113s6vrcBoB9pd|&6lcv z16)LWbaUwTAc-7>7F0;*8N>IPfid&nWA~#UvHOlw&XcK)E2stm-NRK|$T2b8KH{g8 z6X#5cYuhF9um^&dP6*qE%9~S#KMNu41+MFTja4~xURc$U6SQ9LzE+Ledr!(ul{HKw zeiKVu>_ljG8}Y-!d_Nz(qWJGV1Jr|{xJKV~%K46f^)@E0R+>TL`i$QfYSfl1jJQ;? zvNXMIMbF~XooSCFvQMup)lImLJkdM5$V+fQBSUbN6Gfz)KuGlY*N# zrzI+c?G%@M*)9V}{Vcxhf(EF@ynYyAMW5UJZr**akZc~q8ux0ZIOU%v2P+?Lu;3It z>;$P#o;`#NJ@<{%QEG52{9bTygQpE z9QZ>HePDv1d)>ExzpDm5w$9q(v}NZXN~nCmVuyT9ZB|2D;iDB{4?2&+yf8p)e)Pf< z$m>D~A8mFf%aWl`Vc$%K(pZ3YhPB}Ra2zJkCNbTzWqLXjgJE|Hq4Z~ zL!b@Tay~UQ?B<;=6Xx6l)UmA4^8xGNq+6idv=uYUE z6iu-+?h$NGAvcUf>49Z(u`3!%1n=jmeTC%qeGi2>sBVk$;aqI(Rd|4b6#(*P=gvSb ztiNF&GXhS?+hkd8#M&pf#w$}B9qERfodDTUo)lGCg5&J>&Ccq#{4+o!7X z#6yU$9pIwRwE{oXoL;Z~1aIO6f%xmq@O91M*}!9YM>2=gW;VX?e1i>9Q?)uNZ98;6 z`v6l9{>%djxkG69~9>b0CHnJWqjP>5Nr&D?i#&R$2 za}~et#3aVC_^(krf7fRBw=3EeWXtEyKH`*zZERczjHi?=MwpznwQT!fX|O}XNKSn` zGs2}2w~gTyRrif^FY=ixv(wC33vBeR10qpD!QX2M)Kl_SP!1m=#jvR@tSo7-bFiZY=jm^MY-|U61zUS4hmgi;Cue$^SeS(V09z; zZdrnrZqZCw4yRIYUtY0N3iAOWCVTvgwdFP&qOCGUcZa>9w#C~vMyjmlDC${5Kk8cG zT)L`8ddkPc2byJbd7C$gEo;yzySGkXG=lDMISu`))a1|Vs;qA|0P@Jstft*S@@I4u zX*c~=P>~!S(y#Y2wC_CG-r}4V!!}jmzJhwtKTGA6FS0A`s!I){weav6V^DE@vR}Ng zPHikvWH`00%Dq4K;o5G``4;@LSvFQFI`f~ zWcGA9l#`9R;H?w6*ufTj(aeBZC_LJNmW9L^D2#@Eq!Y7}>N0h}8R?mEZ%wz$uTI8m z&tN9|#}-Z77^jcc@^nay_S9N;3MEKI@KqY~da7cP0OF5#t0T)^`6}#{C3ls4I^((f zok4y}_>Df?L4PG^f+!uCFU-*wqgkDcS~!BR=bN>griwrdf#EE~EhYo~f)pU`J&tg^ zR>wnU(0*TcG^2ZNi=Sca=PKufG!MwgHCBx_#wRTB#y;z4vm?{|!!4D5rKhn97rIRj zpT@+Uj7De1E#{_|+6Z^Xx~Y!rcvJZBo_bdNs7B7o%^UFK@DzGexj0>^p!kZ+!vT%E zzDV2SNoj5woBX7Kn$YfH_kr3WlYZ1*Wb>9e&u7Wgy6s2LKF$Lop~{nrnd*bFN>+n7 zxPDM(Ls8*S>YpkMvAGY+7&9V zk(Y?P!Qs??y3B$1Kd$IK9XJhU_*B%L)3!aa81^+~cq$oxnZL0jwoXs0dbYk|X(40t zcJbM=x0S40B(NKQ{7ZcK&R+Pieq!F?d!Q^4-xleE%o*BOb9}O`x^C}qzTe-fMu+c9 zf0NZo_(a|nx=rQ`$B1^^WL;thR&l?E(QeDTA+!u>uht3O2c9OY+n_22MdOMoaqrnG zo+kiMChG+9>Z;;WhWSyV8lq3%iWtrThN}k zVo$L<`W#1IWn<73J~o?#PqnF74506e@|A9V9rrb_Pmeu0JFbQ~as$E?_sM#IPSixT zF>T+d+>f?=Iu-Kmw&6`OEAN$Q$4GS3sqr9!GGd-xgv`I79QR3HuL_vZ8S=f&g|C=qw|NbXI3|Z^ zq&!!24OS(eb0+2kiF4TJCq6ZYitu>yTgt(*w{ga|RroMi*KnEMSE5@IcUqa&M~h$uie!fbU_p2 z!MaglZXHv*jvSuqY0L|4Y>aRV^URBhK4Xcc+5OfH43sK4-29isZ@;m^3MWOK-(uMZ zs0R8YcFFuxHbFBli!}#>KcP_G_1D9wN1X)>3-K>b;RxLq=lM~TYmBE!Pn+gF-hP>1 z$ihbpMmDXd>(v2SqqJCRuf??}Mvwji4SSAvpl^C2Ck``(%u)Pc0G5`L>YE-+RoDO) zYM=G?mE1l+zU1)jFYUEQXyB#VjS9tRVV_*5%xZ54djh;Q!fgIT;H9?ErB#?1>>;zl ztFi}?k$l3w&h(22ezuiLxDAfwabhZX7+pqxVWrY9_q@%R1%EkL6Wl$`yEJ*ICXybu zJx!bRx{!wuUL?s8=dxCHd#>ikh*fsFAyvq6W{tqOn8|g`hc*DUtEa#ItA^#x0G?@E z*tIXbRlowRYx}nApi=RpPR8AynVXy9YJ}{Pt=7g*K{lJ6g*K6=B+J8|XGMB6j zx8zp%&(wzVf!uXS=_L1^t%)-%cDR=0>w|{A*-&V(81I>oYAPzIt*7>fDIK6s#pu*r z^$SG0!Uz3Phx!$EwrOA_(?fcxvS$P9M%76OrVRFTt}AZP)|5$bTPM7gimc|*5SPCupE!2ODt37Q8Rg+$x;z)8t}Hl~4OfrMhu|;0Dho;YbaePe z{}a>%@M)I^2ctLqDZT3jXQ?COSMN9_(go(vT0-bqYZ`PSP9_i#=G~ItMZ~8wZ>k zR8fDA1}eVRxY_r^o5y2kb}rOh^A*XGMkX@3+)f}6-vDbVujnEHncQb>?U{&=E!?OP z0cs9C_+GlMw6r+YtmXnlCNbZ&3(8pT(I24yIDwjkzn_*qf8FEi29yaleVmBFKopf!9SAoFK4GJ=NQ1E9$xTJ*u%+dhrS>J!R zYx-Z#woAnqV8A5al|h#8z!!ds?h&QYVKU3DO;qRH1ZdkCA6GlAnVPx_#femj(= zHmC!ad`>3T2VSFWaFBQt7V`5Ib@RWutx(I$iyv9Qt98q40}@bZpSSRHH4)zNC=Ll! z+;|lt&?h~;or=}+iFT|MC5k%PU9F(PM-jc%4Ne zy@b0Zs-)F`K#uqRufN*Kh~2*Gc}w15o#M~y6Qxz%v`j<|h}ujS!HK*MZprMI3I_;N zHxT%~p3dRf36yB7Izu(~dY;XxRp?s((D^YGz(8#R_zlZ)yNUgqa-@_l1Edx8gF~E4 zc_!s=`R`FT=@-ou_c;&n>W8kC?X<|5?is#u2^ZRHS1WtX^HFyUT(`3e=OZ7Dj<^VZ ztmNK7DrX*8hzdhiPu;Z@lB^avjUj?=Pba>KHANQCFl+@O^v=LhY0yQ&Qk!N%UI><@ z?H5E`K=JQgj&S_5aM4c-#|>DaPdAR^PLGGNy1@m#r?mzaYDCr1E-?|vsE0RUfy3I! zF$XlUyif2sF% zrqEegoz4lf^!KB~1sw`Af~&aDHeVFn!bs4M;%8<$@9#WeUIvxS6ffl4{+gD2gY#a6 z=p-6&hu7o@bQe;Kk*}b|0b55L+~&U`(5Ayd4tXXi#RlOAn67xiHjv6XvCWQUkz_)H zZE*rSP_b4GgA3;3Z6OjMaa#AbN%y?dSm`ppK7PBj#&zKov7_rlAQ&+*X?tl*n~{1Y z2=e0%6!VLJ_2GlEy|9>3D@E2Pvw}zZ>1$=<_TNUahwu7GUR}sZOg9j;=BTMD<)Skd z@|`T-K*il#Za{3Ww?8M_jSX{tH$Jd%V3$jbW7v=p@kY1L5~ps;72s(xcBSzX9U%Q> zWLf~r7Nn3ijWU}@PcqsG^Id`ioL2xD%U>TX6Yb&xN=rU}NdYLEm12#$fj6I)7BLPM z4^_6MEtlZgya16)m|>M=o%8l`gH6pT#`fw*vXS8L?&RjHzs9L*^%H40P@|w8@MhK< zH|>H5HArArkz?Suj!03=XC=k-qZaz6f~RgDLjf2Td@B(|AEWZ)4> zUaqZ*;*+Bm$;rMuRYW1xVZzWMAuzft*`u=prp<)_1-moY=xTjiC;4|hxBS!Az}5Q{ zb8qX^avMET6InCK_5ENXGeVPAVq+A1J<0xQrtS4m^Kc2>^GmBH9V7lU7hY5Cxa@}} z8aWA=ss)O4o%7i<49rnAK|%!)d4&(I;(qW5>dqJnItmNzd9$h%ph87X&}F#4eLl|x zsDX<6ko-NM?T?YbgOe!*VnQpKG~-}$sgSzZ+KQQO^Qee0gKQ}o8dU))K8{dR6Lj+d z4=6|K0t`qc5bhW`k(P{Nt^F>+k0<_SA$N|HEQV0bF76N9C9`YnR{fUeO$c;c-Nh68 zLY~ElWN$yO&((LxkbO=ojbVTE)TEbT)wL7ruhj`_ZELROnJ9(n|6cFybjPTGgT132 z{dIsquvkK?-^c>Q0Ib*I>2#(khaxf$Ibcx?&|ey%TPN^n;Q29u%ZGh`LvA(VjqCE@{FfF$tADy*NjIWTD8RCkwP?fQ)2~A^ZccTLZE%24y3Mra&V~4X zy-g&R&H%MLAm_cHD$g^$@gdSPEt<)hJ{n>t7ZDmX{m-;fu;JP zylyiWDk5&<`cIqF*Lr29nC4!9ys5r9Wj?Nt|1hW5%Fv!h^pFJ$TqD*i@x0T@(uwi% zptEBflsse>h3Ygo2^4yQ_z(OrQIMT6zmfs#1_Df$pG5=ls&#)SNr94;wZ&d;RedHDH$1k~hGO zpv*wE&|!AC*>Q|rFDlDGEt;io-y6|?5;b?aXN?y{IclI~UaR>@;z1uLUM3b%Xcn7s z6Sl*qNU2t@?XX%6V@tk&)p46Q&>_50f0}kL`Yh&%nqY%|>?^ZQH|HVe{g`Dc-o$vjX1AG`f#R>t2wKY1 z3=0&*AeR7o8Zm;&u*ZLJa=0ow{5z@C*$xNA)OEH_+kR=XGIN8-#x zZ$OtaGNb4jw52fQ%#zPiH$YDkclBV#ZCUque*=zxE6* zk%5MrtEFM=)0?oQx_;=)s|x!uX3vvdYQ_hzGv}%mc%~&6G!~X!a-J9sz3gP;YR)}+ zd)Rjw19@kJM|WNnMS+Rma>VmT)SUHyfdS_NM2KnI)GyZ{P+y?fk0E{qJ_3wmIgX6- zH`m*=%?xPf09k`(>(;a%c~kh_ziH2T#7vWq!*4ORqARe)U_cX%My~Go?W{=X4;`m` z03`06^h4tK$9vVX?j*bK z1<%kHX%fHmJ3b0&pTe+ zz>6(|20pzx_1+&-{3vcH{4|krpHcLvXSLUIg`_38Y}OAsr1+uY6);ooyDgN|{+*cX zWaQ-;xVjCI#(Ed1a7HQRkIx$tH3+_H%Kb;wvQiH1xYb4w(lcYc@`QKSMGUzBED3;S zvuwDlz0CN&}g}V#B!Qnj9TgF%WNFUS=Gn@)FanWgyj1 zy#s1Byf+HPkF=4*JoTY8?P$peBXDuv3QZ>JJ0iG8HN9H}`y?)jS@?}HJiwi{rgUD+ zsGXma*5p`){RUb7?vBZ(Zum-yHF&GYl8AEnMft`P76=7hWhXl3TuNkcDg|tQ|I$+=={6YHdVby)9qRbAnL%=qP0zXZ9 zt3co4_heBukPndF#}SRrWk_H1gl>!>(pjp{siwohXK=GaP^FFT#58Fyp3+9CIKWyr zq|B5HMe;L??e9tzUdc2!F&6U*Kui@%Vm`4f*dwaEAYT1;E(FXyv#8pm&Z& zUVD_4_O5Tfx6Z~sBh%{kBbKOqQs@cvtFXCdu?aV)MwZm0ICi}bWiz!Mwa-hZ&dbXf zymYJ_)&HYad6&h>qBgU=YP1bidub7`=|IDAJ%e*^u$b>vj!3owtXQC6RD-59aEca_Kpr~Sf4@4&V3yJzF>I%ZPjXxDg}s*|>YyIGKn zPmsSB4+J}Zk7g9vVctyfwlkK2*fu$No-^?RkIk}ci4N<>VzxD9Ria8+HvT}H#-J-Ptu!f*3&?ZL2?F^5?!+nQpRq2v+z zrL?0J%x^ZQ7J(w`*Oh*^5p(sw$?i?zAkooiY7v0KAr;B;)iWj6(c4dM32A5A=x~ST zGy2o7_>`OWp7&wj+(ZJBSDnBHeTC(0dVk})s|2XZQ;2D*Cxc#yOH;9nQKrr!Z5{(j$$qyOJ>k6Jso^)f0KR2cw!m>|Y_ z(BcU_obQifOI^wStA7XxgwJlSereLN=0}b%h5uW5>tkK@PtRI^{cjb&XR*M!>&Qcd zJvpY}%G^K8NSFUx()&qu{Qqrb^8b(m>wmZL->Ur&?*IQ=&4(Yz4*1(%>Bohd|Ng@d z3H(3Hq_O2S~)=OI6yCYN^O z8csZ`p12CSL8`t(3rij8*T#OGV^#+ZD6nE&IwNRW3{Bwo9z6Bqe&-dV8djA zot5L+bi7VKfuK^zDXn!IWEQp?2d$9mJK9+8;?hm0zzun%nspQoT5p2C>@zlM{J1}K zPz8On{*TguG5Yisbm`$(;IUC{ZX~@Rnx{Y)0d-L0{K~6|eifDXG>=E{fnI$qTgNMb z$uDQ>&;MSIWkf)5wgAHm@^n6C8B^So``L=d@|KzjW$T}f?uDEB5k|^3k|$3|OZ6=@ z9(CFNKxJVm99T`xUJ6H+5Nb-~B*tF4=Vgg527ZIN>7V!Wk{+xow316QvP;q1mYYgP zR{#a29V3?m23|j!NsKtQ3Cr*d?GjdAVQdv#ANanf&wqY?Mbss>w`k?2=liEl0D0V1siMB2UkyGYO3{&{7V@NoT=@@86tOK96YLbtrs zP1iGU*Gwp@3OrO!7w9sg5R@Y`-*-E?{!i*%#K9GX^6==#-Oo2mecUv<5)jp{ntL&3 zV=kSnr%iv^*xcs5H+yyk%7hqwULjTO?(#*4XyZjmW0A9&eAn(u&Ne^{uYVrTN0*^@ zt4l6_n|8N}Gm+i;orR)<4rfn1Fq@~91 z1>LF<8l}4)uLFeuM`b{6tW?2C zK=`4rS4o97RMxv9(9PJL!>NW8PR*;Jzl!|!50PhY4wJeWpr}yUS}aFB`tAR@uw@it z_q`#+SStPBmWy%gf-nFa8;0v-XDYE|LY_IETNq&9QU=`zHn9dK9}uDrqiPm z&Gk)UirYTD_a#}7IaZ?FjQD)eh_s}0WFbOSV(=9Uns1bl-|kB^l6{(7GNmdYVCQ1- zng2=X11U3rvOH7yH?0l-wjjJ$VRB(TuA}wKYaGoY$#mkKWMD&6-Pai;ZNocNB!icb zTp=p(7}Xx$pHjuAkv{3y_Q^>=-7mBzPYgcR7;{B-h*pWTX|4ZBp93tz0oC}J^KUw8 zJQ4-5NH(46W!e8$$a!$jQy)1+ZX>tjqSc*cT zAD4QSFY&zm=p_2J5~q=XOByPL%-;4$UC^+rV-b@6!KgUF-*6gRGhM@CgUHe>rTu`k zW5js+v^#cs^MXDM;ThCh6a19>VKB?{d;E5!4He_MUT$-vs#xse%Z zkY-NQOh~Il6p0g;s2>O$n@ETl{w!FPSHijl5Og?zTXLaMIJu3@IND)lrCn;#{bY$l z(4;z&*PZT(ypX;4RZ?Iu6Dz4USbC+9*nkxWgXgQc<$+rAnZ0Ls1E-)HsHL8YFwyxLS|otTXBYnhHEqOr#KlWq_hz?YTBnwqr)RUj&Uy@JjU zp6|N|;)6*0Wh51D2KpA!$>z-S`r`9}yZgd4pf&!F`gR`cOV423hkI_v6*^wk5`^gs z-YGofu8j>Ya%&_ckmT)Ks8sg^C%- z0nP{8So{Um7XN)f1Ln?m!wTBLj}4k3nMw3X4>A0OUdwRNltSGFCx^bprH2Q?82eo# zcBCa=nXRTXg|kmr_19uMd7jv5Cf`VDXztXWdZR$u6jS;qi4#mbKDIw@|#GAsDLR-jIF^GE!$Q73}2oOUz7H*vNDwLi0DQn^3|xH(AVgQEle z4MkHNhMtE%dOqak3TmpU7=~O5j9nnL#FPPXZ+YYHT*v53dk98>w{yv2%E%_3i2U8J z_C>ZvBmn9?hfnv6TkV!F+nV6rRO+dM!HZe4(F`(lIgo~DvvOaPt*Zjvjr2!?pdLG&I0pnu3i6*4zewxpvuT2 zQQgxZt$-@d%*ew|!nqi}4Ds-dSe_-4ORPBb0Ji#kH$8_xR$(^@15Yr3JN&68>?h)R zaO{FJihnizhaER4hqKF2QcS{0bG))C_nWjBAK5S{!S_Xyq4Jh)l7*2k+Ntt@V|ugN zH6Kq{R%+v3S!O>F!tC+Kv;GccUioeW?)pWz@pG&OPp>K!1RA_G7;Se>P}ft1?pN~m z9bT1i(Z;OCAb4~=s`SQqx>9uR#8id&ixu_YC*SRu+<+4=`26qjzp~<0d$8U)6HQJrk9NqJ8|+ z!lx0^TLap@tjNJM>md2qm(!J3E`Mxt*% z8g3J4%7ETD4Q_r)c@y$2*cmxtM_H&Nzq*L5A`V@OnOQuWkBJRH(qcr$NUEfeUDaCc z4F{~IGh||p)fEjq85h|wroY*he9(_l#PY8wVhKLZ=|wu@CSL@dv#7Zeql zSNN9a$DKxwRob?<9zSa~c1M365(w)I845ne3i-Amb@RgyeAj)7mFW4Xk05+So7qtb z755AaYpu|i1~!mu$E#&2EkXVdbPj_%t)nrf_vb?jfzCMqVHWV|E^&7p8QwSM`y{;} zQ|@w3`L{1 zMXNUs)eO{~G(X~>-{nn+!jzL3#QE}Da~JOD?V5dBtZ%}#h0uz5DG_c?F<&uaPK$8o z1D;fom|0%aupyAMxWhK&UV3+Y5n8^kzq>v!BZ$`ZOQqoW(Naoq8h zSpRr$vXO@?t|Kp5!47Zf{7<(2t@y#&<9~cw@wm5WLvz!qBBG!j=`>+*hMwCVGr>dx zEy}a2eK@{(#YhGe7{+4gtfRcRxvCf`fUf`Wex2?v*!CDxZsS8udY(QEQVRtG=L@nA z{Cus<`X_<`PF4~3wouR$D1jrRfa^rB3VEgU=tsrkPZ;}_5T4oG3GYEn%orc&Pw7_s zt9G@EmRK=Gr72rzYT{Y7V$4#1BC;1$6z&2H__GyVM^Y!|&rib@@n8?MuYuR&#i=+@ zdC--*Q0LFdM+AArb$!JTrM)~uX)&f%jx6A94y@t=m_y{OSs0O1oSwb-_)nLNZLZhhfP3nD2P=E95rWj8qcWc7yB*-(-&UO> z8uF#SO)vINjpYTZ6vrg>*(0Woq$M9b>L#c%*1lF$`7|f<_=&81Oh^!ZAzP)?m3R5^ zAY9_RTSVL#O{ewiwth%X=)JNCiFFLh0$|shp z3{*N%W~ogrSUz{uR*ayP*Q={jHnJ7YjyTe%qY)wJ{dy>^#bV!ZT5amvWD@?yM|bku z6vJl5l>3@ewu941)gzFmJucaQB1jsA09|aT%dz#3XsSWo3?rOWGnPD+G|RgzP+;U% zN%r)OuVz=vY>~#akv_}PO|LqO?i4lrN=_lilM~1T!KVdbvj-QVh|6V%!S!B#HUQKV zoe4a+X;-am<;-e1jJzie2OpU3lao1i@cBvdOF_zt3&6mpVeb zc=GAcJ}t@p4-0w&FSUOceJZcm>KXCq1-26I02wP)3IsRpTB~Vh`Q>o5j?1OhMH79` z=0-`lZrrgvM#JZlz}KmIY02l_#)uQH908qKMO_m;1@wP=*O+NFjp|Z{uRUymv<6fc z^=Yi^i@(mn1*<7>G>Xis`~zYFqUeL%bLua8=5<>H*}+Y4f6oZX$QzHToH&HjlbQzM zU;^7SWe{Ht7lNFds!3s^+mpw4^%~n;A8pk@^$aK@#yi4Sgox*659P?RSz~^RKVNfjO zJQZ(9M@^6R?;Wn=A*3r@K`()!mERAI&z(aqqOkVH_7gnaqf<=&rPY7<)*B7UyYP_C zx2A*zVoLL<#1Ai8gKWVYPA9{fMxb8IvrjIscaFRIt&gq9c4R*e>5#mgc5C~`<9gAZ zTz@xlvtP|IdE|jZz}#vtPH6Y=-P$2WJ{w$41~prQT+~S%;$q=>C){YLb)xX#KEqXy2!z#|z`5w%DY+xorT=?~Q@I z^5GP8%BBN9jaa$aV!nF3M14b?YM6^(-e-X-{MEFHoaRf5v?rik<>sb8a1)+)fN-2f!hVE z)1F#rzfBvWo>s ztT+0aPBmf{K{q+adH;D%#-drwnq?lq?3&$aMTWPuq~^i3?~cR==&$s=`*g0oTDyXV z$h$T5xt*Bqil1tl;_D-6mvu{GZfooJo9hKMqZoOeRWXNW88RlH`sE8q8H^Jq^7^3G zmQjJ~jZ^f>`tem%>-i^yJY>n?u0tZUZo%$+q>~ZfCLAmfoyvqR)=sH|Mzz1! z%o_!58H6b0w@A*90Z;V|^oNh9lgnMDUt*4rM)DmE@2FU-(O3-Ft*P<#UO(h>QvEOT z8MScXuXSmJ46dXCLaGBMk(RPIdPNKZQ~3Oj$YUa}1Dn4`&Do%^?W>=Wy`CSfiiBXZ zLJ3;K;D1xf*tfS}*_P_&$(^bbx>0LNm29(y!n1)-jWGln4w1k*lnP#r2^yRc0SBK54|r!oa#5--wG^Tn0id{kAgtR%>%jk&FP^PHixnsJn|o0wq*7<+;__YcX@ zxkbrSf8;w%slQFMdKPX`fogQ|m3>36`*y={6AKf2ld)`$*|ufMV!c9|Ieu(4&=@4n zukBW|4_BYPv&ZLBKpnm1*3?BXf~!$9x^@Uf8GfJ=+NVpsdibh$1Y|Z+S1;$*R3SBl zHfFNamN12t#!Ouo&j>E1Y=Av(SAt8DuX^`=T3g!fHlBxn# z=cn<*%pg;FT4st60S>e^i_Rc1C}H`qfu|JBG?i~5c=Paiyz%JZvh>1hW+Z-VN;hDa z{4_rfetnN@yNVHq<4L$wFQu~>XTSh=r>LaLrfy5EqCdzyP)shF|A(;lf|6WsX{CG4 zNwpb!7@mu_3+>9fVFo-P?Ey1cMLma5Wix8K;pTy|q?EnaRQ%P9`@i6!6a0fmr<5k? z=6d!~)?~|E2Q?+lq_NRrLJi;(Tc1w?&IOE5jab&9JtSa+FCkMUqN^(hugvE+Gg5uj zjD1BPNcf5&$=f*-BWg79BuVJLy0og@#~+2}-PSCN%Ym0qM_hCa@AgNDR@|J4S`z_W zBUOuv)!9spS#Tghg0z3d-1QWw3RL^JHR5|8R1^f89A1E~XN#QU>i#{|?ixttcgA#uD_pOIG3RAf?Q zVPu=H^zb>j>AgIPV7XZ_ZlmTLveHRv92#}ocS~)+>VU%$WUB$WIbi?A`vc8d;~m^h3wE;2_ktIUVy@8Y3;ts2;r6kn70W6oh_Tqt2cfjVUnH0x@X~DKmHft#+ zK&RYPYk*3qtt;g{o$S^O^u6sXnUBm6EKCE zF(r+OeBP&ugYh4;t7>A}KCLcw7>xA8iKrB0cQ^rOMNEC;4lv+)F(GI70C54XR@2&p zY!9!SLtZs@TN*AiWQcWW!5>+bH;H=8MwOekm{~Vt+;0#`6>IPhd8!d$GVBf`;ZOh=n zXOf4#GNGdGUdE@&u@4ny!d8a_L(L6d4QiOO;_9z>JkD`}yW~96krOFS#!tH+xiz(& zAu93Lf*`>)Y;H@4%88}evzr7_kOdwz=aF{hjKqFdFMI1P7*n@cY(^D=Bzv+Vs6;Os zbT%KBl^5vktSmUC$5*Pc8|lR*csAiibw_aFOJB`=kdnM&yZ?|CHC|gubrg*)2j_6l zLgnV1u&>^zEPwQ)z$HN2m3G~;2I+?_Rb^gES^rj(T*R@`|6r*(mbAB2BV5Q!W{e9N@}DlXIl1U$5Ye%L;5d<48m9Am}iGd`Vi#URQV9~-KQBOKU8X5 zu$tf@&CGeHUR~$Jz@pY=@%GD3jvJ-kGPe!nQSRR!p9kFy4`w!p^+hQKTzCS)JOM?G z^|Xk+gG9fche7fk)Qka`>IlboLvK!a0pbof)_L8wVd8%TUH5Q%+_2kyN3!#YQcf{H z752_H#7OW~P93XDqyd6n59*ntJj*b2m+V3odR+{oh}&gHvnQ2}Pz|AT8$fCgCy zJuGkr;)HPQ1zyP`Q#js_X9G$~2v*k-zx0HG1G9`g6MMy}wX@V;^c-9S-FVOTK{y39 z6eHlzX(RyS8IXmC>W1IN`7DE2n(jyysl{kicJi1CsilOBA-vTNcBNj&(c$z;y%gQ2 zaFJDK+#C1db5Ofmu*i8nB~i3%jxby5O+`qnJk^Go*PSk1|8K`L=fctY=iJAdbe(><|^JH)1nNmw#H%Pz| zAsB!#=iKN=RhbEXhzBZ>;6S7@{2V{((nyCA&A;nWj@WGKsfq4xs}s zyH*~bY|5Xb6;TY`9!4g6ZmWu!LPeNiO?;%D)%?U7bBvMF%8bYfbZRMWCf4g4PlQ0h z-#7*>^mK)J#xpZ4F;402Lx_BQY%2z5yr%T|a3|FrBa->xrULJn2m!|BZJ^=(4HlTB z>^bhDi5p@wBNM9%U^tc)$L=?P4?H77bHa}D|K<)ulMPHcJNkHB7-IShirB|ax5W3P*nxH z(l#Y-HZIjZGQGRW*pw`-8jUSwxynG?k{GnLo4oWf<#ycn&Y(-G>I2bHV2)88CsDEAqc4iQMymRUX+T$JCAlb(f5G8a`Wf{WA2ag_)Qz?Y2l8z%=XNUY zP;W7iWW1*>KGoAHGBK9_nY;zOQi%-P7t zF_~%sV(ixFH|tmSO_rY2S>&qisqio^o|&k zIO4Qjx9d+;&EA|^#(Z$=_j6=SCw?G2z4v)cDYE2cZ(ZRscY!9#WOx0O z;M}hI8|xb{Eq3U_oR1in+D>J4fbc-fyNuHROs4fKo&69#>_dc!1ER~t=oF;v*}<5r znwwRoC#!)f)Aqn7d)iz3i^*wvkb*qH#xsPgH8bTU3kK}UiAartb62LPiyfJWnbchvm0WjNPJYzjH)pR+k>t00wZ2^YkA}J>3PA z1?(3#0knrE%kTBB9|Gzeje^)e7@SFp(9~r{o>r$4i*^ebusD#g0w&lXe$);T-2M@% zb>N0s@b?#sq{5d=Acs>fi}lqdDa)SWr=ajIb6p3V4fQDhpba0l0%BNz4*&#+D~tao zhS(emr+YJ@`q#3Q%;4SM(b||~5lpo)TLMuDtzrN!d_ek>upu2HS3N-+&snn}Af#W3 z+M8e3`|H1byNzokoF>Y!`v;)B{@H%OZB23r0A;9tcK(-{F6N27_vQtUmk7lS&!bcz zfDR=%biWSkn>+O#n$zf$l84%r2Y#a>JC{T0WgIglQ_O?S?2$ZLF52OD^;Idv^mkp} zAMWLxM1m|=70Q5UTGHZ>tj+v;6?f&e1VHVV2;X%;`QI$BZXbn%l+aI8mqJo}Nu|p+ zh##dRRWq;Y?V@sfyg28$U}}ehxYJ%sDYbT#F6mFWs(ue0BF?8~oN>E;636TihjV$* zrOR}F@%7rF@3gLnmDHzlz~oLzm(p1&>vSN4gg#ONXh%ub|2p;k06mRl`lvKi$oVIq z_x-Tw_W&A{>A#69*wjq-AL@}Sj+_KQRTUuIb29(7H|%MgEC?Gv|0f;$KY7{C5u!=!y*H~9L!f}j ztVp(ccUGn94KKHI&|kMI5Q zp6lJu54;keJY~!=@6qltC$?@+GG#GXsO29$J7O=zsG#rq#+;f*D;Mxk^`LdP;Ja{X zW)-*o`4U#*I2zQ(F~14R0lh%28T)UFJ>PYfV&-*x!#BG$2HP!|_#(VKF+6@P)z*&- za(UxJw|8p{!1Jj2P`QkE^ok6Gre6wWahn9j;GZtH#j#8z)8uqfubCXwPwL}CtFW)J zfTAXkSs_Jg#)-DLKGo(ome^#6H3$TTwK9V>_+aX6sMoZr|M966ww>Z__m*u=8!jKc zb>L%*ZszXOt$w#!&toI|^U&R|2eUvo%^qXB<~k*iV@#f|2#mO?1$BEBwvt~@U(2sM zcG628Q;b@>?Q1_7N``Mk`WXqUmB$Wa3;p-N2NgZLjnb`a9sp7CpSLTiD@gIHok3RfIA0Yp` zaWcHf+N0&{T~waJ+hYUGO~c6z_pWHhrD~HGh@;x+uTiTD1A#gMa;*%tWAgpUoz2=z z5`t@94qjMnEDr}A+jUFvu=!g>VI_jLrJ^AM*S_|u+Xi>z6DJ(0Rq=4*+-&vioJL|; z<>L2G@x_)7VX{?> zPM@Z97EhwT;9Sfc4zJ(t${(xjg)J%>x81=;>v!=P3|L97PAI+Ms^^l1sC=|zodS+< z`Bjq@{&6SvdWex%Ei<=C+u0`|g$Opc1l6rqzDBCcA?HD58gnpYs$9HuLfoQy&SuDO zb@eU?MBvbxrN0T1qI1(<#IuWoydZ&Kbz>KifzpgqX63=eJ&#Ls678~O&yPrK} zjx;%Y_F;Ipgb^aCFCiiPk)FOK~pTBOM*YRs!(+DQ5Wf2f8e<{?MRI`H4p-CCoYYY}gU3 zm$yX7?5qm9#g;m3`?ou_ZLB|6 zG{u_xG@fV)ZJrFe9E2teY?p5MOCRLdZ?L)8{Dwr;^66BIxwXbk$U^m+Z^s@%;2y5t zD*S=&e+|jGwN+FgkPDmtcBQ|J^5y-z)|46~hW=PvvIKaOw^t*TqpG*v69Hjo^?0^* zeSm;gfePX3@&1&=+M59xA6#qRhCmK&T-W4{cqvegUOkTPUROGe@83`MUTq5zvvR;^ zC$CJRWuQD}g3#yjaMoD`uz+tI&sB;Vp*+mcSchf@^gf6(5zssAb<~;Eq`h`M2tsA` zHtV0mvv#k((E1nGh5mB_TNeM3!>UI7pOeF7cZ~XtboU9r!!DG9+AHX}Esz~2DQW87 zp0Pge!2_cCWrw|(=}v9H$nEFxG}bpJ}7{YxBg0dP>L~0`d>H^7(P0qkazd9qg4WpiZAruGz^pb>Xmex>dB!qWyz%h=Ap-NkeSI z+l41pK_b#I9FQEuyZYt>_;+*XQJic<$rNA34P92A*!Ev)0(DZwi(^Gal~zKa6^{K_ zF1#xq{=GFrFhZ=P1Z@4U3%^x#DZ9EY7a-aY&|XV_MseRS5g*u23QO4TY|+ z$)u+Mt}+Ez(P>BIdcx&+&&JDLXRr-N021jcZe77jfh~XCzi{pT;G*HzcCEnRy43>7 zrpw#I%@1gaXR|NbBC&#Sf7z<#Hy`*Qxu=7*jo1L3qPK##?~ksb9P@mnEW{#qCNzzW zt;Zu*pfvo-8s^fIx7b4Rghm%t*|2$i%PLBKT7%R7i(go`-$Om@8v0X2e~A{qToa$D zzwh}a4RoDVB`feOP^K$eiru`99@Ln8{YI8u@fD21ei~rNx-u__F={$bYe%9i1ige% zoVq@%R_h-|;-X~$;0#&fax5@pnXV1pz`Qv&7vyArwW z;uOcIYB2lG+sxrv2~B~MfLF7Z;ZaVCxXRFr5;b*~w+(Ks!|yW?OJ!Hm+yNuhJdPg| zGG<-3OlVDo3UWTH{&FpUdKdX?AZ#?PII=eE0!8FWE4Ms`si*95hU}e*`RC~+^mz}q*5njK2mbv5 zc(pR((u_KkRrDY#6?^j(z_0|dN=^6>sE zyoA}VEVlpO?x^Uqc%QJYaPDPpolGuo-2SRt^k{{56-AnPfXfkuj zt(N_?CLY>4Cpr#bX#MW7N{5|oJvGWb%zNGqQ}(`fn11C(7;bPaD6*OP$*s}~>bfac zWxfu;8qgJ{DhNEoXSc^Yr3adgv?w2ww!;F==X$myL^aCU6zz(Q)|si^ zKZT?}Wszz=r+M3@#>Jbjpkl-1-STC=Y&LBtvFPT4!r{GGm8W+o;D( zpz%eUi;IHY7i~NN?W?z*L>>kOap`(^C{s3Z5mh_SxEjhx_kXKdKV(TXi^a$7ggpSkZX%^FP8)ssaFXA z&YF`#S$*Sds(3KqmF>gC^^kwn9llm_4f{4GU=Nh`AxgeADZ|GE4G6;lOX~E+ppm_I z>X(?cvlHDbNImxM>h;4#gCiJDk-n4wz^FvOZ1F|dg%%h?tIU?*TNkwfzP~w^k;-Q5 zJ$!SE+q>~mbjfqumV$9bn@^!ynCf~PK$zrdWD^%CWH|?xFT(SxXEi^`MXQx(_XGdn z^uZ886n2s^ZmBtCrJw}w?_TIt_h1ABPt{|uY6mQmu$E#6o)VjH$y$p^>&&rd<1v-b zA*#f~1C${)f4In#ST*f8CP=pLP@emI-iz0f>XXpQT|U6PScK(BUVe9M=dRzu6d27P z$*5&y4;ge4$fCD9-8PETUjAwEEtrnycYdOR2UMjjY;Wa#D2WJ4*%h~9ddMRrqk@=t zPzy4Ob&&`GEY@BNrLS5wgZ=JwK3hH-%)22hF|29-8Q~V~MQQ!gOtWH?qk3=vf-*nGzZ-hE(U z0grj@4tl>u2LB7kAQs8UrWf7oiWi;N>ZSWoM;%1~v!f1h)2o%L-0`uA6+gc456BER zP;e6rOz}nKmtWZ&g=6xX^d*Y^DG@NflAJHteF-G&$?8cm^Lta1k5tt(-Buk2cLa>y0fghErQ?AhtG~Se|iXYuo(P33J0SCUdmZQcg!K3Ls(M$^f zH_;Q+fiL9^42ksQ=&7?9=;rr}bD^{N<7u+dtZ)fL2w-EE#nz2|TeQkHe|+-P(4sbV z`Aoxxh<^dQOK1B+5!sg2mhSwG8{b;(R_1}0_g^751$qud^hYxX!4PIMEzrI0wZL(7 zt=@=t*ng_o@-cHXecrOZdA~Y)#AuS7#Wj5};-^H^E0JBp(m(wFKoF)IQKo$Yu$x1E zbw&IAFEn(LI}J6B)N64Rd%#!)U3X1^Qv0iYIokB2M6L0t)ca13eyzSC)^{SPm2Xfd zLychcp#UjHa^2K?=xhrNCe}p68tJ*<2lphKW!T;qINqxwHD50#KtdxdI=^@LwAG{dZBDnpU61Ux67~New%xWe|Cn&ohl!>W zy|6c{$T+V#(>xBi8Y&v0)CYLL$|NfD!5?c4*EMApJ$9BvL><@~>roUuc75&(!a%68 zo(n++CJT@l z-9AL#y}w6mCWmAL_tdt}SK9b(y3YtTPeU=)tCo|hY_E9cIS4Z*DsdmtVOPuWsxaWh zWKT4s$WP>J`{x8e0};akHfxx#qb82B4dUo8-g@r9avv<)-X`~M5Y#7;SD^FcoWD91mPBZ=(J zTD<;c1e}F?UVY`(lIK*vVr$jwXCYKxOG`|a-J;s(hk1*0-n&G|CB6=mlTc0baZA|J zlFRW7TlK@`fVcXvV(7ibeo|;iHGfA?MPThuMjh5j{r7NrbQ3&RGh)HSX(v5kxaeAU~R|<17Z>sU z_Wr4!r0XAx$~SfA0aN!X2T2t)>^OH)J?p1~pKijfnm2xF<}jn~T;t*#q7Mcysxg(Q z>VEAPjXHuM4;6uCgY?hXu*KP!d810>MW)bI{eC0INWiLgfS)g7jwTatFj*&tf^KO` zLEVxpz2n}i66F#&>Q^(Se~;kv_3|F-Di7XSteP@a%hKgL6RVui?@RxB)wyROK;6M2 zTNiD6L2w@wW1O8+vM$QUQwUo@Y@%g!j1DOy4s~Ja#zLrPwz)pe%I6Zb%8~8g)cY(d zSnxH1eRPU|;BbD&Q2~o(_sO8cYf$leHu=&{Ck>9+E5~zv3Th^|)M?+SPWIy2tyj;L z^>%tJrM$1K+5@&K!z(`OVokff^Oiv0*hK{*?Axuw<{6ffs9)C0z4d=I-n*|$E8-uZ zOK8l6WS4!!&j>j0hGJ$~qeO?K>%fM4lGuI_KugoShs>Rtby9~eYn<|}?%%0lIUH~- z3FQ186U8+)_AT_b3RPHy4ktp(V((77DR%2BZzQw33k0I4IzJG#v;6W1lR@u1WS&A5 zZ_6CL@Gs&An%A;2@L7y)n&5M4p_E@!W^qRnI4-gK^X{NVt?+psWIje6sCX2I)~fmg zSOK{co354Cl80g1Q@ZHtrXdWcZ#?r7VA4I?{iwcr>lV;i#D?L9IIE+g*da!q3|Q8_ zZuj&M!GIP_q_xRMhq2vI=V}>N%8AfJu^z9sIg!UzYkSCF_|-X!i;i8#7^@A~g5Dd@ zRq{~B5~}SF~=o zbwm_Ziwz+j8O0-{QH6GK)+82B#ndAgDXeTk|J9IFulg<2#6Vxl6u}Cw9yadTq;oz% zYay_xh9K4OLZng5e}?MeNM&9Sf0a6Yu6+o?s8Bi0-+20BdfcYp1u)d_@=!U?C7p%O z+Y_mFYS-+inY4!!09g0Ogfi5hs5Ch&cTM#UMkb1yN^HLqwB294tEuo6w^KIoYJ1on zOFW>BE;|Ku$@u`%S?J)AM5<>KlzhX*{-PjH&>aE2D|doM1LbsDn>OO7xgPI!A7%?u z$LwLa&l*8O zXG3HEE#j+uR{TC93vlZ_JxipWhe$8#nUZ+^|NpFiXCMX5y?l zUU+#P{rxiVb)t(rZ@a64t`iR$+wOUgHd;&Ynv7qM=myC0irbTs2=bMNKn4dpLThX0 zZOsIyu7O!yKKyy=o~F8tv;ktJ)7nWtoy-50*adQd%k zZurUUmw9p;@`-u&PW^6Ll)=SWYj0HE6F-M)>^(z5Bn%;JDS2un&1Ycoi)z^MDmVF| zz}jEa8BSjf%lk!V7)wWUBJQ}lQNrD^+Dd1uJ|;I?~oUn-R>3{@f201Bg39-<>EL>Q6`^RwUaNxqeG3`xTIA|g)m-jlv?!^+hxQyVr zx{>!}V)oow_cz_%o1K`>_UMt65tU|(&(K^$o#wivTKbQM#~7vf0-hxDNXBqIZ*F03 z?KZ=j?=P>9P7?+lDH~gD{gs#VTnS5Ra?i#sCDElaDp1^m2~NnqwJKJ)w}eEh4|~s^ z#?~!po56b9DI<=Dn)wN zX_uCC@vm+V#S_dqED7>&wm5|$&*T-Bq#kxL&Jc=`&QfYP$-89FsH5?3p{3=bs*VWG z54@0%Ts|?Qm;(tcBAK*?yYCZLo;s6+LI~G-wRiR1ecKyMX z2U{A7|B}j@{?YFB%8&q!M%U7fL|kVX-C8wAlxi!&^N^TDnmAZfy@Sl@#1qEM9_inN z;4{|Vr6eepmF8uJIEN;xMVYzE=?@xVc2%MW;yrqv#t;I2 zMZd23V}rF$VGtRq^uaEh^1OyCV`{h{s(bSZrrTMR=(wf7E^2n7Y9Xuw|t9ER@ zSo*O7Ok#!_n@XY`gqhnQ;>uD9X6B~3N)4n@GWyVj*QCxLUw!7pJ^RoG84LD(-9s^x zU$k!skBd-I2W@4`WH%L)C|JWJd&x|7@4h>)p*qC71&VON%0nJ-(}6(N#bda8K6U2# ze9VkO=3rEXSA~yx8xQ2f3Z4KN59#VEledDLZ$dh^d)c?2ql%{D8|-6F9P;<{*E?B&SBE}R714Eud2whgYJ1e7?KfJeBZ`+VjwwBo%I&Q)CPM7$kU)8C)J2IshuUVu}W}MrtDHO9Ool~X$o*8f9{K-77G{WLt zNi&LNi!9$&%Q6R*)AJb_VXl{$vt&bKdYL@e0fI9meAimJSyydWLW>%ErwS*RO}6<8 z*LxaA>XL~v0ygGnhKDsQIDAfZ+4rm7ec@hfH_CD8=Z^-&1H$=jL+Y>U5u!5uy0Ev$ zKu}$9;jUdOl8=I=eJZm#eS}ub9*`OiCnoY4-A3FLm2fRd0j6tV)^e-A6I*x1^sa@6 zlDB!(Ofo7HN$FJco`7wgXrIM$667Xz97h74W>DN~hrFUj<~zB|`vj-rS&yc!NE-j{~Sa0sGBstQ#8LQ)1FVqm(sd-?Gqj3c9+xMr`~!!||U zels7NLoWTWqHZd#^k1UAtmut9&nNS65(D=@+T`pdpibC?Xu=rZO5XZWmh6HiI(oXn zVn}pfxuuF_Y|@-D#iXp0jZMLxlu1>IG5U6UuVn>%C8cFS^MV@dV?^^j+&_%I*o$FI zEG#=hiAR3gNM=e^@-d521FFTQO3?lA*dxlHCnT$@mKQy!!FD|Z?Me=k$Hx~)ep(}B zUEHTlwPDCFh%1m^yMH^7Lzh%s-H&_y=xUF_(_UgWHH*t^3cu8oFy7R~)4+@3Zu=Q*Dtu2br09~gu9!XX?1u;if7KA4Cd;p@Hc0b(p>30 z7w7@ibDnL``~4YL_kJu*ym?-2AWj?IFV}g=$|Uh9GboX(5$T=7y@T)N&75?$$a{5j zq;@ixbt57@=TKbjy|?NXHdw2{yX*AAOA^?KQmA8iV}g1y+-%>KD$#C`au<`ag!2Ri zVHqE+&cB#Dq2uUP4L>SS5tL zr#0fXLpvJj;+q^~(v+|EEtpAuoue$C^GD7#l7fm85qP~?8QY@b?~7^+UGY>U#|91Lu`$cjex=5y z^6w-U9mx3Bt~q4C*dDc+I`N3HrZ*$n{eJEdinaG^R|O14 zD9EvDG$pu6>oPXrPv}%75#aeyAHR4>zP)lkGdR1qbiws;W!!sg`CBxW6>}+iI?~8L zYDRj6BW~5r2y6a{@#pVvjC$jMmcoN!g}e9qNZp?voW?>S9?zmX4L4`n(Vsk)py z?l!q}{pY)FR7Iqy&&wkcYL0V#BY9_^L?Y33d#l8n@|y+wNi?6yneE3o{xIKQg(Kph zWTQu(<$la*eO?fa?yJr7)!aMNRa9;sm->p7d9-4iWRP%v!CWsaIIiMCj=3d<38(g?obyb5}gAfAmlxkVLjyB({momD>7yjDxH_ zFJMUNHt7D=pV$DA`ZN$PyRs3J=%#=FKA`ICyW?MIZ050XX3xr#wkiC3;r*|87<)g& z(Ne&hu&tc$WfYlfOb1>(fjzE#3b0nfL)+Z6YKA-AbQr5F5(uO*VjVfF9J-YBZUPQT zz0hP0@cU;`!eUVeEB&VgHy;7xl)w|5pN$I*o^XNkuxt3>u>Q-t2sn*J9(@OHYPxdM zuG}mZweWMo53@&LeXGWOHT9kU^1hkQIs3Omc+F1CS~ZwIGQWAEt8Qx(xIx;|Oo|a| z!cH}5tZ4OilAIfh)eL|tgeNVUmy6wT+K~B&&}3O9xtDQM!9IBA!U?uMHHz1Cce-ga z)K_RS;Pb<^7tHjVc|_#$GM_)PYiuK=^h~I+$Gh?|F}JCzHys9(Qko_j$94cp5#;;N zmpI>Gk8E7voS*pWo!c(0L)_};)xRUxw>A!otn1zS|FpHQOJ)5V_P?30_N|3zfB#n3 z;N+Tr{P%Ce!TE7t7q-$F#W*P@kw_*PwLO(_5Ht?%*m2hR5aLz83aXK2=+4gZ!a z56Ki63~~KbV?J*cLX?S0Xn`RLWF53lOMMZW);_1Eh|-v+{Tf)YuKs~6AQ z^$!C;O;wyT6o%+NSL$=~!r6ia8v%^L?a+>~s!XzXaF% zCH$!k$9h#YwW{3^p zdou4xnG^))A*1m}*$c5v>!D`P>} z`{#6BhS$pd!t>UZs<_0f5%S+4?!FeF%drpZ-G4du#DonHvwOh8uE3l6mW{=`LNIKK z8U#|j|F66NX5XfDL$QX1`{qJdsmM z&|uu#0r^MJyxT)tr3WYQ{w3f!Zb68ya}T3v#o%N*N=R%kBCnFvC8Yn7>A3KkRKQm6 zYfyg`tFrR+x)$q7jK`}utr`tJ_H+x6yB#j``9A`4hxPdq`r8i}yYJ{V(GIqN`>-Be zf1mI|55U%hy}{N-i+F7nWXdhWyx^snOz{?-H8?vSwT}5vw zBb?!Jtt(b_vCw0FV1%N>3Yi(5XNk-xvBR^Mz~I+crd#vQB!Bv)KziB5qs8Y%YQ+&B zOhOq>N3%MH9sKpHu_}GuE=ylM8POHNqtgqja9RUA`r6kF;TiU-N~@o12-`0Umyq|3c#kO=s!+ACL2M+pu68(4mW8 z!rjZIEmXFcLl??3iMSh`&?iOxeEHkxB@z+@&EHNr)15wYHOIF0e|@GUFTe>AeGX=6 z;?~Bwann8Q(Dj`Q6b+y zzD{6nm-*@X11&jQ*D{<#zma^8ELOE+Z$J{|eoU#rCOXqK9XV1oH&oGjy& zac#8Rt~o4T+&>sq_+&Jg^K|i^kQXY2qTbzJs zOQn8B<4zGRKABB-wPyRef4=Llo%@nmyBECaY-D@M za4Qep_;uFCVlKZbZTnjP0vcQI9XEKD%fS zB~sxSNNK%g;3MjATO1RzTuujkHPg4iKKH+Hr1zKvB`Fs&bA%h)gl3(R0*l{*?BSDJ zby6)G!PHGF-GD!mte>7iN8bu6rojzrD@PY|ki_rbdaiE38_iFh?+y7{B|A3L<1#v= zjqNA~#NyKKB{CoGVH$x!qT>osaW_dDTvxKXbXzE%_uof51VK(mV-^;i+GkgVXKJ z`n@g}vLNbWATR=Nl#9%o#LO%=AUK&AS^1ibm@XWeXwroz%GmDi_n&9BKc&gXAkxPL z3_dhe-V58qaeUTYnfXzz-F{x{kCrq=dG#9G_}8^S+b?5YX{#9dxYs`QsTyB6k(6)s zTm;3P8nBs}BsJ%7-0O-jqlV|S;x%k+t!2pD4c1x|qpHjPrAu=mBtJL!_PY)sWm)c8 zYj&_l)-utle|e{v^8RNeUohc(sY`d+V;Fa`L&&Q^RUPB`Tr1dulljua-!XW0B6&=D zZ$kd4&QY;kpYQaYa0b~)5Jb`EWF0T}7doZE;=yV~z_wmDHmVPI?|O|l$+l-57QHs> zv82uWSXL+@?B`|G@^A;mca>-MsAwooz9GsP9OQQ|C#5B#V45nS)b+{~YMEqv*8 z$)d->Xyoh7ty*v61(N-Yb&~T=Hv3F)^|<$xD%#LS>N7WbFuw^>1I?e#63SKFJGFQU zq6Y@xe|BNUUNl5FLNZB3Ak7F8(DRyw$`yx|_!G?EnRUI_pEo3+TuV8%<(Ckf%xX-r z_WrFO?lG!`eef^c2G3#Owp2Ta#YzVOG8h)~|09+Zh<()Pt)Pf$9KvC2e-lpY@}jRtVCLz$oc9be7p-b3A{J zWY!48c8L%M_>8P{{mHDj;PikXlXNuG;8-`<*Yrn6GAs9%8g?mA6Tk&1HszomovaQ2?m_Jr{bWZiFhC~mdd!#zllr&AyJJbDJ@29lh6zgI5QIad8(*LfB zJ;PBLy_vR@fJA<^=sJ!WE%TTsoJAzPLgyyS8FM%e+XXuxg8}e-tId)1ZMh?3UO1W7 z4>e7^${Xqz#`Ups?h{e@UPP%^L0IE`g|~$h>A7ML2~6F5sZl;cgC%h}49TP0-~=Lh z+bvG!XS-@Gkr8i2O}W4#`2`*5NqBJgG#5G z3d@*#`Vn5w5?}_{M4JqI1BJUAi;+KF8>~|Uj$16X2qoAve>v*az6mQ!bYX^%qx(Z5 zCz5=!lT{?DZZOyhh-#E zZ;K}|8*Iy!<~){dk(b#nbZvldg6R z+jjn|xpDE=x4R}{u%+j(nGakX&h&!R@_E;|kA<+tp~uarbF;bXx!N0T^bW{EJ&=D= z>Io7^$sM`W>0cya68aOAsqozKWF^v<;uX2E&=o%ao&PN11xU~trm7u&u84u1vlF%4 z!YwRVu7xSOrNb*^1L{dmOTQHMJf_}7j%*wW1g?&2b9i3HUH6fTSDM)6M#t#(hF(UO zK9TWK>Uf!Qi$q7sgnHsj|0 z2Az9^UEmp3HeZ~(P1H`biI<{Dz1C#AQ6=(|=Vl*pHyWiJlbsb;GP~suY9rUbS?_t3 zjUc3?X#7Y!LKWe-KU;>x&>7jv?V;sMz)rqSdU38i+2NM?r>Wvw)S_Rx-M!&%AT%-m zU`)vIxEZG2;7Q1JrE^lA`Rh4yFfM&;0pXfySLl4zi?<2mC*wWN)Q_rl%$47LA}c|- zL7G3y5R4WkyqW)1VoD3<5fUp;{v%-`c-E$*X49oaS9qCkSfS2Po5P9X-$pZ8Z{%tQ zQ+^trGC638%Rc&+NVW{s3Tc`#gA#8$ls(%&&^Lef*m9la+!kgMvW`x<_}xO!9zldt zmq>(w=JZ_CiD^9WiX4KOH6zn?I1b8l&9G}yg@4i=ejv<&>&u;|v0N_!xexq0;6x+> zqxgH;N#0?wU9-*i7_Oky{9);? zrG0XYNn_Vs?ee6-Sym!WF2BcLX=pA)?96cPx%3HsbIfcmVIYjvdpRcmspqC*n67!^ zrHLg@oYIz?M6F*pkjCbo_J|}$a+4P#SXuDZ`+^A^)t^~;ZwUjkg6f;@?J%U9ZRnB1 zWfztU=MwAC;*d7#h>qO?>sIl_Gu=E552e2JDtP?3!{G#7M&(dmaz;;odMhW9>skbZ zpgYm5`|4UO(-a(3>z2PJ%6S9+-g1<-Yg)iOMGsz48kLaudy{_TZo-nvAH0p!#V}Mg zRqxnTv3~7Y4y$qR?ReH`z!wqo>@sFIs9#*7c(ry61^nsbg>f|B73{ZHCPka__*>;Q z`(lZm-+PN*4&_+|K?}nm>WA3YTwOwX6Xl-1IBeSQX05kF2w7L;S}ASD?k7<=n&T|< zoCh*^td}V<7K8VR40`FJ%Kv9qkn4c!RT+uJG)zx|rOO`=-1%K!cs&h$9| zODL-W^?c@2sIn%7pa6o>Y@omoxEh+aZEwW<^3tnCb!OEbazv-D$s9jD5$c>*u7;3}I3@Erb0Hed_$n-!nD|xxpoClcK)%6o|EpoXY;!$^9%R8NDuHK?p@A&* z&BObFJ5a_1vV6OunMki*j*`eAk5MPEMYdS5h5Ba3_o#NA0Zd8GfMa$&%|-}pzA!8s zOPdT+9Ffm9)AetF|FKpwJ1qM59D{7pG{)eu%AWIngnpjSKD>u!3Ff!Epy-cpvDFLS zOS`cY6_0p=vM2s|3^CwW^W~7ex?O=4p;W(Hsp9lYN1IxU?!^e~>A%_B)br9U`3>E( zdUo=gGSQDkRiW-3@b=j&aCK$F~3O$DCgC^-F`HFhnnLf{5ok>)0n78 zST@^ppiFIMw59p9YUn(Ny6ReB+tf3-ga|{{6G{>k1QvukTi9}IzUL%b_7)rG<#yN< ztX>UYtbOdy0iCEm0Yo)1^Evq}Jeh*nNpG&Z=a_rAm8-r~>0js`Lx(9Bbkd~Dns=wH z6YGc9<6HFJmibRAUSWZMmW?;4U=n6TnZwvysS!ELsn0s)(^6w_!AnQSig~3;bHD{V z=)o1%6?eumqFuE{5cv>IAFx1|eduQgJfo(URXk}@UHKuwrmodeot2nP2v)+_rZxpU zqsVXn#$s*rzR=kTV)o=+x$8kwDSmI~E&;Dm%i2Fl0$D{I8^|XsXc5BLIR%0?Q(N>` z-cNttK~5*H%8AKL=#nE6f}99=$l=7h7Y!s9O_skf42(us$uxlxzqz^n7EWOv&e`;3 zSAwXOip&Tn#MfQ<&ybfNi@ZBHoN%Hy?CP6Yyt6{b_bZ`X>y=EhV~GY`2ZVGy-aHL= z&o*Y-e2G+4X8Z~Yh_Oqmyu`@5W0h^|%Qzmow zy7}|DB5vDD`(9&NzvNA7KllOT>ut|ypIR0Idmv2ApVn{)Q7Mz*@K=H{a%0VzrIYzp z0}pdsJb}M${-B-1b%U?_Y9xrhcF{vbS!T$Q%zN2)9<@UJK>IFwoJ~D(pKiRRMNp$i z+xl9P#286f<**~FVtIJvWzhB~Q#hSkStayif_dY;7L97hGKCR-bCm!`&sy^BOyUu~ zgjqOe&GazmhvmH;CB}xwYM+L)d#o~IOe8G`ctD-__AI{EeF-ZIG&c7rN*KjNgP8gq zz|0*DO<(vD^0g^TW;e}WbabLZj;T9RQ*t_)3?V`})4;5YwV)Vuc7t`$7-0m5TT-TX z6}!ANs#-PGocsO*-`}F+f7_Yjft}j;129C0Hr5oNI zSzP`8Mn1)xO}eJ)G1TQ3s=lKbaqVk%uI>lRa}%P(2nk<_#XiPu^3qcws)Dqy+X0Fi z6#>?jLl?ZSkd|V4PPd!qExpSn449z+%(Br@V=3MQS&JJ25C{TB>>;Wp`WJIJ9o+ee z{-Jv#QcimrarVv6aQEFc*W|XLQDy4##BcB7_K3vs{&uoINn|NBdrfNWov{sp!;cHX z7YK+;VF7u;k_F$#=tCvq%Y0EWc3ZntKn~_sVLcC6H!)s^p`8L!FPE#o+6MZKIUZ-K zMJM+O#8}?#o7Y*C+S)Y6h)sn)q~mrP##-uHU4d4Kzl`q^gTkv_(s*Z#P@LJDSZ7c{rUFMU1f?)-u#&N!mr&ir28(P#a zuISTR{hG;)kLN_@S0vj%in);+o)?!Z%wRLjU=T>}lXF{~s^sjt1h_opBp6G^UwRY^ zLM28}DaQ|wl~NhL&&2r6rPqP+&P46H>yrKTnW#NT!9LC{nG>}Vw!hi_rhR5RnQsS? z?~;z+X<#Ucr$~nq`MgCH{7=~3^`D-3e$b0QJPy=8^Fe-48|2>p5Hk}fwn{MjV4!m$ zw=jK!UqOv10ts`>+3T&Rl1gs=#jif5w-+$;lv${=(|W*?XHYz%;~I@;EOL43_Hfg*Qa*J8=dn^K@cqL^Q?(wJOHw)OfG zcp$nV-o>0wwy!SgqF)vNo4N@*Me-7ffW2wUlDugndFS0_YT4VPuKUKoR!V8#s*^UG z@b35X8PJ$2Iw8_1cj<0K;z5tCf6R33xY`Qy&&>u(qC`@%f-idE&J!a_7SN zZH;q)-n!XZRwoDfD^~~p!yLy!iv9xfNF-+Dhhx4n6RLOb*|VN)djfJDklsBT!9f4J z3RXUPXXC?+Cv1w9$fX%+ggVzFZDwEh%fyRsvRLo0ulTD^UJliSeD7SLZm5Ze(e00U zvTGUeeaO9I!#Os{iE^nj`=pO~o`P)WzikT8TVvb#b(Y;+88b|F=1kcWcH~A0uZ?IK z^ z>XPd}oWHxn1McG*6(4gA5MQIDJ%|s?Mkl7FX>(%)`H^z77R!bjY`C5t^vI0624=bM z`fys1th$$>8pt<}^o{bL0+~dQ_F})UWnOH8td0+1Iq70!0LcEg>(lr?>$zxUGni~T zY#b{+w|g^f1LV4wy0`9}OF#{uUoRC=zAZZhg~F($+Iqbfuo#x@H?Lm_mv=O|wNz$5xe3xgW|JI{ zG|m69BpEWD3aUG2FVlXN!*|k6v}ve!ht#^oMHUY&3x|tt0uu`qB>#Pl5g+<1U3nkH|M7i>4GdYg0YlFx(*G%L@Sw^T4?t?z@jpI3 zAk467DF3fkQ~`VCmmw$|6FO>ez`*GpFlf-*rq$WAYrQ@{lVXfwj#Koa$@5E6L;qYL zoJAIF8+f(>Qd)E`1Os`*@v#_dfngVwAWYaAz_G_ z?xz}ydAksCIb;a-&p&})gfBh#RK$&1jKJO2|Fx8%XpLA~BP?Z|X5FSvZBVkno$3nX zs=l!aa{bXlM(<58kCuk4u>%3T83M<>eRm+5^P$kVf{h& zt=C#to@?#T|9-BEM}C1;?knv`%F7Q2P8&oT#BP9OdJDnkM*zv}T}KLd6R0Cv)m z{*Vc#s7vJ@6(}VjQZ)ln7 z3$SW~?_XzF0Z8meHD92I5Pk0(8-C=Qp!#&0(vcB^27g!F*a%taz#o-lyn-w> zAUQuyJ-_EsA+u%FmxiuiOeTuYAh!N?x@hJ zIQqn2v@vdX=d$HR(X+pPjRe5C>A4K<3wPyE4wae$B_UJGui+qnIEYT#>=YIMJb^3U z0kcr~8Wq6OyTxlVs!^zUTLo?}Cqek8TzUt&p>hHp=JDbUh(E6}$D?ZB%=Kr#A1`bt zMFb^wJPsc}A2MJ(JQ7^WR8^Dw9!z>z^|*~<4|>-Q8D$7@%l%S(gZ2UIS1HxU;`-2B zUwRq0EZB7!&zfLP$Iu{(^ZG6FV~EJsdU`Pb>-` z@{C41w0N&3vb>g&m+j8~rLT}u-oG-+?w6&dIdqv4Ki^mWc)J|59c47@1(N1g^`Myd zbjAxXFg)GP>Z1J@a?IYFCf=|o-ZRty9YdSTd~81w<@pmlT!n2uLL0uS(IS`sKla`; zuE}+2AI0uM5m9N<6m_8#5eprH6$?eBN{bQ@Art}WB(W?+rCKObRiu{?AcPiT0iqzi z1QH-hga{#Y64K8T(e>_q_TKOLe>msM`E5QZBu}0^cbS>%p1Ecw0JxIzKFBh^x>6?7 zBPw*7d-2uz(KrW}uts9f8>(2^b`)%`-%4KR3Y$u!y4_`ro-i3-NS zryxgaiY&Xxqhj*<%0v7{F+vd?^pv=WYQJPsZKOnb-tW0{Vm*+Vfxgsv-5a3Q{kA+l zQGZ(=xxvF%ULOPQ$?d3-i&G#bO zZ`WXyMpZgnBW&H6mi|_ zD^nz#_)TM#$U%;xm{pE3AR)WTwYrA?Ubu2f18`Rxn@a@@u z9;w&k2Vh?6U*g%3H=#wd@46v{ci%1{71EZ}%p3}kz=^w#{H{is1~6>4RKM1{wzk5- z65uKbvu)B!LZ|#NN{Jd#_nV0+hmPG$X^5X#TY&~3MdjgZ+v9cvn$Rw`I%2H0_t|Xa zMmI9R=67rOfQss9XV{T1wA^9=6sG%HJ$|>Hk1yZXo7Xy|otN0X^Ur3IzFSl3t4>c~ z*H~XmsArfHQdHNHp0pjz*4jzCB#Hr;B1dnM6hLmp?nM4TIg?TXgm03-%pa1mN$LR6 zB>GUS{RrZAUrwDLx4|9%DC_fZUxNp!=JALpL4Eb zn$1*(Tc9D4GuP}pTg%MB*+cnY>wZ6zNcmko%halgL9d~aV)i*KNBl{G6>3o(H`jr7 zM8T(W4vrRy{MDfOfmS(4~pS(}nJnG@!An@}Lyr%aBzb zUh)#1ohfw67uJfFdV#04q4g7o?d40_lz{vey7o%oghqot+Ap3s0S@yqJoownX*S~a zNN=ILi~sVnzq<<}_Ct|l2-fCEWbs_=sbWT`F?Z$I%e58321?|WQ%BCVn5RA4?U9hX z7B#ELRX%afm_NCz+7V*&fW5o)okS69OeW3FBPAN zQI4)XVt`UMSDs8sNXa4dPB?^-_q`9T>Hw@#-09MgQn+rB?D73SQf@#xE`C6=iHw*D zFWKFdZbPL2LvnCcgW7!UhdF~_w%hN%xVo-lU3C0;)eQDjXSM>b&XG-N?bP>h=<(y6 z;n3O93F$IRB=>`xy*LU{E7RK7v$_KnXA!==Duu*Og)Yy4baP85OZ*`h?`0~z}89sz# z@fRW$bzI?kW`*uD&l4NHKCq-?Uw;0B5&_-g7{G%ov+s2?Vf6LFC3)DPjno(Z$AFyb zRWv2?D6*CY0;-r0UtHg ztVm+ms}$I8JAsuMGBW z42KDca;hbT<(>SGuct<##w%l0SQmP=c){NJYK%MFnZgGPK>^Q=xk75l=$@uF}&t zg6id=V=rV+UbmZu5ArYe>iwwVM(w}L1|3H1(MLmVwg;dWBQsUNvg;kwpScJoyUCi7 zM`Wya{jE;!{tu~IZqVzo1VrWSeK?SF97E!XX6TSns(#ewA5@zQ>sXJ zr#(R2z@-H_&KqFyEYM2qG4Vp5dCo2in|JV7$%Hmjc%eMJPI@#G-Fdt_m5q3VMu`zN6OfnNcas zxABjOc!K4HiBkB=d|&X?urGja%3P)~w9r~TfU)IOiMLd;oSE3Qq z)Yor0R*}UZ^sXh)ANj}Gn5&ql%PL7LIP!{Toyi4%Oah&bEjL^a8kH=YncRSk0V1OP;k@o$t!=6e2QY-&5(+_(NccxX7Q+Pkse| zlJI;+vPiU;G64=b@nGs|V2~5HtGsB2IBw@at-DYtGNpOWEQTbQX^oa6`Hc>g)1B|v z1~_!Cx$(&69;q3pVSmMz%gT?<1l+%M--Zf$`x740|4z#n`TDo7OQ@SJuCbL~sGrFr zjcJ3v7k&M9F9-i@*5-8y`D>lE?(L<&E_`0#))pDlHc4lb8D2de4_@*YxVNVOw#Naw z=mT~ja1JzYucz_R7qY8+#ZR+e6J2SlePu@q$nbft+VlCM-83c(|K2a;y*)M8XjTO# zN@h!5;)xcEx&b^zJ!g)PR@!-wRv*%yQ@9K;Jz--nTSSl@IJ#d)QMBTv383FS{&N2< zo4CQ{pDd|tr7WAoA@Br5PCJ)z3?9 zvuSn*dl|H$9`fN0cFx&ZXL;LS;gIn~vFyc5Vh7oK&7*95aK`{s_{?84fC%3p?JW55 zaB+;uBH0}Li!@Sf21Ty(oEh|raKfGXbl7a_hu*03&Nt;q8lctc;0S)qhu6)*jL@KI z-eqbz>P8|um%yAH*^Ril>rb?#4f-$vc^U0|Q>$G85_QuV5t#v(R8+xs1VEN5fqaZB z_J-F&kfSGYrz*9iKU2{U>?#m%A=Ku32js9BIz%yzujdcfW_I-H z!e+}RS0XL3e{Qa=tMy+dx_9QYEz*yuOcJW^I4qqHH~tT5u+V50mhl%iD{OuoAg%BH z%q(*)xqq+BT*(Ingk7G`NgB%3ID%IjrO18w5g@C(^W$Zp45*MOB@DFy;IMY=TRA(^R8o9$Np zdwA6cbQHypa=1R;hlTtYA@AgeU%cv49Se@OquwKzS+XV1*rcB-;Ts=3PwVV9X0IU{ z%sB7FaMoPIU}vE8%J_O`1nRCgfSnj2;_#aAPpo|yZee})DoWB;0e33D0|{joKb?{x zO;yAz6{w%sLqdEi2c9C22xkQ)xO|q0$dWhGslQmxCd>u~_LotoP6hI`s&TatT6X1( z4eSAYUTu#3{t9SMF|TSd4Fc?ocJb3!1yC41QR~pg&>g8iF5c*whzoFm~!3I`g|&~<2>@g@05L!cfWHVS8Idt zpxI_?@6I9?Z0?Msb@r1%7c37yWJxpE3}&gn-X3wfqZHC@rP$}m}zyHuBx_$brquXY?x>ikq zs@nEj<4P~f90Q`S-!AJ6Ad?+baYa+@AwX7okQtW_@PZ5-;2i8{CehTul)>lU0*+o~ zm@#d+#ATMvJs4p1H1uZDgapwC%c{G|pkqI|q338Xb|)$f&mH@!`f&ru zZ-R=kz#}V#TL<-ln>w95;b#Xg0JCn(C@8x?+$1KKR8{X)Eh?kBb*kmy-F&zar}lQv z+Izu*v>vB-T|IS6-UWBZUt*!I@OL(X?)P!a31HNb+}O0s*|BOT_85dV{cyABY>a#D zl&v&jopdciQ66-rka|;xjGkndw*_;r=hLAxgLTH;fi)wC^(sRIZe}9|Ek*C)CIh&e zm&P}_1+}v|T7Y4_{H@=!eVHDe7UbGMN*ONn+tD^vkG!0 zfkij;N^YCEGQwQxd}yaIw2{c8jPwQDT%3FHGv273n$S=DQATp6!A7M{;W zXa`;^k3ZFRVIm;G(A>Wd}sA%?swL#uu_S}3E*seVb1c#1<;+TwRho? zHa1B%fRE}LkT=k`0MP1SK%TkPV%nRj*X^an_Vo+|;4QiDuFnxi?Cg)1$s}sy$l{+m zfUK)j@-Cat*jq08`=^k207YUU{ioXxBM{hUwmS|x5^e1J+7H>nf`0s%f6q8! zy2cb(S@cgwc;N=uTUU|1HY>QHH;v|q@VHGcs-fL~4Iiv!-W?-c*lVr; zLBOJ*%N5fG7}7b}mQe#d(Wx8I znp^ByGjw4~507V>yRK~KhbOI9)%TFX7Lp1Xy+vEV{}VY7mOl80Dl^}r1@UkuVFiqy zq(qL$2y!MQ&F+BJLVsu$KU*_I7}ZN$@{99s(9NGwprQdu_Fq zg0Aef&&$F~ff^~GrYVeLsTe@BIOKFPxNoIpJo)1LE5BE^6oaE$^N6|pvgwL6e0 z@}9Bls<(3#5y1YBPQM!4eefaI64JqTDJ3(KH+z08EyS^->P*pinjyHkB z`UYEGBJ~myCms6~$nMG?D2BWa-td=mx+;wrR;(DVJ_(v*iNCL?D&^No(30!b_@NkAa%#=B&*8&CMzw*sA4K=UMxgM$0a{E{gv9z}@p|y; z+ETz|ToN0;Da3t1CaPM|_DQzn#^sN9wD&lIO0{<7d!+`GOS53?+zHMN>r#*JD;MzV zf?aYyuL+8~XK#e7?YeQP=!&-FkzJsz>VPLCHd~sf1<(gUn`xUuI9ug+sFMIEo^3yJ zL_SlAqzT_JbOZ9|dVFId5FSAV?mq4ZS7OvDB> zUEY(vTq@B0K0#ag;kRKX0(Z){FipP&zU;o?8r>hp8x-*OgRfkB9b|Jf*v9`X zxE>DLL&N9<`Pj5}-W*NchD-j@m-g-RQGKRt+LVFMsc$@0sYD*@?IkOEW`vmN#NV|) zME=nSw%rs8Bt)62POAtjj_uBQk`^3$|5@<0@ok@qo)tg^D{Xg@23U43>1p}AIgX^s z)!Z(z^x!;VOlk3D4i>6tPTZwxb7UaS?ru*uWs@E6$b>M-#6ciDO*HDN>$~15LIB~N z?r%uGjJaWmLr>o9h`d5bl#??V$g_J!6#7vsDE%Q<1CvW@)cQf?zM+u~&bSZ7Wt|`u zA{-=B638Ax;r8=L!Pq2qP07o!$+$i?_j2j21|kozub&vmW9S4UPR3iLy@@u>pMc|Q z3sXnE8gSWin72r+s$rqaYh9_D9Z0@^t7!3o7^3qWx=_pEVau(T)wmNv$Hm1>ZUVs3 z*>ZAl|MUbbqfYD-`*rQrk6FezSD%p|^B00_VVSX$M0?p@@jg}cTPcK$ytNOn8dRXJ z7Joohg8>;TZb&FaDqP`4qWABi&0?y%8JB<>e52I~o7w4<32jMNZ%-53U9myDx)yJQ zRV$EW8)}8v`J?ByGpqlu4U5zPr2{tD7AdKAq9UaAeWX8>QX z<8-GvnigST2ZZ|s30MQc^PDni1Y(9c%E2;Mbf=i1+mtWuNfdi#BK6E<$Bqk|Pk-@R z3lw6M*>(Gd!K?dkl<%v(Q9h`8|4oJ3L1lwSqRPIe8Uk-W6x)@US?%Y zQN=r>FTje5QatmGKQ=4~3x|kY2O5Sw6!s@(E?kEyD^Hu9epepGIfUjdIJPB7ZLkR5 z!+BclB4+)OIm7lm5Q-I0E$vz;v-36NMoQz@OKHKtD14dfJ4l7ZRr3lUwl>wErKK&+ zHrcg}lDQLVvmM|&cQ{S}kN;`}XJ@A%WFGC<2yUQEGtn*8(}tx1P7O#fcAPO*3Fe=h zr$mEcMiRNnCucStX5AW3#b@zgG#-xWVQE_(ZLxOf(dk8`$6pxCFg(N`ZzSH|GB)1^ z*|+&3p3*tbR=#YJ)BWhO4%Xd+QxhpU7FtSxPqvK3M<^9}<9KD8NcdREPi0|?kCVPK zJS>gKigId+^l=zssuqO7fR;aiV>CxsN(UnRY2pYcJwikWu6%*tQ-3FDFyO$q?lU3= zw(@dggBG?g>1X-88b{tXx6_T!ybg)GB{&6`MR*VSL&!zg+s2AX>hZX=({)}xsj;0* zEvO66ENLvzaDY!4B1AZqmB}locHmDN`tDYnMZ7B^8rMF0+{Rm6LGKcXxtG9ts3%4V z@FFbybIZk4HX^ysXAEan~QG|f&C*V(cXevdt%N41a3rI9UR#AYdoUr;Jxi=8Ek2$6YibkE)T{Ho^HT0?rO~$8oC{Z&a)sqUgk1w_Kdm z+voHJZ-v@1)$^Uv2QeD3C+#b1?|d2a=_sre)FAjqG?sXpOhF-xv+B7Nf^~W?1@eBYd8azYw{`rsFCRuaznx)WDOPp_Mm?yOpiUPwi) z9ZE!I$f10^mDCc&MjB@&5)zpDA0H&}ixx6o)*Sa8N#r6>R{mxxPkoRJv-pv;c%W>s z>f3R6u3=(a_Pw6@?l=7_L+Wq3LWgzYcTk!&MtcLR#@?$RHn;Q9XbxKpq_=ge*wU$SuJM;rNXedwre+gF(p-;34WKeSjkw89E%ph}JzU4l^T z4;&|}G)2hH`ZwP940A%>n{FH>tz@~xo4%3-=}xZ zp>fGyg|Z0inzo}%^w>XLY@DVU8P9d;B@uQA#P*R@Y&{M$A9q5Hjnh_URVNoaPmclr zOEf1_SRMx(#q%K8NHLX<0$7WRYWF0Ks$NQe&dw%{f`XGcB$rvq&y$RuMq zUE406(}V^W#YW#TwB^JmSuQ4O*(PG?rXWw`#yTW^H#FjrjP7U-S4+8rhg37R8PX|n zs%(^;$m6tl+)7a>d+YR~**cq3&wAF0+f&w^+E_-^Eao@rtj&({PpJ3Pk%wV)jhScse&Bxgev z&i#Z1E>*qOfEOsEcyTrh5?9#qN>@Jn+Rv*;9r!?M3XsY+IQ6Nrqkpor-c z9HpyUt~+*c9AG4YrV*1C?_ekAS9&qxJ#hA2?fW2Na%vChHlTKXxF~v`a6Y4h8k>x_ z>Nprb$e~6y-1#y^iE+-)wo$+4@9pD#^h`sAJJqw<(bAwY2mh$%sO{c3K|B7UKj}MW zS{E%dR9Bpnbe^K;O@f8{_atX3(8-S<`RxmhqwW@v+QTTUSZ~sZs3nEhiO26KX17p$ z!rM9=WJuzGLfJc-j=?S(HKSXe!N~;g%$WklR5@ z$($0W`q$c77qg)y#%Z8)b;^^CcXoctD~_H}BjhdE_?lMw_9kON(suhu0VM>J>gP;t zcv(EYgZWvZ`udgeVJq2B4_5@&*gT5;8)AeX@sfjWc5C>&xg( z9i9m|IP1KTn}R8PMo>F`pJ)vE)rvE5`zLK-$EmuMO^zbw&ykzciGlpH8Z3sdZ3f*& zXgZ%Od}?WZ4msH(Y(5S1mPw2PQ{(1<*V)nv=6Kt+6GF3Wc z)rJ8R%q50F4h#q2R%DHp$SI-OZIpd(?fBE&kI_r4CIJ%=fyl<*Bv?wBgO%LuIqK~E zLTk~-+|Y!2)^M)!GiQT~k#f{n!NX_pOxI>2cV%T(W89cEXw69pY_$Kw0*v_d6b(DK zD2UInGV=9VuEp5CG-5vV`oeziCyjItahPYHs8=N|1V$4|hpxrkh6IraN?}g(h%Cla zMzvtRS`CV~*tFVG;obFrnrd_ao$cDWAqQWcbSrq)8081&y0rr9jWmeyJ6# zsSI~H6j;>U86&}oGCZu_9GJiaxqh%XHP+qKnN&)7UT_R+!;@MOWNZ-kUHmZShKhwN&TX9@SF^Q>b{9r!A zfc53h8(t78q60zVz`s*CJ7#Y*`s78lBNQ(;z&oCe0h_2aE@&*sFAc!Bxy?{!x{aLN z{#872_x+wkA6;OScQnPTMbdn~JsWR+_*A30+Bsj^4+OQm#MJ{j_caQvKMTi~x1r+W zVcP@+Xj1RylWmue+P-MVGY>dFK5pCBj+ZqQu6rJkM`IdDXBhVMdj-g6;%MLYn z&ied6-ErUAPe&haMFsqLf>zm8Y$;Rdop6_d;Bnw+ih%^^W6S(Fi#(@<&5SM#oVyVi zlLXbNvi;*QZX&%pvxn`sgVlKC`D*t^k&Qre$lsQB?JJNFx9s@Tp&s2~@Mg!y30KLX z$7S5z#l6*?Aw9`N-Ci~E+>a%Ci(Z`fX8Tq%Z)JeK#=qhImb?5> zJ-NimN**zmJRn+OI?D_6<^Uk?bu~hh0b1!AxLASr(XG{PSxO81J~r?KwYSmOFD=O4 zM=AC3YR@W>2PgkWGq34VVlgScK(9oZ_5W^7efL_%AKKQ_7)fI9C=))|glDOgS%s9~7lJ^t=nn^a1x_ff9^QFkY z9rl)Ydsf?KifjeCYJLPB^;JYz`34ijcXC2K(B1O-_Wy0x*B${n3k%WD|69Y>;mm~t zJw^MT%wqrJ%*`ea1wy|`iCB5hGDrX81TyokMGt(`5XrNSoNX5h$hQi~(mjXZEuSF= zM1DlqY(%de!V=zc^?x#?O%Q*q&MX%7MBPNm{KFLL+gy|>`pw(Z6YbKU*LH>Y(;q`G zJH?Epy#VL`9Ptm$U-P9S@PBtw`PyChAP1}6NSz=@N1y~HLv$OrHP?yLSgGa{hj11@ z`D|U_)s6SAsYwI3Z!`_Ia`1|@h><7Ea)g6D(TJZQhp4FDe+%rgQ`bb;KC@_*vXf&# zxj+D2{?-LQzZ&@Gi1+GR|NdL#Vce=z{C?qGxx}go`u&28_Uf1a{#)eZxz(Ne`-SU+ zzjoK}7q-f+j_~*2cW>-lRY<>I`0qpfcP9S3Cw}dL|4PSiVfg>uOyq#|#9+n-A4;j0 z+OOgvA|h#KtLi&7($Dag?filo0=c0go5hv(;xKyMm9P~Hk%w7xrclm{UnUwz;rjgq z&u6*fF4i%Sk6REc5FawUZmhLfwUb(*R#rl{`gMc>p2z&pRef*Pn4cY1Ern0k_8D~` z;lO|2P(-l**A-=FFD2V4_Z>ej25R7B5w&>Ae?RDo%BmT;9spytAc2cQvN?@oOvK9R z1KJ~kucx8-@eW6qCPBbC+37rA@# z!0$%|37BBwjmb-zxT_7o$*7r%yA3;FhFy8q=7k@h;eCFP1c}!RHuuTy0}B{eBgleL zj(jT}!Coxx^e~)v>RlYaH~pdc(i3Wyz=vMbr-6ZEB)ca%SL@3H-sG-dcJ#Ri^^jlj z`kavAun;KcezU1vHHb5f^ukYUJd`Mgh?pEFYUMnZ(M9dR43)Dn??u3-Y z+uTlO2hYDd4bpUBaX_jR6mj}Spj2PhuN**;cYA(egFo>nE2-A5-S)L76mZ{r+SK+= z5`6hL>GFl2Mcu6IoWg9zGg6%W-F>oqZr$@<|Bj;+dCMIa>Vjf>*O57zbv41i$`%9D ztNd$v;|+~@tpvZKSe)m;^hFa+3XWeI9N+h9Lq(@CGZFbe z7w7lqx9<40&&J}7;IISRdHsvHQup#s#M9pNwg0v zeu-hKj^dHx71AY5yElZ*=N5Sv!^|T3`w4xFKUSeX@R)ZCe*q}7FZH8~qZj3OyM(in zw8lA&bM?|t>ehdAp;^-_PkCYt#Zng)?6P`D0B-cI@7Dr`hpgO>N;*VEL&UWAdXNydhv%xcjF=zLvDs&em~8TaEpOP{oXu zLw|pT3H8@uViY?U4YK{v^&I1iq#sN6fU}BQQM1^u>yqX*BUukyR}Q4OxEPpvcK1M@ z*zx%ZjK)l$o8#2PztcO|p{g+G0a0FWwrRpu{Eqj~aPCe^V`o{=>^f=NmTZ1M5=z}H zk>9wI53*w@s3o>5Ei#n2uc|FC{2lB){_@wr((7j$F*~A0SC-SEoLH)PkK>K6Gzce}6DRq}cciwyf(+)fF|Lb@%u*C@ zTZYY$-M@YdmM3mpC51WHl{Smf6bfalUSs4(J2grOSwLwGolL^nDH-V4?lJHfmB>+) zLmsHk=ZOAZg}cA)LtV)HffSqjT|$lFlWma^z&22&vU;l;5cm9n&Dg0apsv83DI_AIFrZ8lUs@b%8kvSX?)c;& zovn5C1&X}Ozxs)6=qf~F%$5iwuX)IJ~RJC1iq9D z-bYKNYx`*&ZshK34pXWrm+A?I&R0k_*OMb${D&m%s%>dYWEM6i+jtqSPrTHA4__=+}e}R~#3D7p(g5|c4+>|FGM+zA( z(6O}V;OLeL^2thv`O}JLkED)4CBcbNU6CoV#-&#q|UsMEE$^7}b zt1X8Dr4{-`9mlBk3sj?xrz6eBpgX}iN3VXy#Y40NBCq_ufOfkp1NQ=K!{54sP*>{D zZ#ylLdMa~xM%VYHW6PlrFWyTFKR5Y37!N%2Qaa0Y@s5E&_|3QUf4LzdZ2i`~n$@>& zE_K3c*lucrY~E-US*q{c+|FHhxAB*muX)lifPe}&msxX};tU_SY`wPR4v>`k}xNe;MAt zUY-QT0N1yH#6`NqS<44K_*%z;^#5;<$kN=p=9LogT^6s79t+xC@I8AI;EUmgzc?mH zeX0LzDl>m6cq~`?;p)9qF28)KFJ`^axlUy1G~B55CE|-&W(P<({Ip-@_lFCA%jwm7 z?JNM^-Kff0w)#Trm$j>S2;6iRr-Qf$BbJv|!Yy$8T zz%pxB?`7hCbL#lz&;MuB?R$A$(#p$YNeSZh|L0@fc;NEm?7h^Eba06ix@fTc1_b5a zmh6g;S7`3YG$4(#2QX6g!F1K&+0l`J)4gpnpy`>0j>ju1tyuc9r zCrt~UJ;6V=AHTa!ya<~GpZurE=*GT0{-5@qZ{1p8G6E(m#!HVJ`0)JEWo^Gvr`Xuo zUFq6BsuqUxO|F!dIE{I#Fnnb>Er)MHNfeoxzP+k{^Q?ee-}TQ+CC>HL_LVoyQQ^x|$t!{0F{nWmMjT{pFyJi4 zzTKxzIjM%{kFyJ;{nN0t{2v(pr(umBgn50$F5FC^7$cWseA ztUl3^ZYK|4`PTfpQXKjigs+x~fw_`(|BR0dY^u#OxcWHh`!-{@VEVE}(&`Q|snl?) zXwbdkHX9U7m~5W(pnLxOSQ|52byd9eHU|Cc(2rJXxKvl@3O=+sbiijfc~L|jC<8Lc zmMv&%^>NSGp;q2Zk0F#g5}sI0bY_#cU1c2jIc#0i3~1H)0}1Ez9L}in96p!+l$$Si zBtnx2WO{vPL7dKrFo4Np6MS_GwMX|~vXv*nOFihdue7&Wmpauq4gvwT=A>-)bd78F zpbiLLG5%BHzRNcK>n1O|oDaEY|mFqyQLm!p2# zmo!ozV(IvH2gaZS%(4FQa|K6;{;jffR*ONW9!^`a114yu!zMHgi?od^g6tw9J2yyU zs+%(=W;Q8l!nr>xo6CATixiF}BS!-wIBd>yFtZD`Ho01p}T3*P0&oJ^MXu zGwI0<&6Y7$eD-ldX((Ljw4&k`w|g&tF1BQWx^s2IU?VJomfQp^I!3)C=ecjQUhDY- z9SR8&YK3Rt9#z2TZ^M_QvYC}XSNaZ(jU*ErM~wgu5|mO&Rp_qUmduH*E@CS%4f_-k zKOR#ZNWS%p_ZZdLpv6<}f=AwMG(V@9UR0r|8zia6!zHrtXu^fFN95GfCh*IoJ?ksB z#5)lA?C%f?%o&JeM8RU-E#Kdw>1T>e0fz;l7=M{Jf4IUI$da_1KsCelmbD0Z1DmJb zKh29-~8_R&`*oTz`-RfS+8;g=2`Kr&_ zf39|+*1>sK3I+5RtXisQW9XeuAryr8KG}m{) z5I){{wE*RVI6E%n6dnDC@mB=&7colXEOo#A<=Xs268*{T%~*#2%z`a`Qq?!iut!{I z8X}bbT(zb@Y)f8cb!c<|ee+sFzkqt?P2tPY(<^6_=0maq;n0W@GHhex9&|3 zU>Zx0YL61oj%%;UfOprnw3f408k@8{ozfF5tydR4?j(Q=|7Fq7uN(4k;X3+o2ZQ9) zx<8m>_yyILhkPF!R``UV;Jp)LUA0@hC^>pJm*<-qBObfjY)cE{f`G$PQgii4zI04- zX+d02P70yB&}HpV4KJW1PaDzQjb2=1CImj|Qzi%C+Gch@{SArEY!FT=)yz8MSch}j zSal$^!}irt=i-`XNK5Aj7DwyaPYiTgQ<1l!KLKP(y){8#`z|_*2S}b)lRgl3@}(7;xm4;I|#)3$ zXI~o(Gxeu^z>?RUYQ(-cSMA>s8L13_I{>+OZEgKmoHO0rxOQj`peYTGQp+|emc}9G zdel@g`iDn&Unx1~_6h~IP9b3Z-bwyE6B$Wi5KEJLdGTc3tFkdN${ovXWSoBeG-U=G(nmn7EGUbs;2H62HY(9ry=Z$ z@gw_#IYb5}I@{L|#LG{CmfsX}LHpp?A{ zBL-Zb%aHfwz2_-J)o4XuNZ<^MkxS@17Z7)=XiJq2BqXn=c(KCXlhGKRdacyEQ+%{vnm3aCsY582n^cosh?WWu~WXMX1qMqx`M$W{4~y=KYQ7~ zPpg>7!B5>xFkGxB!VB%kHJnQ|6b$y(_EQ_8O#45?I5q8KL3f*Dko#rx`CV9qhno3%W;N2X)#XcEGL()DP68(pF-nFPre zE)0B(B`9+?y)^EH^4w#PWiheK^R8@*wCI^~>hfl9z@1dH zae!NiaTFBw=VQqmZF52NKhnJ@xndCrDK%ych)T(!>x_C;j3Bf>apEyCBMV|h<&JHJD?gA9dz(kZjctecrL$y`@k9k63rD3eGW<^} zWu+yFQa#&sMzR22tZLb}(QKn@k&6riHGi*1bH@=Qg)T0pn0wO(9dIyIvUW+k#S%M5 z7^t>%34zg4yVnjWf!N;u9B1(Xm)@aSo(WxR4f_B9-SYG=4+*h<5RjNdj{}Yac3Up} zc`2j5q21r`W>`99Qq=m~Ig2u^((~NaMNtNd{kss7OLVKzM$Q_?Z8vJzEygBYZ-eyK zevV3km0)32iAN_C{7cP|IpT(V+CViEHur46IBbtxf~+V~DE;YA1I`+d(xY-cUxOPGs~uNCZ_vgY{|b5;%3^N#omx#T?IbmPUh&hj zD~a*B#r4gq$ZcI4f_e_L&V; zIp91#0NW7nyKX2T;tYPAv01MN0w|ImHRFE8m`?{3>Sx&L(1bs_G=V44NxFBDJ6r>t z$C7ti8lr?J3ICOE(S*N6W0 zEm=9GUvQehf-OGW_w1mK?P+toCIF}Ds8B>t?G={zZCAw?jg2S#j8QPNhODW`seTj2 zXEHw*Ycz3sa?uQTcrqf>7^B2nk%lPld>WviteAqsLtxGkFaH*zp=#Pva93Kwa*-i8 z!$65<^S>#mAIB5p9(`>PceXWvAD2LK36j}fO6jBedd>&b@5gSoY(?d&5vr7j6U(gA zjT4S0n)|(%r@jn46SN^GlpETy*YS3o>3$6hxB#TUpB)a zc=}eevYd{ux-HVhIp*S@jzSo>XT9otDl6jXFUySVd8DP>Uz41I(6o!?jm43@{D4mE z;*$B9VHTX($soO0$*V#ZbT)S^B?&dO7+K|=Y6Jjyt*95lud#te*v`WTMbM;UTDL| zQ&;e2Is7p-nR!(^lLB`aqDdqA`=b2_O_&WZ_a)}suw z?2)TRdur>4e`B=I?*4S{;C9Th!)L!g9c|HV(zw-2*(YE7V<7?a;W?PkA8&2UiTXpL zW&*Pqha)p%JRC5k=5`m=(<|7XbJpefZj_i>x+3BO!l% z#ZHf(GJnMBADlMRz`Vo3;N%&J^VH+yuxjl;s`P@xsJXcupm1H{u zDiEd150N>Tq&AdCVoQ7qHb4Lg9%3UMz@Vz>`dEd=2}ofQlz{nZkqQHFx*^5`#jE}| z{Rc<>)wK}OePK@o0KJjx*SfElcx|#c zUn;pZQMYUT>abhgASaLkq#PWd>XRDL-$nyCW*^ms#^mVbKL8hqI^fH3@FrFj+SdpB zXoZY6IM^S(1~`pm=I!TG0S60yAMeP~fB-r#byaiSQ0@s`ER^OG8#gLy3qCQ7LGY!a zR&)(FQ1N=a^i_shq7iUxB5@v~d`~MbzRJ(y1k#3IXE3ML5_#Wv;ut&y#^xJ=UXXgJomzpJACiRO^5%jdRB<1ak7SjRhJ>;49@M6Vxr;ARWrTVQQ1L?efFE&~*oZXI&Wg}eoP9s$t_J0Ncg?!082C}A z(NRuGBQfs$2^d@=c>*8zr_s%#b%4?QTXdA4(D&b>A5Wwouc6!iZz>zWlyaDK^N)A! z=_T?J3mAl_Uqb)KR=Py=nS#sl&2+Ce)Ny$%~>ot$J3K>Q){q(d&Vp7y&GOX1^`)zT}!_6|TQ3J>I|EMSP%ZN0K#P=55Sz+NS`ddHOgUiU}P2t6q(yTvC&G6lg+8yIxkR3SWX z8<97g0jO&R$a_;Pip!-A1AA>o!0Jt+P-eBVISd7+O%|;`yZ$v=P(&d+4T0`=r&dPJ z&SVcvsJrix8(X=fK5;g?{#$w}lZNs@fWx>8sdofG*vDm0dWRlFeT=lS$y7^wfPhZ< z+q|X$IdYW@r^|AhXa6xvZdC;Sqh|qD%XyDi!%q~)$9cGsW(Ks1;}L=LgMNH#Im>2M zbaXqYf~PCo&4+cpCXG9nll+EVBHD$@;k@59!>r?pMsUh`AH$~9f0)_JqZ2EDv-z+E z;CnfAty!Z(7C1Z|^z)Oy*LtVv&<$1Kx1WxLk7t@L=1wjjiIcJl+sf()>;S_1w&!IJ za|ZLFgi+HhmNV2FV9l2=_8o)noxbH;w``h30dmmnO0E&u(R!@ftMds_$z@Kx#(x48 zw+sQtky=X&D7BS&p&2?X^mameP(TWT(x~7zeOTgi%b-1&u&ODF!&9?Q?acM3J^W5B zvuM`JJX4MRbuRk%PAQbKx1z`kW&KAYl=r5H6JC#YxQ=6%q%eDJ|1_9AonliB13U>t zd{b#Cq`zWN6Y=Gdi5-$`9~OHj5^#+sq4Z4Ey-L@nY}z5=a^}%f5E-?!pdWt?7D>Zi zz*EASMEyMt<*4VCXhKg~fZ4(9(=2B9+Sm3$W%3Cm9V$hH_Y8=0@itpCKwsars`}Cf zO4Xz^jfq#{8#?haVt?0)W?YObqwUxx}TzIvuL4o`? zIZ#g1)N4qlifiYRQrcfNws`vzQ3u}`o2A9YJT0#h)wG0!0~5zkS0jy0{}5(ujjd*9 zm2^4GEWV@Q%HmR74@)k_afc-qTR?9f&vR2l#TRes@aKDlM{>?A!5I-qudxRTpcI>A zsJM;^5qO1PT_rNwzFQwy_N6xxSX}?)#kkoZop~&p*%n zaqcsk`OdX`uFv*Lq|DU{JIWeP;YC>|fI6le1S}k>=^s7tLH6gf6^%9fbZ38!I@xZV%c^1BbEbyQ`YT7;26NKmaW|_( z*;zE3^X~LBWDSm)hx7R1-4o}2x|(@{c*N?$So-<);jk$Sebc^Ga&7H(pZY*s;iIH$ zE+<*G9Ot=%U;fo`-a_@te0KBAUL1aMkOdDCU0jjRvWEeW&QaCFdMIAf4f8!>{AQIc zYDsiC=ldU5GCc35^^|TkWh*-Ntt=Zhw}QH1@1Gom(ndyQK=-Y+# z{SYD)5{U@@w{-;tPCe6o73l&R;{P*XxSKB(fPW*Ix;fq405tpw1DYmhFAo#`PPTD@ zKy8FUv8}}S2=}-ly}W5srftSw!Q8&stN)r*+6J!EB(=`gwV^=j)4q<+ku>yF`_#>6 z$5JD%oL%2{1A=QES-{^Qw`cL9-NqVe3xE5CTQ6I#{-Z?5)9N_QUbNz4XLtS2u=`tb zLw{9+{`Ec*xoxJ`Cw=T|psw}dy-%q{?A|MLVGcdt1_a#Q>4CG}GnNHdC|s(dyQd%ZbHHt=G^?Q&j<7 z>}|?us~p3NUX~@+b?c)S<-At`Ie_AM(im5c@h(2B(0w^20JBr5_$44Ob+SjEomhoH zc?t>+!d`6JNvKuW_jZ7^Q=q7&+3!*7@ZI?88@M4wvGqP%7QA1C*bsmZ(Q_E3E)KPv z{*VrUdhp=4V301t%T8z8{@Nr>bDXdXqNd%qLZ-}tE}0`LUn7#6%! z4r^sxyS|HW6>tzX-xqAWxpuvaee(+x_mt&CMxYkxhY~jc4Z)ZD$BGJ9l6K6iJvs}3 z;;{MNeEoXNtK3cXKtb^()Yqm7>YdLavkq7e6jwjZhh33Ny`XgP%@4q!*nD5V`uc*B z31aIPQdlW*5m2L}-_|L_SJfi$gw|(3(AZqJkVR#S!19f)FQ`aTq0cIVnk3)l1;Eju z;FfPwK0z7T#CSRB6(A&Ry`MxjF}kd8am!Gy)UX?&P-CsxaXUA#M4Ua4(*v zYXc3&24JHZ-mP+VeE%gQ(1UTHksM!k?g6h$+>^Uu&W~YLzBV8D3LXRDjBNGloqc@b z;_W+8LQIqt$M)E39FH@4CDkd$>;cPt%%0Zjq=FrF$%-6&0{Gp$M zUMYuxOtHAsK2v-G%Q&^uLu>2bD84)fzs@?d8}^*NES?X|$1MqC0)4EvKe7Fv zqyy0LqQ%zlYt&ZSEVKXf-*v=*5W20s3D5S;rfc-!Vtn4BrzB#g66JgD=7-c7aMUQS z{vogP=NF*=cSacq;`H)$GohPIhC&=(+}v63^KpN6P~ZOaRdCaUlc}7Dy^8A(KD4PE zQAMSb4qKmdYLW&<+qly|ck9ExcR(9mr-l_u-1XDooc_x=Q}%s&GkaQ$6Zf7Hcf?sQtmQaLn&Ej16Dow^IS)9a zZ!Qg-6z$j1d?S2>w=-s<9c+&H=H|dcKhJCE0KrVNTRm{mrPJodfUnc3r9Y-adb!{O zukHZJJOh;7xZcwp-E{&iZkY`wE;$QZPKoo@g_+a9YUII~v^F$OO4pO-v=ABK*vKDj zy?kNphsT*0;SIs_we%8<59hd-RHtLAk!ATn%DMd(l{4#pDU;)13gNys7?cgh*9Kfz zy|N4Ks-j18=dvt^8j~S~Tp3p!6@^Okx4741$;N9Asx4o~P4h=e^Fl6=eNHw#gN}m} zat#%@^Yka+>A|p_BWVftPf%Y@F94?bR7UUY@EXR)U`(GIYqwfY;Mia8HX0(h}+HsA@_8 z53MIBVzw!dzL%0c!LfBboLRckflrZ$rc;CzPvFhEO{L?QEvx-krPHgAWRK+GV97n@ zb%}$8uF;bc$HC1az`?@9tp=^_H1D{K;uf(8SWe>NJWQ}j~y)zwAt{T8bsOjLuVCSYDP&hq5)CyHVwzsPx3zwQ8&dR z1{T)8{s-%Db#r!-9A0AFV33l0SL8Z#Pi5^Y*gOdl7yk#d5$>C9qNGOyTSsNHqFgeu zHz^Gzxg}ruPKiP$tLer6V6wmo%44I&K2ZRR-64qs zP(||pxJZ4%$5B--qhf`H7DXAyB|Gn_#pWHj$N_Y2EElAIpedrR{PIu!v)!#Q7v!iY zTx2x&%nCL%u-K3yn_X-8a6eu1WjC~Vi2~eS+J~OEu%V>Io#aF`AYDk26!(8MxuF@p zQ@Nct9yw$$73MXq$&Rg?WzaP?Ar1tcKrLor6CAci>yZ6_-4ND`lsL7C23ETI)j*AWAgtFDBi2YO+Vry&#SpaOajvJKFM%P9|kNh1FX-@r9i!y86c zmZFzqh(x;fzCK$|FE4d7GqYQtUn!;4)C?RraKQbjkf}}7Im7RUKv%qzu5rU-qsCqSW|e&^IK!4e z%X)&VDpZf778N#6u!ZU`nT(W3a%%cZ86t@3)%&eY1*^r>7H*h?JDu0E z=%@%VA(T}Ue|Y;TSgZF}(Gf%~u z!XJO##w%&PzgFr+lW#V)5&PYf<{PdFnT%Qd4Ro_NiDdYgqai+(FGt2se2)C68z|*B zK*X=V-;2FMySbqe{J##MUZ8KFj%|3uF}2Ggw6V>JvIF~*ib_giF&L5do}T+RZ{ED- z>|Chq^TPHEFFYl0ii@<$)R*$oC_{m7Zh0srtL(3yvH5V5$1m%^#Dp@@F(T|Xvn^>o zX(hp{M+1l9$@q1ohDEnR%|<>v!r5m8OQyfm04FT>LOy^6Ew*P%q|JkV$())*(gn-j zGDyt{MziB&QljoHZD&eFMWHDM#}?)EIh}x!kTP-#FY5*8;63%nq!9i zH6|t|{N$RnHdnam*abZdDv&OWG4`>}^UUF+o1sBusK-7EbZlRbrJzZ1XRmidmB&@~ z@vJ4GOTeejmfe?r@|Rrb6DbWd_Od*_QK;i2&@ovPuRjuJEe_h`nl-YZ)+L}A?cD1A z_oY$B?#~PnA59A4Kf9-ukS|Z#?ZXsf5+{RYBZBmEFUYRWDq7;5WkqG-YvP6XTRhu8 zd1Kmz=xEFOU6*!^ekMpZA8D(BO_!5zJ2x_QMYm6+iNWjeHEX?V6D8z!F478K-`JU)a}T8s6dPOx$7DvuBfEfA zBTUt%Jx$$>>df`V>I+ZkGJsl%IG1t?DcLzwb!>A#IR#^a);z9uQk9z+yLJ@IC-$t4 zYj|18BR=_zPgqykGCb%j8)KdS(!ZomMvt`0lrcIdoG`oCC|Q#zh3>FH+(vr9CNEm{ zsk=pES_RYX1qH^M_xEQRGxy1y3{P)6Kfo?U?=hoU^E1=UC#!3N!(fO~4AXHf#xlyR zyoa{d^LBTKwX|mi3Jx=qHcbYhB?u)AMlip02$~*iXLHQ?QpyC&;G|HSbnDc8X(?jQ zy^ge+>3n?Ku`ydb-W9##1*kE-W~K;_>Dkt@sModBFilr*L5PYgIJEYVdmV9l-T$^R z!>QR_oQ^8_dPG^!7Iinill)V;b-7Gs`2jhg#<_m+>Uo!m<||e;-f3~d)<4&$oa^UU zmzQhMPgI?D!w|EdncoouFg9*PH9u;8podfoC9S?FS=7FU4o>O zNh?O!Icj`-e3tF%RKShlusVqc3@?HQ19V_E;!kw6!%?bd*<8+@I1z-9ehmfEoVf>O z;ioRiP7-O0!xgO$7>fRoX`A+xzWc)Ek&V~=2L5*#Q97TK6J~)Ps(fGA))toDb-$#x zd`+#LnpkBP0iO$-v^O)0wW%RJRBye^$4joh_EP5kyq?Gh#J zU+Fhybe1A*2{Cqg$N@8=fS!sT2%jgBwfyNjo_S9sTux6RYlW}u3VeM~{PfG>flQIh z<_!`J*i`d7#kda7ronuhsM%EYnHzRbSazHeF%h7whxkD2-OM-RvuN?<&&*+2Us|L4 z81_9Jb?zt@eM|&_MBmbe)jOi(j90g~B}2c|&+86!ZnF8fd&pPzjY_STe@qxoEwE&G zkKN4vcG&mJ=lQUxKjf6yY40#LvMJ+0v1#_V`iTHX6LC=X{?QIc4mu;f?MY zPG%4Gbj^f`shMyQR?Di%4Rwv0H?MNn6WpiATC3~w7hXla6CQz2xKcEJ8SH+;blas4 zcgG*L=$%rqaVFI1my>LQqk*NOt}k_y>a8`-Jvq)v6uDrS5Ct& zXkSPRCdf{7=YCIn&vjJ{_#I_*{#ywa$HvP0ZEDOpl^4EN-=4>4RWx@DuBSigv0&xI zr|uiiAs4)h4X}*Io#;8;=rPedUHxgL(Q~Yo#CB=d7@}c6O1gv;qDAUVcM~^X?ldO9 z*D`bb_Gmz2{T1qRm~?BuVq)wJeeR^^9>HVAqB)}W>}%X#ed;cEiJLR_ln8CuIY?2V z<;HQG`WXkZ@Bybt-Gev#WVE-8k8;_s)CFXG4tAKwH|1P6v$uI_Dj>2?oH^r*B==bEE-8%n?SwPK$I2(- zP^6nbluwUIb{W6p$~e^PVVbyf##WlmcPd~=1tu` zOeNLTGD1n(nS6Fm*H*j?%#aG3K32dW${|fww3qzTJXxfoCo9ixqQuYDgtgD^BTu7; zYW>fKaB=-k&T{0MIa7^h^?3ZwyM#pcIARlk$D(kKb z{>mislYFz6&0-L)d?j>?o`jE(QPq|d5;>ZrZ3nWcP9dxNE{hT-aDet><$M{onW=>Sn{_9r#7>ure&xM=tVX>*ocj5zxGXxh_2IA_4F`R;k&`^%sFS3C z?csNoOHISHE-WlW=jIBw_xGDdOxVgCHwxu!cy1oGD%{`_&MVpVT5HpC-WFp+xVoLE zsazPXc;~u8bJ8>P&?TPviE8yB|NVTp5Mqq86ksd#nb3@*&DL6~v3D zbA8p!T%D9DH0n`+4@=<2UX#Y=<3Hx^3*q53?^DjM8OcpT8t-DIi$Y)*`mM~K)IWC# zo=R^(?<$+LtCU_?ZJIG*KcN`D_?(Si&e5dYGl%)U31U7z%~F+k`fi*sDVt-BuV&Jf zlhkzV%h(rP>v&=q$iae~t9#S!KRwICs6N-u==U8rxyo%j#jJuAj7T{XACTfF$*aY% zU1-y-=&cs$r~S#%l!mOHxWc6umJ`8y(#=k~{v5%XS;$pOXLT9xRWx%n=Vw%_xCcgeEQh!gL1uH}PiAuOFo%foVMuq06 zCpqpzQB2_@5y|Md5IVxB5kGE8Ha0h*uC?-&&67pd#V}Wt*Vv5?}mAZT_La<0vM{rOG4wvdOg@3gPPEx4 z`${~}SiajBKl1r4ktFl;oqg4O$)nWo(=;NxN!@t=U;$1B&|tpWg`|;3UVPN0&DV;X zQD<{LhtDk?l^BzTwLc(=Cl`#&wz8T?C)?r_Bpr95uea*bAct44mfjmnfNQv4ssB1g zowT-nT&k+xpEJnkaIbV!x6L_n?kwYRJfgYaWILkSF@uUPrrCSUlTmdu`a00ioEG(o zOiqOSG#xE^)%>b|=j`Qh z1ziIkSmshAk3P@(o0PdqIIC$7sapT?K(2<+QMz;ciJ!Y#Yk23_dL$|;y?!pV>MIh^ z>euY=I<->D&Hiz)NY%x_qc#AmYoXWu5brJDQwn@EV7e2Wex-{|l&E2KL1S@un$djb z)g~^>V_&#rbGi^_#uoSMLtTghY2PaMAQ6P%`{`9*xj9IYLv;FKAxBZtVZexgiw58e zP;E3Y%|xk|%JLc1k?2ES^l-asL_2HpM3&<5R`G79-M5`>5s)Y6N}Jm~R_-_-E*}dg zOf4-JzUbe_N^aT3zT1|ey8sUw@DC*s5)P7&-)9MY?7t4@SpYXGDwZwT^?sjX76pyW zM6P?lm|s(ZH%y6jW5;FWUD@wSW0?hgT!+2esNh zrTQpyY{76eP;rc$Oc@;Mc~_JFfDoDY4!FUlkGbWYcw+s#*Yk}L7cKoqug19DpG*%J z4FU1SJ9iuvx;kYvgI#v*?kD~JL_4G^UsKN*cb5Xj-fMd7ovWed__|ZrfEy;^PJV(!0s__2=>~SSm%%&*!OS#ch-wfKoYO`8jM2=@>-s{N;8t+1BF?Y;6N2 zmd0o%n*iwUtW7-5AN7Y^=jr4lw8cHi2PL^3KR5Bm!!MCQEeg^h=^~_`Z16nY)`_pF zy?OTDDa11iRAKRL{b{?<+QMD-{5o>3fTsUA^enP~h<}N%<^2jM&C&FTgOd4Efj^1ox1J~U_GV>PjwteC>+ob!hS|JZU zHD92ZOZ56-hUq=74?@T$#jn0L>&FZIn)2r=>`D%!Sj7SCwE1d?3Z%t~XdJU{cFS@q zC!BLjv|+QCt;31(;q;MXB-m&DDj9~T4%^f}{#+xNTqiGQt1?FB$i7>DWK>$}t46N7 zkhIW{mU!Wi1@}=bTbpp{_aS1qsnBf%AFc=2GNh~u@d-Yh#!3Ln^gRP~7?WgDEmvsY zr1sv;RN#5z__4*^WU`Po9c|re-n#1CkTvh_$%8SmJd9os5%(q+cJrjhZg7JCRgu<#Uuex08J*)Z7i4)*7nay*P5WsAUUYgi{<$;xO=D}HFZ2R2!Zm(#@n6;&Klv6RYo3ckYdg* z?Y+kwTO!TA&#=2gv(SU^HbCBtboj$dos4kbpzcDl{yU-#WdE#6iu_yE6)r&$Rl0Kp zsd9Y^9>|KyiCxY41V0|Q5UG5VL^^`neaIgmAb0lY{Nd#lV3>dF;rP_lrJS6cv@}d7 zZ4_f*us{RKyiu6o8%T%b<_wRSG2U^;!}7<@k=d)T7?7}`hI4aFEQv3V&paAfjJruk ziK}DI-n$xTjO2XrZRR1p4@CoeNi&f|WzpZ428SBLX$lp}vk8=Z&? z7kfQ>&AF;rgweCgJRIRZgjAP1`sO4dW?Cik_Uk>cwrIHCC(Zg!!oNSU6<+VULdwuh z(Ym^=ZL-D&BMV)6t=X=`@Wcz##p>oQZ;S4?5pvRBRt1H+ttyH4n+b|bCG?xB{-!4n zV4H@**EcE#Bz_bHB|O}(z!^O9BG+}b`+;sD6yL~vUtDLWhPzgIE`&AX{PI^Y_!&b# zu~ji#_gMOSDStZb#340C24mt9V!lfA5rN(o$95tX>|`Re#xy!mwi}dFy}ZV2-qX`_ z4Gyo`^cS|gYt`MwbwSK8&LuhNITt!h@8j9@>8{k8vMf&_u&1E>Sh^+*d*xl6O*%9uUyHJ5LGN&|ERFOXXk zBUu2z^UXfdci|JLQ#)i0dr_pQWW2Kl5u$YSF}Q*Sl9hf(u0QSP;q2 zLG+St%E*^J67B8n=H=z(*Sx$c?%CKp@Z<46wV5e#YTnq`c)mwxf>+%&S9#n1t8_=I z!+~>ga)OHq=fdtaJvXu+%PX!qUfi5{c@lV18U-1MaP7^QNrCNTkD#8U!l}T=u&fDT8ILHk47iZ-pve^VNpiGkDyP1s*Oh37!VDIS*QyLax z_|mOoooP(`i}3490@AJH;U9}zhT?`+_M{>AFUE^{#H|ebA)M;Zq}os#9v0=041|2M zW3M3JY;X^3X?;R;G*xt<ia44lZHRPUya7CEe|Ry zlCJ(3vJ$GYz{phbGd`Vdn_p73UHh9iQc7ZL}7b_)qoXe$8C!WI=(8$pXP<*1y(Itq~3i9S2+=o zHIx8Z!-XwvehR@tBobdvPfvdX(WZ-wMky=>b`1nTPUpb?r0#oz6&|s_4gYmMzORkE z5T6f9;P*F`RYB=2H8P{rof4iSq+Yk_E2J8g%v=IV#J+ol4MoV}b${vBZW-yZyoy=Z zbR-7Kjlp58r>&XGjtW&1_ouhy-RB4X%{wg;l!76#d4H{NCH0ffb#Kgz_8%lZV?f#3 z?blC#;gTGDc1Ck)x0bQ1OScUf_4zbqxAC9>C+SMY1<8~DHV?xfvF^Sid#o^bB7ZV; zt;m!MQAmQnnC%=r-p8u#b!x3w)$Y z@o{55jWc|Y35S@X?Q)ZFN~HD%fC0)x&n1Ro6V^3Q8v+)brmc| zm8ri$9iB#(cCkRZXyoo&<=jM*I`!}g7vefT-y^mJ-qVZ=o5^BDwYcT_)3H&Wkp`ky zNB3qgS-4?-tW6ZuKntici2_$C%6u6q8s)2cw)X6+Xr$=TErZLT9se~!(kVxJYTwxR z?~z$?GAmT*t8F*nYJyIF`8XS&?^f?*B+gZW3}S1kNXEkSQY7L~3cj#x5K|HFjJ={? z6&Y9nY21avMiRlqTRvYi!Z$nE+RSVKWIP;Jsm{nq_Vy%&Ezvynub!o^^YBcGENSLuwnsle3a(!?La!JDjZBoZSywj5(Ri zBEALWC9h1~ssTpKUDurFapwEzxz$-Kw^#^fn0?|zI9TGrbZ4<(L@IS(ChLy{oNgQB zZE=>6Qug72qH=9DsTEoMc&h?Qx2{?zTBA+_Mx!J20 z0Kmwj_cxx`4JuwLd|!OwO2qnikW&TO7O^IaU)dH57F1uS+jC)2U|DW-pk!$*hv_rG zt$=#S#jA((*!6vyJSM6~U@4Ti*POWT0KSm%M*$yb?T63-{xE923;Ss*^Xr?_K)527 zCVI(3{y>HvFH*dd;QwHCIAgT3pU3p4Zhw%S(BtEUMwNU{W3Edc4ef-wQ=AqVmTseQ zbm;;wwBz}GVdE<*YNwJ;SvJR60C@KrKv2hj{P@YHY;0yW^H}}+bK%XnAj-VE1Fw2m z>)zdS8;ijZ!W*Ef4UmV8+ge6eFGSOu+V12qe}9fD$bQG2G@-wWyx(-Zu=0LE`315= z@Z#2`~q47ofrT>udbVjPQT-o$3)c z4=GFKLqCiuQ_P#6XxP2J;b|M$&ep)5=Zcj6A{+$yg}+hwic+Zf=0ss$Udlh;n9HVI zc&=)P&STe@Ww-6^>KgOEp{iRY0GzhUr8`fzP-?mIAA$K8HT|5$9|T9f*6q{euWR?` zFe-erneSG;=Btn_v|;%w%MY#|TVCH-t_t%vOf-;c62}~~)9vBj(NEj-#wZ;*CG6|a zk;5NcRg~D&d%L^u0x&X;LRIjKJ~0gLB9F)o+zIwr#S zl68s;zPRBJ(!wAZ8;ltG6^u0ra*$?qb?bYX+Ul#r;~j0tBpPMX1V(IFY_@+uYFe90e>_|{t#dYX9G;s?UL{%^2oIN=^f) ztd>vvp?WPZfj~&h$!P%?dQnmI=%^)fO%R`=zN%t|Qzk|Vs{&NB;lyd(vKHpQvOUB2 z$9_3+k-BokI<*a4bfV62=Ng{xll48loEFspsum zxyIVF2SXaoBoF^>WM`LtADNzC7NRn_Df*zYO_2}#_r)i5W^VO`IZ|HNKAD9I$DwR~ z^kxqY6ntaR7;u8vaKDjUZ-ShfOvb24MMmTPSQWrFNC3&VxBHX3VX;LkOnC0-Q@Y^T zYJ?5Nb@ax$cZeAMmG|&HE9EZ(QE^z*6xBUGuO)@3LHzhM`Lg+J@&;Z|rgfo>&FTbi z$h=R}8?je!!Z%QJ<4JZ#&U`IkGnmt+s@zqo1y(EK zxz2fza~f(g59W!`7QKgF63vw%r~H-bz*VDaw!<(p!R>~@HND5oLU6_w&6-)YvZ5JX zoo#UI%s9uQ(D9+>)02fWLikjZ87`4?(!w*dx&!Kel33K_l)`Jo zZOhjU{w5^9Q-Xm?W?~&UVNEnKvS^NyM_wV7Vfb%;)6T3nb!yG996}GZ*am5wl+r71 zZ*W^(E*K?CMX$RPHIFOL82!pG?cE~2MOw@4Uis_XekVAa7}CJt2g4g;@Ov~aB=&;& znLlD{BRdm3{12K;R|t|E7usa|#UJd(r~K_W|7YSfl(n=i9%_go+dD@JYaZ~_USBv^ zZHix4w;C8kIif=n7soinWu>qDT}%9z$JXtJ)e+P*E?9bjr;Z7CZ!9yB#a_Lguyr~q zoqHMdcp^*Pv=AIWHWYmQ?4dzY@jubpbMNdzzj}Q;Teu<S(d_dK4GDL zEyF>Bzp0G>{{3~(ze+gopl8*xEPLr1d)$yd)ut*Gz5R-m3am=q4o{ShhIQCpg4IvX z<2gj^o{q0hJONCA94IhIxe@nEv*;fIZ?t`e z6R=gY@6S4BmTZz{|3(%Dk0;C=vu%FIXWpN+Z+&f9Hmmuig_+x`Rr`6{>=lyH;CfYJ z^}DY0M3Gipo;KuF+;Sfd;!)tE@O{M+XANupJg+5q8hM=gv5wXTG?VrvVP=T!{s-Xc z7nklB;7eIQe6!te*+#B3a;$hNmOK2kU!ba~|S0DDbF)417HNIK77 z=pPC_{WY5QNTD|%Sie2*?%edPmtmx!z6{Id%(HghC5k0b?p~vGnAgSU4JV)-U#JJ84jZ}4B34ynvufn2UdUnY^#~dn3^c?gEsm1mssZ^l&+-)tGQ49+(7fg z7TvU*v#aK5O-vAh>i3sJHs^L>%I|-DTLA9X-7F^g{jU@aC$j5p(Dd*q>w^y0D85e=*Mg#J6?CPF~BTym2Fxxn3 z&(1IV4j~(|tAP?((DvG3}J!2 z&4cjx1*kYU72^eR3%XrjcMlr)6^v+3hp|781O+FjcfLds7__~{95R^=R1^%%$${Bs z(t4TU6X$uG!h88Mfy`{bBKB;LP0srWuO+P@C56@>7nT4P7%O3oVaB9*+Kixfbi>Yf zD9#A}D%W{%mv*aSC_E|k6X&%EQF8S-iFW6czTcrzhOLw&P1JUM6csx1*`HD+cW-B1 zKcw$c+bR9QH9?v6?N`#J9WnUc{*Y(8e8|_v0!qoQ8|EmOS$tA)knXUmeqi2yF3f4#|tUg?N_S& zulg#wbCla9Uoie7e;yFuFHkwiUzerG=^(agAWo#uCmRx#?bpAXm!2S{;R*{H@EV%! z?q47fSknMFxB!w&)z#H+UnvD$Lm+C7A3OH*oM~nEIYab^!UyADUZDy`N&$H543e=K zv6pZp7X3uTm;W;LNC^(FP3my=pkQ};DmUYI|U z#|s&r?bpAPHi2>9sc6TcNBi=%59-zCf+W%+RenfFNLqDu|JSV;?B(ncjd|=0TD>O) zxey81kH;13`CL@=_;8^)3EwIQ?)=l@Xg>$s#E%d&cB~?+Q;Z;niK|(=0B&=SZGMe+ zJFwy1eGM>@7AR3{z!W||fK?qakUh!IbW2h~qB&07x$={>iHXUfr>p=2+4|L{qt81y zJMV;}Y_uacX$IR9`-^m6(pI-hH1F(t5a4n5qw74#mo^iYr0#k$o88jLiv7Mo0L>qR;{*hR{@a2)x5N+@hPEApy zYz4j&N~liP*rvc@W-d^DW zych{?$@Ay6&?p<;ivu1rb#ni(p{aw<@8>g2eC1S@q~IS^_evZD_``$A8Lo$FfOE!B z-7&`IPi<|1@L=D-EXuBhUGx#;7p&=-BL8%2Ev@caLLAC?H38)xi5@eBLSPC@_T^j; zg86IQs@l^JabKEZSIk9HTM~!>-<_^B`|Q*zWp$2XJb8>u02D(&v8Sdca(rT9Uv0tS z;-bcn%Yw(>(>FGCI~~Xv=HoP^#YUat#(y$co4opu6F3Igx|@^)hvv}+df5|GqH(@c z5C@V3F_$z*Dv_r+ya3lXev7cFsFzwbl1ow~4QHWAb4D`&?W-)*T@iRZW9+Ky5C_o_ zW3(j2?}oR9!mAlPs{u%vR$+hdb1RLU2Ot>%t|k#A?p^}!6jas#omC)pS9HYn!V6&6 zHmOt-Gc!VlOoqa%Uxiy;&H!|C=O|J9MHL=wI9Srhx=*}%u9eeyK7J_%fNJMiL}l;! z03L2Ku~70tzt4MGtZHhH5!w7vnbmv5OG7J+1c8g^;TIRtzQc3Pxm^ll8Th(|C%pD# zuy6D}tE5}(v?#y{3Mg|Q;me!Lq?>B8uBuVbZ0=bvu^Sj)I2@kVwl{vVU8r(m;cSYo zb!TiqiJd4&ruk#qS{?+n>dPGQG@}>?yd;5VzEe|ayoC;f3KxO?xac-*+}_>&Ee0?_l<*<(LE`!#`>BC*}~{jj{m{p+<|k)YtXSP}-hCqGKu zpiei+F53u59#V}qTsq898tH6vR|bFYqq1L~&moD#{yJsZT?!?;RD{6!0OW|KfhVqu z-JP9!H}v(#sJF(y&WDD>;ihsJ9bMQ+^jt}PHJd+#@BcfUmpbSU!LV-~A$CxOJ782* zIi$M}MppG>yeV-kc2%bt;}-anWr{4k^40yyd=RvKt9W)6sZPKfW80Udn!lJ!Vs~xj z58P%3{(xfueSRTExPqtD^;BWfz_@wEL=TICx#dwhF}Q2ZMo^Np(NkhAlYVpwV#VX` zNsk#PTfn=y{8b-Af_L`0d;KUbGAo)>9W#Kq=pi|v0i)F>^~2QObINe+B!mOWS9n)H zklf*6;$HT=^LUfq@BkPX0KdG_K`u6eBPJeovaqJ621aRk9poAxM!bn;4C>fnO=s++RHT^B&;aC%bO*=vG!$Nj&<-M=FVVi8?Pj;I@$J zlOoTHKI)>8T|M<6lz|&VLX4ra6bKYeTX-wX>R<6z8v({`HlyIpqBkJfT=+6Je)9U6 zfAUyP&}C?4?h0kNZ{pU@ng$4`aU0bDTf2-181G`ctvZ1_&*`Y!Z^`ZiO~kDwYr z^UbS_yOUE+tvrcfJL>@TLTpTM=AzL^*P9f-T-p8&dIa-*qvFxkPqU+z<&Q*GOw17C zFCZ3Rk1`)|l?2<5Z4x2hwAhc;NRWBoyY%h#=(91ieT38a4=W#q_Uk4v5#be+8?Tuj zSu_J)Se!LQQYA=f1L7uTi;4F3fvf9FNSJRJO;Q`}-(fugX|}v|D+xx414UH-aVL2= zvBvU^wr%TDjI|5nzS%lV#qYBL`k8vqCgw=bX;nGB`iUPCv_0Gg_Y(0pWW8|WFT4S> zATlsFZ(5RZ$_nrG!+Cx^c6zU_O^5ajg35>{NPAC+m~uIo=u85Fkbvek6{tPNB00I;#Mn9|-B|>q`Ln z$C#+7qZb{&|8cte<`x~2r=e&UI(2WO>TV;r`aer57ib--G#c`Vo#vgAEl@qN5PxBv z;F~Q5$ok3oDXdP5O0?MOYVs~CSxMhjK(Un|tT0M)@N+-qn~_&|N4#8sSoBc{^R{Z=iJ2^e6jj8D* zlg#cWk&H7oi49+Z^dv}>XAr<3?0E#S{vGrijkz!Sd>C$lM$GDOYtkDx;pCRCr%iR%fZ7Xogr=G>Gx|6Lyy z8sczY?^G|X$10A!S)$ZB4x+2WC;_(L?AW;CZ!SGkPaVI$$KW-|VRr((-C_@@bmIBI z7pbu54o29+*<#?19_|CH*iNh3u94S5aXw|RqYD8&q$pZQvo zW##yWtR-0E8DC;1ybJyPZ3CHUVPz2MV`J)WVgJ)}vb`d1gM#nTCf@?a^M(&J8M6ZA zXU!j;9f{YnP6kDU2gLONg@Re&=CE@~TGSy^B@>3K5z! z-W~V^K%Dm>elcn_0OQZSStxTCY2;D_v>EMwQrC9kK`U_N%9Yh_96ElV;kJ@aQR<@Bf$-;q2fliCFrTyFY| zgF;t9K$KvZvPz6yGc}U!8@FEyv(CX6o(1|Rr;vYP1FmhT2as`bAQMcI9FibUf6ln{ z6YRqA_RdbriOI?MTid%3b@B#Zsi0|PDNsrOMF|I5e_1%5?3?oV2C#AhU(*XLG|qHK z=lEr_hI$WuYe*&l*ZfEkIJK&KZ}8z)ZM7XcawWXq_Z8dpJoovx%JS?ULN}~Lp7iMFl=V-^E?Bqs_}JwGGKpcq(x*XCkbf<1)pg#M|a3lBoayO1YlZS!u_W$8C-5mYfyNn zL@TfQ=0?FSu0*hUk(E)5O~UJ_P|jd4*q`9lv?4+v&0xsAys=4R6Zq( zWT<2gD9V8SnkIGFH>=rK$F_exe$diW@y3}-e&W=6DpZs|$T*6d%J$;ZV-{!hGzQB_a@mDdj_grIx_u^Axc zUUSe3)aSanmF)1ta&ud0OF|>I3y^+U7oe`z1hVs#lm96Iy$7)seNMlPZUN;lyGg_Q zu=^w<__#UF;sM3Wxd6%D;jjy>7&eiM)@;X(0S9MXPo)Dk)$Hdp7Oe=81{8;|UXVvW zJ{EpHTLyyxL=(S_tJE--mu+;c2&jr}mEsLli3x_yR2chmvw(MPWgdb7gS3_Oww%*! zrJ$o8lsN6j=5>ZGE&s_24|)8U%dN)cQ%3j1?i2LuJs|LezaaxoRVzzq5r0%*C(*eD z3MTU0s#Y~VbHBz<~3E#!z^xo zwfJU@iCGP#hleKNwoW!Rc{&1}1PVR?N)`bvpB^l8Sx3pWHBQ{Jr)XRQ>rx5;?e;u2 z{)Wx4W6|Khg&j;yAYO3ZnEQB5Ssy75pSzS^^=Zm?YLX=T6VuVDxkMG~Lba;4G`Rq-`La)ZUQS|2s8rEVH6Xn zUcMkLEuEH?^%2x7M?;it@)S-s8H`3Jyj=9zhE>LR4Kq~}l8_erN=oQc)NwHbi z9WU9HMa`ew1u8#)OJ@zp?8m6@jQm#R9ULZbGWLO-UTwj4neso+3lNYGj;^cJ$YcQaeqBjOu1Qz`!yy+gPkJsDo}D?C=EFyVR*bG6n`^EMefUIaJ99!7qzvu zHKo9$rq*2&74`bP*gW_;m^$x8*@C75ncqBtzeKc@Bf~)SN1Mf2TU{w4HNnrEr9vxg z0J#UHqQL**d#l>5oB7W2!f_!ANr=7w`SW1d07RK!uc6AB;*T_<*|SIWXsGSoYa2bZ z|M)9gDa6!$*wvJmP>d=x#Rt+{%jLj`3Q=2!+kP3<2kp?abwUJ`#PmWziqHAGuDR_$ zZPRwrQk508*f@Z?Ts0UR)#l4cGREqh*8?~&NQ_l!+4Q9}_79YR)Htl;*K-41I5aOl)(&miezo4=t3jme%%X)v6zZSv#XDg{A zJKIN5rv$v&wak2172;zqPD5Ex)fb@43Az`>a&2`FaqT#AKkg?;qbmuEZT9IYAHV2_ zngX!Lp$Gv*C(Ysh zMZvv|uhyUrTWK>~zs3@XggQ6LPH^*cWGSKt_WHvLb;DYiH(R2J9mR6x@l;*{J)^Y1 z=-+>9Is3>ts9D&v|5;{vTI+r@jrfr#7p~ug285OVvb1wwE(Jv+1U)q%S3N&xVnMqc zFiF-YEl>Uo_P2gsg$iIF^HgXLMYN)R*di5LSpf|QoOVoLNz_z+iTl=v89{fD2uaY` zCsYD-DvRR#Gs(62?;Ivz0eV0C+HCNlm3N^%GO?C`>#GZMIVQsL+j-dhY*IJ$*{)F8 zceIUcbl8OG!a$o=&?E?Dlj~~}vDqbq;wQP#E#1*o*QIu5lK22`cfFkH-V^ErSA7q` zRH(Q)A3jDsJ^;J*^eM+)Yzj4_)~QEH97zZEVWr-Po##7s@rgW)(Ml_^YyE+2-VDJf zfn?XWrr9k?%7)*+rW>E>TrE_cnAu~UQ`LqWix>;1m(||^ID#)dvML zhP&H-zsz?Ex8_2k6zM74ANFh3>;CWpLAWu!$>j_pPr7Z!zQUEOmFsUqT8WkNsYZ4%$L`<-GQS;T*^f&! zv9`E9DHxbkFBnXRAeudQH}nj3XzN92&>q~9-EyuK1SK#Ob&n!yir1WEsV1en$LIEj z7v#A2&2??5D&H`$ybgHpZ;vdvjp$*bGpF$2+8Em#92Mfv!7_gup93)76~zsYh$Glg z&F!m>eThGsEi?sU^59*ScJSH`Yu za7fC~Zi{2j>q#mc&8S`CmoVwGIX|wFtB2XY)LuPe?JRNiG72>8Fj7LZ3v%H)A5OXM zqJz;Kfdu*3$A1_`Ca4-HlZEE+ZZBIzUWl&}4}pa+qM) zZZyS%H&O;vzMoMDwh?OdQ24f;PLeJ^g^I77Zw-mRlO@?OD%qHu*dJ4Ytw#iV4RCBq z>``zAY@9h_m7yabA^6g!$EYgVzH@kB^@+UTv!&p;Hn^#4HrDTI1ILc;o^dTq_ zGs*rsL~zy~^b7)YwkI5bv4lZqRhW-HisIHKAfe(9rOg80%T}w@BH&=JP#3lq&>}-t6i`N(A_+U<0)-H)BFdDipg@RWLm*Ls z1Z6})2ni!fga`?Q5J(7F_Z7vq-@E?qNB`IB|9kM|fv;j?KA-DcXS~mOpX*#lb?}q- zlxQKz>)Y1-pwOV*E~C%BU6OWH!Us2wDA9x={AW8v(X|^Eom~~V?i)W>$GiP2p``}2 zu>J04fj;KC=~805tfpokCtNO}Ukrhv!*=~7yEJH`J%5S|%vL*8rOjpANwAMY+ds>q z9`|h;|$GlVCCH?n>F9<8$H&5$raVrhz$+_zs$BVK9w+GL>S=f*_?#@t!@2pg%% zs~u-#Ne)cTA4x#5N6}Hcz-^j4m(pGV%b6qw>jD#I|Fvl9lr3DW2?rNKs5F;**6+o* z;kN2S35{R{t+>sp<}tf0)e3{1J#~F(n()ELg80L!e<>F!UPs;X`R$0H)I86DIX+VJ zwNqS~?{VNZuoPPX6sYB~8!L`3*{ue=geQI}Z5}a75Kl5z`fiIyt%Q$vKMdrJv<8CV zSmRHc*Bu2HN(QHJnQm#Vxp&lIJ|UMYCR6-Y?>n0=Yg+x6VQ=Oqapv~H>EAxyR5Qau zC^L3gn<4o1RL6oleZLKHDiU@WrCaYq;%6D{vLfmnkB?7AJ`uQ)|9rQ~ueNkqMeh-K z+oA`w|I6sAw`HKbd{uY3_4lte@u6-%dfRAe|FYGa9)NVK7!1!SGLIa4K&$jOZ4pN7 zBKbIUpp0=f{l`t>~l=??eq6;x6d}(Ia$HBQy&qVZSvit zJ4VWOQ)i__R$Kmo$_qy1*TE)5>EHe3VA4dkCUAJjb*IWaPme@Q8Zq$$92NPVrJ0rz z5l%nk%`TN%aW-i{Lf#vnhMm||6$ z9P%ux*Z7DxMEcs7DJ~9mKU5LZV$r?z4^2#-!sI{xOzs&|qJ6&sQ9e0n@u~n@uxEP6 zXT?^nqnv{|P(yfjCo6p;xVz+MunSQ!PmI1(G4>AUxvqGBQ@7x_2jwg}EM8wafso&S z$EGse)wz_BQCRmz>foIziL7}XP*uM`n*>Ew@pCvZlGG)6gb z=FQP+-ZKl{o6480%HZBT)x6>0N!Xt55yVsyIsYiyhJVhza_TbO?1cSrWUR%zTVS?* z@V2YgkqHM9ty;3(atgxg6cPLNH-1dUjn$iW1z-2|)$Jee5tc7|qO=jl)QI^3TzX5d zIaHCVYWUb%cthtex4B2V?W(+fo(r?A6$tqY!>1P*aimB?nh;whSu$zW2jm2f1!c38}$M~7Wqa9>kQnCfYQEChOYh zLB=T!shBV#KEJ$L5=UUkxsz@+(^eHstEdWi{ijjba3h9pZld~WXuMhk#u(OD6{1Re z(wuJDJJlTi?LQab?=Wcou-aN_=2auI6r;T}tLx4&Zz0iT?hT225xKz9mmd$!0qk3o zEqvg15@+lPd^#yu>m#<{&YTMTu_P(@@|^0j;>+E~oh^bPg*SKZSf?5(Yc;gIBJ+giYu% z+L1BzCszC&zS(=Gnyp3_Lc*smP+xnM+<HbllwsyTR`JI5QtCf5M<5rqM!eUA8qIu6^}y2tqofqpoG)C%)A;_uzam zWeiJh6jsmhk=ngCKSHy~BR^?Vi`WHtZVNpce@I762r=PqwR~bLH~`#!gSXbMwTA%m z;$a}Yq!m6`>Di*uQnY;~%diYJcZRw%&Zw_5cOTZoBp1$zV^sM}HvA0L9W(SM6pa=% z7?MWJTPQ$O6#2}?QAxO{*YwR;uDJDR&X{LJC#u@~bggxf>B!i`;Cg&dO)SUbKK4Ro ztu1`&VzWAabdxaX-g33Jb#v7z!jMM8vmG{dCVg3TjCL1Ir&N#9g$Boj?0l|&b+(MaH?6wD0(#SU9!ppfb=2sD_=ADV3u6{H_NRg=FWv+0f%@#~zy%j~@tjx$ysn9x{RCgT?S zz~yXZD?u>be=Ny9a@)aOW0H|M$O9O-l=%B78)Pl}SvDf4OC()^%+HS>$Glt^zTffOu^{eYt~T{0d9`C>-S#_au=mU@@I3u7 zD>_h8(FjT=zjmcpU+6t z7%b+NK9adl*G~U3EGi78@NC^locBAOdR}Jg^>jZx+5-k_9`+OV@6WX?SD{{qm7lCE z3P}pS(;(KW{=YYQI?Gsot9zC8&V-U;!5joV{)e9x6=ivgI_bIl38}%Qb+arA(LLSnR&0+Z!w}h3;J;-mW)EmgD2J(8h7Uh4 zf|1rWMledrCD(_1N|S=2im+Nu?)ep(#5o2{HD)_JI)rytiFe&bi*Gb-oqC~E?^rv` zzdzI843W29=o__M$HG%x4!rZ2TKKtVGxI2CrN?D#q_Woub;Dh5jb*v2K<=2ax#|JV zDTOz>vF>kg)EFI+4X&lgHVJXp57sn@n5sMM2`DFU1A5K!iWQytUU=>1Lw|uY=8lZK zdUn^&goa|*EUgpJo|k#6Yp@v2M<7OHr^hCQregP#3}3S741*mnii&kMR)g#%Z zE|^~x366)30yWd6n+^U3_NGakg_9ecEHJtcolHd?aZo*ooox>G#ZW@Ops8H@1BI)_ zN9ZH2dEu#JWW>-GU%iDgkb1K9GKK`r&8lq#RYm>DL1*<-w@d(mj<5AQ&~g=X@V#5v zHEya%<=NEnYns$VNlOvmYkE{qjm!to*93K}YQb&mrd=dM5q&HGT&DBTp1uF;CwpAh z+HcbWXOfxGQFgs?$oZQ^uoFrx;1nv+Dt|>qF>*>W`}%PlxF*Y6nTbyw3?KFRHiy(-nR#w)MuP?GqV9hhO+vJYSad$t@mK%Z zHw2kzgLSp2{-!X;%|jK+Y-DLYiFCJ2@M%61iTI6DCOZ`}Y+EkW)(x?NCN}1Qu(mr| zfyrZZ269rqN;Otd-9)CCu`bfhwPWLtwyEN4NjR&Fyap;ch;6X&lG@JO+SW9aQ~O5i zDA)=CKr}z1t!G+RSM!})9crA4CWoS}TC@9ysy1VnSM-M0Ous)0LRM>Np>yw0pvSVu z&M^lYg=tDbk>Bv@N+TVu=E{nA6R_i#o8M)J4Y_EAZM086uOq%$>>Z?*$~wN*)uSow zT+_0O0}1;1$Wc>$baF~?DsFcI@g$1b>x*-OMRiiKjG5thSvA8HH5XNI72I7}<&|L7 z%KjEl-$dSe8&iH-A_7)w66wO?Rx>*2=7#u* ztDqEe2j$yqYqI}Ey#1rAcX2&14ZVxc`g{4Wb)i3!x2-Fi`oUd#lYbhU;BT~)RF_zW zu^HPB^wE@%5I2cagz|P?WtY?^q_OtL%FLDY5;SxAOeqj`OqrSxx7l)GgRU`Z67e+m zT(Ceg7FC))Tw#y~*HQP5XFHD0(^v_QP4YoKSo0-J1>fpY;FP=V`MZ*d=!k++GPsp? zwQ9mc4RsW-Or!F{B$X60%sU|s$2=FcmPxTyq6LaM(5W@*AQm_;rC~rF<0D==^#dx$ z)&#>H@2#(lQ}#NL=E{P^b(mC}H<=>@m+5ogz=Zj)jD%0DcRVmfi}7@G`eSDu_2ueq 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%0D1r7gN?UN{i_>#8qmGeYE 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 0 { @@ -292,7 +304,7 @@ func parseTimeout(requestJson []byte, defaultTimeout time.Duration) time.Duratio return defaultTimeout } -func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { +func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper) []error { errL := []error{} if req.ID == "" { return []error{errors.New("request missing required field: \"id\"")} @@ -314,30 +326,39 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { // If automatically filling source TID is enabled then validate that // source.TID exists and If it doesn't, fill it with a randomly generated UUID if deps.cfg.AutoGenSourceTID { - if err := validateAndFillSourceTID(req); err != nil { + if err := validateAndFillSourceTID(req.BidRequest); err != nil { return []error{err} } } var aliases map[string]string - if bidExt, err := deps.parseBidExt(req.Ext); err != nil { + reqExt, err := req.GetRequestExt() + if err != nil { + return []error{fmt.Errorf("request.ext is invalid: %v", err)} + } + reqPrebid := reqExt.GetPrebid() + if err := deps.parseBidExt(req); err != nil { return []error{err} - } else if bidExt != nil { - aliases = bidExt.Prebid.Aliases + } else if reqPrebid != nil { + aliases = reqPrebid.Aliases if err := deps.validateAliases(aliases); err != nil { return []error{err} } - if err := deps.validateBidAdjustmentFactors(bidExt.Prebid.BidAdjustmentFactors, aliases); err != nil { + if err := deps.validateBidAdjustmentFactors(reqPrebid.BidAdjustmentFactors, aliases); err != nil { + return []error{err} + } + + if err := validateSChains(reqPrebid.SChains); err != nil { return []error{err} } - if err := validateSChains(bidExt); err != nil { + if err := deps.validateEidPermissions(reqPrebid.Data, aliases); err != nil { return []error{err} } - if err := deps.validateEidPermissions(bidExt, aliases); err != nil { + if err := validateCustomRates(reqPrebid.CurrencyConversions); err != nil { return []error{err} } } @@ -346,19 +367,19 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { return append(errL, errors.New("request.site or request.app must be defined, but not both.")) } - if err := deps.validateSite(req.Site); err != nil { + if err := deps.validateSite(req); err != nil { return append(errL, err) } - if err := deps.validateApp(req.App); err != nil { + if err := deps.validateApp(req); err != nil { return append(errL, err) } - if err := deps.validateUser(req.User, aliases); err != nil { + if err := deps.validateUser(req, aliases); err != nil { return append(errL, err) } - if err := validateRegs(req.Regs); err != nil { + if err := validateRegs(req); err != nil { return append(errL, err) } @@ -366,17 +387,18 @@ func (deps *endpointDeps) validateRequest(req *openrtb2.BidRequest) []error { return append(errL, err) } - if ccpaPolicy, err := ccpa.ReadFromRequest(req); err != nil { + if ccpaPolicy, err := ccpa.ReadFromRequestWrapper(req); err != nil { return append(errL, err) } else if _, err := ccpaPolicy.Parse(exchange.GetValidBidders(aliases)); 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), WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) - consentWriter := ccpa.ConsentWriter{Consent: ""} - if err := consentWriter.Write(req); err != nil { - return append(errL, fmt.Errorf("Unable to remove invalid CCPA consent from the request. (%v)", err)) + regsExt, err := req.GetRegExt() + if err != nil { + return append(errL, err) } + regsExt.SetUSPrivacy("") } else { return append(errL, err) } @@ -429,18 +451,42 @@ func (deps *endpointDeps) validateBidAdjustmentFactors(adjustmentFactors map[str return nil } -func validateSChains(req *openrtb_ext.ExtRequest) error { - _, err := exchange.BidderToPrebidSChains(req) +func validateSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) error { + _, err := exchange.BidderToPrebidSChains(sChains) return err } -func (deps *endpointDeps) validateEidPermissions(req *openrtb_ext.ExtRequest, aliases map[string]string) error { - if req == nil || req.Prebid.Data == nil { +// validateCustomRates throws a bad input error if any of the 3-digit currency codes found in +// the bidRequest.ext.prebid.currency field is invalid, malfomed or does not represent any actual +// currency. No error is thrown if bidRequest.ext.prebid.currency is invalid or empty. +func validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) error { + if bidReqCurrencyRates == nil { return nil } - uniqueSources := make(map[string]struct{}, len(req.Prebid.Data.EidPermissions)) - for i, eid := range req.Prebid.Data.EidPermissions { + for fromCurrency, rates := range bidReqCurrencyRates.ConversionRates { + // Check if fromCurrency is a valid 3-letter currency code + if _, err := currency.ParseISO(fromCurrency); err != nil { + return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", fromCurrency)} + } + + // Check if currencies mapped to fromCurrency are valid 3-letter currency codes + for toCurrency := range rates { + if _, err := currency.ParseISO(toCurrency); err != nil { + return &errortypes.BadInput{Message: fmt.Sprintf("currency code %s is not recognized or malformed", toCurrency)} + } + } + } + return nil +} + +func (deps *endpointDeps) validateEidPermissions(prebid *openrtb_ext.ExtRequestPrebidData, aliases map[string]string) error { + if prebid == nil { + return nil + } + + uniqueSources := make(map[string]struct{}, len(prebid.EidPermissions)) + for i, eid := range prebid.EidPermissions { if len(eid.Source) == 0 { return fmt.Errorf(`request.ext.prebid.data.eidpermissions[%d] missing required field: "source"`, i) } @@ -657,7 +703,7 @@ func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.Cont // Context is only recommended, so none is a valid type. return nil } - if cType < native1.ContextTypeContent || cType > native1.ContextTypeProduct { + if cType < native1.ContextTypeContent || (cType > native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound) { return fmt.Errorf("request.imp[%d].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) } if cSubtype < 0 { @@ -666,28 +712,27 @@ func validateNativeContextTypes(cType native1.ContextType, cSubtype native1.Cont if cSubtype == 0 { return nil } - - if cSubtype >= 500 { - return fmt.Errorf("request.imp[%d].native.request.contextsubtype can't be greater than or equal to 500. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) - } if cSubtype >= native1.ContextSubTypeGeneral && cSubtype <= native1.ContextSubTypeUserGenerated { - if cType != native1.ContextTypeContent { + if cType != native1.ContextTypeContent && cType < openrtb_ext.NativeExchangeSpecificLowerBound { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil } if cSubtype >= native1.ContextSubTypeSocial && cSubtype <= native1.ContextSubTypeChat { - if cType != native1.ContextTypeSocial { + if cType != native1.ContextTypeSocial && cType < openrtb_ext.NativeExchangeSpecificLowerBound { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil } if cSubtype >= native1.ContextSubTypeSelling && cSubtype <= native1.ContextSubTypeProductReview { - if cType != native1.ContextTypeProduct { + if cType != native1.ContextTypeProduct && cType < openrtb_ext.NativeExchangeSpecificLowerBound { return fmt.Errorf("request.imp[%d].native.request.context is %d, but contextsubtype is %d. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex, cType, cSubtype) } return nil } + if cSubtype >= openrtb_ext.NativeExchangeSpecificLowerBound { + return nil + } return fmt.Errorf("request.imp[%d].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", impIndex) } @@ -697,7 +742,7 @@ func validateNativePlacementType(pt native1.PlacementType, impIndex int) error { // Placement Type is only reccomended, not required. return nil } - if pt < native1.PlacementTypeFeed || pt > native1.PlacementTypeRecommendationWidget { + if pt < native1.PlacementTypeFeed || (pt > native1.PlacementTypeRecommendationWidget && pt < openrtb_ext.NativeExchangeSpecificLowerBound) { return fmt.Errorf("request.imp[%d].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex) } return nil @@ -803,14 +848,14 @@ func validateNativeAssetTitle(title *nativeRequests.Title, impIndex int, assetIn } func validateNativeEventTracker(tracker nativeRequests.EventTracker, impIndex int, eventIndex int) error { - if tracker.Event < native1.EventTypeImpression || tracker.Event > native1.EventTypeViewableVideo50 { + if tracker.Event < native1.EventTypeImpression || (tracker.Event > native1.EventTypeViewableVideo50 && tracker.Event < openrtb_ext.NativeExchangeSpecificLowerBound) { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) } if len(tracker.Methods) < 1 { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex) } for methodIndex, method := range tracker.Methods { - if method < native1.EventTrackingMethodImage || method > native1.EventTrackingMethodJS { + if method < native1.EventTrackingMethodImage || (method > native1.EventTrackingMethodJS && method < openrtb_ext.NativeExchangeSpecificLowerBound) { return fmt.Errorf("request.imp[%d].native.request.eventtrackers[%d].methods[%d] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", impIndex, eventIndex, methodIndex) } } @@ -852,7 +897,7 @@ func validateNativeAssetVideo(video *nativeRequests.Video, impIndex int, assetIn } func validateNativeAssetData(data *nativeRequests.Data, impIndex int, assetIndex int) error { - if data.Type < native1.DataAssetTypeSponsored || data.Type > native1.DataAssetTypeCTAText { + if data.Type < native1.DataAssetTypeSponsored || (data.Type > native1.DataAssetTypeCTAText && data.Type < 500) { return fmt.Errorf("request.imp[%d].native.request.assets[%d].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", impIndex, assetIndex) } @@ -1014,15 +1059,11 @@ func isBidderToValidate(bidder string) bool { } } -func (deps *endpointDeps) parseBidExt(ext json.RawMessage) (*openrtb_ext.ExtRequest, error) { - if len(ext) < 1 { - return nil, nil - } - var tmpExt openrtb_ext.ExtRequest - if err := json.Unmarshal(ext, &tmpExt); err != nil { - return nil, fmt.Errorf("request.ext is invalid: %v", err) +func (deps *endpointDeps) parseBidExt(req *openrtb_ext.RequestWrapper) error { + if _, err := req.GetRequestExt(); err != nil { + return fmt.Errorf("request.ext is invalid: %v", err) } - return &tmpExt, nil + return nil } func (deps *endpointDeps) validateAliases(aliases map[string]string) error { @@ -1042,124 +1083,112 @@ func (deps *endpointDeps) validateAliases(aliases map[string]string) error { return nil } -func (deps *endpointDeps) validateSite(site *openrtb2.Site) error { - if site == nil { +func (deps *endpointDeps) validateSite(req *openrtb_ext.RequestWrapper) error { + if req.Site == nil { return nil } - if site.ID == "" && site.Page == "" { + if req.Site.ID == "" && req.Site.Page == "" { return errors.New("request.site should include at least one of request.site.id or request.site.page.") } - if len(site.Ext) > 0 { - var s openrtb_ext.ExtSite - if err := json.Unmarshal(site.Ext, &s); err != nil { - return err - } + siteExt, err := req.GetSiteExt() + if err != nil { + return err + } + siteAmp := siteExt.GetAmp() + if siteAmp < 0 || siteAmp > 1 { + return errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) } return nil } -func (deps *endpointDeps) validateApp(app *openrtb2.App) error { - if app == nil { +func (deps *endpointDeps) validateApp(req *openrtb_ext.RequestWrapper) error { + if req.App == nil { return nil } - if app.ID != "" { - if _, found := deps.cfg.BlacklistedAppMap[app.ID]; found { - return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", app.ID)} - } - } - - if len(app.Ext) > 0 { - var a openrtb_ext.ExtApp - if err := json.Unmarshal(app.Ext, &a); err != nil { - return err + if req.App.ID != "" { + if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found { + return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)} } } - return nil + _, err := req.GetAppExt() + return err } -func (deps *endpointDeps) validateUser(user *openrtb2.User, aliases map[string]string) error { - if user == nil { - return nil - } - +func (deps *endpointDeps) validateUser(req *openrtb_ext.RequestWrapper, aliases map[string]string) error { // The following fields were previously uints in the OpenRTB library we use, but have // since been changed to ints. We decided to maintain the non-negative check. - if user.Geo != nil && user.Geo.Accuracy < 0 { - return errors.New("request.user.geo.accuracy must be a positive number") + if req != nil && req.BidRequest != nil && req.User != nil { + if req.User.Geo != nil && req.User.Geo.Accuracy < 0 { + return errors.New("request.user.geo.accuracy must be a positive number") + } } - if user.Ext != nil { - // Creating ExtUser object to check if DigiTrust is valid - var userExt openrtb_ext.ExtUser - if err := json.Unmarshal(user.Ext, &userExt); err == nil { - if userExt.DigiTrust != nil && userExt.DigiTrust.Pref != 0 { - // DigiTrust is not valid. Return error. - return errors.New("request.user contains a digitrust object that is not valid.") - } - // Check if the buyeruids are valid - if userExt.Prebid != nil { - if len(userExt.Prebid.BuyerUIDs) < 1 { - return 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 userExt.Prebid.BuyerUIDs { - if _, ok := deps.bidderMap[bidderName]; !ok { - if _, ok := aliases[bidderName]; !ok { - return fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases.", bidderName) - } - } + userExt, err := req.GetUserExt() + if err != nil { + return fmt.Errorf("request.user.ext object is not valid: %v", err) + } + // Check if the buyeruids are valid + prebid := userExt.GetPrebid() + if prebid != nil { + if len(prebid.BuyerUIDs) < 1 { + return 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 { + if _, ok := aliases[bidderName]; !ok { + return fmt.Errorf("request.user.ext.%s is neither a known bidder name nor an alias in request.ext.prebid.aliases.", bidderName) } } - // Check Universal User ID - if userExt.Eids != nil { - if len(userExt.Eids) == 0 { - return fmt.Errorf("request.user.ext.eids must contain at least one element or be undefined") - } - uniqueSources := make(map[string]struct{}, len(userExt.Eids)) - for eidIndex, eid := range userExt.Eids { - if eid.Source == "" { - return fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex) - } - if _, ok := uniqueSources[eid.Source]; ok { - return fmt.Errorf("request.user.ext.eids must contain unique sources") - } - uniqueSources[eid.Source] = struct{}{} + } + } + // Check Universal User ID + eids := userExt.GetEid() + if eids != nil { + if len(*eids) == 0 { + return errors.New("request.user.ext.eids must contain at least one element or be undefined") + } + uniqueSources := make(map[string]struct{}, len(*eids)) + for eidIndex, eid := range *eids { + if eid.Source == "" { + return fmt.Errorf("request.user.ext.eids[%d] missing required field: \"source\"", eidIndex) + } + if _, ok := uniqueSources[eid.Source]; ok { + return errors.New("request.user.ext.eids must contain unique sources") + } + uniqueSources[eid.Source] = struct{}{} - if eid.ID == "" && eid.Uids == nil { - return fmt.Errorf("request.user.ext.eids[%d] must contain either \"id\" or \"uids\" field", eidIndex) - } - if eid.ID == "" { - if len(eid.Uids) == 0 { - return fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex) - } - for uidIndex, uid := range eid.Uids { - if uid.ID == "" { - return fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex) - } - } + if eid.ID == "" && eid.Uids == nil { + return fmt.Errorf("request.user.ext.eids[%d] must contain either \"id\" or \"uids\" field", eidIndex) + } + if eid.ID == "" { + if len(eid.Uids) == 0 { + return fmt.Errorf("request.user.ext.eids[%d].uids must contain at least one element or be undefined", eidIndex) + } + for uidIndex, uid := range eid.Uids { + if uid.ID == "" { + return fmt.Errorf("request.user.ext.eids[%d].uids[%d] missing required field: \"id\"", eidIndex, uidIndex) } } } - } else { - return fmt.Errorf("request.user.ext object is not valid: %v", err) } } return nil } -func validateRegs(regs *openrtb2.Regs) error { - if regs != nil && len(regs.Ext) > 0 { - var regsExt openrtb_ext.ExtRegs - if err := json.Unmarshal(regs.Ext, ®sExt); err != nil { - return fmt.Errorf("request.regs.ext is invalid: %v", err) - } - if regsExt.GDPR != nil && (*regsExt.GDPR < 0 || *regsExt.GDPR > 1) { - return errors.New("request.regs.ext.gdpr must be either 0 or 1.") - } +func validateRegs(req *openrtb_ext.RequestWrapper) error { + regsExt, err := req.GetRegExt() + if err != nil { + return fmt.Errorf("request.regs.ext is invalid: %v", err) + } + regExt := regsExt.GetExt() + gdprJSON, hasGDPR := regExt["gdpr"] + if hasGDPR && (string(gdprJSON) != "0" && string(gdprJSON) != "1") { + return errors.New("request.regs.ext.gdpr must be either 0 or 1.") } return nil } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 75d0610cb34..4835dd92943 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -17,17 +17,19 @@ import ( "testing" "time" - "github.com/mxmCherry/openrtb/v15/openrtb2" - "github.com/prebid/prebid-server/stored_requests" - "github.com/buger/jsonparser" jsonpatch "github.com/evanphx/json-patch" + "github.com/mxmCherry/openrtb/v15/native1" + nativeRequests "github.com/mxmCherry/openrtb/v15/native1/request" + "github.com/mxmCherry/openrtb/v15/openrtb2" analyticsConf "github.com/prebid/prebid-server/analytics/config" "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/metrics" "github.com/prebid/prebid-server/openrtb_ext" + "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/stretchr/testify/assert" @@ -44,11 +46,13 @@ 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"` + 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"` + MockBidder mockBidExchangeBidder `json:"mockBidder"` } func TestJsonSampleRequests(t *testing.T) { @@ -104,6 +108,22 @@ func TestJsonSampleRequests(t *testing.T) { "Requests with first party data context info found in imp[i].ext.prebid.bidder,context", "first-party-data", }, + { + "Assert we correctly use the server conversion rates when needed", + "currency-conversion/server-rates/valid", + }, + { + "Assert we correctly throw an error when no conversion rate was found in the server conversions map", + "currency-conversion/server-rates/errors", + }, + { + "Assert we correctly use request-defined custom currency rates when present in root.ext", + "currency-conversion/custom-rates/valid", + }, + { + "Assert we correctly validate request-defined custom currency rates when present in root.ext", + "currency-conversion/custom-rates/errors", + }, } for _, test := range testSuites { testCaseFiles, err := getTestFiles(filepath.Join("sample-requests", test.sampleRequestsSubDir)) @@ -247,6 +267,7 @@ func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse o assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile) assert.Equalf(t, expectedBidResponse.BidID, actualBidResponse.BidID, "BidResponse.BidID doesn't match expected. Test: %s\n", testFile) assert.Equalf(t, expectedBidResponse.NBR, actualBidResponse.NBR, "BidResponse.NBR doesn't match expected. Test: %s\n", testFile) + assert.Equalf(t, expectedBidResponse.Cur, actualBidResponse.Cur, "BidResponse.Cur doesn't match expected. Test: %s\n", testFile) //Assert []SeatBid and their Bid elements independently of their order assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid array doesn't match expected. Test: %s\n", testFile) @@ -440,8 +461,10 @@ func doRequest(t *testing.T, test testCase) (int, string) { bidderMap := exchange.GetActiveBidders(bidderInfos) disabledBidders := exchange.GetDisabledBiddersErrorMessages(bidderInfos) + mockExchange := newMockBidExchange(test.Config.MockBidder, test.Config.CurrencyRates) + endpoint, _ := NewEndpoint( - &mockBidExchange{}, + mockExchange, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, @@ -1183,6 +1206,113 @@ func TestContentType(t *testing.T) { } } +func TestValidateCustomRates(t *testing.T) { + boolTrue := true + boolFalse := false + + testCases := []struct { + desc string + inBidReqCurrencies *openrtb_ext.ExtRequestCurrency + outCurrencyError error + }{ + { + desc: "nil input, no errors expected", + inBidReqCurrencies: nil, + outCurrencyError: nil, + }, + { + desc: "empty custom currency rates but UsePBSRates is set to false, we don't return error nor warning", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{}, + UsePBSRates: &boolFalse, + }, + outCurrencyError: nil, + }, + { + desc: "empty custom currency rates but UsePBSRates is set to true, no need to return error because we can use PBS rates", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{}, + UsePBSRates: &boolTrue, + }, + outCurrencyError: nil, + }, + { + desc: "UsePBSRates is nil and defaults to true, bidExt fromCurrency is invalid, expect bad input error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "GBP": 1.2, + "MXN": 0.05, + "JPY": 0.95, + }, + }, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, bidExt fromCurrency is invalid, expect bad input error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "GBP": 1.2, + "MXN": 0.05, + "JPY": 0.95, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, some of the bidExt 'to' Currencies are invalid, expect bad input error when parsing the first invalid currency code", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "USD": { + "FOO": 10.0, + "MXN": 0.05, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "UsePBSRates set to false, some of the bidExt 'from' and 'to' currencies are invalid, expect bad input error when parsing the first invalid currency code", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "FOO": { + "MXN": 0.05, + "CAD": 0.95, + }, + }, + UsePBSRates: &boolFalse, + }, + outCurrencyError: &errortypes.BadInput{Message: "currency code FOO is not recognized or malformed"}, + }, + { + desc: "All 3-digit currency codes exist, expect no error", + inBidReqCurrencies: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: map[string]map[string]float64{ + "USD": { + "MXN": 0.05, + }, + "MXN": { + "JPY": 10.0, + "EUR": 10.95, + }, + }, + UsePBSRates: &boolFalse, + }, + }, + } + + for _, tc := range testCases { + actualErr := validateCustomRates(tc.inBidReqCurrencies) + + assert.Equal(t, tc.outCurrencyError, actualErr, tc.desc) + } +} + func TestValidateImpExt(t *testing.T) { type testCase struct { description string @@ -1457,7 +1587,7 @@ func TestCurrencyTrunc(t *testing.T) { Cur: []string{"USD", "EUR"}, } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"} assert.ElementsMatch(t, errL, []error{&expectedError}) @@ -1503,14 +1633,12 @@ func TestCCPAInvalid(t *testing.T) { }, } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedWarning := errortypes.Warning{ Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", WarningCode: errortypes.InvalidPrivacyConsentWarningCode} assert.ElementsMatch(t, errL, []error{&expectedWarning}) - - assert.Empty(t, req.Regs.Ext, "Invalid Consent Removed From Request") } func TestNoSaleInvalid(t *testing.T) { @@ -1554,7 +1682,7 @@ func TestNoSaleInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`), } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided") assert.ElementsMatch(t, errL, []error{expectedError}) @@ -1601,7 +1729,7 @@ func TestValidateSourceTID(t *testing.T) { }, } - deps.validateRequest(&req) + deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID") } @@ -1643,7 +1771,7 @@ func TestSChainInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.") assert.ElementsMatch(t, errL, []error{expectedError}) @@ -1862,7 +1990,7 @@ func TestEidPermissionsInvalid(t *testing.T) { Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`), } - errL := deps.validateRequest(&req) + errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}) expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`) assert.ElementsMatch(t, errL, []error{expectedError}) @@ -1877,11 +2005,6 @@ func TestValidateEidPermissions(t *testing.T) { request *openrtb_ext.ExtRequest expectedError error }{ - { - description: "Valid - Nil ext", - request: nil, - expectedError: nil, - }, { description: "Valid - Empty ext", request: &openrtb_ext.ExtRequest{}, @@ -1966,7 +2089,7 @@ func TestValidateEidPermissions(t *testing.T) { endpoint := &endpointDeps{bidderMap: knownBidders} for _, test := range testCases { - result := endpoint.validateEidPermissions(test.request, knownAliases) + result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases) assert.Equal(t, test.expectedError, result, test.description) } } @@ -2145,6 +2268,425 @@ func TestAuctionWarnings(t *testing.T) { assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect") } +func TestValidateNativeContextTypes(t *testing.T) { + impIndex := 4 + + testCases := []struct { + description string + givenContextType native1.ContextType + givenSubType native1.ContextSubType + expectedError string + }{ + { + description: "No Types Specified", + givenContextType: 0, + givenSubType: 0, + expectedError: "", + }, + { + description: "All Types Exchange Specific", + givenContextType: 500, + givenSubType: 500, + expectedError: "", + }, + { + description: "Context Type Known Value - Sub Type Unspecified", + givenContextType: 1, + givenSubType: 0, + expectedError: "", + }, + { + description: "Context Type Negative", + givenContextType: -1, + givenSubType: 0, + expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Context Type Just Above Range", + givenContextType: 4, // Range is currently 1-3 + givenSubType: 0, + expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Sub Type Negative", + givenContextType: 1, + givenSubType: -1, + expectedError: "request.imp[4].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Content - Sub Type Just Below Range", + givenContextType: 1, // Content constant + givenSubType: 9, // Content range is currently 10-15 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Content - Sub Type In Range", + givenContextType: 1, // Content constant + givenSubType: 10, // Content range is currently 10-15 + expectedError: "", + }, + { + description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary", + givenContextType: 500, + givenSubType: 10, // Content range is currently 10-15 + expectedError: "", + }, + { + description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary + 1", + givenContextType: 501, + givenSubType: 10, // Content range is currently 10-15 + expectedError: "", + }, + { + description: "Content - Sub Type Just Above Range", + givenContextType: 1, // Content constant + givenSubType: 16, // Content range is currently 10-15 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Content - Sub Type Exchange Specific Boundary", + givenContextType: 1, // Content constant + givenSubType: 500, + expectedError: "", + }, + { + description: "Content - Sub Type Exchange Specific Boundary + 1", + givenContextType: 1, // Content constant + givenSubType: 501, + expectedError: "", + }, + { + description: "Content - Invalid Context Type", + givenContextType: 2, // Not content constant + givenSubType: 10, // Content range is currently 10-15 + expectedError: "request.imp[4].native.request.context is 2, but contextsubtype is 10. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Social - Sub Type Just Below Range", + givenContextType: 2, // Social constant + givenSubType: 19, // Social range is currently 20-22 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Social - Sub Type In Range", + givenContextType: 2, // Social constant + givenSubType: 20, // Social range is currently 20-22 + expectedError: "", + }, + { + description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary", + givenContextType: 500, + givenSubType: 20, // Social range is currently 20-22 + expectedError: "", + }, + { + description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary + 1", + givenContextType: 501, + givenSubType: 20, // Social range is currently 20-22 + expectedError: "", + }, + { + description: "Social - Sub Type Just Above Range", + givenContextType: 2, // Social constant + givenSubType: 23, // Social range is currently 20-22 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Social - Sub Type Exchange Specific Boundary", + givenContextType: 2, // Social constant + givenSubType: 500, + expectedError: "", + }, + { + description: "Social - Sub Type Exchange Specific Boundary + 1", + givenContextType: 2, // Social constant + givenSubType: 501, + expectedError: "", + }, + { + description: "Social - Invalid Context Type", + givenContextType: 3, // Not social constant + givenSubType: 20, // Social range is currently 20-22 + expectedError: "request.imp[4].native.request.context is 3, but contextsubtype is 20. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Product - Sub Type Just Below Range", + givenContextType: 3, // Product constant + givenSubType: 29, // Product range is currently 30-32 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Product - Sub Type In Range", + givenContextType: 3, // Product constant + givenSubType: 30, // Product range is currently 30-32 + expectedError: "", + }, + { + description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary", + givenContextType: 500, + givenSubType: 30, // Product range is currently 30-32 + expectedError: "", + }, + { + description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary + 1", + givenContextType: 501, + givenSubType: 30, // Product range is currently 30-32 + expectedError: "", + }, + { + description: "Product - Sub Type Just Above Range", + givenContextType: 3, // Product constant + givenSubType: 33, // Product range is currently 30-32 + expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + { + description: "Product - Sub Type Exchange Specific Boundary", + givenContextType: 3, // Product constant + givenSubType: 500, + expectedError: "", + }, + { + description: "Product - Sub Type Exchange Specific Boundary + 1", + givenContextType: 3, // Product constant + givenSubType: 501, + expectedError: "", + }, + { + description: "Product - Invalid Context Type", + givenContextType: 1, // Not product constant + givenSubType: 30, // Product range is currently 30-32 + expectedError: "request.imp[4].native.request.context is 1, but contextsubtype is 30. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", + }, + } + + for _, test := range testCases { + err := validateNativeContextTypes(test.givenContextType, test.givenSubType, impIndex) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} + +func TestValidateNativePlacementType(t *testing.T) { + impIndex := 4 + + testCases := []struct { + description string + givenPlacementType native1.PlacementType + expectedError string + }{ + { + description: "Not Specified", + givenPlacementType: 0, + expectedError: "", + }, + { + description: "Known Value", + givenPlacementType: 1, // Range is currently 1-4 + expectedError: "", + }, + { + description: "Exchange Specific - Boundary", + givenPlacementType: 500, + expectedError: "", + }, + { + description: "Exchange Specific - Boundary + 1", + givenPlacementType: 501, + expectedError: "", + }, + { + description: "Negative", + givenPlacementType: -1, + expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + { + description: "Just Above Range", + givenPlacementType: 5, // Range is currently 1-4 + expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + } + + for _, test := range testCases { + err := validateNativePlacementType(test.givenPlacementType, impIndex) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} + +func TestValidateNativeEventTracker(t *testing.T) { + impIndex := 4 + eventIndex := 8 + + testCases := []struct { + description string + givenEvent nativeRequests.EventTracker + expectedError string + }{ + { + description: "Valid", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "", + }, + { + description: "Event - Exchange Specific - Boundary", + givenEvent: nativeRequests.EventTracker{ + Event: 500, + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "", + }, + { + description: "Event - Exchange Specific - Boundary + 1", + givenEvent: nativeRequests.EventTracker{ + Event: 501, + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "", + }, + { + description: "Event - Negative", + givenEvent: nativeRequests.EventTracker{ + Event: -1, + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Event - Just Above Range", + givenEvent: nativeRequests.EventTracker{ + Event: 5, // Range is currently 1-4 + Methods: []native1.EventTrackingMethod{1}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Methods - Many Valid", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{1, 2}, + }, + expectedError: "", + }, + { + description: "Methods - Empty", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Methods - Exchange Specific - Boundary", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{500}, + }, + expectedError: "", + }, + { + description: "Methods - Exchange Specific - Boundary + 1", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{501}, + }, + expectedError: "", + }, + { + description: "Methods - Negative", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{-1}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Methods - Just Above Range", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{3}, // Known values are currently 1-2 + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + { + description: "Methods - Mixed Valid + Invalid", + givenEvent: nativeRequests.EventTracker{ + Event: 1, + Methods: []native1.EventTrackingMethod{1, -1}, + }, + expectedError: "request.imp[4].native.request.eventtrackers[8].methods[1] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", + }, + } + + for _, test := range testCases { + err := validateNativeEventTracker(test.givenEvent, impIndex, eventIndex) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} + +func TestValidateNativeAssetData(t *testing.T) { + impIndex := 4 + assetIndex := 8 + + testCases := []struct { + description string + givenData nativeRequests.Data + expectedError string + }{ + { + description: "Valid", + givenData: nativeRequests.Data{Type: 1}, + expectedError: "", + }, + { + description: "Exchange Specific - Boundary", + givenData: nativeRequests.Data{Type: 500}, + expectedError: "", + }, + { + description: "Exchange Specific - Boundary + 1", + givenData: nativeRequests.Data{Type: 501}, + expectedError: "", + }, + { + description: "Not Specified", + givenData: nativeRequests.Data{}, + expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + { + description: "Negative", + givenData: nativeRequests.Data{Type: -1}, + expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + { + description: "Just Above Range", + givenData: nativeRequests.Data{Type: 13}, // Range is currently 1-12 + expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", + }, + } + + for _, test := range testCases { + err := validateNativeAssetData(&test.givenData, impIndex, assetIndex) + if test.expectedError == "" { + assert.NoError(t, err, test.description) + } else { + assert.EqualError(t, err, test.expectedError, test.description) + } + } +} + // warningsCheckExchange is a well-behaved exchange which stores all incoming warnings. type warningsCheckExchange struct { auctionRequest exchange.AuctionRequest @@ -2170,7 +2712,49 @@ func (e *nobidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReque } type mockBidExchange struct { - gotRequest *openrtb2.BidRequest + mockBidder mockBidExchangeBidder + pbsRates map[string]map[string]float64 +} + +func newMockBidExchange(bidder mockBidExchangeBidder, mockCurrencyConversionRates map[string]map[string]float64) *mockBidExchange { + if bidder.BidCurrency == "" { + bidder.BidCurrency = "USD" + } + + return &mockBidExchange{ + mockBidder: bidder, + pbsRates: mockCurrencyConversionRates, + } +} + +// getAuctionCurrencyRates copies the logic of the exchange package for testing purposes +func (e *mockBidExchange) getAuctionCurrencyRates(customRates *openrtb_ext.ExtRequestCurrency) currency.Conversions { + if customRates == nil { + // The timestamp is required for the function signature, but is not used and its + // value has no significance in the tests + return currency.NewRates(time.Now(), e.pbsRates) + } + + usePbsRates := true + if customRates.UsePBSRates != nil { + usePbsRates = *customRates.UsePBSRates + } + + if !usePbsRates { + // The timestamp is required for the function signature, but is not used and its + // value has no significance in the tests + return currency.NewRates(time.Now(), customRates.ConversionRates) + } + + // Both PBS and custom rates can be used, check if ConversionRates is not empty + if len(customRates.ConversionRates) == 0 { + // Custom rates map is empty, use PBS rates only + return currency.NewRates(time.Now(), e.pbsRates) + } + + // Return an AggregateConversions object that includes both custom and PBS currency rates but will + // prioritize custom rates over PBS rates whenever a currency rate is found in both + return currency.NewAggregateConversions(currency.NewRates(time.Time{}, customRates.ConversionRates), currency.NewRates(time.Now(), e.pbsRates)) } // mockBidExchange is a well-behaved exchange that lists the bidders found in every bidRequest.Imp[i].Ext @@ -2181,6 +2765,36 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq BidID: "test bid id", NBR: openrtb2.NoBidReasonCodeUnknownError.Ptr(), } + + // Use currencies inside r.BidRequest.Cur, if any, and convert currencies if needed + if len(r.BidRequest.Cur) == 0 { + r.BidRequest.Cur = []string{"USD"} + } + + var currencyFrom string = e.mockBidder.getBidCurrency() + var conversionRate float64 = 0.00 + var err error + + var requestExt openrtb_ext.ExtRequest + if len(r.BidRequest.Ext) > 0 { + if err := json.Unmarshal(r.BidRequest.Ext, &requestExt); err != nil { + return nil, fmt.Errorf("request.ext is invalid: %v", err) + } + } + + conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) + for _, bidReqCur := range r.BidRequest.Cur { + if conversionRate, err = conversions.GetRate(currencyFrom, bidReqCur); err == nil { + bidResponse.Cur = bidReqCur + break + } + } + + if conversionRate == 0 { + // Can't have bids if there's not even a 1 USD to 1 USD conversion rate + return nil, errors.New("Can't produce bid with no valid currency to use or currency conversion to convert to.") + } + if len(r.BidRequest.Imp) > 0 { var SeatBidMap = make(map[string]openrtb2.SeatBid, 0) for _, imp := range r.BidRequest.Imp { @@ -2205,9 +2819,17 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq for bidderNameOrAlias := range bidderExts { if isBidderToValidate(bidderNameOrAlias) { if val, ok := SeatBidMap[bidderNameOrAlias]; ok { - val.Bid = append(val.Bid, openrtb2.Bid{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}) + val.Bid = append(val.Bid, openrtb2.Bid{ID: e.mockBidder.getBidId(bidderNameOrAlias)}) } else { - SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{Seat: fmt.Sprintf("%s-bids", bidderNameOrAlias), Bid: []openrtb2.Bid{{ID: fmt.Sprintf("%s-bid", bidderNameOrAlias)}}} + SeatBidMap[bidderNameOrAlias] = openrtb2.SeatBid{ + Seat: e.mockBidder.getSeatName(bidderNameOrAlias), + Bid: []openrtb2.Bid{ + { + ID: e.mockBidder.getBidId(bidderNameOrAlias), + Price: e.mockBidder.getBidPrice() * conversionRate, + }, + }, + } } } } @@ -2220,6 +2842,24 @@ func (e *mockBidExchange) HoldAuction(ctx context.Context, r exchange.AuctionReq return bidResponse, nil } +type mockBidExchangeBidder struct { + BidCurrency string `json:"currency"` + BidPrice float64 `json:"price"` +} + +func (bidder mockBidExchangeBidder) getBidCurrency() string { + return bidder.BidCurrency +} +func (bidder mockBidExchangeBidder) getBidPrice() float64 { + return bidder.BidPrice +} +func (bidder mockBidExchangeBidder) getSeatName(bidderNameOrAlias string) string { + return fmt.Sprintf("%s-bids", bidderNameOrAlias) +} +func (bidder mockBidExchangeBidder) getBidId(bidderNameOrAlias string) string { + return fmt.Sprintf("%s-bid", bidderNameOrAlias) +} + type brokenExchange struct{} func (e *brokenExchange) HoldAuction(ctx context.Context, r exchange.AuctionRequest, debugLog *exchange.DebugLog) (*openrtb2.BidResponse, error) { diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go index a2299517695..00a2b254b32 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -104,6 +104,7 @@ func NewCTVEndpoint( 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 @@ -136,10 +137,11 @@ func (deps *ctvEndpointDeps) CTVAuctionEndpoint(w http.ResponseWriter, r *http.R }() //Parse ORTB Request and do Standard Validation - request, errL = deps.parseRequest(r) + reqWrapper, errL = deps.parseRequest(r) if errortypes.ContainsFatalError(errL) && writeError(errL, w, &deps.labels) { return } + request = reqWrapper.BidRequest util.JLogf("Original BidRequest", request) //TODO: REMOVE LOG diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 1aa2a7fc890..359bae11d4c 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -1,7 +1,6 @@ package openrtb2 import ( - "encoding/json" "fmt" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -10,26 +9,27 @@ import ( "github.com/prebid/prebid-server/openrtb_ext" ) -func processInterstitials(req *openrtb2.BidRequest) error { - var devExt openrtb_ext.ExtDevice +func processInterstitials(req *openrtb_ext.RequestWrapper) error { unmarshalled := true for i := range req.Imp { if req.Imp[i].Instl == 1 { + var prebid *openrtb_ext.ExtDevicePrebid if unmarshalled { if req.Device.Ext == nil { // No special interstitial support requested, so bail as there is nothing to do return nil } - err := json.Unmarshal(req.Device.Ext, &devExt) + deviceExt, err := req.GetDeviceExt() if err != nil { return err } - if devExt.Prebid.Interstitial == nil { + prebid = deviceExt.GetPrebid() + if prebid.Interstitial == nil { // No special interstitial support requested, so bail as there is nothing to do return nil } } - err := processInterstitialsForImp(&req.Imp[i], &devExt, req.Device) + err := processInterstitialsForImp(&req.Imp[i], prebid, req.Device) if err != nil { return err } @@ -38,7 +38,7 @@ func processInterstitials(req *openrtb2.BidRequest) error { return nil } -func processInterstitialsForImp(imp *openrtb2.Imp, devExt *openrtb_ext.ExtDevice, device *openrtb2.Device) error { +func processInterstitialsForImp(imp *openrtb2.Imp, devExtPrebid *openrtb_ext.ExtDevicePrebid, device *openrtb2.Device) error { var maxWidth, maxHeight, minWidth, minHeight int64 if imp.Banner == nil { // custom interstitial support is only available for banner requests. @@ -56,8 +56,8 @@ func processInterstitialsForImp(imp *openrtb2.Imp, devExt *openrtb_ext.ExtDevice maxWidth = device.W maxHeight = device.H } - minWidth = (maxWidth * int64(devExt.Prebid.Interstitial.MinWidthPerc)) / 100 - minHeight = (maxHeight * int64(devExt.Prebid.Interstitial.MinHeightPerc)) / 100 + minWidth = (maxWidth * devExtPrebid.Interstitial.MinWidthPerc) / 100 + minHeight = (maxHeight * devExtPrebid.Interstitial.MinHeightPerc) / 100 imp.Banner.Format = genInterstitialFormat(minWidth, maxWidth, minHeight, maxHeight) if len(imp.Banner.Format) == 0 { return &errortypes.BadInput{Message: fmt.Sprintf("Unable to set interstitial size list for Imp id=%s (No valid sizes between %dx%d and %dx%d)", imp.ID, minWidth, minHeight, maxWidth, maxHeight)} diff --git a/endpoints/openrtb2/interstitial_test.go b/endpoints/openrtb2/interstitial_test.go index 1d7ad9e3d6b..fe0ed966c3c 100644 --- a/endpoints/openrtb2/interstitial_test.go +++ b/endpoints/openrtb2/interstitial_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -34,7 +35,7 @@ var request = &openrtb2.BidRequest{ func TestInterstitial(t *testing.T) { myRequest := request - if err := processInterstitials(myRequest); err != nil { + if err := processInterstitials(&openrtb_ext.RequestWrapper{BidRequest: myRequest}); err != nil { t.Fatalf("Error processing interstitials: %v", err) } targetFormat := []openrtb2.Format{ diff --git a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json index c3ab09d4883..75c859d212b 100644 --- a/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json +++ b/endpoints/openrtb2/sample-requests/account-required/no-account/not-required-no-acct.json @@ -66,6 +66,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json index a72d184c81c..ae930384499 100644 --- a/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json +++ b/endpoints/openrtb2/sample-requests/account-required/with-account/required-with-acct.json @@ -68,6 +68,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json index 55e45041e6e..00906c89772 100644 --- a/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json +++ b/endpoints/openrtb2/sample-requests/aliased/multiple-alias.json @@ -87,6 +87,7 @@ } ], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/aliased/simple.json b/endpoints/openrtb2/sample-requests/aliased/simple.json index a99907ab370..677d3d8cf53 100644 --- a/endpoints/openrtb2/sample-requests/aliased/simple.json +++ b/endpoints/openrtb2/sample-requests/aliased/simple.json @@ -27,19 +27,20 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, - "seatbid": [ - { - "bid": [ - { - "id": "alias1-bid", - "impid": "", - "price": 0 - } - ], - "seat": "alias1-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "alias1-bid", + "impid": "", + "price": 0 + } + ], + "seat": "alias1-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json new file mode 100644 index 00000000000..03877031294 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/conversion-disabled.json @@ -0,0 +1,46 @@ +{ + "description": "request.ext.prebid.currency.rates empty, usepbsrates is false, a conversion is needed but conversions are disabled", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json new file mode 100644 index 00000000000..6a727e9615c --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-default.json @@ -0,0 +1,52 @@ +{ + "description": "currency in request.cur cannot be converted because conversion rate not found in either custom currency rates nor server rates. usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["GBP"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + } + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json new file mode 100644 index 00000000000..5549fa9b688 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-currency-missing-usepbs-false.json @@ -0,0 +1,53 @@ +{ + "description": "currency in request.cur cannot be converted because usepbsrates set to false not allowing for PBS to use its rates. Default to price of 0", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "JPY": 2.00 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json new file mode 100644 index 00000000000..f4e19f3a4c5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-empty-usepbs-false.json @@ -0,0 +1,49 @@ +{ + "description": "usepbsrates set to false forces BidRequest to use custom currency rates but bidRequest.ext.prebid.currency.rates field is empty", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json new file mode 100644 index 00000000000..39857650f12 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/errors/custom-rates-invalid-usepbs-false.json @@ -0,0 +1,53 @@ +{ + "description": "False usepbsrates forces BidRequest use custom currency rates but bidRequest.ext.prebid.currency.rates field comes with invalid currency codes", + "config": { + "currencyRates":{ + "USD": { + "MXN": 5.09 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "FOO": 10.0 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedReturnCode": 400, + "expectedErrorMessage": "Invalid request: currency code " +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json new file mode 100644 index 00000000000..0741ea4d315 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/conversion-disabled.json @@ -0,0 +1,62 @@ +{ + "description": "request.ext.prebid.currency.rates empty, usepbsrates set to false, request succeeded because no conversion was needed", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": {}, + "usepbsrates": false + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "USD", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 1.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json new file mode 100644 index 00000000000..fb65a852355 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rate-not-found-usepbsrates-default.json @@ -0,0 +1,70 @@ +{ + "description": "request comes with custom rates but request.cur currency is only found in the server rates. Error wasn't thrown because usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "JPY": 15.00, + "EUR": 0.85 + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json new file mode 100644 index 00000000000..80790a52543 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-default.json @@ -0,0 +1,69 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates defaults to true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + } + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json new file mode 100644 index 00000000000..ef372c1cf66 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-false.json @@ -0,0 +1,70 @@ +{ + "description": "request.ext.prebid.currency substitutes those of the currency conversion server because usepbsrates is false", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + }, + "usepbsrates": false + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json new file mode 100644 index 00000000000..276e8da43c2 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/custom-rates-override-usepbsrates-true.json @@ -0,0 +1,70 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates was true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "MXN": 5.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 5.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json new file mode 100644 index 00000000000..624f0784dac --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/reverse-currency-conversion.json @@ -0,0 +1,66 @@ +{ + "description": "USD BidRequest gets converted because mockbidder bids in foreign currency, custom conversion rate is used", + "config": { + "currencyRates":{ + "USD": { + "MXN": 8.00 + } + }, + "mockBidder": { + "currency": "MXN", + "price": 20.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "ext": { + "prebid": { + "currency": { + "rates": { + "USD": { + "MXN": 10.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "nbr":0, + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json new file mode 100644 index 00000000000..929c2e0cbd5 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/custom-rates/valid/server-rates-usepbsrates-true.json @@ -0,0 +1,70 @@ +{ + "description": "Despite being more expensive, custom conversion rate overrides that of the currency conversion service. usepbsrates was true", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ], + "cur": ["MXN"], + "ext": { + "prebid": { + "aliases": { + "unknown": "appnexus" + }, + "currency": { + "rates": { + "USD": { + "CAD": 5.00 + } + }, + "usepbsrates": true + } + } + } + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json new file mode 100644 index 00000000000..dc0d7ce6042 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/errors/no-conversion-found.json @@ -0,0 +1,38 @@ +{ + "description": "bid request calls for a bid in foreign currency MXN but conversion rate is not found in the currency conversion service.", + "config": { + "currencyRates":{ + "USD": { + "GBP": 0.80 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "cur": ["MXN"], + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedReturnCode": 500, + "expectedErrorMessage": "Critical error while running the auction: Can't produce bid with no valid currency to use or currency conversion to convert to." +} diff --git a/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json new file mode 100644 index 00000000000..84788d5ada1 --- /dev/null +++ b/endpoints/openrtb2/sample-requests/currency-conversion/server-rates/valid/simple-conversion.json @@ -0,0 +1,55 @@ +{ + "description": "bid request calls for a bid in foreign currency but mockbidder bids in USD. Conversion rate is applied", + "config": { + "currencyRates":{ + "USD": { + "MXN": 2.00 + } + }, + "mockBidder": { + "currency": "USD", + "price": 1.00 + } + }, + "mockBidRequest": { + "id": "some-request-id", + "site": { + "page": "test.somepage.com" + }, + "cur": ["MXN"], + "imp": [ + { + "id": "my-imp-id", + "video": { + "mimes": [ + "video/mp4" + ] + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + } + ] + }, + "expectedBidResponse": { + "id":"some-request-id", + "bidid":"test bid id", + "cur": "MXN", + "nbr":0, + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 2.00 + } + ], + "seat": "appnexus-bids" + } + ] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/disabled/good/partial.json b/endpoints/openrtb2/sample-requests/disabled/good/partial.json index 3549abaa934..735e7c5ede1 100644 --- a/endpoints/openrtb2/sample-requests/disabled/good/partial.json +++ b/endpoints/openrtb2/sample-requests/disabled/good/partial.json @@ -58,6 +58,7 @@ "expectedBidResponse": { "id":"some-request-id", "bidid":"test bid id", + "cur": "USD", "nbr":0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json index c36ae0cd41d..a4b716b2040 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-ext-bidder.json @@ -43,7 +43,8 @@ "seat": "appnexus-bids" }], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json index ad6298db39a..27e8c46d9d7 100644 --- a/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json +++ b/endpoints/openrtb2/sample-requests/first-party-data/valid-fpd-allowed-with-prebid-bidder.json @@ -47,7 +47,8 @@ "seat": "appnexus-bids" }], "bidid": "test bid id", + "cur": "USD", "nbr": 0 }, "expectedReturnCode": 200 -} \ No newline at end of file +} diff --git a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json b/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json deleted file mode 100644 index 9b422380edf..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-native/contextsubtype-greater-than-max.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "description": "Native bid request comes with a subcontext type greater than 500. Return error", - "mockBidRequest": { - "id": "req-id", - "site": { - "page": "some.page.com" - }, - "tmax": 500, - "imp": [ - { - "id": "some-imp", - "native": { - "request": "{\"context\":1,\"contextsubtype\":550,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}]}" - }, - "ext": { - "appnexus": { - "placementId": 12883451 - } - } - } - ] - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request" -} - diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json b/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json deleted file mode 100644 index 1fb7169fced..00000000000 --- a/endpoints/openrtb2/sample-requests/invalid-whole/digitrust.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "description": "Invalid digitrust object in user extension", - "mockBidRequest": { - "id": "request-with-invalid-digitrust-obj", - "site": { - "page": "test.somepage.com" - }, - "imp": [ - { - "id": "my-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 600 - } - ] - }, - "pmp": { - "deals": [ - { - "id": "some-deal-id" - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - } - } - } - ], - "user": { - "yob": 1989, - "ext": { - "digitrust": { - "id": "sample-digitrust-id", - "keyv": 1, - "pref": 1 - } - } - } - }, - "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.user contains a digitrust object that is not valid.\n" -} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json b/endpoints/openrtb2/sample-requests/invalid-whole/invalid-source.json index 8385f924a56..5aa7fd4dea1 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.prebid.source of type string" + "expectedErrorMessage": "Invalid request: json: cannot unmarshal object into Go struct field ExtAppPrebid.source of type string" } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json index afdabdab7cf..4a315911906 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/regs-ext-gdpr-string.json @@ -44,5 +44,5 @@ } }, "expectedReturnCode": 400, - "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go struct field ExtRegs.gdpr of type int8\n" + "expectedErrorMessage": "Invalid request: request.regs.ext.gdpr must be either 0 or 1.\n" } 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 a8e94008cf1..ab44e3e2428 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 openrtb_ext.ExtRegs\n" + "expectedErrorMessage": "Invalid request: request.regs.ext is invalid: json: cannot unmarshal string into Go value of type map[string]json.RawMessage\n" } 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 b61be105df0..a26db8a5695 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 struct field ExtUser.consent of type string\n" + "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string\n" } 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 08eed44b2b0..c4646550dd2 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 struct field ExtUser.consent of type string" + "expectedErrorMessage": "Invalid request: request.user.ext object is not valid: json: cannot unmarshal number into Go value of type string" } diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json index 15af8551da6..e556b15d4f2 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-hmin.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur":"USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json index 5d986bcf755..06673bcdf32 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-img-no-wmin.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur":"USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json index 1e55cdda63f..9b8763491a3 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-id.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json index 36a1745cb19..22ffc7f50d8 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json +++ b/endpoints/openrtb2/sample-requests/valid-native/asset-with-no-id.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json index 98cdeedadbe..e60e2028637 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json +++ b/endpoints/openrtb2/sample-requests/valid-native/assets-with-unique-ids.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json index dbf7b9c5e0d..a3b7101d8d5 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json +++ b/endpoints/openrtb2/sample-requests/valid-native/context-product-compatible-subtype.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json index 41fb833d770..77e8ce10a41 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json +++ b/endpoints/openrtb2/sample-requests/valid-native/context-social-compatible-subtype.json @@ -23,19 +23,20 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, - "seatbid": [ - { - "bid": [ - { - "id": "appnexus-bid", - "impid": "", - "price": 0 - } - ], - "seat": "appnexus-bids" - } - ] + "seatbid": [ + { + "bid": [ + { + "id": "appnexus-bid", + "impid": "", + "price": 0 + } + ], + "seat": "appnexus-bids" + } + ] }, "expectedReturnCode": 200 } diff --git a/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json new file mode 100644 index 00000000000..214031177ca --- /dev/null +++ b/endpoints/openrtb2/sample-requests/valid-native/eventtracker-exchange-specific.json @@ -0,0 +1,36 @@ +{ + "description": "Well formed native request with video asset using an exchange specific event tracker", + "mockBidRequest": { + "id": "req-id", + "site": { + "page": "some.page.com" + }, + "tmax": 500, + "imp": [{ + "id": "some-imp", + "native": { + "request": "{\"context\":1,\"plcmttype\":1,\"assets\":[{\"title\":{\"len\":90}},{\"img\":{\"hmin\":30,\"wmin\":20}},{\"video\":{\"mimes\":[\"video/mp4\"],\"minduration\":5,\"maxduration\":10,\"protocols\":[1]}},{\"data\":{\"type\":2}}],\"eventtrackers\":[{\"event\":500,\"methods\":[1]}]}" + }, + "ext": { + "appnexus": { + "placementId": 12883451 + } + } + }] + }, + "expectedBidResponse": { + "id": "req-id", + "bidid": "test bid id", + "nbr": 0, + "cur": "USD", + "seatbid": [{ + "bid": [{ + "id": "appnexus-bid", + "impid": "", + "price": 0 + }], + "seat": "appnexus-bids" + }] + }, + "expectedReturnCode": 200 +} diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json index 1ad97c8ff8f..5ebc4e697e4 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json +++ b/endpoints/openrtb2/sample-requests/valid-native/request-no-context.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json index 88af803684d..5518b7a06bc 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json +++ b/endpoints/openrtb2/sample-requests/valid-native/request-plcmttype-empty.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json index ab192e14881..fcc7b72d62a 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json +++ b/endpoints/openrtb2/sample-requests/valid-native/video-asset-event-tracker.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { diff --git a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json index 0ec3c993251..f920c52a591 100644 --- a/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json +++ b/endpoints/openrtb2/sample-requests/valid-native/with-video-asset.json @@ -23,6 +23,7 @@ "expectedBidResponse": { "id": "req-id", "bidid": "test bid id", + "cur": "USD", "nbr": 0, "seatbid": [ { 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 f875fa880bc..46af51635f9 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/all-ext.json @@ -115,6 +115,7 @@ } ], "bidid":"test bid id", + "cur":"USD", "nbr":0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json index 2c6a34f569e..d592cb66fcb 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/prebid-test-ad.json @@ -44,6 +44,7 @@ } ], "bidid": "test bid id", + "cur":"USD", "nbr": 0 }, "expectedReturnCode": 200 diff --git a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json index e238f3c07c7..cb2cec992fe 100644 --- a/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json +++ b/endpoints/openrtb2/sample-requests/valid-whole/exemplary/skadn.json @@ -42,7 +42,8 @@ "seat": "appnexus-bids" }], "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/supplementary/digitrust.json b/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json deleted file mode 100644 index 5cd070745ab..00000000000 --- a/endpoints/openrtb2/sample-requests/valid-whole/supplementary/digitrust.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "description": "Well formed amp request with digitrust extension that should run properly", - "mockBidRequest": { - "id": "request-with-valid-digitrust-obj", - "site": { - "page": "test.somepage.com" - }, - "imp": [ - { - "id": "my-imp-id", - "banner": { - "format": [ - { - "w": 300, - "h": 600 - } - ] - }, - "pmp": { - "deals": [ - { - "id": "some-deal-id" - } - ] - }, - "ext": { - "appnexus": { - "placementId": 12883451 - } - } - } - ], - "user": { - "yob": 1989, - "ext": { - "digitrust": { - "id": "sample-digitrust-id", - "keyv": 1, - "pref": 0 - } - } - } - }, - "expectedBidResponse": { - "id":"request-with-valid-digitrust-obj", - "bidid":"test bid id", - "nbr":0 - }, - "expectedReturnCode": 200 -} diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 2ab3bbb0829..227f6c4a943 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -128,11 +128,13 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re cacheTTL = int64(deps.cfg.CacheURL.DefaultTTLs.Video) } debugLog := exchange.DebugLog{ - Enabled: strings.EqualFold(debugQuery, "true"), - CacheType: prebid_cache_client.TypeXML, - TTL: cacheTTL, - Regexp: deps.debugLogRegexp, + Enabled: strings.EqualFold(debugQuery, "true"), + CacheType: prebid_cache_client.TypeXML, + TTL: cacheTTL, + Regexp: deps.debugLogRegexp, + DebugOverride: exchange.IsDebugOverrideEnabled(r.Header.Get(exchange.DebugOverrideHeader), deps.cfg.Debug.OverrideToken), } + debugLog.DebugEnabledOrOverridden = debugLog.Enabled || debugLog.DebugOverride defer func() { if len(debugLog.CacheKey) > 0 && vo.VideoResponse == nil { @@ -157,7 +159,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re } resolvedRequest := requestJson - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { debugLog.Data.Request = string(requestJson) if headerBytes, err := json.Marshal(r.Header); err == nil { debugLog.Data.Headers = string(headerBytes) @@ -209,7 +211,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re //create full open rtb req from full video request mergeData(videoBidReq, bidReq) // If debug query param is set, force the response to enable test flag - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { bidReq.Test = 1 } @@ -239,7 +241,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re // Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers). deps.setFieldsImplicitly(r, bidReq) // move after merge - errL = deps.validateRequest(bidReq) + errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}) if errortypes.ContainsFatalError(errL) { handleError(&labels, w, errL, &vo, &debugLog) return @@ -274,13 +276,16 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re return } + secGPC := r.Header.Get("Sec-GPC") + auctionRequest := exchange.AuctionRequest{ - BidRequest: bidReq, - Account: *account, - UserSyncs: usersyncs, - RequestType: labels.RType, - StartTime: start, - LegacyLabels: labels, + BidRequest: bidReq, + Account: *account, + UserSyncs: usersyncs, + RequestType: labels.RType, + StartTime: start, + LegacyLabels: labels, + GlobalPrivacyControlHeader: secGPC, } response, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog) @@ -303,7 +308,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re bidResp.Ext = response.Ext } - if len(bidResp.AdPods) == 0 && debugLog.Enabled { + if len(bidResp.AdPods) == 0 && debugLog.DebugEnabledOrOverridden { err := debugLog.PutDebugLogError(deps.cache, deps.cfg.CacheURL.ExpectedTimeMillis, vo.Errors) if err != nil { vo.Errors = append(vo.Errors, err) @@ -341,7 +346,7 @@ func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []P } func handleError(labels *metrics.Labels, w http.ResponseWriter, errL []error, vo *analytics.VideoObject, debugLog *exchange.DebugLog) { - if debugLog != nil && debugLog.Enabled { + if debugLog != nil && debugLog.DebugEnabledOrOverridden { if rawUUID, err := uuid.NewV4(); err == nil { debugLog.CacheKey = rawUUID.String() } diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index 9ede7147686..5452d6c2c39 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1041,8 +1041,10 @@ func TestHandleErrorDebugLog(t *testing.T) { Headers: "test headers string", Response: "test response string", }, - TTL: int64(3600), - Regexp: regexp.MustCompile(`[<>]`), + TTL: int64(3600), + Regexp: regexp.MustCompile(`[<>]`), + DebugOverride: false, + DebugEnabledOrOverridden: true, } handleError(&labels, recorder, []error{err1, err2}, &vo, &debugLog) @@ -1085,11 +1087,13 @@ func TestCCPA(t *testing.T) { description string testFilePath string expectConsentString bool + expectEmptyConsent bool }{ { description: "Missing Consent", testFilePath: "sample-requests/video/video_valid_sample.json", expectConsentString: false, + expectEmptyConsent: true, }, { description: "Valid Consent", @@ -1130,7 +1134,7 @@ func TestCCPA(t *testing.T) { } if test.expectConsentString { assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent") - } else { + } else if test.expectEmptyConsent { assert.Empty(t, extRegs.USPrivacy, test.description+":consent") } diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 0d68c15bea8..caeb09a858c 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -389,9 +389,9 @@ func makeRequest(uri string, existingSyncs map[string]string) *http.Request { func doRequest(req *http.Request, metrics metrics.MetricsEngine, validFamilyNames []string, gdprAllowsHostCookies bool, gdprReturnsError bool) *httptest.ResponseRecorder { cfg := config.Configuration{} perms := &mockPermsSetUID{ - allowHost: gdprAllowsHostCookies, - errorHost: gdprReturnsError, - allowPI: true, + allowHost: gdprAllowsHostCookies, + errorHost: gdprReturnsError, + personalInfoAllowed: true, } analytics := analyticsConf.NewPBSAnalytics(&cfg.Analytics) syncers := make(map[openrtb_ext.BidderName]usersync.Usersyncer) @@ -422,9 +422,9 @@ func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *users } type mockPermsSetUID struct { - allowHost bool - errorHost bool - allowPI bool + allowHost bool + errorHost bool + personalInfoAllowed bool } func (g *mockPermsSetUID) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { @@ -439,8 +439,8 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { - return g.allowPI, g.allowPI, g.allowPI, nil +func (g *mockPermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { + return g.personalInfoAllowed, g.personalInfoAllowed, g.personalInfoAllowed, nil } func newFakeSyncer(familyName string) usersync.Usersyncer { diff --git a/errortypes/code.go b/errortypes/code.go index f8206525b27..554357ea88a 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -11,6 +11,7 @@ const ( BidderTemporarilyDisabledErrorCode BlacklistedAcctErrorCode AcctRequiredErrorCode + NoConversionRateErrorCode NoBidPriceErrorCode ) @@ -20,6 +21,7 @@ const ( InvalidPrivacyConsentWarningCode = iota + 10000 AccountLevelDebugDisabledWarningCode BidderLevelDebugDisabledWarningCode + DisabledCurrencyConversionWarningCode ) // Coder provides an error or warning code with severity. diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go index 6ac494448ca..b24fde3020e 100755 --- a/exchange/adapter_builders.go +++ b/exchange/adapter_builders.go @@ -4,6 +4,8 @@ import ( "github.com/prebid/prebid-server/adapters" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/acuityads" + "github.com/prebid/prebid-server/adapters/adagio" + "github.com/prebid/prebid-server/adapters/adf" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adgeneration" "github.com/prebid/prebid-server/adapters/adhese" @@ -22,15 +24,20 @@ import ( "github.com/prebid/prebid-server/adapters/adxcg" "github.com/prebid/prebid-server/adapters/adyoulike" "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/applogy" "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/audienceNetwork" "github.com/prebid/prebid-server/adapters/avocet" + "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/between" "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/bmtm" "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/colossus" "github.com/prebid/prebid-server/adapters/connectad" @@ -42,6 +49,7 @@ import ( "github.com/prebid/prebid-server/adapters/decenterads" "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/e_volution" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -52,15 +60,18 @@ import ( "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/improvedigital" "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/ix" "github.com/prebid/prebid-server/adapters/jixie" + "github.com/prebid/prebid-server/adapters/kayzen" "github.com/prebid/prebid-server/adapters/kidoz" "github.com/prebid/prebid-server/adapters/krushmedia" "github.com/prebid/prebid-server/adapters/kubient" "github.com/prebid/prebid-server/adapters/lockerdome" "github.com/prebid/prebid-server/adapters/logicad" "github.com/prebid/prebid-server/adapters/lunamedia" + "github.com/prebid/prebid-server/adapters/madvertise" "github.com/prebid/prebid-server/adapters/marsmedia" "github.com/prebid/prebid-server/adapters/mgid" "github.com/prebid/prebid-server/adapters/mobfoxpb" @@ -70,6 +81,7 @@ import ( "github.com/prebid/prebid-server/adapters/nobid" "github.com/prebid/prebid-server/adapters/onetag" "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/pangle" @@ -80,12 +92,14 @@ import ( "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sa_lunamedia" "github.com/prebid/prebid-server/adapters/sharethrough" "github.com/prebid/prebid-server/adapters/silvermob" "github.com/prebid/prebid-server/adapters/smaato" "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/smartyads" + "github.com/prebid/prebid-server/adapters/smilewanted" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" @@ -116,113 +130,128 @@ import ( func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder { return map[openrtb_ext.BidderName]adapters.Builder{ - openrtb_ext.Bidder33Across: ttx.Builder, - openrtb_ext.BidderAcuityAds: acuityads.Builder, - openrtb_ext.BidderAdform: adform.Builder, - openrtb_ext.BidderAdgeneration: adgeneration.Builder, - openrtb_ext.BidderAdhese: adhese.Builder, - openrtb_ext.BidderAdkernel: adkernel.Builder, - openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder, - openrtb_ext.BidderAdman: adman.Builder, - openrtb_ext.BidderAdmixer: admixer.Builder, - openrtb_ext.BidderAdOcean: adocean.Builder, - openrtb_ext.BidderAdoppler: adoppler.Builder, - openrtb_ext.BidderAdpone: adpone.Builder, - openrtb_ext.BidderAdot: adot.Builder, - openrtb_ext.BidderAdprime: adprime.Builder, - openrtb_ext.BidderAdtarget: adtarget.Builder, - openrtb_ext.BidderAdtelligent: adtelligent.Builder, - openrtb_ext.BidderAdvangelists: advangelists.Builder, - openrtb_ext.BidderAdxcg: adxcg.Builder, - openrtb_ext.BidderAdyoulike: adyoulike.Builder, - openrtb_ext.BidderAJA: aja.Builder, - openrtb_ext.BidderAMX: amx.Builder, - openrtb_ext.BidderApplogy: applogy.Builder, - openrtb_ext.BidderAppnexus: appnexus.Builder, - openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder, - openrtb_ext.BidderAvocet: avocet.Builder, - openrtb_ext.BidderBeachfront: beachfront.Builder, - openrtb_ext.BidderBeintoo: beintoo.Builder, - openrtb_ext.BidderBetween: between.Builder, - openrtb_ext.BidderBidmachine: bidmachine.Builder, - openrtb_ext.BidderBrightroll: brightroll.Builder, - openrtb_ext.BidderColossus: colossus.Builder, - openrtb_ext.BidderConnectAd: connectad.Builder, - openrtb_ext.BidderConsumable: consumable.Builder, - openrtb_ext.BidderConversant: conversant.Builder, - openrtb_ext.BidderCpmstar: cpmstar.Builder, - openrtb_ext.BidderCriteo: criteo.Builder, - openrtb_ext.BidderDatablocks: datablocks.Builder, - openrtb_ext.BidderDecenterAds: decenterads.Builder, - openrtb_ext.BidderDeepintent: deepintent.Builder, - openrtb_ext.BidderDmx: dmx.Builder, - openrtb_ext.BidderEmxDigital: emx_digital.Builder, - openrtb_ext.BidderEngageBDR: engagebdr.Builder, - openrtb_ext.BidderEPlanning: eplanning.Builder, - openrtb_ext.BidderEpom: epom.Builder, - openrtb_ext.BidderGamma: gamma.Builder, - openrtb_ext.BidderGamoshi: gamoshi.Builder, - openrtb_ext.BidderGrid: grid.Builder, - openrtb_ext.BidderGumGum: gumgum.Builder, - openrtb_ext.BidderImprovedigital: improvedigital.Builder, - openrtb_ext.BidderInMobi: inmobi.Builder, - openrtb_ext.BidderInvibes: invibes.Builder, - openrtb_ext.BidderIx: ix.Builder, - openrtb_ext.BidderJixie: jixie.Builder, - openrtb_ext.BidderKidoz: kidoz.Builder, - openrtb_ext.BidderKrushmedia: krushmedia.Builder, - openrtb_ext.BidderKubient: kubient.Builder, - openrtb_ext.BidderLockerDome: lockerdome.Builder, - openrtb_ext.BidderLogicad: logicad.Builder, - openrtb_ext.BidderLunaMedia: lunamedia.Builder, - openrtb_ext.BidderMarsmedia: marsmedia.Builder, - openrtb_ext.BidderMediafuse: adtelligent.Builder, - openrtb_ext.BidderMgid: mgid.Builder, - openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, - openrtb_ext.BidderMobileFuse: mobilefuse.Builder, - openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, - openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, - openrtb_ext.BidderNoBid: nobid.Builder, - openrtb_ext.BidderOneTag: onetag.Builder, - openrtb_ext.BidderOpenx: openx.Builder, - openrtb_ext.BidderOrbidder: orbidder.Builder, - openrtb_ext.BidderOutbrain: outbrain.Builder, - openrtb_ext.BidderPangle: pangle.Builder, - openrtb_ext.BidderPubmatic: pubmatic.Builder, - openrtb_ext.BidderPubnative: pubnative.Builder, - openrtb_ext.BidderPulsepoint: pulsepoint.Builder, - openrtb_ext.BidderRevcontent: revcontent.Builder, - openrtb_ext.BidderRhythmone: rhythmone.Builder, - openrtb_ext.BidderRTBHouse: rtbhouse.Builder, - openrtb_ext.BidderRubicon: rubicon.Builder, - openrtb_ext.BidderSharethrough: sharethrough.Builder, - openrtb_ext.BidderSilverMob: silvermob.Builder, - openrtb_ext.BidderSmaato: smaato.Builder, - openrtb_ext.BidderSmartAdserver: smartadserver.Builder, - openrtb_ext.BidderSmartRTB: smartrtb.Builder, - openrtb_ext.BidderSmartyAds: smartyads.Builder, - openrtb_ext.BidderSomoaudience: somoaudience.Builder, - openrtb_ext.BidderSonobi: sonobi.Builder, - openrtb_ext.BidderSovrn: sovrn.Builder, - openrtb_ext.BidderSpotX: spotx.Builder, - openrtb_ext.BidderSynacormedia: synacormedia.Builder, - openrtb_ext.BidderTappx: tappx.Builder, - openrtb_ext.BidderTelaria: telaria.Builder, - openrtb_ext.BidderTriplelift: triplelift.Builder, - openrtb_ext.BidderTripleliftNative: triplelift_native.Builder, - openrtb_ext.BidderTrustX: grid.Builder, - openrtb_ext.BidderUcfunnel: ucfunnel.Builder, - openrtb_ext.BidderUnicorn: unicorn.Builder, - openrtb_ext.BidderUnruly: unruly.Builder, - openrtb_ext.BidderValueImpression: valueimpression.Builder, - openrtb_ext.BidderVASTBidder: vastbidder.Builder, - openrtb_ext.BidderVerizonMedia: verizonmedia.Builder, - openrtb_ext.BidderVisx: visx.Builder, - openrtb_ext.BidderVrtcal: vrtcal.Builder, - openrtb_ext.BidderYeahmobi: yeahmobi.Builder, - openrtb_ext.BidderYieldlab: yieldlab.Builder, - openrtb_ext.BidderYieldmo: yieldmo.Builder, - openrtb_ext.BidderYieldone: yieldone.Builder, - openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, + openrtb_ext.Bidder33Across: ttx.Builder, + openrtb_ext.BidderAcuityAds: acuityads.Builder, + openrtb_ext.BidderAdagio: adagio.Builder, + openrtb_ext.BidderAdf: adf.Builder, + openrtb_ext.BidderAdform: adform.Builder, + openrtb_ext.BidderAdgeneration: adgeneration.Builder, + openrtb_ext.BidderAdhese: adhese.Builder, + openrtb_ext.BidderAdkernel: adkernel.Builder, + openrtb_ext.BidderAdkernelAdn: adkernelAdn.Builder, + openrtb_ext.BidderAdman: adman.Builder, + openrtb_ext.BidderAdmixer: admixer.Builder, + openrtb_ext.BidderAdOcean: adocean.Builder, + openrtb_ext.BidderAdoppler: adoppler.Builder, + openrtb_ext.BidderAdpone: adpone.Builder, + openrtb_ext.BidderAdot: adot.Builder, + openrtb_ext.BidderAdprime: adprime.Builder, + openrtb_ext.BidderAdtarget: adtarget.Builder, + openrtb_ext.BidderAdtelligent: adtelligent.Builder, + openrtb_ext.BidderAdvangelists: advangelists.Builder, + openrtb_ext.BidderAdxcg: adxcg.Builder, + openrtb_ext.BidderAdyoulike: adyoulike.Builder, + openrtb_ext.BidderAJA: aja.Builder, + openrtb_ext.BidderAlgorix: algorix.Builder, + openrtb_ext.BidderAMX: amx.Builder, + openrtb_ext.BidderApplogy: applogy.Builder, + openrtb_ext.BidderAppnexus: appnexus.Builder, + openrtb_ext.BidderAudienceNetwork: audienceNetwork.Builder, + openrtb_ext.BidderAvocet: avocet.Builder, + openrtb_ext.BidderAxonix: axonix.Builder, + openrtb_ext.BidderBeachfront: beachfront.Builder, + openrtb_ext.BidderBeintoo: beintoo.Builder, + openrtb_ext.BidderBetween: between.Builder, + openrtb_ext.BidderBidmachine: bidmachine.Builder, + openrtb_ext.BidderBidmyadz: bidmyadz.Builder, + openrtb_ext.BidderBidsCube: bidscube.Builder, + openrtb_ext.BidderBmtm: bmtm.Builder, + openrtb_ext.BidderBrightroll: brightroll.Builder, + openrtb_ext.BidderColossus: colossus.Builder, + openrtb_ext.BidderConnectAd: connectad.Builder, + openrtb_ext.BidderConsumable: consumable.Builder, + openrtb_ext.BidderConversant: conversant.Builder, + openrtb_ext.BidderCpmstar: cpmstar.Builder, + openrtb_ext.BidderCriteo: criteo.Builder, + openrtb_ext.BidderDatablocks: datablocks.Builder, + openrtb_ext.BidderDecenterAds: decenterads.Builder, + openrtb_ext.BidderDeepintent: deepintent.Builder, + openrtb_ext.BidderDmx: dmx.Builder, + openrtb_ext.BidderEmxDigital: emx_digital.Builder, + openrtb_ext.BidderEngageBDR: engagebdr.Builder, + openrtb_ext.BidderEPlanning: eplanning.Builder, + openrtb_ext.BidderEpom: epom.Builder, + openrtb_ext.BidderEVolution: evolution.Builder, + openrtb_ext.BidderGamma: gamma.Builder, + openrtb_ext.BidderGamoshi: gamoshi.Builder, + openrtb_ext.BidderGrid: grid.Builder, + openrtb_ext.BidderGumGum: gumgum.Builder, + openrtb_ext.BidderImprovedigital: improvedigital.Builder, + openrtb_ext.BidderInMobi: inmobi.Builder, + openrtb_ext.BidderInteractiveoffers: interactiveoffers.Builder, + openrtb_ext.BidderInvibes: invibes.Builder, + openrtb_ext.BidderIx: ix.Builder, + openrtb_ext.BidderJixie: jixie.Builder, + openrtb_ext.BidderKayzen: kayzen.Builder, + openrtb_ext.BidderKidoz: kidoz.Builder, + openrtb_ext.BidderKrushmedia: krushmedia.Builder, + openrtb_ext.BidderKubient: kubient.Builder, + openrtb_ext.BidderLockerDome: lockerdome.Builder, + openrtb_ext.BidderLogicad: logicad.Builder, + openrtb_ext.BidderLunaMedia: lunamedia.Builder, + openrtb_ext.BidderSaLunaMedia: salunamedia.Builder, + openrtb_ext.BidderMadvertise: madvertise.Builder, + openrtb_ext.BidderMarsmedia: marsmedia.Builder, + openrtb_ext.BidderMediafuse: adtelligent.Builder, + openrtb_ext.BidderMgid: mgid.Builder, + openrtb_ext.BidderMobfoxpb: mobfoxpb.Builder, + openrtb_ext.BidderMobileFuse: mobilefuse.Builder, + openrtb_ext.BidderNanoInteractive: nanointeractive.Builder, + openrtb_ext.BidderNinthDecimal: ninthdecimal.Builder, + openrtb_ext.BidderNoBid: nobid.Builder, + openrtb_ext.BidderOneTag: onetag.Builder, + openrtb_ext.BidderOpenx: openx.Builder, + openrtb_ext.BidderOperaads: operaads.Builder, + openrtb_ext.BidderOrbidder: orbidder.Builder, + openrtb_ext.BidderOutbrain: outbrain.Builder, + openrtb_ext.BidderPangle: pangle.Builder, + openrtb_ext.BidderPubmatic: pubmatic.Builder, + openrtb_ext.BidderPubnative: pubnative.Builder, + openrtb_ext.BidderPulsepoint: pulsepoint.Builder, + openrtb_ext.BidderRevcontent: revcontent.Builder, + openrtb_ext.BidderRhythmone: rhythmone.Builder, + openrtb_ext.BidderRTBHouse: rtbhouse.Builder, + openrtb_ext.BidderRubicon: rubicon.Builder, + openrtb_ext.BidderSharethrough: sharethrough.Builder, + openrtb_ext.BidderSilverMob: silvermob.Builder, + openrtb_ext.BidderSmaato: smaato.Builder, + openrtb_ext.BidderSmartAdserver: smartadserver.Builder, + openrtb_ext.BidderSmartRTB: smartrtb.Builder, + openrtb_ext.BidderSmartyAds: smartyads.Builder, + openrtb_ext.BidderSmileWanted: smilewanted.Builder, + openrtb_ext.BidderSomoaudience: somoaudience.Builder, + openrtb_ext.BidderSonobi: sonobi.Builder, + openrtb_ext.BidderSovrn: sovrn.Builder, + openrtb_ext.BidderSpotX: spotx.Builder, + openrtb_ext.BidderSynacormedia: synacormedia.Builder, + openrtb_ext.BidderTappx: tappx.Builder, + openrtb_ext.BidderTelaria: telaria.Builder, + openrtb_ext.BidderTriplelift: triplelift.Builder, + openrtb_ext.BidderTripleliftNative: triplelift_native.Builder, + openrtb_ext.BidderTrustX: grid.Builder, + openrtb_ext.BidderUcfunnel: ucfunnel.Builder, + openrtb_ext.BidderUnicorn: unicorn.Builder, + openrtb_ext.BidderUnruly: unruly.Builder, + openrtb_ext.BidderValueImpression: valueimpression.Builder, + openrtb_ext.BidderVASTBidder: vastbidder.Builder, + openrtb_ext.BidderVerizonMedia: verizonmedia.Builder, + openrtb_ext.BidderViewdeos: adtelligent.Builder, + openrtb_ext.BidderVisx: visx.Builder, + openrtb_ext.BidderVrtcal: vrtcal.Builder, + openrtb_ext.BidderYeahmobi: yeahmobi.Builder, + openrtb_ext.BidderYieldlab: yieldlab.Builder, + openrtb_ext.BidderYieldmo: yieldmo.Builder, + openrtb_ext.BidderYieldone: yieldone.Builder, + openrtb_ext.BidderZeroClickFraud: zeroclickfraud.Builder, } } diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go index 8af6d11ad60..9ab27561de0 100644 --- a/exchange/adapter_util.go +++ b/exchange/adapter_util.go @@ -4,33 +4,13 @@ import ( "fmt" "net/http" - "github.com/prebid/prebid-server/metrics" - "github.com/prebid/prebid-server/adapters" - "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" ) func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) { - exchangeBidders := buildExchangeBiddersLegacy(cfg.Adapters, infos) - - exchangeBiddersModern, errs := buildExchangeBidders(cfg, infos, client, me) - if len(errs) > 0 { - return nil, errs - } - - // Merge legacy and modern bidders, giving priority to the modern bidders. - for bidderName, bidder := range exchangeBiddersModern { - exchangeBidders[bidderName] = bidder - } - - wrapWithMiddleware(exchangeBidders) - - return exchangeBidders, nil -} - -func buildExchangeBidders(cfg *config.Configuration, infos config.BidderInfos, client *http.Client, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]adaptedBidder, []error) { bidders, errs := buildBidders(cfg.Adapters, infos, newAdapterBuilders()) if len(errs) > 0 { return nil, errs @@ -38,16 +18,12 @@ func buildExchangeBidders(cfg *config.Configuration, infos config.BidderInfos, c exchangeBidders := make(map[openrtb_ext.BidderName]adaptedBidder, len(bidders)) for bidderName, bidder := range bidders { - info, infoFound := infos[string(bidderName)] - if !infoFound { - errs = append(errs, fmt.Errorf("%v: bidder info not found", bidder)) - continue - } - exchangeBidders[bidderName] = adaptBidder(bidder, client, cfg, me, bidderName, info.Debug) + info := infos[string(bidderName)] + exchangeBidder := adaptBidder(bidder, client, cfg, me, bidderName, info.Debug) + exchangeBidder = addValidatedBidderMiddleware(exchangeBidder) + exchangeBidders[bidderName] = exchangeBidder } - return exchangeBidders, nil - } func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderInfos, builders map[openrtb_ext.BidderName]adapters.Builder) (map[openrtb_ext.BidderName]adapters.Bidder, []error) { @@ -61,11 +37,6 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderIn continue } - // Ignore Legacy Bidders - if bidderName == openrtb_ext.BidderLifestreet { - continue - } - info, infoFound := infos[string(bidderName)] if !infoFound { errs = append(errs, fmt.Errorf("%v: bidder info not found", bidder)) @@ -84,34 +55,13 @@ func buildBidders(adapterConfig map[string]config.Adapter, infos config.BidderIn errs = append(errs, fmt.Errorf("%v: %v", bidder, builderErr)) continue } - - bidderWithInfoEnforcement := adapters.BuildInfoAwareBidder(bidderInstance, info) - - bidders[bidderName] = bidderWithInfoEnforcement + bidders[bidderName] = adapters.BuildInfoAwareBidder(bidderInstance, info) } } return bidders, errs } -func buildExchangeBiddersLegacy(adapterConfig map[string]config.Adapter, infos config.BidderInfos) map[openrtb_ext.BidderName]adaptedBidder { - bidders := make(map[openrtb_ext.BidderName]adaptedBidder, 2) - - // Lifestreet - if infos[string(openrtb_ext.BidderLifestreet)].Enabled { - adapter := lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, adapterConfig[string(openrtb_ext.BidderLifestreet)].Endpoint) - bidders[openrtb_ext.BidderLifestreet] = adaptLegacyAdapter(adapter) - } - - return bidders -} - -func wrapWithMiddleware(bidders map[openrtb_ext.BidderName]adaptedBidder) { - for name, bidder := range bidders { - bidders[name] = addValidatedBidderMiddleware(bidder) - } -} - // GetActiveBidders returns a map of all active bidder names. func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderName { activeBidders := make(map[string]openrtb_ext.BidderName) @@ -127,7 +77,9 @@ func GetActiveBidders(infos config.BidderInfos) map[string]openrtb_ext.BidderNam // GetDisabledBiddersErrorMessages returns a map of error messages for disabled bidders. func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string { - disabledBidders := make(map[string]string) + disabledBidders := map[string]string{ + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + } for name, info := range infos { if !info.Enabled { diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go index c9f1907d314..a8e8ef5e30a 100644 --- a/exchange/adapter_util_test.go +++ b/exchange/adapter_util_test.go @@ -1,7 +1,6 @@ package exchange import ( - "context" "errors" "net/http" "testing" @@ -9,10 +8,8 @@ import ( "github.com/mxmCherry/openrtb/v15/openrtb2" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/adapters/appnexus" - "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/rubicon" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/currency" metrics "github.com/prebid/prebid-server/metrics/config" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" @@ -23,64 +20,19 @@ var ( infoDisabled = config.BidderInfo{Enabled: false} ) -func TestBuildAdaptersSuccess(t *testing.T) { - client := &http.Client{} - cfg := &config.Configuration{Adapters: map[string]config.Adapter{ - "appnexus": {}, - "lifestreet": {Endpoint: "anyEndpoint"}, - }} - infos := map[string]config.BidderInfo{ - "appnexus": infoEnabled, - "lifestreet": infoEnabled, - } - metricEngine := &metrics.DummyMetricsEngine{} - - bidders, errs := BuildAdapters(client, cfg, infos, metricEngine) - - appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}) - appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled) - appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil) - appnexusBidderValidated := addValidatedBidderMiddleware(appnexusBidderAdapted) - - idLegacyAdapted := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")} - idLegacyValidated := addValidatedBidderMiddleware(idLegacyAdapted) - - expectedBidders := map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: appnexusBidderValidated, - openrtb_ext.BidderLifestreet: idLegacyValidated, - } - - assert.Equal(t, expectedBidders, bidders) - assert.Empty(t, errs) -} - -func TestBuildAdaptersErrors(t *testing.T) { - client := &http.Client{} - cfg := &config.Configuration{Adapters: map[string]config.Adapter{"unknown": {}}} - infos := map[string]config.BidderInfo{} - metricEngine := &metrics.DummyMetricsEngine{} - - bidders, errs := BuildAdapters(client, cfg, infos, metricEngine) - - expectedErrors := []error{ - errors.New("unknown: unknown bidder"), - } - - assert.Empty(t, bidders) - assert.Equal(t, expectedErrors, errs) -} - -func TestBuildExchangeBidders(t *testing.T) { +func TestBuildAdapters(t *testing.T) { client := &http.Client{} metricEngine := &metrics.DummyMetricsEngine{} appnexusBidder, _ := appnexus.Builder(openrtb_ext.BidderAppnexus, config.Adapter{}) appnexusBidderWithInfo := adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled) appnexusBidderAdapted := adaptBidder(appnexusBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderAppnexus, nil) + appnexusValidated := addValidatedBidderMiddleware(appnexusBidderAdapted) rubiconBidder, _ := rubicon.Builder(openrtb_ext.BidderRubicon, config.Adapter{}) rubiconBidderWithInfo := adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled) rubiconBidderAdapted := adaptBidder(rubiconBidderWithInfo, client, &config.Configuration{}, metricEngine, openrtb_ext.BidderRubicon, nil) + rubiconbidderValidated := addValidatedBidderMiddleware(rubiconBidderAdapted) testCases := []struct { description string @@ -90,42 +42,42 @@ func TestBuildExchangeBidders(t *testing.T) { expectedErrors []error }{ { - description: "Invalid - Builder Errors", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "unknown": {}}, - bidderInfos: map[string]config.BidderInfo{}, - expectedErrors: []error{ - errors.New("appnexus: bidder info not found"), - errors.New("unknown: unknown bidder"), - }, - }, - { - description: "Success - None", + description: "No Bidders", adapterConfig: map[string]config.Adapter{}, bidderInfos: map[string]config.BidderInfo{}, expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{}, }, { - description: "Success - One", + description: "One Bidder", adapterConfig: map[string]config.Adapter{"appnexus": {}}, bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: appnexusBidderAdapted, + openrtb_ext.BidderAppnexus: appnexusValidated, }, }, { - description: "Success - Many", + description: "Many Bidders", adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "rubicon": infoEnabled}, expectedBidders: map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: appnexusBidderAdapted, - openrtb_ext.BidderRubicon: rubiconBidderAdapted, + openrtb_ext.BidderAppnexus: appnexusValidated, + openrtb_ext.BidderRubicon: rubiconbidderValidated, + }, + }, + { + description: "Invalid - Builder Errors", + adapterConfig: map[string]config.Adapter{"appnexus": {}, "unknown": {}}, + bidderInfos: map[string]config.BidderInfo{}, + expectedErrors: []error{ + errors.New("appnexus: bidder info not found"), + errors.New("unknown: unknown bidder"), }, }, } for _, test := range testCases { cfg := &config.Configuration{Adapters: test.adapterConfig} - bidders, errs := buildExchangeBidders(cfg, test.bidderInfos, client, metricEngine) + bidders, errs := BuildAdapters(client, cfg, test.bidderInfos, metricEngine) assert.Equal(t, test.expectedBidders, bidders, test.description+":bidders") assert.ElementsMatch(t, test.expectedErrors, errs, test.description+":errors") } @@ -139,8 +91,6 @@ func TestBuildBidders(t *testing.T) { rubiconBidder := fakeBidder{"b"} rubiconBuilder := fakeBuilder{rubiconBidder, nil}.Builder - inconsequentialBuilder := fakeBuilder{fakeBidder{"whatevs"}, nil}.Builder - testCases := []struct { description string adapterConfig map[string]config.Adapter @@ -210,15 +160,6 @@ func TestBuildBidders(t *testing.T) { openrtb_ext.BidderRubicon: adapters.BuildInfoAwareBidder(rubiconBidder, infoEnabled), }, }, - { - description: "Success - Ignores Legacy", - adapterConfig: map[string]config.Adapter{"appnexus": {}, "lifestreet": {}}, - bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled, "lifestreet": infoEnabled}, - builders: map[openrtb_ext.BidderName]adapters.Builder{openrtb_ext.BidderAppnexus: appnexusBuilder, openrtb_ext.BidderLifestreet: inconsequentialBuilder}, - expectedBidders: map[openrtb_ext.BidderName]adapters.Bidder{ - openrtb_ext.BidderAppnexus: adapters.BuildInfoAwareBidder(appnexusBidder, infoEnabled), - }, - }, { description: "Success - Ignores Disabled", adapterConfig: map[string]config.Adapter{"appnexus": {}, "rubicon": {}}, @@ -252,53 +193,6 @@ func TestBuildBidders(t *testing.T) { } } -func TestBuildExchangeBiddersLegacy(t *testing.T) { - cfg := config.Adapter{Endpoint: "anyEndpoint"} - - expectedLifestreet := &adaptedAdapter{lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, "anyEndpoint")} - - testCases := []struct { - description string - adapterConfig map[string]config.Adapter - bidderInfos map[string]config.BidderInfo - expected map[openrtb_ext.BidderName]adaptedBidder - }{ - { - description: "Active", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, - bidderInfos: map[string]config.BidderInfo{"lifestreet": infoEnabled}, - expected: map[openrtb_ext.BidderName]adaptedBidder{"lifestreet": expectedLifestreet}, - }, - { - description: "Disabled", - adapterConfig: map[string]config.Adapter{"lifestreet": cfg}, - bidderInfos: map[string]config.BidderInfo{"lifestreet": infoDisabled}, - expected: map[openrtb_ext.BidderName]adaptedBidder{}, - }, - } - - for _, test := range testCases { - result := buildExchangeBiddersLegacy(test.adapterConfig, test.bidderInfos) - assert.Equal(t, test.expected, result, test.description) - } -} - -func TestWrapWithMiddleware(t *testing.T) { - appNexusBidder := fakeAdaptedBidder{} - - bidders := map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: appNexusBidder, - } - - wrapWithMiddleware(bidders) - - expected := map[openrtb_ext.BidderName]adaptedBidder{ - openrtb_ext.BidderAppnexus: &validatedBidder{appNexusBidder}, - } - - assert.Equal(t, expected, bidders) -} - func TestGetActiveBidders(t *testing.T) { testCases := []struct { description string @@ -342,24 +236,32 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) { { description: "None", bidderInfos: map[string]config.BidderInfo{}, - expected: map[string]string{}, + expected: map[string]string{ + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + }, }, { description: "Enabled", bidderInfos: map[string]config.BidderInfo{"appnexus": infoEnabled}, - expected: map[string]string{}, + expected: map[string]string{ + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + }, }, { description: "Disabled", bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled}, expected: map[string]string{ - "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, }, }, { description: "Mixed", bidderInfos: map[string]config.BidderInfo{"appnexus": infoDisabled, "openx": infoEnabled}, - expected: map[string]string{"appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`}, + expected: map[string]string{ + "lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`, + "appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`, + }, }, } @@ -369,12 +271,6 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) { } } -type fakeAdaptedBidder struct{} - -func (fakeAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { - return nil, nil -} - type fakeBidder struct { name string } diff --git a/exchange/auction.go b/exchange/auction.go index f2c37f7a8bd..2b5d6f75aeb 100644 --- a/exchange/auction.go +++ b/exchange/auction.go @@ -17,14 +17,21 @@ import ( "github.com/prebid/prebid-server/prebid_cache_client" ) +const ( + DebugOverrideHeader string = "x-pbs-debug-override" +) + type DebugLog struct { - Enabled bool - CacheType prebid_cache_client.PayloadType - Data DebugData - TTL int64 - CacheKey string - CacheString string - Regexp *regexp.Regexp + Enabled bool + CacheType prebid_cache_client.PayloadType + Data DebugData + TTL int64 + CacheKey string + CacheString string + Regexp *regexp.Regexp + DebugOverride bool + //little optimization, it stores value of debugLog.Enabled || debugLog.DebugOverride + DebugEnabledOrOverridden bool } type DebugData struct { @@ -47,6 +54,10 @@ func (d *DebugLog) BuildCacheString() { d.CacheString = fmt.Sprintf("%s%s%s%s", xml.Header, d.Data.Request, d.Data.Headers, d.Data.Response) } +func IsDebugOverrideEnabled(debugHeader, configOverrideToken string) bool { + return configOverrideToken != "" && debugHeader == configOverrideToken +} + func (d *DebugLog) PutDebugLogError(cache prebid_cache_client.Client, timeout int, errors []error) error { if len(d.Data.Response) == 0 && len(errors) == 0 { d.Data.Response = "No response or errors created" @@ -238,7 +249,7 @@ func (a *auction) doCache(ctx context.Context, cache prebid_cache_client.Client, } } - if len(toCache) > 0 && debugLog != nil && debugLog.Enabled { + if len(toCache) > 0 && debugLog != nil && debugLog.DebugEnabledOrOverridden { debugLog.CacheKey = hbCacheID debugLog.BuildCacheString() if jsonBytes, err := json.Marshal(debugLog.CacheString); err == nil { diff --git a/exchange/auction_test.go b/exchange/auction_test.go index 1730309287c..04aef256a81 100644 --- a/exchange/auction_test.go +++ b/exchange/auction_test.go @@ -132,6 +132,56 @@ func TestCacheJSON(t *testing.T) { } } +func TestIsDebugOverrideEnabled(t *testing.T) { + type inTest struct { + debugHeader string + configToken string + } + type aTest struct { + desc string + in inTest + result bool + } + testCases := []aTest{ + { + desc: "test debug header is empty, config token is empty", + in: inTest{debugHeader: "", configToken: ""}, + result: false, + }, + { + desc: "test debug header is present, config token is empty", + in: inTest{debugHeader: "TestToken", configToken: ""}, + result: false, + }, + { + desc: "test debug header is empty, config token is present", + in: inTest{debugHeader: "", configToken: "TestToken"}, + result: false, + }, + { + desc: "test debug header is present, config token is present, not equal", + in: inTest{debugHeader: "TestToken123", configToken: "TestToken"}, + result: false, + }, + { + desc: "test debug header is present, config token is present, equal", + in: inTest{debugHeader: "TestToken", configToken: "TestToken"}, + result: true, + }, + { + desc: "test debug header is present, config token is present, not case equal", + in: inTest{debugHeader: "TestTokeN", configToken: "TestToken"}, + result: false, + }, + } + + for _, test := range testCases { + result := IsDebugOverrideEnabled(test.in.debugHeader, test.in.configToken) + assert.Equal(t, test.result, result, test.desc) + } + +} + // LoadCacheSpec reads and parses a file as a test case. If something goes wrong, it returns an error. func loadCacheSpec(filename string) (*cacheSpec, error) { specData, err := ioutil.ReadFile(filename) diff --git a/exchange/bidder.go b/exchange/bidder.go index 07d222c9602..8dcc9b5b856 100644 --- a/exchange/bidder.go +++ b/exchange/bidder.go @@ -49,7 +49,7 @@ type adaptedBidder interface { // // Any errors will be user-facing in the API. // Error messages should help publishers understand what might account for "bad" bids. - requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) + requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) } // pbsOrtbBid is a Bid returned by an adaptedBidder. @@ -128,7 +128,7 @@ type bidderAdapterConfig struct { DebugInfo config.DebugInfo } -func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { reqData, errs := bidder.Bidder.MakeRequests(request, reqInfo) if len(reqData) == 0 { @@ -139,6 +139,19 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B return nil, errs } + if reqInfo.GlobalPrivacyControlHeader == "1" { + for i := 0; i < len(reqData); i++ { + if reqData[i].Headers != nil { + reqHeader := reqData[i].Headers.Clone() + reqHeader.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader) + reqData[i].Headers = reqHeader + } else { + reqData[i].Headers = http.Header{} + reqData[i].Headers.Add("Sec-GPC", reqInfo.GlobalPrivacyControlHeader) + } + } + } + // Make any HTTP requests in parallel. // If the bidder only needs to make one, save some cycles by just using the current one. responseChannel := make(chan *httpCallInfo, len(reqData)) @@ -165,19 +178,25 @@ func (bidder *bidderAdapter) requestBid(ctx context.Context, request *openrtb2.B httpInfo := <-responseChannel // If this is a test bid, capture debugging info from the requests. // Write debug data to ext in case if: + // - headerDebugAllowed (debug override header specified correct) - it overrides all other debug restrictions // - debugContextKey (url param) in true // - account debug is allowed // - bidder debug is allowed - if debugInfo := ctx.Value(DebugContextKey); debugInfo != nil && debugInfo.(bool) { - if accountDebugAllowed { - if bidder.config.DebugInfo.Allow { - seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) - } else { - debugDisabledWarning := errortypes.Warning{ - WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, - Message: "debug turned off for bidder", + if headerDebugAllowed { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugInfo := ctx.Value(DebugContextKey) + if debugInfo != nil && debugInfo.(bool) { + if accountDebugAllowed { + if bidder.config.DebugInfo.Allow { + seatBid.httpCalls = append(seatBid.httpCalls, makeExt(httpInfo)) + } else { + debugDisabledWarning := errortypes.Warning{ + WarningCode: errortypes.BidderLevelDebugDisabledWarningCode, + Message: "debug turned off for bidder", + } + errs = append(errs, &debugDisabledWarning) } - errs = append(errs, &debugDisabledWarning) } } } diff --git a/exchange/bidder_test.go b/exchange/bidder_test.go index a3a0acbe5f0..87e1a8d8366 100644 --- a/exchange/bidder_test.go +++ b/exchange/bidder_test.go @@ -94,7 +94,7 @@ func TestSingleBidder(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) // Make sure the goodSingleBidder was called with the expected arguments. if bidderImpl.httpResponse == nil { @@ -167,7 +167,7 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) expectedHttpCalls := []*openrtb_ext.ExtHttpCall{ { @@ -183,6 +183,85 @@ func TestRequestBidRemovesSensitiveHeaders(t *testing.T) { assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCalls) } +func TestSetGPCHeader(t *testing.T) { + server := httptest.NewServer(mockHandler(200, "getBody", "responseJson")) + defer server.Close() + + requestHeaders := http.Header{} + requestHeaders.Add("Content-Type", "application/json") + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("requestJson"), + Headers: requestHeaders, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{}, + }, + } + + debugInfo := &config.DebugInfo{Allow: true} + ctx := context.Background() + ctx = context.WithValue(ctx, DebugContextKey, true) + + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) + + expectedHttpCall := []*openrtb_ext.ExtHttpCall{ + { + Uri: server.URL, + RequestBody: "requestJson", + RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "Sec-Gpc": {"1"}}, + ResponseBody: "responseJson", + Status: 200, + }, + } + + assert.Empty(t, errs) + assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCall) +} + +func TestSetGPCHeaderNil(t *testing.T) { + server := httptest.NewServer(mockHandler(200, "getBody", "responseJson")) + defer server.Close() + + bidderImpl := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: server.URL, + Body: []byte("requestJson"), + Headers: nil, + }, + bidResponse: &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{}, + }, + } + + debugInfo := &config.DebugInfo{Allow: true} + ctx := context.Background() + ctx = context.WithValue(ctx, DebugContextKey, true) + + bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo) + currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) + seatBid, errs := bidder.requestBid(ctx, &openrtb2.BidRequest{}, "test", 1, currencyConverter.Rates(), &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}, true, false) + + expectedHttpCall := []*openrtb_ext.ExtHttpCall{ + { + Uri: server.URL, + RequestBody: "requestJson", + RequestHeaders: map[string][]string{"Sec-Gpc": {"1"}}, + ResponseBody: "responseJson", + Status: 200, + }, + } + + assert.Empty(t, errs) + assert.ElementsMatch(t, seatBid.httpCalls, expectedHttpCall) +} + // TestMultiBidder makes sure all the requests get sent, and the responses processed. // Because this is done in parallel, it should be run under the race detector. func TestMultiBidder(t *testing.T) { @@ -225,7 +304,7 @@ func TestMultiBidder(t *testing.T) { } bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true) if seatBid == nil { t.Fatalf("SeatBid should exist, because bids exist.") @@ -485,7 +564,7 @@ func TestMultiCurrencies(t *testing.T) { {currency: "USD", price: 1.3 * 1.3050530256}, }, expectedBadCurrencyErrors: []error{ - errors.New("Currency conversion rate not found: 'JPY' => 'USD'"), + currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"}, }, description: "Case 6 - Bidder respond with a mix of currencies and one unknown on all HTTP responses", }, @@ -508,9 +587,9 @@ func TestMultiCurrencies(t *testing.T) { }, expectedBids: []bid{}, expectedBadCurrencyErrors: []error{ - errors.New("Currency conversion rate not found: 'JPY' => 'USD'"), - errors.New("Currency conversion rate not found: 'BZD' => 'USD'"), - errors.New("Currency conversion rate not found: 'DKK' => 'USD'"), + currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "BZD", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "DKK", ToCur: "USD"}, }, description: "Case 7 - Bidder respond with currencies not having any rate on all HTTP responses", }, @@ -602,6 +681,7 @@ func TestMultiCurrencies(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) // Verify: @@ -640,9 +720,9 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "EUR", "EUR"}, expectedBidsCount: 0, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, }, description: "Case 2 - Bidder respond with the same currency (not default one) on all HTTP responses", }, @@ -674,7 +754,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"EUR", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'EUR' => 'USD'"), + currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"}, }, description: "Case 7 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -682,7 +762,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", "USD"}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'GBP' => 'USD'"), + currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"}, }, description: "Case 8 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses", }, @@ -690,7 +770,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { bidCurrency: []string{"GBP", "", ""}, expectedBidsCount: 2, expectedBadCurrencyErrors: []error{ - fmt.Errorf("Constant rates doesn't proceed to any conversions, cannot convert 'GBP' => 'USD'"), + currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"}, }, description: "Case 9 - Bidder responds with a mix of not set and empty currencies (default currency) in HTTP responses", }, @@ -747,6 +827,7 @@ func TestMultiCurrencies_RateConverterNotSet(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) // Verify: @@ -920,6 +1001,7 @@ func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + false, ) // Verify: @@ -1224,6 +1306,7 @@ func TestMobileNativeTypes(t *testing.T) { currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, + true, ) var actualValue string @@ -1237,7 +1320,7 @@ func TestMobileNativeTypes(t *testing.T) { func TestErrorReporting(t *testing.T) { bidder := adaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + bids, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, false) if bids != nil { t.Errorf("There should be no seatbid if no http requests are returned.") } @@ -1458,7 +1541,7 @@ func TestCallRecordAdapterConnections(t *testing.T) { // Run requestBid using an http.Client with a mock handler bidder := adaptBidder(bidderImpl, server.Client(), &config.Configuration{}, metrics, openrtb_ext.BidderAppnexus, nil) currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) + _, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, "test", bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true, true) // Assert no errors assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs) diff --git a/exchange/bidder_validate_bids.go b/exchange/bidder_validate_bids.go index 3d2eb0b8e42..d58c9ebba50 100644 --- a/exchange/bidder_validate_bids.go +++ b/exchange/bidder_validate_bids.go @@ -28,8 +28,8 @@ type validatedBidder struct { bidder adaptedBidder } -func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { - seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed) +func (v *validatedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { + seatBid, errs := v.bidder.requestBid(ctx, request, name, bidAdjustment, conversions, reqInfo, accountDebugAllowed, headerDebugAllowed) if validationErrors := removeInvalidBids(request, seatBid); len(validationErrors) > 0 { errs = append(errs, validationErrors...) } @@ -110,8 +110,11 @@ func validateBid(bid *pbsOrtbBid) (bool, error) { if bid.bid.ImpID == "" { return false, fmt.Errorf("Bid \"%s\" missing required field 'impid'", bid.bid.ID) } - if bid.bid.Price <= 0.0 { - return false, fmt.Errorf("Bid \"%s\" does not contain a positive 'price'", bid.bid.ID) + if bid.bid.Price < 0.0 { + return false, fmt.Errorf("Bid \"%s\" does not contain a positive (or zero if there is a deal) 'price'", bid.bid.ID) + } + if bid.bid.Price == 0.0 && bid.bid.DealID == "" { + return false, fmt.Errorf("Bid \"%s\" does not contain positive 'price' which is required since there is no deal set for this bid", bid.bid.ID) } if bid.bid.CrID == "" { return false, fmt.Errorf("Bid \"%s\" missing creative ID", bid.bid.ID) diff --git a/exchange/bidder_validate_bids_test.go b/exchange/bidder_validate_bids_test.go index 3bb43559856..37c7bbec1eb 100644 --- a/exchange/bidder_validate_bids_test.go +++ b/exchange/bidder_validate_bids_test.go @@ -39,11 +39,20 @@ func TestAllValidBids(t *testing.T) { CrID: "789", }, }, + { + bid: &openrtb2.Bid{ + ID: "zeroPriceBid", + ImpID: "444", + Price: 0.00, + CrID: "555", + DealID: "777", + }, + }, }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) - assert.Len(t, seatBid.bids, 3) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) + assert.Len(t, seatBid.bids, 4) assert.Len(t, errs, 0) } @@ -79,13 +88,30 @@ func TestAllBadBids(t *testing.T) { CrID: "blah", }, }, + { + bid: &openrtb2.Bid{ + ID: "zeroPriceBidNoDeal", + ImpID: "444", + Price: 0.00, + CrID: "555", + DealID: "", + }, + }, + { + bid: &openrtb2.Bid{ + ID: "negativePrice", + ImpID: "999", + Price: -0.10, + CrID: "888", + }, + }, {}, }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, 0) - assert.Len(t, errs, 5) + assert.Len(t, errs, 7) } func TestMixedBids(t *testing.T) { @@ -122,13 +148,39 @@ func TestMixedBids(t *testing.T) { CrID: "blah", }, }, + { + bid: &openrtb2.Bid{ + ID: "zeroPriceBid", + ImpID: "444", + Price: 0.00, + CrID: "555", + DealID: "777", + }, + }, + { + bid: &openrtb2.Bid{ + ID: "zeroPriceBidNoDeal", + ImpID: "444", + Price: 0.00, + CrID: "555", + DealID: "", + }, + }, + { + bid: &openrtb2.Bid{ + ID: "negativePrice", + ImpID: "999", + Price: -0.10, + CrID: "888", + }, + }, {}, }, }, }) - seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) - assert.Len(t, seatBid.bids, 2) - assert.Len(t, errs, 3) + seatBid, errs := bidder.requestBid(context.Background(), &openrtb2.BidRequest{}, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) + assert.Len(t, seatBid.bids, 3) + assert.Len(t, errs, 5) } func TestCurrencyBids(t *testing.T) { @@ -246,7 +298,7 @@ func TestCurrencyBids(t *testing.T) { Cur: tc.brqCur, } - seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true) + seatBid, errs := bidder.requestBid(context.Background(), request, openrtb_ext.BidderAppnexus, 1.0, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, true, false) assert.Len(t, seatBid.bids, expectedValidBids) assert.Len(t, errs, expectedErrs) } @@ -257,6 +309,6 @@ type mockAdaptedBidder struct { errorResponse []error } -func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { +func (b *mockAdaptedBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (*pbsOrtbSeatBid, []error) { return b.bidResponse, b.errorResponse } diff --git a/exchange/cachetest/debuglog_enabled.json b/exchange/cachetest/debuglog_enabled.json index e6c85c57055..faba3ed690d 100644 --- a/exchange/cachetest/debuglog_enabled.json +++ b/exchange/cachetest/debuglog_enabled.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { diff --git a/exchange/events.go b/exchange/events.go index 9742e50e424..06f26b7e333 100644 --- a/exchange/events.go +++ b/exchange/events.go @@ -2,10 +2,10 @@ package exchange import ( "encoding/json" + "github.com/mxmCherry/openrtb/v15/openrtb2" "time" - jsonpatch "github.com/evanphx/json-patch" - "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/evanphx/json-patch" "github.com/prebid/prebid-server/analytics" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/endpoints/events" diff --git a/exchange/exchange.go b/exchange/exchange.go index b28c60e4fbe..fa8b51901ff 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -52,19 +52,19 @@ type IdFetcher interface { } type exchange struct { - adapterMap map[openrtb_ext.BidderName]adaptedBidder - bidderInfo config.BidderInfos - me metrics.MetricsEngine - cache prebid_cache_client.Client - cacheTime time.Duration - gDPR gdpr.Permissions - currencyConverter *currency.RateConverter - externalURL string - UsersyncIfAmbiguous bool - privacyConfig config.Privacy - categoriesFetcher stored_requests.CategoryFetcher - bidIDGenerator BidIDGenerator - trakerURL string + adapterMap map[openrtb_ext.BidderName]adaptedBidder + bidderInfo config.BidderInfos + me metrics.MetricsEngine + cache prebid_cache_client.Client + cacheTime time.Duration + gDPR gdpr.Permissions + currencyConverter *currency.RateConverter + externalURL string + gdprDefaultValue gdpr.Signal + privacyConfig config.Privacy + categoriesFetcher stored_requests.CategoryFetcher + bidIDGenerator BidIDGenerator + trakerURL string } // Container to pass out response ext data from the GetAllBids goroutines back into the main thread @@ -101,18 +101,33 @@ func (big *bidIDGenerator) New() (string, error) { return rawUuid.String(), err } +type deduplicateChanceGenerator interface { + Generate() bool +} + +type randomDeduplicateBidBooleanGenerator struct{} + +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, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange { + gdprDefaultValue := gdpr.SignalYes + if cfg.GDPR.DefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + return &exchange{ - adapterMap: adapters, - bidderInfo: infos, - cache: cache, - cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond, - categoriesFetcher: categoriesFetcher, - currencyConverter: currencyConverter, - externalURL: cfg.ExternalURL, - gDPR: gDPR, - me: metricsEngine, - UsersyncIfAmbiguous: cfg.GDPR.UsersyncIfAmbiguous, + adapterMap: adapters, + bidderInfo: infos, + cache: cache, + cacheTime: time.Duration(cfg.CacheURL.ExpectedTimeMillis) * time.Millisecond, + categoriesFetcher: categoriesFetcher, + currencyConverter: currencyConverter, + externalURL: cfg.ExternalURL, + gDPR: gDPR, + me: metricsEngine, + gdprDefaultValue: gdprDefaultValue, privacyConfig: config.Privacy{ CCPA: cfg.CCPA, GDPR: cfg.GDPR, @@ -126,12 +141,13 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid // AuctionRequest holds the bid request for the auction // and all other information needed to process that request type AuctionRequest struct { - BidRequest *openrtb2.BidRequest - Account config.Account - UserSyncs IdFetcher - RequestType metrics.RequestType - StartTime time.Time - Warnings []error + BidRequest *openrtb2.BidRequest + Account config.Account + UserSyncs IdFetcher + RequestType metrics.RequestType + StartTime time.Time + Warnings []error + GlobalPrivacyControlHeader string // LegacyLabels is included here for temporary compatability with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. @@ -161,13 +177,13 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } if debugLog == nil { - debugLog = &DebugLog{Enabled: false} + debugLog = &DebugLog{Enabled: false, DebugEnabledOrOverridden: false} } requestDebugInfo := getDebugInfo(r.BidRequest, requestExt) - debugInfo := requestDebugInfo && r.Account.DebugAllow - debugLog.Enabled = debugLog.Enabled && r.Account.DebugAllow + debugInfo := debugLog.DebugEnabledOrOverridden || (requestDebugInfo && r.Account.DebugAllow) + debugLog.Enabled = debugLog.DebugEnabledOrOverridden || r.Account.DebugAllow if debugInfo { ctx = e.makeDebugContext(ctx, debugInfo) @@ -178,10 +194,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * recordImpMetrics(r.BidRequest, e.me) // Make our best guess if GDPR applies - usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(r.BidRequest) + gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequest) // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, usersyncIfAmbiguous, e.privacyConfig, &r.Account) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, e.me, gdprDefaultValue, e.privacyConfig, &r.Account) e.me.RecordRequestPrivacy(privacyLabels) @@ -194,9 +210,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * defer cancel() // Get currency rates conversions for the auction - conversions := e.currencyConverter.Rates() + conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions) - adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow) + adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, r.Account.DebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride) var auc *auction var cacheErrs []error @@ -213,7 +229,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * //If includebrandcategory is present in ext then CE feature is on. if requestExt.Prebid.Targeting != nil && requestExt.Prebid.Targeting.IncludeBrandCategory != nil { var rejections []string - bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r.BidRequest, requestExt, adapterBids, e.categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err = applyCategoryMapping(ctx, r.BidRequest, requestExt, adapterBids, e.categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) if err != nil { return nil, fmt.Errorf("Error in category mapping : %s", err.Error()) } @@ -247,7 +263,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) } else { @@ -268,7 +284,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } else { bidResponseExt = e.makeExtBidResponse(adapterBids, adapterExtra, r, debugInfo, errs) - if debugLog.Enabled { + if debugLog.DebugEnabledOrOverridden { if bidRespExtBytes, err := json.Marshal(bidResponseExt); err == nil { debugLog.Data.Response = string(bidRespExtBytes) @@ -279,7 +295,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * } } - if !r.Account.DebugAllow && requestDebugInfo { + if !r.Account.DebugAllow && requestDebugInfo && !debugLog.DebugOverride { accountDebugDisabledWarning := openrtb_ext.ExtBidderMessage{ Code: errortypes.AccountLevelDebugDisabledWarningCode, Message: "debug turned off for account", @@ -299,8 +315,8 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * return e.buildBidResponse(ctx, liveAdapters, adapterBids, r.BidRequest, adapterExtra, auc, bidResponseExt, cacheInstructions.returnCreative, errs) } -func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) bool { - usersyncIfAmbiguous := e.UsersyncIfAmbiguous +func (e *exchange) parseGDPRDefaultValue(bidRequest *openrtb2.BidRequest) gdpr.Signal { + gdprDefaultValue := e.gdprDefaultValue var geo *openrtb2.Geo = nil if bidRequest.User != nil && bidRequest.User.Geo != nil { @@ -312,14 +328,14 @@ func (e *exchange) parseUsersyncIfAmbiguous(bidRequest *openrtb2.BidRequest) boo // If we have a country set, and it is on the list, we assume GDPR applies if not set on the request. // Otherwise we assume it does not apply as long as it appears "valid" (is 3 characters long). if _, found := e.privacyConfig.GDPR.EEACountriesMap[strings.ToUpper(geo.Country)]; found { - usersyncIfAmbiguous = false + gdprDefaultValue = gdpr.SignalYes } else if len(geo.Country) == 3 { // The country field is formatted properly as a three character country code - usersyncIfAmbiguous = true + gdprDefaultValue = gdpr.SignalNo } } - return usersyncIfAmbiguous + return gdprDefaultValue } func recordImpMetrics(bidRequest *openrtb2.BidRequest, metricsEngine metrics.MetricsEngine) { @@ -411,7 +427,9 @@ func (e *exchange) getAllBids( bidderRequests []BidderRequest, bidAdjustments map[string]float64, conversions currency.Conversions, - accountDebugAllowed bool) ( + accountDebugAllowed bool, + globalPrivacyControlHeader string, + headerDebugAllowed bool) ( map[openrtb_ext.BidderName]*pbsOrtbSeatBid, map[openrtb_ext.BidderName]*seatResponseExtra, bool) { // Set up pointers to the bid results @@ -441,9 +459,10 @@ func (e *exchange) getAllBids( if givenAdjustment, ok := bidAdjustments[string(bidderRequest.BidderName)]; ok { adjustmentFactor = givenAdjustment } - var reqInfo adapters.ExtraRequestInfo + reqInfo := adapters.NewExtraRequestInfo(conversions) reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType - bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed) + reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader + bids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest.BidRequest, bidderRequest.BidderName, adjustmentFactor, conversions, &reqInfo, accountDebugAllowed, headerDebugAllowed) // Add in time reporting elapsed := time.Since(start) @@ -499,7 +518,6 @@ func (e *exchange) getAllBids( bidsFound = true bidIDsCollision = recordAdaptorDuplicateBidIDs(e.me, adapterBids) } - } if bidIDsCollision { // record this request count this request if bid collision is detected @@ -644,7 +662,7 @@ func encodeBidResponseExt(bidResponseExt *openrtb_ext.ExtBidResponse) ([]byte, e return buffer.Bytes(), err } -func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { +func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, requestExt *openrtb_ext.ExtRequest, seatBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, categoriesFetcher stored_requests.CategoryFetcher, targData *targetData, booleanGenerator deduplicateChanceGenerator) (map[string]string, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []string, error) { res := make(map[string]string) type bidDedupe struct { @@ -787,7 +805,7 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, currBidPrice = 0 } if dupeBidPrice == currBidPrice { - if rand.Intn(100) < 50 { + if booleanGenerator.Generate() { dupeBidPrice = -1 } else { currBidPrice = -1 @@ -802,11 +820,16 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, } 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) - rejections = updateRejections(rejections, dupe.bidID, "Bid was deduplicated") } else { - oldSeatBid.bids = append(oldSeatBid.bids[:dupe.bidIndex], oldSeatBid.bids[dupe.bidIndex+1:]...) + // 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) @@ -817,9 +840,9 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, continue } } - dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } res[bidID] = categoryDuration + dedupe[dupeKey] = bidDedupe{bidderName: bidderName, bidIndex: bidInd, bidID: bidID, bidPrice: pb} } if len(bidsToRemove) > 0 { @@ -845,6 +868,24 @@ func applyCategoryMapping(ctx context.Context, bidRequest *openrtb2.BidRequest, return res, seatBids, rejections, nil } +func removeBidById(seatBid *pbsOrtbSeatBid, bidID string) { + //Find index of bid to remove + dupeBidIndex := -1 + for i, bid := range seatBid.bids { + if bid.bid.ID == bidID { + dupeBidIndex = i + break + } + } + if dupeBidIndex != -1 { + if dupeBidIndex < len(seatBid.bids)-1 { + seatBid.bids = append(seatBid.bids[:dupeBidIndex], seatBid.bids[dupeBidIndex+1:]...) + } else if dupeBidIndex == len(seatBid.bids)-1 { + seatBid.bids = seatBid.bids[:len(seatBid.bids)-1] + } + } +} + func updateRejections(rejections []string, bidID string, reason string) []string { message := fmt.Sprintf("bid rejected [bid ID: %s] reason: %s", bidID, reason) return append(rejections, message) @@ -986,6 +1027,34 @@ func (e *exchange) getBidCacheInfo(bid *pbsOrtbBid, auction *auction) (cacheInfo return } +func (e *exchange) getAuctionCurrencyRates(requestRates *openrtb_ext.ExtRequestCurrency) currency.Conversions { + if requestRates == nil { + // No bidRequest.ext.currency field was found, use PBS rates as usual + return e.currencyConverter.Rates() + } + + // If bidRequest.ext.currency.usepbsrates is nil, we understand its value as true. It will be false + // only if it's explicitly set to false + usePbsRates := requestRates.UsePBSRates == nil || *requestRates.UsePBSRates + + if !usePbsRates { + // At this point, we can safely assume the ConversionRates map is not empty because + // validateCustomRates(bidReqCurrencyRates *openrtb_ext.ExtRequestCurrency) would have + // thrown an error under such conditions. + return currency.NewRates(time.Time{}, requestRates.ConversionRates) + } + + // Both PBS and custom rates can be used, check if ConversionRates is not empty + if len(requestRates.ConversionRates) == 0 { + // Custom rates map is empty, use PBS rates only + return e.currencyConverter.Rates() + } + + // Return an AggregateConversions object that includes both custom and PBS currency rates but will + // prioritize custom rates over PBS rates whenever a currency rate is found in both + return currency.NewAggregateConversions(currency.NewRates(time.Time{}, requestRates.ConversionRates), e.currencyConverter.Rates()) +} + func findCacheID(bid *pbsOrtbBid, auction *auction) (string, bool) { if bid != nil && bid.bid != nil && auction != nil { if id, found := auction.cacheIds[bid.bid]; found { diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index fbce03f6810..6f49b754bb9 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -32,6 +32,7 @@ import ( "github.com/buger/jsonparser" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/yudai/gojsondiff" "github.com/yudai/gojsondiff/formatter" ) @@ -87,8 +88,8 @@ func TestNewExchange(t *testing.T) { // 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 */ + // 1) Adapter with a '& char in its endpoint property + // https://github.com/prebid/prebid-server/issues/465 cfg := &config.Configuration{ Adapters: make(map[string]config.Adapter, 1), } @@ -96,7 +97,7 @@ func TestCharacterEscape(t *testing.T) { Endpoint: "http://ib.adnxs.com/openrtb2?query1&query2", //Note the '&' character in there } - /* 2) Init new exchange with said configuration */ + // 2) Init new exchange with said configuration //Other parameters also needed to create exchange handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) @@ -115,7 +116,7 @@ func TestCharacterEscape(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) e := NewExchange(adapters, nil, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) - /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ + // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs //liveAdapters []openrtb_ext.BidderName, liveAdapters := make([]openrtb_ext.BidderName, 1) liveAdapters[0] = "appnexus" @@ -148,10 +149,10 @@ func TestCharacterEscape(t *testing.T) { var errList []error - /* 4) Build bid response */ + // 4) Build bid response bidResp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, errList) - /* 5) Assert we have no errors and one '&' character as we are supposed to */ + // 5) Assert we have no errors and one '&' character as we are supposed to if err != nil { t.Errorf("exchange.buildBidResponse returned unexpected error: %v", err) } @@ -177,8 +178,9 @@ func TestDebugBehaviour(t *testing.T) { } type debugData struct { - bidderLevelDebugAllowed bool - accountLevelDebugAllowed bool + bidderLevelDebugAllowed bool + accountLevelDebugAllowed bool + headerOverrideDebugAllowed bool } type aTest struct { @@ -193,57 +195,78 @@ func TestDebugBehaviour(t *testing.T) { desc: "test flag equals zero, ext debug flag false, no debug info expected", in: inTest{test: 0, debug: false}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals zero, ext debug flag true, debug info expected", in: inTest{test: 0, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals 1, ext debug flag false, debug info expected", in: inTest{test: 1, debug: false}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag equals 1, ext debug flag true, debug info expected", in: inTest{test: 1, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected", in: inTest{test: 2, debug: false}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: false, }, { desc: "test flag not equal to 0 nor 1, ext debug flag true, debug info expected", in: inTest{test: -1, debug: true}, out: outTest{debugInfoIncluded: true}, - debugData: debugData{true, true}, + debugData: debugData{true, true, false}, generateWarnings: true, }, { desc: "test account level debug disabled", in: inTest{test: -1, debug: true}, out: outTest{debugInfoIncluded: false}, - debugData: debugData{true, false}, + debugData: debugData{true, false, false}, generateWarnings: true, }, { - desc: "test bidder level debug disabled", + desc: "test header override enabled when all other debug options are disabled", + in: inTest{test: -1, debug: false}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{false, false, true}, + generateWarnings: false, + }, + { + desc: "test header override and url debug options are enabled when all other debug options are disabled", in: inTest{test: -1, debug: true}, - out: outTest{debugInfoIncluded: false}, - debugData: debugData{false, true}, - generateWarnings: true, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{false, false, true}, + generateWarnings: false, + }, + { + desc: "test header override and url and bidder debug options are enabled when account debug option is disabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, false, true}, + generateWarnings: false, + }, + { + desc: "test all debug options are enabled", + in: inTest{test: -1, debug: true}, + out: outTest{debugInfoIncluded: true}, + debugData: debugData{true, true, true}, + generateWarnings: false, }, } @@ -323,9 +346,12 @@ func TestDebugBehaviour(t *testing.T) { WarningCode: errortypes.InvalidPrivacyConsentWarningCode}) auctionRequest.Warnings = errL } - + debugLog := &DebugLog{} + if test.debugData.headerOverrideDebugAllowed { + debugLog = &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true} + } // Run test - outBidResponse, err := e.HoldAuction(ctx, auctionRequest, nil) + outBidResponse, err := e.HoldAuction(ctx, auctionRequest, debugLog) // Assert no HoldAuction error assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err) @@ -339,6 +365,11 @@ func TestDebugBehaviour(t *testing.T) { assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set") assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp has incorrect value") + if test.debugData.headerOverrideDebugAllowed { + assert.Empty(t, actualExt.Warnings, "warnings should be empty") + assert.Empty(t, actualExt.Errors, "errors should be empty") + } + if test.out.debugInfoIncluded { assert.NotNilf(t, actualExt, "%s. ext.debug field is expected to be included in this outBidResponse.Ext and not be nil. outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug) @@ -358,13 +389,13 @@ func TestDebugBehaviour(t *testing.T) { assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty") } - if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed { + if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed { assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning") assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present") assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present") } - if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed { + if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed { if test.generateWarnings { assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning") } else { @@ -508,6 +539,432 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) { } +func TestOverrideWithCustomCurrency(t *testing.T) { + + mockCurrencyClient := &fakeCurrencyRatesHttpClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + } + mockCurrencyConverter := currency.NewRateConverter( + mockCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + + type testIn struct { + customCurrencyRates json.RawMessage + bidRequestCurrency string + } + type testResults struct { + numBids int + bidRespPrice float64 + bidRespCurrency string + } + + testCases := []struct { + desc string + in testIn + expected testResults + }{ + { + desc: "Blank currency field in ext. bidRequest comes with a valid currency but conversion rate was not found in PBS. Return no bids", + in: testIn{ + customCurrencyRates: json.RawMessage(`{ "prebid": { "currency": {} } } `), + bidRequestCurrency: "GBP", + }, + expected: testResults{}, + }, + { + desc: "valid request.ext.prebid.currency, expect custom rates to override those of the currency rate server", + in: testIn{ + customCurrencyRates: json.RawMessage(`{ + "prebid": { + "currency": { + "rates": { + "USD": { + "MXN": 20.00, + "EUR": 10.95 + } + } + } + } + }`), + bidRequestCurrency: "MXN", + }, + expected: testResults{ + numBids: 1, + bidRespPrice: 20.00, + bidRespCurrency: "MXN", + }, + }, + } + + // Init mock currency conversion service + mockCurrencyConverter.Run() + + // Init an exchange to run an auction from + noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } + mockAppnexusBidService := httptest.NewServer(http.HandlerFunc(noBidServer)) + defer mockAppnexusBidService.Close() + + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + oneDollarBidBidder := &goodSingleBidder{ + httpRequest: &adapters.RequestData{ + Method: "POST", + Uri: mockAppnexusBidService.URL, + Body: []byte("{\"key\":\"val\"}"), + Headers: http.Header{}, + }, + } + + e := new(exchange) + e.cache = &wellBehavedCache{} + e.me = &metricsConf.DummyMetricsEngine{} + e.gDPR = gdpr.AlwaysAllow{} + e.currencyConverter = mockCurrencyConverter + e.categoriesFetcher = categoriesFetcher + e.bidIDGenerator = &mockBidIDGenerator{false, false} + + // Define mock incoming bid requeset + mockBidRequest := &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Ext: json.RawMessage(`{"appnexus": {"placementId": 1}}`), + }}, + Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)}, + } + + // Run tests + for _, test := range testCases { + + oneDollarBidBidder.bidResponse = &adapters.BidderResponse{ + Bids: []*adapters.TypedBid{ + { + Bid: &openrtb2.Bid{Price: 1.00}, + }, + }, + Currency: "USD", + } + + e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderAppnexus: adaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderAppnexus, nil), + } + + // Set custom rates in extension + mockBidRequest.Ext = test.in.customCurrencyRates + + // Set bidRequest currency list + mockBidRequest.Cur = []string{test.in.bidRequestCurrency} + + auctionRequest := AuctionRequest{ + BidRequest: mockBidRequest, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + } + + // Run test + outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) + + // Assertions + assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err) + + if test.expected.numBids > 0 { + // Assert out currency + assert.Equal(t, test.expected.bidRespCurrency, outBidResponse.Cur, "Bid response currency is wrong: %s \n", test.desc) + + // Assert returned bid + if !assert.NotNil(t, outBidResponse, "outBidResponse is nil: %s \n", test.desc) { + return + } + if !assert.NotEmpty(t, outBidResponse.SeatBid, "outBidResponse.SeatBid is empty: %s", test.desc) { + return + } + if !assert.NotEmpty(t, outBidResponse.SeatBid[0].Bid, "outBidResponse.SeatBid[0].Bid is empty: %s", test.desc) { + return + } + + // Assert returned bid price matches the currency conversion + assert.Equal(t, test.expected.bidRespPrice, outBidResponse.SeatBid[0].Bid[0].Price, "Bid response seatBid price is wrong: %s", test.desc) + } else { + assert.Len(t, outBidResponse.SeatBid, 0, "outBidResponse.SeatBid should be empty: %s", test.desc) + } + } +} + +func TestAdapterCurrency(t *testing.T) { + fakeCurrencyClient := &fakeCurrencyRatesHttpClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`, + } + currencyConverter := currency.NewRateConverter( + fakeCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + currencyConverter.Run() + + // Initialize Mock Bidder + // - Response purposefully causes PBS-Core to stop processing the request, since this test is only + // interested in the call to MakeRequests and nothing after. + mockBidder := &mockBidder{} + mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return([]*adapters.RequestData(nil), []error(nil)) + + // Initialize Real Exchange + e := exchange{ + cache: &wellBehavedCache{}, + me: &metricsConf.DummyMetricsEngine{}, + gDPR: gdpr.AlwaysAllow{}, + currencyConverter: currencyConverter, + categoriesFetcher: nilCategoryFetcher{}, + bidIDGenerator: &mockBidIDGenerator{false, false}, + adapterMap: map[openrtb_ext.BidderName]adaptedBidder{ + openrtb_ext.BidderName("foo"): adaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.DummyMetricsEngine{}, openrtb_ext.BidderName("foo"), nil), + }, + } + + // Define Bid Request + request := &openrtb2.BidRequest{ + ID: "some-request-id", + Imp: []openrtb2.Imp{{ + ID: "some-impression-id", + Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}}, + Ext: json.RawMessage(`{"foo": {"placementId": 1}}`), + }}, + Site: &openrtb2.Site{ + Page: "prebid.org", + Ext: json.RawMessage(`{"amp":0}`), + }, + Cur: []string{"USD"}, + Ext: json.RawMessage(`{"prebid": {"currency": {"rates": {"USD": {"MXN": 20.00}}}}}`), + } + + // Run Auction + auctionRequest := AuctionRequest{ + BidRequest: request, + Account: config.Account{}, + UserSyncs: &emptyUsersync{}, + } + response, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{}) + 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") + + // Test Currency Converter Properly Passed To Adapter + if assert.NotNil(t, mockBidder.lastExtraRequestInfo, "Currency Conversion Argument") { + converted, err := mockBidder.lastExtraRequestInfo.ConvertCurrency(2.0, "USD", "MXN") + assert.NoError(t, err, "Currency Conversion Error") + assert.Equal(t, 40.0, converted, "Currency Conversion Response") + } +} + +func TestGetAuctionCurrencyRates(t *testing.T) { + + pbsRates := map[string]map[string]float64{ + "MXN": { + "USD": 20.13, + "EUR": 27.82, + "JPY": 5.09, // "MXN" to "JPY" rate not found in customRates + }, + } + + customRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // different rate than in pbsRates + "EUR": 27.82, // same as in pbsRates + "GBP": 31.12, // not found in pbsRates at all + }, + } + + expectedRateEngineRates := map[string]map[string]float64{ + "MXN": { + "USD": 25.00, // rates engine will prioritize the value found in custom rates + "EUR": 27.82, // same value in both the engine reads the custom entry first + "JPY": 5.09, // the engine will find it in the pbsRates conversions + "GBP": 31.12, // the engine will find it in the custom conversions + }, + } + + boolTrue := true + boolFalse := false + + type testInput struct { + pbsRates map[string]map[string]float64 + bidExtCurrency *openrtb_ext.ExtRequestCurrency + } + type testOutput struct { + constantRates bool + resultingRates map[string]map[string]float64 + } + testCases := []struct { + desc string + given testInput + expected testOutput + }{ + { + "valid pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "valid pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + resultingRates: expectedRateEngineRates, + }, + }, + { + "nil pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "nil pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates identical to customRates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + ConversionRates: customRates, + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + resultingRates: customRates, + }, + }, + { + "valid pbsRates, empty ConversionRates, false UsePBSRates. Because pbsRates cannot be used, default to constant rates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "valid pbsRates, nil ConversionRates, UsePBSRates defaults to true. Resulting rates will be identical to pbsRates", + testInput{ + pbsRates: pbsRates, + bidExtCurrency: nil, + }, + testOutput{ + resultingRates: pbsRates, + }, + }, + { + "nil pbsRates, empty ConversionRates, false UsePBSRates. Default to constant rates", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolFalse, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "customRates empty, UsePBSRates set to true, pbsRates are nil. Return default constant rates converter", + testInput{ + pbsRates: nil, + bidExtCurrency: &openrtb_ext.ExtRequestCurrency{ + // ConversionRates inCustomRates not initialized makes for a zero-length map + UsePBSRates: &boolTrue, + }, + }, + testOutput{ + constantRates: true, + }, + }, + { + "nil customRates, nil pbsRates, UsePBSRates defaults to true. Return default constant rates converter", + testInput{ + pbsRates: nil, + bidExtCurrency: nil, + }, + testOutput{ + constantRates: true, + }, + }, + } + + for _, tc := range testCases { + + // Test setup: + jsonPbsRates, err := json.Marshal(tc.given.pbsRates) + if err != nil { + t.Fatalf("Failed to marshal PBS rates: %v", err) + } + + // Init mock currency conversion service + mockCurrencyClient := &fakeCurrencyRatesHttpClient{ + responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`, + } + mockCurrencyConverter := currency.NewRateConverter( + mockCurrencyClient, + "currency.fake.com", + 24*time.Hour, + ) + mockCurrencyConverter.Run() + + e := new(exchange) + e.currencyConverter = mockCurrencyConverter + + // Run test + auctionRates := e.getAuctionCurrencyRates(tc.given.bidExtCurrency) + + // When fromCurrency and toCurrency are the same, a rate of 1.00 is always expected + rate, err := auctionRates.GetRate("USD", "USD") + assert.NoError(t, err, tc.desc) + assert.Equal(t, float64(1), rate, tc.desc) + + // If we expect an error, assert we have one along with a conversion rate of zero + if tc.expected.constantRates { + rate, err := auctionRates.GetRate("USD", "MXN") + assert.Error(t, err, tc.desc) + assert.Equal(t, float64(0), rate, tc.desc) + } else { + for fromCurrency, rates := range tc.expected.resultingRates { + for toCurrency, expectedRate := range rates { + actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency) + assert.NoError(t, err, tc.desc) + assert.Equal(t, expectedRate, actualRate, tc.desc) + } + } + } + } +} + func TestReturnCreativeEndToEnd(t *testing.T) { sampleAd := "" @@ -710,7 +1167,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { testExternalCacheHost := "www.externalprebidcache.net" testExternalCachePath := "endpoints/cache" - /* 1) An adapter */ + // 1) An adapter bidderName := openrtb_ext.BidderName("appnexus") cfg := &config.Configuration{ @@ -731,7 +1188,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { adapterList := make([]openrtb_ext.BidderName, 0, 2) testEngine := metricsConf.NewMetricsEngine(cfg, adapterList) - /* 2) Init new exchange with said configuration */ + // 2) Init new exchange with said configuration handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) } server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer)) defer server.Close() @@ -748,7 +1205,7 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) pbc := pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine) e := NewExchange(adapters, pbc, cfg, &metricsConf.DummyMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange) - /* 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs */ + // 3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs liveAdapters := []openrtb_ext.BidderName{bidderName} //adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid, @@ -850,10 +1307,10 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) { var errList []error - /* 4) Build bid response */ + // 4) Build bid response bid_resp, err := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, errList) - /* 5) Assert we have no errors and the bid response we expected*/ + // 5) Assert we have no errors and the bid response we expected assert.NoError(t, err, "[TestGetBidCacheInfo] buildBidResponse() threw an error") expectedBidResponse := &openrtb2.BidResponse{ @@ -1331,7 +1788,7 @@ func newRaceCheckingRequest(t *testing.T) *openrtb2.BidRequest { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Regs: &openrtb2.Regs{ COPPA: 1, @@ -1493,7 +1950,7 @@ func TestPanicRecoveryHighLevel(t *testing.T) { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Imp: []openrtb2.Imp{{ ID: "some-imp-id", @@ -1615,6 +2072,13 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { eeac[c] = s } + var gdprDefaultValue string + if spec.AssumeGDPRApplies { + gdprDefaultValue = "1" + } else { + gdprDefaultValue = "0" + } + privacyConfig := config.Privacy{ CCPA: config.CCPA{ Enforce: spec.EnforceCCPA, @@ -1623,9 +2087,9 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) { Enforce: spec.EnforceLMT, }, GDPR: config.GDPR{ - Enabled: spec.GDPREnabled, - UsersyncIfAmbiguous: !spec.AssumeGDPRApplies, - EEACountriesMap: eeac, + Enabled: spec.GDPREnabled, + DefaultValue: gdprDefaultValue, + EEACountriesMap: eeac, }, } bidIdGenerator := &mockBidIDGenerator{} @@ -1766,19 +2230,24 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] t.Fatalf("Failed to create a category Fetcher: %v", error) } + gdprDefaultValue := gdpr.SignalYes + if privacyConfig.GDPR.DefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + return &exchange{ - adapterMap: bidderAdapters, - me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), - cache: &wellBehavedCache{}, - cacheTime: 0, - gDPR: gdpr.AlwaysFail{}, - currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - UsersyncIfAmbiguous: privacyConfig.GDPR.UsersyncIfAmbiguous, - privacyConfig: privacyConfig, - categoriesFetcher: categoriesFetcher, - bidderInfo: bidderInfos, - externalURL: "http://localhost", - bidIDGenerator: bidIDGenerator, + adapterMap: bidderAdapters, + me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), + cache: &wellBehavedCache{}, + cacheTime: 0, + gDPR: &permissionsMock{allowAllBidders: true}, + currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), + gdprDefaultValue: gdprDefaultValue, + privacyConfig: privacyConfig, + categoriesFetcher: categoriesFetcher, + bidderInfo: bidderInfos, + externalURL: "http://localhost", + bidIDGenerator: bidIDGenerator, } } @@ -1801,6 +2270,14 @@ func (big *mockBidIDGenerator) New() (string, error) { } +type fakeRandomDeduplicateBidBooleanGenerator struct { + returnValue bool +} + +func (m *fakeRandomDeduplicateBidBooleanGenerator) Generate() bool { + return m.returnValue +} + func newExtRequest() openrtb_ext.ExtRequest { priceGran := openrtb_ext.PriceGranularity{ Precision: 2, @@ -1901,7 +2378,7 @@ func TestCategoryMapping(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -1957,7 +2434,7 @@ func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2010,7 +2487,7 @@ func TestCategoryMappingTranslateCategoriesNil(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message") @@ -2093,7 +2570,7 @@ func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be no bid rejection messages") @@ -2164,7 +2641,7 @@ func TestCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages") @@ -2245,7 +2722,7 @@ func TestNoCategoryDedupe(t *testing.T) { adapterBids[bidderName1] = &seatBid - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.Equal(t, nil, err, "Category mapping error should be empty") assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages") @@ -2311,7 +2788,7 @@ func TestCategoryMappingBidderName(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2366,7 +2843,7 @@ func TestCategoryMappingBidderNameNoCategories(t *testing.T) { adapterBids[bidderName1] = &seatBid1 adapterBids[bidderName2] = &seatBid2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Empty(t, rejections, "There should be 0 bid rejection messages") @@ -2466,9 +2943,9 @@ func TestBidRejectionErrors(t *testing.T) { seatBid := pbsOrtbSeatBid{bids: innerBids, currency: "USD"} adapterBids[bidderName] = &seatBid - bidRequest := openrtb2.BidRequest{} - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &test.reqExt, adapterBids, categoriesFetcher, targData) + + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &test.reqExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) if len(test.expectedCatDur) > 0 { // Bid deduplication case @@ -2532,7 +3009,7 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { adapterBids[bidderNameApn1] = &seatBidApn1 adapterBids[bidderNameApn2] = &seatBidApn2 - bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData) + bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}) assert.NoError(t, err, "Category mapping error should be empty") assert.Len(t, rejections, 1, "There should be 1 bid rejection message") @@ -2551,9 +3028,171 @@ func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) { } else { assert.Nil(t, seatBidApn1.bids, "Appnexus_1 seat bid should not have any bids back") assert.Len(t, seatBidApn2.bids, 1, "Appnexus_2 seat bid should have only one back") + } + } +} + +func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) { + // This test covers a very rare de-duplication case where bid needs to be removed from already processed bidder + // This happens when current processing bidder has a bid that has same de-duplication key as a bid from already processed bidder + // and already processed bid was selected to be removed + + //In this test case bids bid_idApn1_1 and bid_idApn1_2 will be removed due to hardcoded "fakeRandomDeduplicateBidBooleanGenerator{true}" + + // Also there are should be more than one bids in bidder to test how we remove single element from bids array. + // In case there is just one bid to remove - we remove the entire bidder. + categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") + if error != nil { + t.Errorf("Failed to create a category Fetcher: %v", error) + } + + bidRequest := openrtb2.BidRequest{} + requestExt := newExtRequestTranslateCategories(nil) + + targData := &targetData{ + priceGranularity: requestExt.Prebid.Targeting.PriceGranularity, + includeWinners: true, + } + + requestExt.Prebid.Targeting.DurationRangeSec = []int{30} + requestExt.Prebid.Targeting.IncludeBrandCategory.WithCategory = false + + cats1 := []string{"IAB1-3"} + cats2 := []string{"IAB1-4"} + + bidApn1_1 := openrtb2.Bid{ID: "bid_idApn1_1", ImpID: "imp_idApn1_1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1} + + 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 := pbsOrtbBid{&bidApn1_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + + bid1_Apn2_1 := pbsOrtbBid{&bidApn2_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn2_2 := pbsOrtbBid{&bidApn2_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + + innerBidsApn1 := []*pbsOrtbBid{ + &bid1_Apn1_1, + &bid1_Apn1_2, + } + + innerBidsApn2 := []*pbsOrtbBid{ + &bid1_Apn2_1, + &bid1_Apn2_2, + } + + adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid) + + seatBidApn1 := pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"} + bidderNameApn1 := openrtb_ext.BidderName("appnexus1") + + seatBidApn2 := pbsOrtbSeatBid{bids: innerBidsApn2, currency: "USD"} + bidderNameApn2 := openrtb_ext.BidderName("appnexus2") + + adapterBids[bidderNameApn1] = &seatBidApn1 + adapterBids[bidderNameApn2] = &seatBidApn2 + + _, adapterBids, rejections, err := applyCategoryMapping(nil, &bidRequest, &requestExt, adapterBids, categoriesFetcher, targData, &fakeRandomDeduplicateBidBooleanGenerator{true}) + + assert.NoError(t, err, "Category mapping error should be empty") + + //Total number of bids from all bidders in this case should be 2 + bidsFromFirstBidder := adapterBids[bidderNameApn1] + bidsFromSecondBidder := adapterBids[bidderNameApn2] + + totalNumberOfbids := 0 + + //due to random map order we need to identify what bidder was first + firstBidderIndicator := true + + if bidsFromFirstBidder.bids != nil { + totalNumberOfbids += len(bidsFromFirstBidder.bids) + } + + if bidsFromSecondBidder.bids != nil { + firstBidderIndicator = false + totalNumberOfbids += len(bidsFromSecondBidder.bids) + } + + assert.Equal(t, 2, totalNumberOfbids, "2 bids total should be returned") + assert.Len(t, rejections, 2, "2 bids should be de-duplicated") + + if firstBidderIndicator { + assert.Len(t, adapterBids[bidderNameApn1].bids, 2) + assert.Len(t, adapterBids[bidderNameApn2].bids, 0) + + assert.Equal(t, "bid_idApn1_1", adapterBids[bidderNameApn1].bids[0].bid.ID, "Incorrect expected bid 1 id") + assert.Equal(t, "bid_idApn1_2", adapterBids[bidderNameApn1].bids[1].bid.ID, "Incorrect expected bid 2 id") + + assert.Equal(t, "bid rejected [bid ID: bid_idApn2_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1") + assert.Equal(t, "bid rejected [bid ID: bid_idApn2_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2") + + } else { + assert.Len(t, adapterBids[bidderNameApn1].bids, 0) + assert.Len(t, adapterBids[bidderNameApn2].bids, 2) + + assert.Equal(t, "bid_idApn2_1", adapterBids[bidderNameApn2].bids[0].bid.ID, "Incorrect expected bid 1 id") + assert.Equal(t, "bid_idApn2_2", adapterBids[bidderNameApn2].bids[1].bid.ID, "Incorrect expected bid 2 id") + + assert.Equal(t, "bid rejected [bid ID: bid_idApn1_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1") + assert.Equal(t, "bid rejected [bid ID: bid_idApn1_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2") + + } +} + +func TestRemoveBidById(t *testing.T) { + cats1 := []string{"IAB1-3"} + + bidApn1_1 := openrtb2.Bid{ID: "bid_idApn1_1", ImpID: "imp_idApn1_1", Price: 10.0000, Cat: cats1, W: 1, H: 1} + 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 := pbsOrtbBid{&bidApn1_1, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_2 := pbsOrtbBid{&bidApn1_2, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + bid1_Apn1_3 := pbsOrtbBid{&bidApn1_3, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, 0, false, ""} + + type aTest struct { + desc string + inBidName string + outBids []*pbsOrtbBid + } + testCases := []aTest{ + { + desc: "remove element from the middle", + inBidName: "bid_idApn1_2", + outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_3}, + }, + { + desc: "remove element from the end", + inBidName: "bid_idApn1_3", + outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2}, + }, + { + desc: "remove element from the beginning", + inBidName: "bid_idApn1_1", + outBids: []*pbsOrtbBid{&bid1_Apn1_2, &bid1_Apn1_3}, + }, + { + desc: "remove element that doesn't exist", + inBidName: "bid_idApn", + outBids: []*pbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2, &bid1_Apn1_3}, + }, + } + for _, test := range testCases { + + innerBidsApn1 := []*pbsOrtbBid{ + &bid1_Apn1_1, + &bid1_Apn1_2, + &bid1_Apn1_3, } + seatBidApn1 := &pbsOrtbSeatBid{bids: innerBidsApn1, currency: "USD"} + + removeBidById(seatBidApn1, test.inBidName) + assert.Len(t, seatBidApn1.bids, len(test.outBids), test.desc) + assert.ElementsMatch(t, seatBidApn1.bids, test.outBids, "Incorrect bids in response") } } @@ -2893,7 +3532,7 @@ type validatingBidder struct { mockResponses map[string]bidderResponse } -func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { +func (b *validatingBidder) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (seatBid *pbsOrtbSeatBid, errs []error) { if expectedRequest, ok := b.expectations[string(name)]; ok { if expectedRequest != nil { if expectedRequest.BidAdjustment != bidAdjustment { @@ -3072,7 +3711,7 @@ func (e *mockUsersync) LiveSyncCount() int { type panicingAdapter struct{} -func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { +func (panicingAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed, headerDebugAllowed bool) (posb *pbsOrtbSeatBid, errs []error) { panic("Panic! Panic! The world is ending!") } @@ -3094,6 +3733,36 @@ func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, return "", nil } +// fakeCurrencyRatesHttpClient is a simple http client mock returning a constant response body +type fakeCurrencyRatesHttpClient struct { + responseBody string +} + +func (m *fakeCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) { + return &http.Response{ + Status: "200 OK", + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(strings.NewReader(m.responseBody)), + }, nil +} + +type mockBidder struct { + mock.Mock + lastExtraRequestInfo *adapters.ExtraRequestInfo +} + +func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + m.lastExtraRequestInfo = reqInfo + + args := m.Called(request, reqInfo) + return args.Get(0).([]*adapters.RequestData), args.Get(1).([]error) +} + +func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + args := m.Called(internalRequest, externalRequest, response) + return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error) +} + //TestApplyAdvertiserBlocking verifies advertiser blocking //Currently it is expected to work only with TagBidders and not woth // normal bidders diff --git a/exchange/exchangetest/debuglog_enabled.json b/exchange/exchangetest/debuglog_enabled.json index 851bda69097..8475482f35b 100644 --- a/exchange/exchangetest/debuglog_enabled.json +++ b/exchange/exchangetest/debuglog_enabled.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { diff --git a/exchange/exchangetest/debuglog_enabled_no_bids.json b/exchange/exchangetest/debuglog_enabled_no_bids.json index 4823acf8f16..b9bb15df7fb 100644 --- a/exchange/exchangetest/debuglog_enabled_no_bids.json +++ b/exchange/exchangetest/debuglog_enabled_no_bids.json @@ -1,6 +1,8 @@ { "debugLog": { "Enabled": true, + "DebugEnabledOrOverridden": true, + "DebugOverride": false, "CacheType": "xml", "TTL": 3600, "Data": { diff --git a/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json b/exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json similarity index 100% rename from exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json rename to exchange/exchangetest/firstpartydata-imp-ext-multiple-prebid-bidders.json diff --git a/exchange/exchangetest/request-other-user-ext.json b/exchange/exchangetest/request-other-user-ext.json index 9bd4c02fb42..f9fb3264c3c 100644 --- a/exchange/exchangetest/request-other-user-ext.json +++ b/exchange/exchangetest/request-other-user-ext.json @@ -12,11 +12,6 @@ "buyeruids": { "appnexus": "explicit-appnexus" } - }, - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 } } }, @@ -48,14 +43,7 @@ }, "user": { "id": "foo", - "buyeruid": "explicit-appnexus", - "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } - } + "buyeruid": "explicit-appnexus" }, "imp": [ { diff --git a/exchange/exchangetest/request-user-no-prebid.json b/exchange/exchangetest/request-user-no-prebid.json index bb36ba8aeeb..aae11606baa 100644 --- a/exchange/exchangetest/request-user-no-prebid.json +++ b/exchange/exchangetest/request-user-no-prebid.json @@ -8,11 +8,6 @@ "user": { "id": "foo", "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } } }, "imp": [ @@ -45,11 +40,6 @@ "id": "foo", "buyeruid": "implicit-appnexus", "ext": { - "digitrust": { - "id": "digi-id", - "keyv": 1, - "pref": 2 - } } }, "imp": [ diff --git a/exchange/legacy.go b/exchange/legacy.go deleted file mode 100644 index 0e7d1590686..00000000000 --- a/exchange/legacy.go +++ /dev/null @@ -1,375 +0,0 @@ -package exchange - -import ( - "context" - "encoding/json" - "errors" - "fmt" - - "github.com/buger/jsonparser" - "github.com/mxmCherry/openrtb/v15/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/pbs" - "github.com/prebid/prebid-server/usersync" -) - -// AdaptLegacyAdapter turns a bidder.Adapter into an adaptedBidder. -// -// This is a temporary function which helps make the transition to OpenRTB smooth. Bidders which have not been -// updated yet can use this to be "OpenRTB-ish". They'll bid as well as they can, given the limitations of the -// legacy protocol -func adaptLegacyAdapter(adapter adapters.Adapter) adaptedBidder { - return &adaptedAdapter{ - adapter: adapter, - } -} - -type adaptedAdapter struct { - adapter adapters.Adapter -} - -// requestBid attempts to bid on OpenRTB requests using the legacy protocol. -// -// This is not ideal. OpenRTB provides a superset of the legacy data structures. -// For requests which use those features, the best we can do is respond with "no bid". -func (bidder *adaptedAdapter) requestBid(ctx context.Context, request *openrtb2.BidRequest, name openrtb_ext.BidderName, bidAdjustment float64, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, accountDebugAllowed bool) (*pbsOrtbSeatBid, []error) { - legacyRequest, legacyBidder, errs := bidder.toLegacyAdapterInputs(request, name) - if legacyRequest == nil || legacyBidder == nil { - return nil, errs - } - - legacyBids, err := bidder.adapter.Call(ctx, legacyRequest, legacyBidder) - if err != nil { - errs = append(errs, err) - } - - for i := 0; i < len(legacyBids); i++ { - legacyBids[i].Price = legacyBids[i].Price * bidAdjustment - } - - finalResponse, moreErrs := toNewResponse(legacyBids, legacyBidder, name) - return finalResponse, append(errs, moreErrs...) -} - -// ---------------------------------------------------------------------------- -// Request transformations. - -// toLegacyAdapterInputs is a best-effort transformation of an OpenRTB BidRequest into the args needed to run a legacy Adapter. -// If the OpenRTB request is too complex, it fails with an error. -// If the error is nil, then the PBSRequest and PBSBidder are valid. -func (bidder *adaptedAdapter) toLegacyAdapterInputs(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSRequest, *pbs.PBSBidder, []error) { - legacyReq, err := bidder.toLegacyRequest(req) - if err != nil { - return nil, nil, []error{err} - } - - legacyBidder, errs := toLegacyBidder(req, name) - if legacyBidder == nil { - return nil, nil, errs - } - - return legacyReq, legacyBidder, errs -} - -func (bidder *adaptedAdapter) toLegacyRequest(req *openrtb2.BidRequest) (*pbs.PBSRequest, error) { - acctId, err := toAccountId(req) - if err != nil { - return nil, err - } - - tId, err := toTransactionId(req) - if err != nil { - return nil, err - } - - isSecure, err := toSecure(req) - if err != nil { - return nil, err - } - - isDebug := false - var requestExt openrtb_ext.ExtRequest - if req.Ext != nil { - err = json.Unmarshal(req.Ext, &requestExt) - if err != nil { - return nil, fmt.Errorf("Error decoding Request.ext : %s", err.Error()) - } - } - - if requestExt.Prebid.Debug { - isDebug = true - } - - url := "" - domain := "" - if req.Site != nil { - url = req.Site.Page - domain = req.Site.Domain - } - - cookie := usersync.NewPBSCookie() - if req.User != nil { - if req.User.BuyerUID != "" { - cookie.TrySync(bidder.adapter.Name(), req.User.BuyerUID) - } - - // This shouldn't be appnexus-specific... but this line does correctly invert the - // logic from adapters/openrtb_util.go, which will preserve this questionable behavior in legacy adapters. - if req.User.ID != "" { - cookie.TrySync("adnxs", req.User.ID) - } - } - - return &pbs.PBSRequest{ - AccountID: acctId, - Tid: tId, - // CacheMarkup is excluded because no legacy adapters read from it - // SortBids is excluded because no legacy adapters read from it - // MaxKeyLength is excluded because no legacy adapters read from it - Secure: isSecure, - TimeoutMillis: req.TMax, - // AdUnits is excluded because no legacy adapters read from it - IsDebug: isDebug, - App: req.App, - Device: req.Device, - // PBSUser is excluded because rubicon is the only adapter which reads from it, and they're supporting OpenRTB directly - // SDK is excluded because that information doesn't exist in openrtb2. - // Bidders is excluded because no legacy adapters read from it - User: req.User, - Cookie: cookie, - Url: url, - Domain: domain, - // Start is excluded because no legacy adapters read from it - Regs: req.Regs, - }, nil -} - -func toAccountId(req *openrtb2.BidRequest) (string, error) { - if req.Site != nil && req.Site.Publisher != nil { - return req.Site.Publisher.ID, nil - } - if req.App != nil && req.App.Publisher != nil { - return req.App.Publisher.ID, nil - } - return "", errors.New("bidrequest.site.publisher.id or bidrequest.app.publisher.id required for legacy bidders.") -} - -func toTransactionId(req *openrtb2.BidRequest) (string, error) { - if req.Source != nil { - return req.Source.TID, nil - } - return "", errors.New("bidrequest.source.tid required for legacy bidders.") -} - -func toSecure(req *openrtb2.BidRequest) (secure int8, err error) { - secure = -1 - for _, imp := range req.Imp { - if imp.Secure != nil { - thisVal := *imp.Secure - if thisVal == 0 { - if secure == 1 { - err = errors.New("bidrequest.imp[i].secure must be consistent for legacy bidders. Mixing 0 and 1 are not allowed.") - return - } - secure = 0 - } else if thisVal == 1 { - if secure == 0 { - err = errors.New("bidrequest.imp[i].secure must be consistent for legacy bidders. Mixing 0 and 1 are not allowed.") - return - } - secure = 1 - } - } - } - if secure == -1 { - secure = 0 - } - - return -} - -func toLegacyBidder(req *openrtb2.BidRequest, name openrtb_ext.BidderName) (*pbs.PBSBidder, []error) { - adUnits, errs := toPBSAdUnits(req) - if len(adUnits) > 0 { - return &pbs.PBSBidder{ - BidderCode: string(name), - // AdUnitCode is excluded because no legacy adapters read from it - // ResponseTime is excluded because no legacy adapters read from it - // NumBids is excluded because no legacy adapters read from it - // Error is excluded because no legacy adapters read from it - // NoCookie is excluded because no legacy adapters read from it - // NoBid is excluded because no legacy adapters read from it - // UsersyncInfo is excluded because no legacy adapters read from it - // Debug is excluded because legacy adapters only use it in nil-safe ways. - // They *do* write to it, though, so it may be read when unpacking the response. - AdUnits: adUnits, - }, errs - } else { - return nil, errs - } -} - -func toPBSAdUnits(req *openrtb2.BidRequest) ([]pbs.PBSAdUnit, []error) { - adUnits := make([]pbs.PBSAdUnit, len(req.Imp)) - var errs []error = nil - nextAdUnit := 0 - for i := 0; i < len(req.Imp); i++ { - err := initPBSAdUnit(&(req.Imp[i]), &(adUnits[nextAdUnit])) - if err != nil { - errs = append(errs, err) - } else { - nextAdUnit++ - } - } - return adUnits[:nextAdUnit], errs -} - -func initPBSAdUnit(imp *openrtb2.Imp, adUnit *pbs.PBSAdUnit) error { - var sizes []openrtb2.Format = nil - - video := pbs.PBSVideo{} - if imp.Video != nil { - video.Mimes = imp.Video.MIMEs - video.Minduration = imp.Video.MinDuration - video.Maxduration = imp.Video.MaxDuration - if imp.Video.StartDelay != nil { - video.Startdelay = int64(*imp.Video.StartDelay) - } - if imp.Video.Skip != nil { - video.Skippable = int(*imp.Video.Skip) - } - if len(imp.Video.PlaybackMethod) == 1 { - video.PlaybackMethod = int8(imp.Video.PlaybackMethod[0]) - } - if len(imp.Video.Protocols) > 0 { - video.Protocols = make([]int8, len(imp.Video.Protocols)) - for i := 0; i < len(imp.Video.Protocols); i++ { - video.Protocols[i] = int8(imp.Video.Protocols[i]) - } - } - // Fixes #360 - if imp.Video.W != 0 && imp.Video.H != 0 { - sizes = append(sizes, openrtb2.Format{ - W: imp.Video.W, - H: imp.Video.H, - }) - } - } - topFrame := int8(0) - if imp.Banner != nil { - topFrame = imp.Banner.TopFrame - sizes = append(sizes, imp.Banner.Format...) - } - - params, _, _, err := jsonparser.Get(imp.Ext, "bidder") - if err != nil { - return err - } - - mediaTypes := make([]pbs.MediaType, 0, 2) - if imp.Banner != nil { - mediaTypes = append(mediaTypes, pbs.MEDIA_TYPE_BANNER) - } - if imp.Video != nil { - mediaTypes = append(mediaTypes, pbs.MEDIA_TYPE_VIDEO) - } - if len(mediaTypes) == 0 { - return errors.New("legacy bidders can only bid on banner and video ad units") - } - - adUnit.Sizes = sizes - adUnit.TopFrame = topFrame - adUnit.Code = imp.ID - adUnit.BidID = imp.ID - adUnit.Params = json.RawMessage(params) - adUnit.Video = video - adUnit.MediaTypes = mediaTypes - adUnit.Instl = imp.Instl - - return nil -} - -// ---------------------------------------------------------------------------- -// Response transformations. - -// toNewResponse is a best-effort transformation of legacy Bids into an OpenRTB response. -func toNewResponse(bids pbs.PBSBidSlice, bidder *pbs.PBSBidder, name openrtb_ext.BidderName) (*pbsOrtbSeatBid, []error) { - newBids, errs := transformBids(bids) - return &pbsOrtbSeatBid{ - bids: newBids, - httpCalls: transformDebugs(bidder.Debug), - }, errs -} - -func transformBids(legacyBids pbs.PBSBidSlice) ([]*pbsOrtbBid, []error) { - newBids := make([]*pbsOrtbBid, 0, len(legacyBids)) - var errs []error - for _, legacyBid := range legacyBids { - if legacyBid != nil { - newBid, err := transformBid(legacyBid) - if err == nil { - newBids = append(newBids, newBid) - } else { - errs = append(errs, err) - } - } - } - return newBids, errs -} - -func transformBid(legacyBid *pbs.PBSBid) (*pbsOrtbBid, error) { - newBid := transformBidToOrtb(legacyBid) - - newBidType, err := openrtb_ext.ParseBidType(legacyBid.CreativeMediaType) - if err != nil { - return nil, err - } - - return &pbsOrtbBid{ - bid: newBid, - bidType: newBidType, - }, nil -} - -func transformBidToOrtb(legacyBid *pbs.PBSBid) *openrtb2.Bid { - return &openrtb2.Bid{ - ID: legacyBid.BidID, - ImpID: legacyBid.AdUnitCode, - CrID: legacyBid.Creative_id, - // legacyBid.CreativeMediaType is handled by transformBid(), because it doesn't exist on the openrtb2.Bid - // legacyBid.BidderCode is handled by the exchange, which already knows which bidder we are. - // legacyBid.BidHash is ignored, because it doesn't get sent in the response anyway - Price: legacyBid.Price, - NURL: legacyBid.NURL, - AdM: legacyBid.Adm, - W: legacyBid.Width, - H: legacyBid.Height, - DealID: legacyBid.DealId, - // TODO #216: Support CacheID here - // TODO: #216: Support CacheURL here - // ResponseTime is handled by the exchange, since it doesn't exist in the OpenRTB Bid - // AdServerTargeting is handled by the exchange. Rubicon's adapter is the only one which writes to it, - // but that doesn't matter since they're supporting OpenRTB directly. - } -} - -func transformDebugs(legacyDebugs []*pbs.BidderDebug) []*openrtb_ext.ExtHttpCall { - newDebug := make([]*openrtb_ext.ExtHttpCall, 0, len(legacyDebugs)) - for _, legacyDebug := range legacyDebugs { - if legacyDebug != nil { - newDebug = append(newDebug, transformDebug(legacyDebug)) - } - } - return newDebug -} - -func transformDebug(legacyDebug *pbs.BidderDebug) *openrtb_ext.ExtHttpCall { - return &openrtb_ext.ExtHttpCall{ - Uri: legacyDebug.RequestURI, - RequestBody: legacyDebug.RequestBody, - ResponseBody: legacyDebug.ResponseBody, - Status: legacyDebug.StatusCode, - } -} diff --git a/exchange/legacy_test.go b/exchange/legacy_test.go deleted file mode 100644 index cbb5fda4fcc..00000000000 --- a/exchange/legacy_test.go +++ /dev/null @@ -1,505 +0,0 @@ -package exchange - -import ( - "context" - "encoding/json" - "errors" - "net/http" - "reflect" - "testing" - "time" - - "github.com/buger/jsonparser" - jsonpatch "github.com/evanphx/json-patch" - "github.com/mxmCherry/openrtb/v15/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/pbs" - "github.com/prebid/prebid-server/usersync" -) - -func TestSiteVideo(t *testing.T) { - ortbRequest := &openrtb2.BidRequest{ - ID: "request-id", - TMax: 1000, - Site: &openrtb2.Site{ - Page: "http://www.site.com", - Domain: "site.com", - Publisher: &openrtb2.Publisher{ - ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", - }, - }, - Source: &openrtb2.Source{ - TID: "transaction-id", - }, - User: &openrtb2.User{ - ID: "host-id", - BuyerUID: "bidder-id", - }, - Test: 1, - Imp: []openrtb2.Imp{{ - ID: "imp-id", - Video: &openrtb2.Video{ - MIMEs: []string{"video/mp4"}, - MinDuration: 20, - MaxDuration: 40, - Protocols: []openrtb2.Protocol{openrtb2.ProtocolVAST10}, - StartDelay: openrtb2.StartDelayGenericMidRoll.Ptr(), - }, - Banner: &openrtb2.Banner{ - Format: []openrtb2.Format{{ - W: 300, - H: 250, - }}, - }, - Ext: json.RawMessage(`{"bidder":{"cp":512379,"ct":486653,"cf":"300x250"}}`), - }}, - } - - mockAdapter := mockLegacyAdapter{} - - exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) - if len(errs) > 0 { - t.Errorf("Unexpected error requesting bids: %v", errs) - } - - if mockAdapter.gotRequest == nil { - t.Fatalf("Mock adapter never received a request.") - } - - if mockAdapter.gotBidder == nil { - t.Fatalf("Mock adapter never received a bidder.") - } - - assertEquivalentRequests(t, ortbRequest, mockAdapter.gotRequest) - - if mockAdapter.gotBidder.BidderCode != string(openrtb_ext.BidderRubicon) { - t.Errorf("Wrong bidder code. Expected %s, got %s", string(openrtb_ext.BidderRubicon), mockAdapter.gotBidder.BidderCode) - } - assertEquivalentBidder(t, ortbRequest, mockAdapter.gotBidder) -} - -func TestAppBanner(t *testing.T) { - ortbRequest := newAppOrtbRequest() - ortbRequest.TMax = 1000 - ortbRequest.User = &openrtb2.User{ - ID: "host-id", - BuyerUID: "bidder-id", - } - ortbRequest.Test = 1 - - mockAdapter := mockLegacyAdapter{} - - exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) - if len(errs) > 0 { - t.Errorf("Unexpected error requesting bids: %v", errs) - } - - if mockAdapter.gotRequest == nil { - t.Fatalf("Mock adapter never received a request.") - } - - if mockAdapter.gotBidder == nil { - t.Fatalf("Mock adapter never received a bidder.") - } - if mockAdapter.gotBidder.BidderCode != string(openrtb_ext.BidderRubicon) { - t.Errorf("Wrong bidder code. Expected %s, got %s", string(openrtb_ext.BidderRubicon), mockAdapter.gotBidder.BidderCode) - } - - assertEquivalentRequests(t, ortbRequest, mockAdapter.gotRequest) - assertEquivalentBidder(t, ortbRequest, mockAdapter.gotBidder) -} - -func TestBidTransforms(t *testing.T) { - bidAdjustment := 0.3 - initialBidPrice := 0.5 - legalBid := &pbs.PBSBid{ - BidID: "bid-1", - AdUnitCode: "adunit-1", - Creative_id: "creative-1", - CreativeMediaType: "banner", - Price: initialBidPrice, - NURL: "nurl", - Adm: "ad-markup", - Width: 10, - Height: 20, - DealId: "some-deal", - } - mockAdapter := mockLegacyAdapter{ - returnedBids: pbs.PBSBidSlice{ - legalBid, - &pbs.PBSBid{ - CreativeMediaType: "unsupported", - }, - }, - } - - exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - seatBid, errs := exchangeBidder.requestBid(context.Background(), newAppOrtbRequest(), openrtb_ext.BidderRubicon, bidAdjustment, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) - if len(errs) != 1 { - t.Fatalf("Bad error count. Expected 1, got %d", len(errs)) - } - if errs[0].Error() != "invalid BidType: unsupported" { - t.Errorf("Unexpected error message. Got %s", errs[0].Error()) - } - - if len(seatBid.bids) != 1 { - t.Fatalf("Bad bid count. Expected 1, got %d", len(seatBid.bids)) - } - theBid := seatBid.bids[0] - if theBid.bidType != openrtb_ext.BidTypeBanner { - t.Errorf("Bad BidType. Expected banner, got %s", theBid.bidType) - } - if theBid.bid.ID != legalBid.BidID { - t.Errorf("Bad id. Expected %s, got %s", legalBid.NURL, theBid.bid.NURL) - } - if theBid.bid.ImpID != legalBid.AdUnitCode { - t.Errorf("Bad impid. Expected %s, got %s", legalBid.AdUnitCode, theBid.bid.ImpID) - } - if theBid.bid.CrID != legalBid.Creative_id { - t.Errorf("Bad creativeid. Expected %s, got %s", legalBid.Creative_id, theBid.bid.CrID) - } - if theBid.bid.Price != initialBidPrice*bidAdjustment { - t.Errorf("Bad price. Expected %f, got %f", initialBidPrice*bidAdjustment, theBid.bid.Price) - } - if theBid.bid.NURL != legalBid.NURL { - t.Errorf("Bad NURL. Expected %s, got %s", legalBid.NURL, theBid.bid.NURL) - } - if theBid.bid.AdM != legalBid.Adm { - t.Errorf("Bad adm. Expected %s, got %s", legalBid.Adm, theBid.bid.AdM) - } - if theBid.bid.W != legalBid.Width { - t.Errorf("Bad adm. Expected %d, got %d", legalBid.Width, theBid.bid.W) - } - if theBid.bid.H != legalBid.Height { - t.Errorf("Bad adm. Expected %d, got %d", legalBid.Height, theBid.bid.H) - } - if theBid.bid.DealID != legalBid.DealId { - t.Errorf("Bad dealid. Expected %s, got %s", legalBid.DealId, theBid.bid.DealID) - } -} - -func TestInsecureImps(t *testing.T) { - insecure := int8(0) - bidReq := &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{ - Secure: &insecure, - }, { - Secure: &insecure, - }}, - } - isSecure, err := toSecure(bidReq) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - if isSecure != 0 { - t.Errorf("Final request should be insecure. Got %d", isSecure) - } -} - -func TestSecureImps(t *testing.T) { - secure := int8(1) - bidReq := &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{ - Secure: &secure, - }, { - Secure: &secure, - }}, - } - isSecure, err := toSecure(bidReq) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - if isSecure != 1 { - t.Errorf("Final request should be secure. Got %d", isSecure) - } -} - -func TestMixedSecureImps(t *testing.T) { - insecure := int8(0) - secure := int8(1) - bidReq := &openrtb2.BidRequest{ - Imp: []openrtb2.Imp{{ - Secure: &insecure, - }, { - Secure: &secure, - }}, - } - _, err := toSecure(bidReq) - if err == nil { - t.Error("No error was received, but we should have gotten one.") - } -} - -func newAppOrtbRequest() *openrtb2.BidRequest { - return &openrtb2.BidRequest{ - ID: "request-id", - App: &openrtb2.App{ - Publisher: &openrtb2.Publisher{ - ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", - }, - }, - Source: &openrtb2.Source{ - TID: "transaction-id", - }, - Imp: []openrtb2.Imp{{ - ID: "imp-id", - Banner: &openrtb2.Banner{ - Format: []openrtb2.Format{{ - W: 300, - H: 250, - }}, - }, - Ext: json.RawMessage(`{"bidder":{"cp":512379,"ct":486653,"cf":"300x250"}}`), - }}, - } -} - -func TestErrorResponse(t *testing.T) { - ortbRequest := &openrtb2.BidRequest{ - ID: "request-id", - App: &openrtb2.App{ - Publisher: &openrtb2.Publisher{ - ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", - }, - }, - Source: &openrtb2.Source{ - TID: "transaction-id", - }, - Imp: []openrtb2.Imp{{ - ID: "imp-id", - Banner: &openrtb2.Banner{ - Format: []openrtb2.Format{{ - W: 300, - H: 250, - }}, - }, - Ext: json.RawMessage(`{"bidder":{"cp":512379,"ct":486653,"cf":"300x250"}}`), - }}, - } - - mockAdapter := mockLegacyAdapter{ - returnedError: errors.New("adapter failed"), - } - - exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - _, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderRubicon, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) - if len(errs) != 1 { - t.Fatalf("Bad error count. Expected 1, got %d", len(errs)) - } - if errs[0].Error() != "adapter failed" { - t.Errorf("Unexpected error message. Got %s", errs[0].Error()) - } -} - -func TestWithTargeting(t *testing.T) { - ortbRequest := &openrtb2.BidRequest{ - ID: "request-id", - App: &openrtb2.App{ - Publisher: &openrtb2.Publisher{ - ID: "b1c81a38-1415-42b7-8238-0d2d64016c27", - }, - }, - Source: &openrtb2.Source{ - TID: "transaction-id", - }, - Imp: []openrtb2.Imp{{ - ID: "imp-id", - Banner: &openrtb2.Banner{ - Format: []openrtb2.Format{{ - W: 300, - H: 250, - }}, - }, - Ext: json.RawMessage(`{"bidder": {"placementId": "1959066997713356_1959836684303054"}}`), - }}, - } - - mockAdapter := mockLegacyAdapter{ - returnedBids: []*pbs.PBSBid{{ - CreativeMediaType: "banner", - }}, - } - exchangeBidder := adaptLegacyAdapter(&mockAdapter) - currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0)) - bid, errs := exchangeBidder.requestBid(context.Background(), ortbRequest, openrtb_ext.BidderAudienceNetwork, 1.0, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, true) - if len(errs) != 0 { - t.Fatalf("This should not produce errors. Got %v", errs) - } - if len(bid.bids) != 1 { - t.Fatalf("We should get one bid back.") - } - if bid.bids[0] == nil { - t.Errorf("The returned bid should not be nil.") - } -} - -// assertEquivalentFields compares the OpenRTB request with the legacy request, using the mappings defined here: -// https://gist.github.com/dbemiller/68aa3387189fa17d3addfb9818dd4d97 -func assertEquivalentRequests(t *testing.T, req *openrtb2.BidRequest, legacy *pbs.PBSRequest) { - if req.Site != nil { - if req.Site.Publisher.ID != legacy.AccountID { - t.Errorf("Account ID did not translate. OpenRTB: %s, Legacy: %s.", req.Site.Publisher.ID, legacy.AccountID) - } - if req.Site.Page != legacy.Url { - t.Errorf("url did not translate. OpenRTB: %v, Legacy: %v.", req.Site.Page, legacy.Url) - } - if req.Site.Domain != legacy.Domain { - t.Errorf("domain did not translate. OpenRTB: %v, Legacy: %v.", req.Site.Domain, legacy.Domain) - } - } else if req.App != nil { - if req.App.Publisher.ID != legacy.AccountID { - t.Errorf("Account ID did not translate. OpenRTB: %s, Legacy: %s.", req.Site.Publisher.ID, legacy.AccountID) - } - } else { - t.Errorf("req.site and req.app are nil. This request was invalid.") - } - - if req.Source.TID != legacy.Tid { - t.Errorf("TID did not translate. OpenRTB: %s, Legacy: %s.", req.Source.TID, legacy.Tid) - } - - expectedSecure := int8(0) - if req.Imp[0].Secure != nil { - expectedSecure = int8(*req.Imp[0].Secure) - } - - if expectedSecure != legacy.Secure { - t.Errorf("tmax did not translate. OpenRTB: %d, Legacy: %d.", expectedSecure, legacy.Secure) - } - // TODO: Secure - - if req.TMax != legacy.TimeoutMillis { - t.Errorf("tmax did not translate. OpenRTB: %d, Legacy: %d.", req.TMax, legacy.TimeoutMillis) - } - - if req.App != legacy.App { - t.Errorf("app did not translate. OpenRTB: %v, Legacy: %v.", req.App, legacy.App) - } - if req.Device != legacy.Device { - t.Errorf("device did not translate. OpenRTB: %v, Legacy: %v.", req.Device, legacy.Device) - } - if req.User != legacy.User { - t.Errorf("user did not translate. OpenRTB: %v, Legacy: %v.", req.User, legacy.User) - } - if req.User != nil { - if id, _, _ := legacy.Cookie.GetUID("someFamily"); id != req.User.BuyerUID { - t.Errorf("bidder usersync did not translate. OpenRTB: %v, Legacy: %v.", req.User.BuyerUID, id) - } - if id, _, _ := legacy.Cookie.GetUID("adnxs"); id != req.User.ID { - t.Errorf("user ID did not translate. OpenRTB: %v, Legacy: %v.", req.User.ID, id) - } - } -} - -func assertEquivalentBidder(t *testing.T, req *openrtb2.BidRequest, legacy *pbs.PBSBidder) { - if len(req.Imp) != len(legacy.AdUnits) { - t.Errorf("Wrong number of Imps. Expected %d, got %d", len(req.Imp), len(legacy.AdUnits)) - return - } - for i := 0; i < len(req.Imp); i++ { - assertEquivalentImp(t, i, &req.Imp[i], &legacy.AdUnits[i]) - } -} - -func assertEquivalentImp(t *testing.T, index int, imp *openrtb2.Imp, legacy *pbs.PBSAdUnit) { - if imp.ID != legacy.BidID { - t.Errorf("imp[%d].id did not translate. OpenRTB %s, legacy %s", index, imp.ID, legacy.BidID) - } - - if imp.Instl != legacy.Instl { - t.Errorf("imp[%d].instl did not translate. OpenRTB %d, legacy %d", index, imp.Instl, legacy.Instl) - } - - if params, _, _, _ := jsonparser.Get(imp.Ext, "bidder"); !jsonpatch.Equal(params, legacy.Params) { - t.Errorf("imp[%d].ext.bidder did not translate. OpenRTB %s, legacy %s", index, string(params), string(legacy.Params)) - } - - if imp.Banner != nil { - if imp.Banner.TopFrame != legacy.TopFrame { - t.Errorf("imp[%d].topframe did not translate. OpenRTB %d, legacy %d", index, imp.Banner.TopFrame, legacy.TopFrame) - } - if imp.Banner.Format[0].W != legacy.Sizes[0].W { - t.Errorf("imp[%d].format[0].w did not translate. OpenRTB %d, legacy %d", index, imp.Banner.Format[0].W, legacy.Sizes[0].W) - } - if imp.Banner.Format[0].H != legacy.Sizes[0].H { - t.Errorf("imp[%d].format[0].h did not translate. OpenRTB %d, legacy %d", index, imp.Banner.Format[0].H, legacy.Sizes[0].H) - } - } - - if imp.Video != nil { - if !reflect.DeepEqual(imp.Video.MIMEs, legacy.Video.Mimes) { - t.Errorf("imp[%d].video.mimes did not translate. OpenRTB %v, legacy %v", index, imp.Video.MIMEs, legacy.Video.Mimes) - } - if len(imp.Video.Protocols) != len(imp.Video.Protocols) { - t.Errorf("len(imp[%d].video.protocols) did not match. OpenRTB %d, legacy %d", index, len(imp.Video.Protocols), len(imp.Video.Protocols)) - return - } - for i := 0; i < len(imp.Video.Protocols); i++ { - if int8(imp.Video.Protocols[i]) != legacy.Video.Protocols[i] { - t.Errorf("imp[%d].video.protocol[%d] did not match. OpenRTB %d, legacy %d", index, i, imp.Video.Protocols[i], imp.Video.Protocols[i]) - } - } - if len(imp.Video.PlaybackMethod) > 0 { - if int8(imp.Video.PlaybackMethod[0]) != legacy.Video.PlaybackMethod { - t.Errorf("imp[%d].video.playbackmethod[0] did not translate. OpenRTB %d, legacy %d", index, int8(imp.Video.PlaybackMethod[0]), legacy.Video.PlaybackMethod) - } - } - if imp.Video.Skip == nil { - if legacy.Video.Skippable != 0 { - t.Errorf("imp[%d].video.skip did not translate. OpenRTB nil, legacy %d", index, legacy.Video.Skippable) - } - } else { - if int(*imp.Video.Skip) != legacy.Video.Skippable { - t.Errorf("imp[%d].video.skip did not translate. OpenRTB %d, legacy %d", index, *imp.Video.Skip, legacy.Video.Skippable) - } - } - if imp.Video.StartDelay == nil { - if legacy.Video.Startdelay != 0 { - t.Errorf("imp[%d].video.startdelay did not translate. OpenRTB nil, legacy %d", index, legacy.Video.Startdelay) - } - } else { - if int64(*imp.Video.StartDelay) != legacy.Video.Startdelay { - t.Errorf("imp[%d].video.startdelay did not translate. OpenRTB %d, legacy %d", index, int64(*imp.Video.StartDelay), legacy.Video.Startdelay) - } - } - if imp.Video.MaxDuration != legacy.Video.Maxduration { - t.Errorf("imp[%d].video.maxduration did not translate. OpenRTB %d, legacy %d", index, imp.Video.MaxDuration, legacy.Video.Maxduration) - } - if imp.Video.MinDuration != legacy.Video.Minduration { - t.Errorf("imp[%d].video.minduration did not translate. OpenRTB %d, legacy %d", index, imp.Video.MinDuration, legacy.Video.Minduration) - } - } -} - -type mockLegacyAdapter struct { - returnedBids pbs.PBSBidSlice - returnedError error - gotRequest *pbs.PBSRequest - gotBidder *pbs.PBSBidder -} - -func (a *mockLegacyAdapter) Name() string { - return "someFamily" -} - -func (a *mockLegacyAdapter) SkipNoCookies() bool { - return false -} - -func (a *mockLegacyAdapter) GetUsersyncInfo() (*usersync.UsersyncInfo, error) { - return nil, nil -} - -func (a *mockLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - a.gotRequest = req - a.gotBidder = bidder - return a.returnedBids, a.returnedError -} diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go index aa07ed0c77b..f38a6c0266c 100644 --- a/exchange/targeting_test.go +++ b/exchange/targeting_test.go @@ -87,15 +87,15 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op } ex := &exchange{ - adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), - me: &metricsConf.DummyMetricsEngine{}, - cache: &wellBehavedCache{}, - cacheTime: time.Duration(0), - gDPR: gdpr.AlwaysAllow{}, - currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), - UsersyncIfAmbiguous: false, - categoriesFetcher: categoriesFetcher, - bidIDGenerator: &mockBidIDGenerator{false, false}, + adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), + me: &metricsConf.DummyMetricsEngine{}, + cache: &wellBehavedCache{}, + cacheTime: time.Duration(0), + gDPR: gdpr.AlwaysAllow{}, + currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), + gdprDefaultValue: gdpr.SignalYes, + categoriesFetcher: categoriesFetcher, + bidIDGenerator: &mockBidIDGenerator{false, false}, } imps := buildImps(t, mockBids) diff --git a/exchange/utils.go b/exchange/utils.go index 38d21751f8f..258d52d9055 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -28,18 +28,16 @@ var integrationTypeMap = map[metrics.RequestType]config.IntegrationType{ const unknownBidder string = "" -func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { +func BidderToPrebidSChains(sChains []*openrtb_ext.ExtRequestPrebidSChain) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) { bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain) - if req != nil { - for _, schainWrapper := range req.Prebid.SChains { - for _, bidder := range schainWrapper.Bidders { - if _, present := bidderToSChains[bidder]; present { - return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+ - "it must contain no more than one per bidder.", bidder) - } else { - bidderToSChains[bidder] = &schainWrapper.SChain - } + for _, schainWrapper := range sChains { + for _, bidder := range schainWrapper.Bidders { + if _, present := bidderToSChains[bidder]; present { + return nil, fmt.Errorf("request.ext.prebid.schains contains multiple schains for bidder %s; "+ + "it must contain no more than one per bidder.", bidder) + } else { + bidderToSChains[bidder] = &schainWrapper.SChain } } } @@ -56,9 +54,10 @@ func cleanOpenRTBRequests(ctx context.Context, req AuctionRequest, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, - usersyncIfAmbiguous bool, + metricsEngine metrics.MetricsEngine, + gdprDefaultValue gdpr.Signal, privacyConfig config.Privacy, - account *config.Account) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { + account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { impsByBidder, err := splitImps(req.BidRequest.Imp) if err != nil { @@ -71,9 +70,10 @@ func cleanOpenRTBRequests(ctx context.Context, return } - bidderRequests, errs = getAuctionBidderRequests(req, requestExt, impsByBidder, aliases) + var allBidderRequests []BidderRequest + allBidderRequests, errs = getAuctionBidderRequests(req, requestExt, impsByBidder, aliases) - if len(bidderRequests) == 0 { + if len(allBidderRequests) == 0 { return } @@ -85,7 +85,7 @@ func cleanOpenRTBRequests(ctx context.Context, if err != nil { errs = append(errs, err) } - gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && !usersyncIfAmbiguous) + gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes) ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &req.Account, aliases, integrationTypeMap[req.LegacyLabels.RType]) if err != nil { @@ -117,7 +117,10 @@ func cleanOpenRTBRequests(ctx context.Context, } // bidder level privacy policies - for _, bidderRequest := range bidderRequests { + allowedBidderRequests = make([]BidderRequest, 0, len(allBidderRequests)) + for _, bidderRequest := range allBidderRequests { + bidRequestAllowed := true + // CCPA privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) @@ -133,7 +136,9 @@ func cleanOpenRTBRequests(ctx context.Context, } } var publisherID = req.LegacyLabels.PubID - _, geo, id, err := gDPR.PersonalInfoAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent, weakVendorEnforcement) + bidReq, geo, id, err := gDPR.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent, weakVendorEnforcement) + bidRequestAllowed = bidReq + if err == nil { privacyEnforcement.GDPRGeo = !geo privacyEnforcement.GDPRID = !id @@ -141,9 +146,16 @@ func cleanOpenRTBRequests(ctx context.Context, privacyEnforcement.GDPRGeo = true privacyEnforcement.GDPRID = true } + + if !bidRequestAllowed { + metricsEngine.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName) + } } - privacyEnforcement.Apply(bidderRequest.BidRequest) + if bidRequestAllowed { + privacyEnforcement.Apply(bidderRequest.BidRequest) + allowedBidderRequests = append(allowedBidderRequests, bidderRequest) + } } return @@ -164,7 +176,8 @@ func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestT } func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) { - ccpaPolicy, err := ccpa.ReadFromRequest(orig) + // Quick extra wrapper until RequestWrapper makes its way into CleanRequests + ccpaPolicy, err := ccpa.ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: orig}) if err != nil { return privacy.NilPolicyEnforcer{}, err } @@ -234,9 +247,12 @@ func getAuctionBidderRequests(req AuctionRequest, var sChainsByBidder map[string]*openrtb_ext.ExtRequestPrebidSChainSChain - sChainsByBidder, err = BidderToPrebidSChains(requestExt) - if err != nil { - return nil, []error{err} + // Quick extra wrapper until RequestWrapper makes its way into CleanRequests + if requestExt != nil { + sChainsByBidder, err = BidderToPrebidSChains(requestExt.Prebid.SChains) + if err != nil { + return nil, []error{err} + } } reqExt, err := getExtJson(req.BidRequest, requestExt) @@ -364,7 +380,7 @@ func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { userExt.Prebid = nil // Remarshal (instead of removing) if the ext has other known fields - if userExt.Consent != "" || userExt.DigiTrust != nil || len(userExt.Eids) > 0 { + if userExt.Consent != "" || len(userExt.Eids) > 0 { if newUserExtBytes, err := json.Marshal(userExt); err != nil { return nil, err } else { diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 55a0950aac6..1d13928b59c 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -14,14 +14,16 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) // permissionsMock mocks the Permissions interface for tests -// -// It only allows appnexus for GDPR consent type permissionsMock struct { - personalInfoAllowed bool - personalInfoAllowedError error + allowAllBidders bool + allowedBidders []openrtb_ext.BidderName + passGeo bool + passID bool + activitiesError error } func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, gdpr gdpr.Signal, consent string) (bool, error) { @@ -32,8 +34,18 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { - return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowedError +func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { + if p.allowAllBidders { + return true, p.passGeo, p.passID, p.activitiesError + } + + for _, allowedBidder := range p.allowedBidders { + if bidder == allowedBidder { + allowBidRequest = true + } + } + + return allowBidRequest, p.passGeo, p.passID, p.activitiesError } func assertReq(t *testing.T, bidderRequests []BidderRequest, @@ -465,7 +477,9 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) + metricsMock := metrics.MetricsEngineMock{} + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, gdpr.SignalNo, privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -620,8 +634,9 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { context.Background(), auctionReq, nil, - &permissionsMock{personalInfoAllowed: true}, - true, + &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, + &metrics.MetricsEngineMock{}, + gdpr.SignalNo, privacyConfig, nil) result := bidderRequests[0] @@ -681,7 +696,9 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { Enforce: true, }, } - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -721,7 +738,9 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { UserSyncs: &emptyUsersync{}, } - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}, nil) + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -828,7 +847,9 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { UserSyncs: &emptyUsersync{}, } - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissionsMock{}, true, config.Privacy{}, nil) + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1409,7 +1430,9 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1425,7 +1448,6 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { } func TestCleanOpenRTBRequestsGDPR(t *testing.T) { - tcf1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY" tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" trueValue, falseValue := true, false @@ -1437,7 +1459,7 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdprConsent string gdprScrub bool permissionsError error - userSyncIfAmbiguous bool + gdprDefaultValue string expectPrivacyLabels metrics.PrivacyLabels expectError bool }{ @@ -1448,129 +1470,125 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdpr: "1", gdprConsent: "malformed", gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: "", }, }, { - description: "Enforce - TCF 1", - gdprAccountEnabled: &trueValue, - gdprHostEnabled: true, - gdpr: "1", - gdprConsent: tcf1Consent, - gdprScrub: true, - expectPrivacyLabels: metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }, - }, - { - description: "Enforce - TCF 2", + description: "Enforce", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "1", gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - TCF 1", + description: "Not Enforce", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "0", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", }, }, { - description: "Enforce - TCF 1; GDPR signal extraction error", + description: "Enforce; GDPR signal extraction error", gdprAccountEnabled: &trueValue, gdprHostEnabled: true, gdpr: "0{", - gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY", + gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, expectError: true, }, { - description: "Enforce - TCF 1; account GDPR enabled, host GDPR setting disregarded", + description: "Enforce; account GDPR enabled, host GDPR setting disregarded", gdprAccountEnabled: &trueValue, gdprHostEnabled: false, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - TCF 1; account GDPR disabled, host GDPR setting disregarded", + description: "Not Enforce; account GDPR disabled, host GDPR setting disregarded", gdprAccountEnabled: &falseValue, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", }, }, { - description: "Enforce - TCF 1; account GDPR not specified, host GDPR enabled", + description: "Enforce; account GDPR not specified, host GDPR enabled", gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - TCF 1; account GDPR not specified, host GDPR disabled", + description: "Not Enforce; account GDPR not specified, host GDPR disabled", gdprAccountEnabled: nil, gdprHostEnabled: false, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: false, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", }, }, { - description: "Enforce - Ambiguous signal, don't sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf1Consent, - gdprScrub: true, - userSyncIfAmbiguous: false, + description: "Enforce - Ambiguous signal, don't sync user if ambiguous", + gdprAccountEnabled: nil, + gdprHostEnabled: true, + gdpr: "null", + gdprConsent: tcf2Consent, + gdprScrub: true, + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, { - description: "Not Enforce - Ambiguous signal, sync user if ambiguous", - gdprAccountEnabled: nil, - gdprHostEnabled: true, - gdpr: "null", - gdprConsent: tcf1Consent, - gdprScrub: false, - userSyncIfAmbiguous: true, + description: "Not Enforce - Ambiguous signal, sync user if ambiguous", + gdprAccountEnabled: nil, + gdprHostEnabled: true, + gdpr: "null", + gdprConsent: tcf2Consent, + gdprScrub: false, + gdprDefaultValue: "0", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: false, GDPRTCFVersion: "", @@ -1581,12 +1599,13 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { gdprAccountEnabled: nil, gdprHostEnabled: true, gdpr: "1", - gdprConsent: tcf1Consent, + gdprConsent: tcf2Consent, gdprScrub: true, permissionsError: errors.New("Some error"), + gdprDefaultValue: "1", expectPrivacyLabels: metrics.PrivacyLabels{ GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, + GDPRTCFVersion: metrics.TCFVersionV2, }, }, } @@ -1600,8 +1619,8 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { privacyConfig := config.Privacy{ GDPR: config.GDPR{ - Enabled: test.gdprHostEnabled, - UsersyncIfAmbiguous: test.userSyncIfAmbiguous, + Enabled: test.gdprHostEnabled, + DefaultValue: test.gdprDefaultValue, TCF2: config.TCF2{ Enabled: true, }, @@ -1620,12 +1639,18 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { Account: accountConfig, } + gdprDefaultValue := gdpr.SignalYes + if test.gdprDefaultValue == "0" { + gdprDefaultValue = gdpr.SignalNo + } + results, privacyLabels, errs := cleanOpenRTBRequests( context.Background(), auctionReq, nil, - &permissionsMock{personalInfoAllowed: !test.gdprScrub, personalInfoAllowedError: test.permissionsError}, - test.userSyncIfAmbiguous, + &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, + &metrics.MetricsEngineMock{}, + gdprDefaultValue, privacyConfig, nil) result := results[0] @@ -1647,6 +1672,97 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { } } +func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { + testCases := []struct { + description string + gdprEnforced bool + gdprAllowedBidders []openrtb_ext.BidderName + expectedBidders []openrtb_ext.BidderName + expectedBlockedBidders []openrtb_ext.BidderName + }{ + { + description: "gdpr enforced, one request allowed and one request blocked", + gdprEnforced: true, + gdprAllowedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, + expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, + expectedBlockedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderRubicon}, + }, + { + description: "gdpr enforced, two requests allowed and no requests blocked", + gdprEnforced: true, + gdprAllowedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, + expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, + expectedBlockedBidders: []openrtb_ext.BidderName{}, + }, + { + description: "gdpr not enforced, two requests allowed and no requests blocked", + gdprEnforced: false, + gdprAllowedBidders: []openrtb_ext.BidderName{}, + expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, + expectedBlockedBidders: []openrtb_ext.BidderName{}, + }, + } + + for _, test := range testCases { + req := newBidRequest(t) + req.Regs = &openrtb2.Regs{ + Ext: json.RawMessage(`{"gdpr":1}`), + } + req.Imp[0].Ext = json.RawMessage(`{"appnexus": {"placementId": 1}, "rubicon": {}}`) + + privacyConfig := config.Privacy{ + GDPR: config.GDPR{ + Enabled: test.gdprEnforced, + DefaultValue: "0", + TCF2: config.TCF2{ + Enabled: true, + }, + }, + } + + accountConfig := config.Account{ + GDPR: config.AccountGDPR{ + Enabled: nil, + }, + } + + auctionReq := AuctionRequest{ + BidRequest: req, + UserSyncs: &emptyUsersync{}, + Account: accountConfig, + } + + metricsMock := metrics.MetricsEngineMock{} + metricsMock.Mock.On("RecordAdapterGDPRRequestBlocked", mock.Anything).Return() + + results, _, errs := cleanOpenRTBRequests( + context.Background(), + auctionReq, + nil, + &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, + &metricsMock, + gdpr.SignalNo, + privacyConfig, + nil) + + // extract bidder name from each request in the results + bidders := []openrtb_ext.BidderName{} + for _, req := range results { + bidders = append(bidders, req.BidderName) + } + + assert.Empty(t, errs, test.description) + assert.ElementsMatch(t, bidders, test.expectedBidders, test.description) + + for _, blockedBidder := range test.expectedBlockedBidders { + metricsMock.AssertCalled(t, "RecordAdapterGDPRRequestBlocked", blockedBidder) + } + for _, allowedBidder := range test.expectedBidders { + metricsMock.AssertNotCalled(t, "RecordAdapterGDPRRequestBlocked", allowedBidder) + } + } +} + // newAdapterAliasBidRequest builds a BidRequest with aliases func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { dnt := int8(1) @@ -1672,7 +1788,7 @@ func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { User: &openrtb2.User{ ID: "our-id", BuyerUID: "their-id", - Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), }, Regs: &openrtb2.Regs{ Ext: json.RawMessage(`{"gdpr":1}`), @@ -1717,7 +1833,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest { ID: "our-id", BuyerUID: "their-id", Yob: 1982, - Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`), + Ext: json.RawMessage(`{}`), }, Imp: []openrtb2.Imp{{ ID: "some-imp-id", @@ -1792,7 +1908,7 @@ func TestBidderToPrebidChains(t *testing.T) { }, } - output, err := BidderToPrebidSChains(&input) + output, err := BidderToPrebidSChains(input.Prebid.SChains) assert.Nil(t, err) assert.Equal(t, len(output), 4) @@ -1818,7 +1934,7 @@ func TestBidderToPrebidChainsDiscardMultipleChainsForBidder(t *testing.T) { }, } - output, err := BidderToPrebidSChains(&input) + output, err := BidderToPrebidSChains(input.Prebid.SChains) assert.NotNil(t, err) assert.Nil(t, output) @@ -1831,7 +1947,7 @@ func TestBidderToPrebidChainsNilSChains(t *testing.T) { }, } - output, err := BidderToPrebidSChains(&input) + output, err := BidderToPrebidSChains(input.Prebid.SChains) assert.Nil(t, err) assert.Equal(t, len(output), 0) @@ -1844,7 +1960,7 @@ func TestBidderToPrebidChainsZeroLengthSChains(t *testing.T) { }, } - output, err := BidderToPrebidSChains(&input) + output, err := BidderToPrebidSChains(input.Prebid.SChains) assert.Nil(t, err) assert.Equal(t, len(output), 0) diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 616d0f0ae07..d2d282b2fec 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -5,6 +5,7 @@ import ( "net/http" "strconv" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/errortypes" @@ -25,12 +26,11 @@ type Permissions interface { // Determines whether or not to send PI information to a bidder, or mask it out. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) + AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowBidReq bool, passGeo bool, passID bool, err error) } // Versions of the GDPR TCF technical specification. const ( - tcf1SpecVersion uint8 = 1 tcf2SpecVersion uint8 = 2 ) @@ -40,12 +40,31 @@ func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ return &AlwaysAllow{} } + gdprDefaultValue := SignalYes + if cfg.DefaultValue == "0" { + gdprDefaultValue = SignalNo + } + + purposeConfigs := map[consentconstants.Purpose]config.TCF2Purpose{ + 1: cfg.TCF2.Purpose1, + 2: cfg.TCF2.Purpose2, + 3: cfg.TCF2.Purpose3, + 4: cfg.TCF2.Purpose4, + 5: cfg.TCF2.Purpose5, + 6: cfg.TCF2.Purpose6, + 7: cfg.TCF2.Purpose7, + 8: cfg.TCF2.Purpose8, + 9: cfg.TCF2.Purpose9, + 10: cfg.TCF2.Purpose10, + } + permissionsImpl := &permissionsImpl{ - cfg: cfg, - vendorIDs: vendorIDs, + cfg: cfg, + gdprDefaultValue: gdprDefaultValue, + purposeConfigs: purposeConfigs, + vendorIDs: vendorIDs, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: newVendorListFetcherTCF1(cfg), - tcf2SpecVersion: newVendorListFetcherTCF2(ctx, cfg, client, vendorListURLMaker)}, + tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)}, } if cfg.HostVendorID == 0 { diff --git a/gdpr/impl.go b/gdpr/impl.go index 55d1cd4aeb0..5f7e3e73fe2 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -5,8 +5,8 @@ import ( "fmt" "github.com/prebid/go-gdpr/api" - tcf1constants "github.com/prebid/go-gdpr/consentconstants" - consentconstants "github.com/prebid/go-gdpr/consentconstants/tcf2" + "github.com/prebid/go-gdpr/consentconstants" + tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" "github.com/prebid/go-gdpr/vendorconsent" tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2" "github.com/prebid/go-gdpr/vendorlist" @@ -28,9 +28,11 @@ const ( ) type permissionsImpl struct { - cfg config.GDPR - vendorIDs map[openrtb_ext.BidderName]uint16 - fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) + cfg config.GDPR + gdprDefaultValue Signal + purposeConfigs map[consentconstants.Purpose]config.TCF2Purpose + vendorIDs map[openrtb_ext.BidderName]uint16 + fetchVendorList map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error) } func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { @@ -40,7 +42,7 @@ func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Sig return true, nil } - return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent) + return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent, false) } func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { @@ -52,18 +54,19 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ id, ok := p.vendorIDs[bidder] if ok { - return p.allowSync(ctx, id, consent) + vendorException := p.isVendorException(consentconstants.Purpose(1), bidder) + return p.allowSync(ctx, id, consent, vendorException) } return false, nil } -func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, +func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, - weakVendorEnforcement bool) (allowPI bool, allowGeo bool, allowID bool, err error) { + weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { if _, ok := p.cfg.NonStandardPublisherMap[PublisherID]; ok { return true, true, true, nil } @@ -79,13 +82,15 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, } if id, ok := p.vendorIDs[bidder]; ok { - return p.allowPI(ctx, id, consent, weakVendorEnforcement) + return p.allowActivities(ctx, id, bidder, consent, weakVendorEnforcement) + } else if weakVendorEnforcement { + return p.allowActivities(ctx, 0, bidder, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() } -func (p *permissionsImpl) defaultVendorPermissions() (allowPI bool, allowGeo bool, allowID bool, err error) { +func (p *permissionsImpl) defaultVendorPermissions() (allowBidRequest bool, passGeo bool, passID bool, err error) { return false, false, false, nil } @@ -94,14 +99,14 @@ func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { return gdprSignal } - if p.cfg.UsersyncIfAmbiguous { + if p.gdprDefaultValue == SignalNo { return SignalNo } return SignalYes } -func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string) (bool, error) { +func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string, vendorException bool) (bool, error) { if consent == "" { return false, nil @@ -116,81 +121,75 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } - // InfoStorageAccess is the same across TCF 1 and TCF 2 - if parsedConsent.Version() == 2 { - if !p.cfg.TCF2.Purpose1.Enabled { - // We are not enforcing purpose 1 - return true, nil - } - consent, ok := parsedConsent.(tcf2.ConsentMetadata) - if !ok { - err := fmt.Errorf("Unable to access TCF2 parsed consent") - return false, err - } - return p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, false), nil - } - if vendor.Purpose(consentconstants.InfoStorageAccess) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && parsedConsent.VendorConsent(vendorID) { + if !p.cfg.TCF2.Purpose1.Enabled { return true, nil } - return false, nil + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) + if !ok { + err := fmt.Errorf("Unable to access TCF2 parsed consent") + return false, err + } + return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, vendorException, false), nil } -func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { +func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, bidder openrtb_ext.BidderName, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) if err != nil { return false, false, false, err } + // vendor will be nil if not a valid TCF2 consent string if vendor == nil { - return false, false, false, nil + if weakVendorEnforcement && parsedConsent.Version() == 2 { + vendor = vendorTrue{} + } else { + return false, false, false, nil + } } - if parsedConsent.Version() == 2 { - if p.cfg.TCF2.Enabled { - return p.allowPITCF2(parsedConsent, vendor, vendorID, weakVendorEnforcement) - } - if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && (parsedConsent.VendorConsent(vendorID) || weakVendorEnforcement) { - return true, true, true, nil - } - } else { - if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) { - return true, true, true, nil - } + if !p.cfg.TCF2.Enabled { + return true, false, false, nil } - return false, false, false, nil -} -func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowPI bool, allowGeo bool, allowID bool, err error) { - consent, ok := parsedConsent.(tcf2.ConsentMetadata) - err = nil - allowPI = false - allowGeo = false - allowID = false + consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata) if !ok { err = fmt.Errorf("Unable to access TCF2 parsed consent") return } + if p.cfg.TCF2.SpecialPurpose1.Enabled { - allowGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) + vendorException := p.isSpecialPurposeVendorException(bidder) + passGeo = vendorException || (consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement)) } else { - allowGeo = true + passGeo = true + } + if p.cfg.TCF2.Purpose2.Enabled { + vendorException := p.isVendorException(consentconstants.Purpose(2), bidder) + allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), vendorException, weakVendorEnforcement) + } else { + allowBidRequest = true } for i := 2; i <= 10; i++ { - if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i), weakVendorEnforcement) { - allowID = true + vendorException := p.isVendorException(consentconstants.Purpose(i), bidder) + if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), vendorException, weakVendorEnforcement) { + passID = true break } } - // Set to true so any purpose check can flip it to false - allowPI = true - if p.cfg.TCF2.Purpose1.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.InfoStorageAccess, weakVendorEnforcement) - } - if p.cfg.TCF2.Purpose2.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.BasicAdserving, weakVendorEnforcement) + + return +} + +func (p *permissionsImpl) isVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (vendorException bool) { + if _, ok := p.purposeConfigs[purpose].VendorExceptionMap[bidder]; ok { + vendorException = true } - if p.cfg.TCF2.Purpose7.Enabled { - allowPI = allowPI && p.checkPurpose(consent, vendor, vendorID, consentconstants.AdPerformance, weakVendorEnforcement) + return +} + +func (p *permissionsImpl) isSpecialPurposeVendorException(bidder openrtb_ext.BidderName) (vendorException bool) { + if _, ok := p.cfg.TCF2.SpecialPurpose1.VendorExceptionMap[bidder]; ok { + vendorException = true } return } @@ -199,16 +198,20 @@ const pubRestrictNotAllowed = 0 const pubRestrictRequireConsent = 1 const pubRestrictRequireLegitInterest = 2 -func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose tcf1constants.Purpose, weakVendorEnforcement bool) bool { - if purpose == consentconstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { +func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, vendorException, weakVendorEnforcement bool) bool { + if purpose == tcf2ConsentConstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() { return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed } if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) { return false } - purposeAllowed := consent.PurposeAllowed(purpose) && (weakVendorEnforcement || (vendor.Purpose(purpose) && consent.VendorConsent(vendorID))) - legitInterest := consent.PurposeLITransparency(purpose) && (weakVendorEnforcement || (vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID))) + if vendorException { + return true + } + + purposeAllowed := p.consentEstablished(consent, vendor, vendorID, purpose, weakVendorEnforcement) + legitInterest := p.legitInterestEstablished(consent, vendor, vendorID, purpose, weakVendorEnforcement) if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) { return purposeAllowed @@ -221,6 +224,38 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api. return purposeAllowed || legitInterest } +func (p *permissionsImpl) consentEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool { + if !consent.PurposeAllowed(purpose) { + return false + } + if weakVendorEnforcement { + return true + } + if !p.purposeConfigs[purpose].EnforceVendors { + return true + } + if vendor.Purpose(purpose) && consent.VendorConsent(vendorID) { + return true + } + return false +} + +func (p *permissionsImpl) legitInterestEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool { + if !consent.PurposeLITransparency(purpose) { + return false + } + if weakVendorEnforcement { + return true + } + if !p.purposeConfigs[purpose].EnforceVendors { + return true + } + if vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID) { + return true + } + return false +} + func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) { parsedConsent, err = vendorconsent.ParseString(consent) if err != nil { @@ -232,9 +267,10 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons } version := parsedConsent.Version() - if version < 1 || version > 2 { + if version != 2 { return } + vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion()) if err != nil { return @@ -265,21 +301,25 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B return true, nil } -func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { +func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { return true, true, true, nil } -// Exporting to allow for easy test setups -type AlwaysFail struct{} +// vendorTrue claims everything. +type vendorTrue struct{} -func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { - return false, nil +func (v vendorTrue) Purpose(purposeID consentconstants.Purpose) bool { + return true } - -func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { - return false, nil +func (v vendorTrue) PurposeStrict(purposeID consentconstants.Purpose) bool { + return true } - -func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (bool, bool, bool, error) { - return false, false, false, nil +func (v vendorTrue) LegitimateInterest(purposeID consentconstants.Purpose) bool { + return true +} +func (v vendorTrue) LegitimateInterestStrict(purposeID consentconstants.Purpose) bool { + return true +} +func (v vendorTrue) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpecialPurpose bool) { + return true } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index b13d469a955..f7d90f3673b 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -9,6 +9,7 @@ import ( "github.com/prebid/prebid-server/config" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/go-gdpr/vendorlist" "github.com/prebid/go-gdpr/vendorlist2" @@ -18,12 +19,12 @@ import ( func TestDisallowOnEmptyConsent(t *testing.T) { perms := permissionsImpl{ cfg: config.GDPR{ - HostVendorID: 3, - UsersyncIfAmbiguous: true, + HostVendorID: 3, + DefaultValue: "0", }, - vendorIDs: nil, + gdprDefaultValue: SignalNo, + vendorIDs: nil, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: failedListFetcher, tcf2SpecVersion: failedListFetcher, }, } @@ -49,106 +50,128 @@ func TestAllowOnSignalNo(t *testing.T) { } func TestAllowedSyncs(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, - {ID: 3, Purposes: []int{1}}, + vendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABCAAIAAAAAAAAAAACEAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2AndPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABoAAAAAMw") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2AndPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, true, allowSync) } func TestProhibitedPurposes(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, // cookie reads/writes - {ID: 3, Purposes: []int{3}}, // ad personalization + vendor2NoPurpose1Consent := "CPGWkCaPGWkCaApAAAENABCAAAAAAAAAAAAAABEAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.TCF2Purpose{ + Enabled: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2NoPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BON3PCUON3PCUABABBAAABAAAAAAMw") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, vendor2NoPurpose1Consent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } func TestProhibitedVendors(t *testing.T) { - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ - VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1}}, // cookie reads/writes - {ID: 3, Purposes: []int{3}}, // ad personalization + purpose1NoVendorConsent := "CPGWkCaPGWkCaApAAAENABCAAIAAAAAAAAAAABAAAAAA" + vendorListData := MarshalVendorList(vendorList{ + VendorListVersion: 2, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{1}, + }, }, }) perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, + TCF2: config.TCF2{ + Purpose1: config.TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 3, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + } - allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") + allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, purpose1NoVendorConsent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) - allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, "BOS2bx5OS2bx5ABABBAAABoAAAAAFA") + allowSync, err = perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderPubmatic, SignalYes, purpose1NoVendorConsent) assertNilErr(t, err) assertBoolsEqual(t, false, allowSync) } @@ -159,7 +182,6 @@ func TestMalformedConsent(t *testing.T) { HostVendorID: 2, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(nil), tcf2SpecVersion: listFetcher(nil), }, } @@ -169,134 +191,149 @@ func TestMalformedConsent(t *testing.T) { assertBoolsEqual(t, false, sync) } -func TestAllowPersonalInfo(t *testing.T) { +func TestAllowActivities(t *testing.T) { bidderAllowedByConsent := openrtb_ext.BidderAppnexus bidderBlockedByConsent := openrtb_ext.BidderRubicon - consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" + vendor2AndPurpose2Consent := "CPGWbY_PGWbY_GYAAAENABCAAEAAAAAAAAAAACEAAAAA" tests := []struct { description string bidderName openrtb_ext.BidderName publisherID string - userSyncIfAmbiguous bool + gdprDefaultValue string gdpr Signal consent string - allowPI bool + passID bool weakVendorEnforcement bool }{ { - description: "Allow PI - Non standard publisher", - bidderName: bidderBlockedByConsent, - publisherID: "appNexusAppID", - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: consent, - allowPI: true, + description: "Allow PI - Non standard publisher", + bidderName: bidderBlockedByConsent, + publisherID: "appNexusAppID", + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Allow PI - known vendor with No GDPR", - bidderName: bidderBlockedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalNo, - consent: consent, - allowPI: true, + description: "Allow PI - known vendor with No GDPR", + bidderName: bidderBlockedByConsent, + gdprDefaultValue: "1", + gdpr: SignalNo, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Allow PI - known vendor with Yes GDPR", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: consent, - allowPI: true, + description: "Allow PI - known vendor with Yes GDPR", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: true, - gdpr: SignalAmbiguous, - consent: "", - allowPI: true, + description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "0", + gdpr: SignalAmbiguous, + consent: "", + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and non-empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: true, - gdpr: SignalAmbiguous, - consent: consent, - allowPI: true, + description: "PI allowed according to host setting gdprDefaultValue 0 - known vendor with ambiguous GDPR and non-empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "0", + gdpr: SignalAmbiguous, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalAmbiguous, - consent: "", - allowPI: false, + description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalAmbiguous, + consent: "", + passID: false, }, { - description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and non-empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalAmbiguous, - consent: consent, - allowPI: true, + description: "PI allowed according to host setting gdprDefaultValue 1 - known vendor with ambiguous GDPR and non-empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalAmbiguous, + consent: vendor2AndPurpose2Consent, + passID: true, }, { - description: "Don't allow PI - known vendor with Yes GDPR and empty consent", - bidderName: bidderAllowedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: "", - allowPI: false, + description: "Don't allow PI - known vendor with Yes GDPR and empty consent", + bidderName: bidderAllowedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: "", + passID: false, }, { - description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", - bidderName: bidderBlockedByConsent, - userSyncIfAmbiguous: false, - gdpr: SignalYes, - consent: consent, - allowPI: false, + description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", + bidderName: bidderBlockedByConsent, + gdprDefaultValue: "1", + gdpr: SignalYes, + consent: vendor2AndPurpose2Consent, + passID: false, }, } - - vendorListData := tcf1MarshalVendorList(tcf1VendorList{ + vendorListData := MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: []tcf1Vendor{ - {ID: 2, Purposes: []int{1, 3}}, + Vendors: map[string]*vendor{ + "2": { + ID: 2, + Purposes: []int{2}, + }, }, }) + perms := permissionsImpl{ cfg: config.GDPR{ HostVendorID: 2, NonStandardPublisherMap: map[string]struct{}{"appNexusAppID": {}}, + TCF2: config.TCF2{ + Enabled: true, + Purpose2: config.TCF2Purpose{ + Enabled: true, + EnforceVendors: true, + }, + }, }, vendorIDs: map[openrtb_ext.BidderName]uint16{ openrtb_ext.BidderAppnexus: 2, }, fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), - }), tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 1: parseVendorListData(t, vendorListData), + 1: parseVendorListDataV2(t, vendorListData), }), }, } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2, + } for _, tt := range tests { - perms.cfg.UsersyncIfAmbiguous = tt.userSyncIfAmbiguous + perms.cfg.DefaultValue = tt.gdprDefaultValue + if tt.gdprDefaultValue == "0" { + perms.gdprDefaultValue = SignalNo + } else { + perms.gdprDefaultValue = SignalYes + } - allowPI, _, _, err := perms.PersonalInfoAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) + _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) assert.Nil(t, err, tt.description) - assert.Equal(t, tt.allowPI, allowPI, tt.description) + assert.Equal(t, tt.passID, passID, tt.description) } } -func buildTCF2VendorList34() tcf2VendorList { - return tcf2VendorList{ +func buildVendorList34() vendorList { + return vendorList{ VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{ + Vendors: map[string]*vendor{ "2": { ID: 2, Purposes: []int{1}, @@ -332,81 +369,112 @@ func buildTCF2VendorList34() tcf2VendorList { } } -var tcf2Config = config.GDPR{ - HostVendorID: 2, - TCF2: config.TCF2{ - Enabled: true, - Purpose1: config.PurposeDetail{Enabled: true}, - Purpose2: config.PurposeDetail{Enabled: true}, - Purpose7: config.PurposeDetail{Enabled: true}, - SpecialPurpose1: config.PurposeDetail{Enabled: true}, - }, +func allPurposesEnabledPermissions() (perms permissionsImpl) { + perms = permissionsImpl{ + cfg: config.GDPR{ + HostVendorID: 2, + TCF2: config.TCF2{ + Enabled: true, + Purpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose2: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose3: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose4: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose5: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose6: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose7: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose8: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose9: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + Purpose10: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + SpecialPurpose1: config.TCF2Purpose{Enabled: true, EnforceVendors: true}, + }, + }, + } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2, + consentconstants.Purpose(3): perms.cfg.TCF2.Purpose3, + consentconstants.Purpose(4): perms.cfg.TCF2.Purpose4, + consentconstants.Purpose(5): perms.cfg.TCF2.Purpose5, + consentconstants.Purpose(6): perms.cfg.TCF2.Purpose6, + consentconstants.Purpose(7): perms.cfg.TCF2.Purpose7, + consentconstants.Purpose(8): perms.cfg.TCF2.Purpose8, + consentconstants.Purpose(9): perms.cfg.TCF2.Purpose9, + consentconstants.Purpose(10): perms.cfg.TCF2.Purpose10, + } + return } -type tcf2TestDef struct { +type testDef struct { description string bidder openrtb_ext.BidderName consent string - allowPI bool - allowGeo bool - allowID bool + allowBid bool + passGeo bool + passID bool weakVendorEnforcement bool } -func TestAllowPersonalInfoTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 20, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - 74: parseVendorListDataV2(t, vendorListData), - }), - }, +func TestAllowActivitiesGeoAndID(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 20, + openrtb_ext.BidderAudienceNetwork: 55, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + 74: parseVendorListDataV2(t, vendorListData), + }), } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8 - // PI needs all purposes to succeed - testDefs := []tcf2TestDef{ + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes and vendors 2, 6, 8 + testDefs := []testDef{ { description: "Appnexus vendor test, insufficient purposes claimed", bidder: openrtb_ext.BidderAppnexus, consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - allowPI: false, - allowGeo: false, - allowID: false, + allowBid: false, + passGeo: false, + passID: false, }, { description: "Appnexus vendor test, insufficient purposes claimed, basic enforcement", bidder: openrtb_ext.BidderAppnexus, consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - allowPI: true, - allowGeo: true, - allowID: true, + allowBid: true, + passGeo: true, + passID: true, + weakVendorEnforcement: true, + }, + { + description: "Unknown vendor test, insufficient purposes claimed, basic enforcement", + bidder: openrtb_ext.BidderAudienceNetwork, + consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", + allowBid: true, + passGeo: true, + passID: true, weakVendorEnforcement: true, }, { description: "Pubmatic vendor test, flex purposes claimed", bidder: openrtb_ext.BidderPubmatic, consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - allowPI: true, - allowGeo: true, - allowID: true, + allowBid: true, + passGeo: true, + passID: true, }, { description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", bidder: openrtb_ext.BidderRubicon, consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - allowPI: true, - allowGeo: false, - allowID: true, + allowBid: true, + passGeo: false, + passID: true, }, { // This requires publisher restrictions on any claimed purposes, 2-10. Vendor must declare all claimed purposes @@ -415,232 +483,109 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { description: "OpenX vendor test, Specific purposes/LIs claimed, no geo claimed, Publisher restrictions apply", bidder: openrtb_ext.BidderOpenx, consent: "CPAavcCPAavcCAGABCFRBKCsAP_AAH_AAAqIHFNf_X_fb3_j-_59_9t0eY1f9_7_v-0zjgeds-8Nyd_X_L8X5mM7vB36pq4KuR4Eu3LBAQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XT_ZKY79_____7__-_____7_f__-__3_vp9V---wOJAIMBAUAgAEMAAQIFCIQAAQhiQAAAABBCIBQJIAEqgAWVwEdoIEACAxAQgQAgBBQgwCAAQAAJKAgBACwQCAAiAQAAgAEAIAAEIAILACQEAAAEAJCAAiACECAgiAAg5DAgIgCCAFABAAAuJDACAMooASBAPGQGAAKAAqACGAEwALgAjgBlgDUAHZAPsA_ACMAFLAK2AbwBMQCbAFogLYAYEAw8BkQDOQGeAM-EQHwAVABWAC4AIYAZAAywBqADZAHYAPwAgABGAClgFPANYAdUA-QCGwEOgIvASIAmwBOwCkQFyAMCAYSAw8Bk4DOQGfCQAYADgBzgN_CQTgAEAALgAoACoAGQAOAAeABAACIAFQAMIAaABqADyAIYAigBMgCqAKwAWAAuABvADmAHoAQ0AiACJgEsAS4AmgBSgC3AGGAMgAZcA1ADVAGyAO8AewA-IB9gH6AQAAjABQQClgFPAL8AYoA1gBtADcAG8AOIAegA-QCGwEOgIqAReAkQBMQCZQE2AJ2AUOApEBYoC2AFyALvAYEAwYBhIDDQGHgMiAZIAycBlwDOQGfANIAadA1gDWQoAEAYQaBIACoAKwAXABDADIAGWANQAbIA7AB-AEAAIKARgApYBT4C0ALSAawA3gB1QD5AIbAQ6Ai8BIgCbAE7AKRAXIAwIBhIDDwGMAMnAZyAzwBnwcAEAA4Bv4qA2ABQAFQAQwAmABcAEcAMsAagA7AB-AEYAKXAWgBaQDeAJBATEAmwBTYC2AFyAMCAYeAyIBnIDPAGfANyHQWQAFwAUABUADIAHAAQAAiABdADAAMYAaABqADwAH0AQwBFACZAFUAVgAsABcADEAGYAN4AcwA9ACGAERAJYAmABNACjAFKALEAW4AwwBkADKAGiANQAbIA3wB3gD2gH2AfoBGACVAFBAKeAWKAtAC0gFzALyAX4AxQBuADiQHTAdQA9ACGwEOgIiAReAkEBIgCbAE7AKHAU0AqwBYsC2ALZAXAAuQBdoC7wGEgMNAYeAxIBjADHgGSAMnAZUAywBlwDOQGfANEgaQBpIDSwGnANYAbGPABAIqAb-QgZgALAAoABkAEQALgAYgBDACYAFUALgAYgAzABvAD0AI4AWIAygBqADfAHfAPsA_ACMAFBAKGAU-AtAC0gF-AMUAdQA9ACQQEiAJsAU0AsUBaMC2ALaAXAAuQBdoDDwGJAMiAZOAzkBngDPgGiANJAaWA4AlAyAAQAAsACgAGQAOAAigBgAGIAPAAiABMACqAFwAMQAZgA2gCGgEQARIAowBSgC3AGEAMoAaoA2QB3gD8AIwAU-AtAC0gGKANwAcQA6gCHQEXgJEATYAsUBbAC7QGHgMiAZOAywBnIDPAGfANIAawA4AmACARUA38pBBAAXABQAFQAMgAcABAACKAGAAYwA0ADUAHkAQwBFACYAFIAKoAWAAuABiADMAHMAQwAiABRgClAFiALcAZQA0QBqgDZAHfAPsA_ACMAFBAKGAVsAuYBeQDaAG4APQAh0BF4CRAE2AJ2AUOApoBWwCxQFsALgAXIAu0BhoDDwGMAMiAZIAycBlwDOQGeAM-gaQBpMDWANZAbGVABAA-Ab-A.YAAAAAAAAAAA", - allowPI: true, - allowGeo: false, - allowID: true, + allowBid: true, + passGeo: false, + passID: true, }, } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) - assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) - assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) - assert.EqualValuesf(t, td.allowID, allowID, "AllowGeo failure on %s", td.description) + allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description) + assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description) + assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) } } -func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, +func TestAllowActivitiesWhitelist(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), } + // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array perms.cfg.NonStandardPublisherMap = map[string]struct{}{"appNexusAppID": {}} - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed") - assert.EqualValuesf(t, true, allowPI, "AllowPI failure") - assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure") - assert.EqualValuesf(t, true, allowID, "AllowID failure") + _, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed") + assert.EqualValuesf(t, true, passGeo, "PassGeo failure") + assert.EqualValuesf(t, true, passID, "PassID failure") } -func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 32, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 15: parseVendorListDataV2(t, vendorListData), - }), - }, +func TestAllowActivitiesPubRestrict(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 32, + openrtb_ext.BidderRubicon: 8, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 15: parseVendorListDataV2(t, vendorListData), + }), } // COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA - vendors 1-10 legit interest only, // Pub restriction on purpose 7, consent only ... no allowPI will pass, no Special purpose 1 consent - testDefs := []tcf2TestDef{ + testDefs := []testDef{ { description: "Appnexus vendor test, insufficient purposes claimed", bidder: openrtb_ext.BidderAppnexus, consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", - allowPI: false, - allowGeo: false, - allowID: false, + passGeo: false, + passID: false, }, { description: "Pubmatic vendor test, flex purposes claimed", bidder: openrtb_ext.BidderPubmatic, consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", - allowPI: false, - allowGeo: false, - allowID: false, + passGeo: false, + passID: false, }, { description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", bidder: openrtb_ext.BidderRubicon, consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", - allowPI: false, - allowGeo: false, - allowID: true, - }, - } - - for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) - assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) - assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) - assert.EqualValuesf(t, td.allowID, allowID, "AllowPI failure on %s", td.description) - } -} - -func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 10, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } - perms.cfg.TCF2.PurposeOneTreatment.Enabled = true - perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = true - - // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set - testDefs := []tcf2TestDef{ - { - description: "Appnexus vendor test, insufficient purposes claimed", - bidder: openrtb_ext.BidderAppnexus, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowPI: false, - allowGeo: false, - allowID: false, - }, - { - description: "Pubmatic vendor test, flex purposes claimed", - bidder: openrtb_ext.BidderPubmatic, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowPI: true, - allowGeo: true, - allowID: true, - }, - { - description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", - bidder: openrtb_ext.BidderRubicon, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowPI: true, - allowGeo: false, - allowID: true, + passGeo: false, + passID: true, }, } for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) - assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) - assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) - assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description) + _, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description) + assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) } } -func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 10, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } - perms.cfg.TCF2.PurposeOneTreatment.Enabled = true - perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = false - - // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set - // Purpose one treatment will fail PI, but allow passing the IDs. - testDefs := []tcf2TestDef{ - { - description: "Appnexus vendor test, insufficient purposes claimed", - bidder: openrtb_ext.BidderAppnexus, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowPI: false, - allowGeo: false, - allowID: false, - }, - { - description: "Pubmatic vendor test, flex purposes claimed", - bidder: openrtb_ext.BidderPubmatic, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowPI: false, - allowGeo: true, - allowID: true, - }, - { - description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", - bidder: openrtb_ext.BidderRubicon, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowPI: false, - allowGeo: false, - allowID: true, - }, - } +func TestAllowSync(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) - for _, td := range testDefs { - allowPI, allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) - assert.EqualValuesf(t, td.allowPI, allowPI, "AllowPI failure on %s", td.description) - assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) - assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description) + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, } -} - -func TestAllowSyncTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consensts to purposes and vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, true, allowSync, "HostCookiesAllowed failure") @@ -650,27 +595,25 @@ func TestAllowSyncTCF2(t *testing.T) { assert.EqualValuesf(t, true, allowSync, "BidderSyncAllowed failure") } -func TestProhibitedPurposeSyncTCF2(t *testing.T) { - tcf2VendorList34 := buildTCF2VendorList34() - tcf2VendorList34.Vendors["8"].Purposes = []int{7} - vendorListData := tcf2MarshalVendorList(tcf2VendorList34) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } +func TestProhibitedPurposeSync(t *testing.T) { + vendorList34 := buildVendorList34() + vendorList34.Vendors["8"].Purposes = []int{7} + vendorListData := MarshalVendorList(vendorList34) + + perms := allPurposesEnabledPermissions() perms.cfg.HostVendorID = 8 + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") @@ -680,26 +623,24 @@ func TestProhibitedPurposeSyncTCF2(t *testing.T) { assert.EqualValuesf(t, false, allowSync, "BidderSyncAllowed failure") } -func TestProhibitedVendorSyncTCF2(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 6, - openrtb_ext.BidderRubicon: 8, - openrtb_ext.BidderOpenx: 10, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } +func TestProhibitedVendorSync(t *testing.T) { + vendorListData := MarshalVendorList(buildVendorList34()) + + perms := allPurposesEnabledPermissions() perms.cfg.HostVendorID = 10 + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + openrtb_ext.BidderOpenx: 10, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + } - // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes for vendors 2, 6, 8 + // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : full consents to purposes for vendors 2, 6, 8 allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA") assert.NoErrorf(t, err, "Error processing HostCookiesAllowed") assert.EqualValuesf(t, false, allowSync, "HostCookiesAllowed failure") @@ -776,58 +717,350 @@ func assertStringsEqual(t *testing.T, expected string, actual string) { func TestNormalizeGDPR(t *testing.T) { tests := []struct { - description string - userSyncIfAmbiguous bool - giveSignal Signal - wantSignal Signal + description string + gdprDefaultValue string + giveSignal Signal + wantSignal Signal }{ { - description: "Don't normalize - Signal No and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalNo, - wantSignal: SignalNo, + description: "Don't normalize - Signal No and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalNo, + wantSignal: SignalNo, }, { - description: "Don't normalize - Signal No and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalNo, - wantSignal: SignalNo, + description: "Don't normalize - Signal No and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalNo, + wantSignal: SignalNo, }, { - description: "Don't normalize - Signal Yes and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalYes, - wantSignal: SignalYes, + description: "Don't normalize - Signal Yes and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalYes, + wantSignal: SignalYes, }, { - description: "Don't normalize - Signal Yes and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalYes, - wantSignal: SignalYes, + description: "Don't normalize - Signal Yes and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalYes, + wantSignal: SignalYes, }, { - description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous false", - userSyncIfAmbiguous: false, - giveSignal: SignalAmbiguous, - wantSignal: SignalYes, + description: "Normalize - Signal Ambiguous and gdprDefaultValue 1", + gdprDefaultValue: "1", + giveSignal: SignalAmbiguous, + wantSignal: SignalYes, }, { - description: "Normalize - Signal Ambiguous and userSyncIfAmbiguous true", - userSyncIfAmbiguous: true, - giveSignal: SignalAmbiguous, - wantSignal: SignalNo, + description: "Normalize - Signal Ambiguous and gdprDefaultValue 0", + gdprDefaultValue: "0", + giveSignal: SignalAmbiguous, + wantSignal: SignalNo, }, } for _, tt := range tests { perms := permissionsImpl{ cfg: config.GDPR{ - UsersyncIfAmbiguous: tt.userSyncIfAmbiguous, + DefaultValue: tt.gdprDefaultValue, }, } + if tt.gdprDefaultValue == "0" { + perms.gdprDefaultValue = SignalNo + } else { + perms.gdprDefaultValue = SignalYes + } + normalizedSignal := perms.normalizeGDPR(tt.giveSignal) assert.Equal(t, tt.wantSignal, normalizedSignal, tt.description) } } + +func TestAllowActivitiesBidRequests(t *testing.T) { + purpose2AndVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAADAQAAAAAA" + purpose2ConsentWithoutVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAABIAAAAA" + + purpose2AndVendorLI := "CPF_61ePF_61eFxAAAENAiCAAAAAAEAAAAAAAAAAIAIAAA" + purpose2LIWithoutVendorLI := "CPF_61ePF_61eFxAAAENAiCAAAAAAEAAAAAAABIAAAAA" + + testDefs := []struct { + description string + purpose2Enabled bool + purpose2EnforceVendors bool + bidder openrtb_ext.BidderName + consent string + allowBid bool + passGeo bool + passID bool + weakVendorEnforcement bool + }{ + { + description: "Bid blocked - p2 enabled, user consents to p2 but not vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, + allowBid: false, + passGeo: false, + passID: false, + }, + { + description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 but not vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: false, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, + allowBid: true, + passGeo: false, + passID: true, + }, + { + description: "Bid allowed - p2 disabled, user consents to p2 but not vendor, vendor consents to p2", + purpose2Enabled: false, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, + allowBid: true, + passGeo: false, + passID: false, + }, + { + description: "Bid allowed - p2 enabled, user consents to p2 and vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2AndVendorConsent, + allowBid: true, + passGeo: false, + passID: true, + }, + { + description: "Bid blocked - p2 enabled, user consents to p2 LI but not vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderRubicon, + consent: purpose2LIWithoutVendorLI, + allowBid: false, + passGeo: false, + passID: false, + }, + { + description: "Bid allowed - p2 enabled, user consents to p2 LI and vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: true, + bidder: openrtb_ext.BidderRubicon, + consent: purpose2AndVendorLI, + allowBid: true, + passGeo: false, + passID: true, + }, + { + description: "Bid allowed - p2 enabled not enforcing vendors, user consents to p2 LI but not vendor, vendor consents to p2", + purpose2Enabled: true, + purpose2EnforceVendors: false, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2AndVendorLI, + allowBid: true, + passGeo: false, + passID: true, + }, + } + + for _, td := range testDefs { + vendorListData := MarshalVendorList(buildVendorList34()) + + perms := allPurposesEnabledPermissions() + perms.vendorIDs = map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderPubmatic: 6, + openrtb_ext.BidderRubicon: 8, + } + perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + } + perms.cfg.TCF2.Purpose2.Enabled = td.purpose2Enabled + p2Config := perms.purposeConfigs[consentconstants.Purpose(2)] + p2Config.Enabled = td.purpose2Enabled + p2Config.EnforceVendors = td.purpose2EnforceVendors + perms.purposeConfigs[consentconstants.Purpose(2)] = p2Config + + allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description) + assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description) + assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) + } +} + +func TestTCF1Consent(t *testing.T) { + bidderAllowedByConsent := openrtb_ext.BidderAppnexus + tcf1Consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" + + perms := permissionsImpl{ + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 2, + }, + } + + bidReq, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, "", SignalYes, tcf1Consent, false) + + assert.Nil(t, err, "TCF1 consent - no error returned") + assert.Equal(t, false, bidReq, "TCF1 consent - bid request not allowed") + assert.Equal(t, false, passGeo, "TCF1 consent - passing geo not allowed") + assert.Equal(t, false, passID, "TCF1 consent - passing id not allowed") +} + +func TestAllowActivitiesVendorException(t *testing.T) { + noPurposeOrVendorConsentAndPubRestrictsP2 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAACEAAgAgAA" + noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA" + + testDefs := []struct { + description string + p2VendorExceptionMap map[openrtb_ext.BidderName]struct{} + sp1VendorExceptionMap map[openrtb_ext.BidderName]struct{} + bidder openrtb_ext.BidderName + consent string + allowBid bool + passGeo bool + passID bool + }{ + { + description: "Bid/ID blocked by publisher - p2 enabled with p2 vendor exception, pub restricts p2 for vendor", + p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsP2, + allowBid: false, + passGeo: false, + passID: false, + }, + { + description: "Bid/ID allowed by vendor exception - p2 enabled with p2 vendor exception, pub restricts none", + p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowBid: true, + passGeo: false, + passID: true, + }, + { + description: "Geo blocked - sp1 enabled but no consent", + p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowBid: false, + passGeo: false, + passID: false, + }, + { + description: "Geo allowed by vendor exception - sp1 enabled with sp1 vendor exception", + p2VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + sp1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowBid: false, + passGeo: true, + passID: false, + }, + } + + for _, td := range testDefs { + vendorListData := MarshalVendorList(buildVendorList34()) + perms := permissionsImpl{ + cfg: config.GDPR{ + HostVendorID: 2, + TCF2: config.TCF2{ + Enabled: true, + Purpose2: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p2VendorExceptionMap}, + SpecialPurpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.sp1VendorExceptionMap}, + }, + }, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 32, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2, + consentconstants.Purpose(3): perms.cfg.TCF2.Purpose3, + } + + allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, false) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description) + assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description) + assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) + } +} + +func TestBidderSyncAllowedVendorException(t *testing.T) { + noPurposeOrVendorConsentAndPubRestrictsP1 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAAQAAAAAAAAAAIIACACA" + noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA" + + testDefs := []struct { + description string + p1VendorExceptionMap map[openrtb_ext.BidderName]struct{} + bidder openrtb_ext.BidderName + consent string + allowSync bool + }{ + { + description: "Sync blocked by no consent - p1 enabled, no p1 vendor exception, pub restricts none", + p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowSync: false, + }, + { + description: "Sync blocked by publisher - p1 enabled with p1 vendor exception, pub restricts p1 for vendor", + p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsP1, + allowSync: false, + }, + { + description: "Sync allowed by vendor exception - p1 enabled with p1 vendor exception, pub restricts none", + p1VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}}, + bidder: openrtb_ext.BidderAppnexus, + consent: noPurposeOrVendorConsentAndPubRestrictsNone, + allowSync: true, + }, + } + + for _, td := range testDefs { + vendorListData := MarshalVendorList(buildVendorList34()) + perms := permissionsImpl{ + cfg: config.GDPR{ + HostVendorID: 2, + TCF2: config.TCF2{ + Enabled: true, + Purpose1: config.TCF2Purpose{Enabled: true, VendorExceptionMap: td.p1VendorExceptionMap}, + }, + }, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderAppnexus: 32, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{ + consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1, + } + + allowSync, err := perms.BidderSyncAllowed(context.Background(), td.bidder, SignalYes, td.consent) + assert.NoErrorf(t, err, "Error processing BidderSyncAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowSync, allowSync, "AllowSync failure on %s", td.description) + } +} diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index bc7eab40647..24489e73265 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -26,33 +26,7 @@ type saveVendors func(uint16, api.VendorList) // // Nothing in this file is exported. Public APIs can be found in gdpr.go -func newVendorListFetcherTCF1(cfg config.GDPR) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { - if len(cfg.TCF1.FallbackGVLPath) == 0 { - return func(_ context.Context, vendorListVersion uint16) (vendorlist.VendorList, error) { - return nil, makeVendorListNotFoundError(vendorListVersion) - } - } - - fallback := loadFallbackGVLForTCF1(cfg.TCF1.FallbackGVLPath) - return func(_ context.Context, _ uint16) (vendorlist.VendorList, error) { - return fallback, nil - } -} - -func loadFallbackGVLForTCF1(fallbackGVLPath string) vendorlist.VendorList { - fallbackContents, err := ioutil.ReadFile(fallbackGVLPath) - if err != nil { - glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err) - } - - fallback, err := vendorlist.ParseEagerly(fallbackContents) - if err != nil { - glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err) - } - return fallback -} - -func newVendorListFetcherTCF2(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { +func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { cacheSave, cacheLoad := newVendorListCache() preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go index 27f1bc3b996..95529e4e334 100644 --- a/gdpr/vendorlist-fetching_test.go +++ b/gdpr/vendorlist-fetching_test.go @@ -14,71 +14,15 @@ import ( "github.com/prebid/prebid-server/config" ) -func TestTCF1FetcherInitialLoad(t *testing.T) { - // Loads two vendor lists during initialization by setting the latest vendor list version to 2. - - server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ - vendorListLatestVersion: 2, - vendorLists: map[int]string{ - 1: tcf1VendorList1, - 2: tcf1VendorList2, - }, - }))) - defer server.Close() - - testCases := []test{ - { - description: "Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 1, - }, - expected: vendorListFallbackExpected, - }, - { - description: "Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: true, - vendorListVersion: 2, - }, - expected: vendorListFallbackExpected, - }, - { - description: "No Fallback - Vendor List 1", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 1, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - { - description: "No Fallback - Vendor List 2", - setup: testSetup{ - enableTCF1Fallback: false, - vendorListVersion: 2, - }, - expected: testExpected{ - errorMessage: "gdpr vendor list version 2 does not exist, or has not been loaded yet. Try again in a few minutes", - }, - }, - } - - for _, test := range testCases { - runTestTCF1(t, test, server) - } -} - -func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { +func TestFetcherDynamicLoadListExists(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor lists will be dynamically loaded. server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2VendorList1, - 2: tcf2VendorList2, + 1: vendorList1, + 2: vendorList2, }, }))) defer server.Close() @@ -91,17 +35,17 @@ func TestTCF2FetcherDynamicLoadListExists(t *testing.T) { expected: vendorList2Expected, } - runTestTCF2(t, test, server) + runTest(t, test, server) } -func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { +func TestFetcherDynamicLoadListDoesntExist(t *testing.T) { // Loads the first vendor list during initialization by setting the latest vendor list version to 1. // All other vendor list load attempts will be done dynamically. server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2VendorList1, + 1: vendorList1, }, }))) defer server.Close() @@ -116,30 +60,30 @@ func TestTCF2FetcherDynamicLoadListDoesntExist(t *testing.T) { }, } - runTestTCF2(t, test, server) + runTest(t, test, server) } -func TestTCF2FetcherThrottling(t *testing.T) { +func TestFetcherThrottling(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ - 1: tcf2MarshalVendorList(tcf2VendorList{ + 1: MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1}}}, }), - 2: tcf2MarshalVendorList(tcf2VendorList{ + 2: MarshalVendorList(vendorList{ VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2}}}, }), - 3: tcf2MarshalVendorList(tcf2VendorList{ + 3: MarshalVendorList(vendorList{ VendorListVersion: 3, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{1, 2, 3}}}, }), }, }))) defer server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) // Dynamically Load List 2 Successfully _, errList1 := fetcher(context.Background(), 2) @@ -151,7 +95,7 @@ func TestTCF2FetcherThrottling(t *testing.T) { assert.EqualError(t, errList2, "gdpr vendor list version 3 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestTCF2MalformedVendorlist(t *testing.T) { +func TestMalformedVendorlist(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(mockServer(serverSettings{ vendorListLatestVersion: 1, vendorLists: map[int]string{ @@ -160,30 +104,30 @@ func TestTCF2MalformedVendorlist(t *testing.T) { }))) defer server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) _, err := fetcher(context.Background(), 1) // Fetching should fail since vendor list could not be unmarshalled. assert.Error(t, err) } -func TestTCF2ServerUrlInvalid(t *testing.T) { +func TestServerUrlInvalid(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() invalidURLGenerator := func(uint16) string { return " http://invalid-url-has-leading-whitespace" } - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), invalidURLGenerator) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator) _, err := fetcher(context.Background(), 1) assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") } -func TestTCF2ServerUnavailable(t *testing.T) { +func TestServerUnavailable(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) server.Close() - fetcher := newVendorListFetcherTCF2(context.Background(), testConfig(), server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server)) _, err := fetcher(context.Background(), 1) assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes") @@ -213,24 +157,14 @@ func TestVendorListURLMaker(t *testing.T) { } } -var tcf1VendorList1 = tcf1MarshalVendorList(tcf1VendorList{ +var vendorList1 = MarshalVendorList(vendorList{ VendorListVersion: 1, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2}}}, }) -var tcf2VendorList1 = tcf2MarshalVendorList(tcf2VendorList{ - VendorListVersion: 1, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2}}}, -}) - -var tcf1VendorList2 = tcf1MarshalVendorList(tcf1VendorList{ +var vendorList2 = MarshalVendorList(vendorList{ VendorListVersion: 2, - Vendors: []tcf1Vendor{{ID: 12, Purposes: []int{2, 3}}}, -}) - -var tcf2VendorList2 = tcf2MarshalVendorList(tcf2VendorList{ - VendorListVersion: 2, - Vendors: map[string]*tcf2Vendor{"12": {ID: 12, Purposes: []int{2, 3}}}, + Vendors: map[string]*vendor{"12": {ID: 12, Purposes: []int{2, 3}}}, }) var vendorList2Expected = testExpected{ @@ -245,27 +179,12 @@ var vendorListFallbackExpected = testExpected{ vendorPurposes: map[int]bool{1: true, 2: false, 3: true}, } -type tcf1VendorList struct { - VendorListVersion uint16 `json:"vendorListVersion"` - Vendors []tcf1Vendor `json:"vendors"` +type vendorList struct { + VendorListVersion uint16 `json:"vendorListVersion"` + Vendors map[string]*vendor `json:"vendors"` } -type tcf1Vendor struct { - ID uint16 `json:"id"` - Purposes []int `json:"purposeIds"` -} - -func tcf1MarshalVendorList(vendorList tcf1VendorList) string { - json, _ := json.Marshal(vendorList) - return string(json) -} - -type tcf2VendorList struct { - VendorListVersion uint16 `json:"vendorListVersion"` - Vendors map[string]*tcf2Vendor `json:"vendors"` -} - -type tcf2Vendor struct { +type vendor struct { ID uint16 `json:"id"` Purposes []int `json:"purposes"` LegIntPurposes []int `json:"legIntPurposes"` @@ -273,7 +192,7 @@ type tcf2Vendor struct { SpecialPurposes []int `json:"specialPurposes"` } -func tcf2MarshalVendorList(vendorList tcf2VendorList) string { +func MarshalVendorList(vendorList vendorList) string { json, _ := json.Marshal(vendorList) return string(json) } @@ -323,8 +242,7 @@ type test struct { } type testSetup struct { - enableTCF1Fallback bool - vendorListVersion uint16 + vendorListVersion uint16 } type testExpected struct { @@ -334,31 +252,9 @@ type testExpected struct { vendorPurposes map[int]bool } -func runTestTCF1(t *testing.T, test test, server *httptest.Server) { +func runTest(t *testing.T, test test, server *httptest.Server) { config := testConfig() - if test.setup.enableTCF1Fallback { - config.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json" - } - - fetcher := newVendorListFetcherTCF1(config) - vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) - - if test.expected.errorMessage != "" { - assert.EqualError(t, err, test.expected.errorMessage, test.description+":error") - } else { - assert.NoError(t, err, test.description+":vendorlist") - assert.Equal(t, test.expected.vendorListVersion, vendorList.Version(), test.description+":vendorlistid") - vendor := vendorList.Vendor(test.expected.vendorID) - for id, expected := range test.expected.vendorPurposes { - result := vendor.Purpose(consentconstants.Purpose(id)) - assert.Equalf(t, expected, result, "%s:vendor-%d:purpose-%d", test.description, vendorList.Version(), id) - } - } -} - -func runTestTCF2(t *testing.T, test test, server *httptest.Server) { - config := testConfig() - fetcher := newVendorListFetcherTCF2(context.Background(), config, server.Client(), testURLMaker(server)) + fetcher := newVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server)) vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion) if test.expected.errorMessage != "" { @@ -387,8 +283,5 @@ func testConfig() config.GDPR { InitVendorlistFetch: 60 * 1000, ActiveVendorlistFetch: 1000 * 5, }, - TCF1: config.TCF1{ - FetchGVL: true, - }, } } diff --git a/go.mod b/go.mod index dee3615b79b..6228581eaae 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/prebid/prebid-server -go 1.14 +go 1.16 require ( github.com/BurntSushi/toml v0.3.1 // indirect @@ -11,7 +11,7 @@ require ( github.com/beevik/etree v1.0.2 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect github.com/blang/semver v3.5.1+incompatible - github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 + github.com/buger/jsonparser v1.1.1 github.com/cespare/xxhash v1.0.0 // indirect github.com/chasex/glog v0.0.0-20160217080310-c62392af379c github.com/coocood/freecache v1.0.1 @@ -23,15 +23,15 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/influxdata/influxdb v1.6.1 github.com/julienschmidt/httprouter v1.1.0 - github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/lib/pq v1.0.0 github.com/magiconair/properties v1.8.0 github.com/mattn/go-colorable v0.1.2 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/copystructure v1.1.2 github.com/mitchellh/mapstructure v1.0.0 // indirect github.com/mxmCherry/openrtb/v15 v15.0.0 github.com/pelletier/go-toml v1.2.0 // indirect - github.com/prebid/go-gdpr v0.8.3 + github.com/prebid/go-gdpr v0.9.0 github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect @@ -46,7 +46,7 @@ require ( github.com/spf13/pflag v1.0.2 // indirect github.com/spf13/viper v1.1.0 github.com/stretchr/objx v0.1.1 // indirect - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.7.0 github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect @@ -55,7 +55,7 @@ require ( github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb - golang.org/x/text v0.3.3 + golang.org/x/text v0.3.6 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 0ccf122d248..d8cbf58754a 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/buger/jsonparser v0.0.0-20180318095312-2cac668e8456/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cespare/xxhash v1.0.0 h1:naDmySfoNg0nKS62/ujM6e71ZgM2AoVdaqGwMG0w18A= github.com/cespare/xxhash v1.0.0/go.mod h1:fX/lfQBkSCDXZSUgv6jVIu/EVA3/JNseAX5asI4c4T4= github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s= @@ -70,6 +72,11 @@ github.com/julienschmidt/httprouter v1.1.0 h1:7wLdtIiIpzOkC9u6sXOozpBauPdskj3ru4 github.com/julienschmidt/httprouter v1.1.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= @@ -80,15 +87,25 @@ github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0= +github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I= github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mxmCherry/openrtb/v15 v15.0.0 h1:inLuQ3Bsima9HLB2v6WjbtEFF69SWOT5Dux4QZtYdrw= +github.com/mxmCherry/openrtb/v15 v15.0.0/go.mod h1:TVgncsz6MOzbL7lhun1lNuUBzVBlVDbxf9Fyy1TyhZA= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= @@ -97,8 +114,8 @@ github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prebid/go-gdpr v0.8.3 h1:rjCZNV0AdKygiGHpVhNB42usjEpTN3qidXUPB1yarb0= -github.com/prebid/go-gdpr v0.8.3/go.mod h1:TGzgqQDGKOVUkbqmY25K4uvcwMywSddXEaY4zUFiVBQ= +github.com/prebid/go-gdpr v0.9.0 h1:FL1ZXuccMYOPIt69mIHF2AyRhv8ezvtjnUoAE3Ph8O0= +github.com/prebid/go-gdpr v0.9.0/go.mod h1:OfBxLfd+JfP3OAJ1MhI4JYAV3dSMQYT1QAb80DHpZFo= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed h1:0dloFFFNNDG7c+8qtkYw2FdADrWy9s5cI8wHp6tK3Mg= github.com/prometheus/client_golang v0.0.0-20180623155954-77e8f2ddcfed/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= @@ -129,8 +146,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303 h1:Va10CytCCYRm4xBTses5ZDeDjeIQjhaiC9nRCe/yflI= github.com/vrischmann/go-metrics-influxdb v0.0.0-20160917065939-43af8332c303/go.mod h1:Xdcad1nGVhQfhoV0go+/4WaI/RZkWlvfjkVCdpMTxPY= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -139,6 +157,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY= github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xorcare/pointer v1.1.0 h1:sFwXOhRF8QZ0tyVZrtxWGIoVZNEmRzBCaFWdONPQIUM= +github.com/xorcare/pointer v1.1.0/go.mod h1:6KLhkOh6YbuvZkT4YbxIbR/wzLBjyMxOiNzZhJTor2Y= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d h1:yJIizrfO599ot2kQ6Af1enICnwBD3XoxgX3MrMwot2M= github.com/yudai/gojsondiff v0.0.0-20170107030110-7b1b7adf999d/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= @@ -172,8 +192,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -199,3 +220,5 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 294cc141169..e73df6272c6 100644 --- a/main.go +++ b/main.go @@ -37,12 +37,12 @@ func main() { cfg, err := loadConfig() if err != nil { - glog.Fatalf("Configuration could not be loaded or did not pass validation: %v", err) + glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err) } err = serve(Rev, cfg) if err != nil { - glog.Errorf("prebid-server failed: %v", err) + glog.Exitf("prebid-server failed: %v", err) } } */ @@ -59,7 +59,7 @@ func InitPrebidServer(configFile string) { err = serve(Rev, cfg) if err != nil { - glog.Errorf("prebid-server failed: %v", err) + glog.Exitf("prebid-server failed: %v", err) } } diff --git a/metrics/config/metrics.go b/metrics/config/metrics.go index 6c9344b325b..e9a436eed49 100644 --- a/metrics/config/metrics.go +++ b/metrics/config/metrics.go @@ -147,9 +147,9 @@ func (me *MultiMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { } } -func (me *MultiMetricsEngine) RecordTLSHandshakeTime(bidderName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { +func (me *MultiMetricsEngine) RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { for _, thisME := range *me { - thisME.RecordTLSHandshakeTime(bidderName, tlsHandshakeTime) + thisME.RecordTLSHandshakeTime(adapterName, tlsHandshakeTime) } } @@ -286,9 +286,34 @@ func (me *MultiMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.Adapt } } +// RecordAdapterGDPRRequestBlocked across all engines +func (me *MultiMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { + for _, thisME := range *me { + thisME.RecordAdapterGDPRRequestBlocked(adapter) + } +} + // DummyMetricsEngine is a Noop metrics engine in case no metrics are configured. (may also be useful for tests) type DummyMetricsEngine struct{} +func (me *DummyMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) { +} + +func (me *DummyMetricsEngine) RecordRequestHavingDuplicateBidID() { +} + +func (me *DummyMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, startTime time.Time) { +} + +func (me *DummyMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) { +} + +func (me *DummyMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) { +} + +func (me *DummyMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) { +} + // RecordRequest as a noop func (me *DummyMetricsEngine) RecordRequest(labels metrics.Labels) { } @@ -338,7 +363,7 @@ func (me *DummyMetricsEngine) RecordDNSTime(dnsLookupTime time.Duration) { } // RecordTLSHandshakeTime as a noop -func (me *DummyMetricsEngine) RecordTLSHandshakeTime(bidderName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { +func (me *DummyMetricsEngine) RecordTLSHandshakeTime(adapterName openrtb_ext.BidderName, tlsHandshakeTime time.Duration) { } // RecordAdapterBidReceived as a noop @@ -393,26 +418,6 @@ func (me *DummyMetricsEngine) RecordTimeoutNotice(success bool) { func (me *DummyMetricsEngine) RecordRequestPrivacy(privacy metrics.PrivacyLabels) { } -// RecordAdapterDuplicateBidID as a noop -func (me *DummyMetricsEngine) RecordAdapterDuplicateBidID(adaptor string, collisions int) { -} - -// RecordRequestHavingDuplicateBidID as a noop -func (me *DummyMetricsEngine) RecordRequestHavingDuplicateBidID() { -} - -// RecordPodImpGenTime as a noop -func (me *DummyMetricsEngine) RecordPodImpGenTime(labels metrics.PodLabels, start time.Time) { -} - -// RecordPodCombGenTime as a noop -func (me *DummyMetricsEngine) RecordPodCombGenTime(labels metrics.PodLabels, elapsedTime time.Duration) { -} - -// RecordPodCompititveExclusionTime as a noop -func (me *DummyMetricsEngine) RecordPodCompititveExclusionTime(labels metrics.PodLabels, elapsedTime time.Duration) { -} - -// RecordAdapterVideoBidDuration as a noop -func (me *DummyMetricsEngine) RecordAdapterVideoBidDuration(labels metrics.AdapterLabels, videoBidDuration int) { +// RecordAdapterGDPRRequestBlocked as a noop +func (me *DummyMetricsEngine) RecordAdapterGDPRRequestBlocked(adapter openrtb_ext.BidderName) { } diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 5b70b53bb1a..7f9643330e3 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -118,6 +118,8 @@ func TestMultiMetricsEngine(t *testing.T) { metricsEngine.RecordStoredImpCacheResult(metrics.CacheHit, 5) metricsEngine.RecordAccountCacheResult(metrics.CacheHit, 6) + metricsEngine.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus) + metricsEngine.RecordRequestQueueTime(false, metrics.ReqTypeVideo, time.Duration(1)) //Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][metrics.RequestStatusXX] with the new boolean values added to metrics.Labels @@ -161,6 +163,8 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "StoredReqCache.Hit", goEngine.StoredReqCacheMeter[metrics.CacheHit].Count(), 4) 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) } func VerifyMetrics(t *testing.T, name string, actual int64, expected int64) { diff --git a/metrics/go_metrics.go b/metrics/go_metrics.go index 2529eaf4765..45dece19f7d 100644 --- a/metrics/go_metrics.go +++ b/metrics/go_metrics.go @@ -88,19 +88,20 @@ type Metrics struct { // AdapterMetrics houses the metrics for a particular adapter type AdapterMetrics struct { - NoCookieMeter metrics.Meter - ErrorMeters map[AdapterError]metrics.Meter - NoBidMeter metrics.Meter - GotBidsMeter metrics.Meter - RequestTimer metrics.Timer - PriceHistogram metrics.Histogram - BidsReceivedMeter metrics.Meter - PanicMeter metrics.Meter - MarkupMetrics map[openrtb_ext.BidType]*MarkupDeliveryMetrics - ConnCreated metrics.Counter - ConnReused metrics.Counter - ConnWaitTime metrics.Timer - TLSHandshakeTimer metrics.Timer + NoCookieMeter metrics.Meter + ErrorMeters map[AdapterError]metrics.Meter + NoBidMeter metrics.Meter + GotBidsMeter metrics.Meter + RequestTimer metrics.Timer + PriceHistogram metrics.Histogram + BidsReceivedMeter metrics.Meter + PanicMeter metrics.Meter + MarkupMetrics map[openrtb_ext.BidType]*MarkupDeliveryMetrics + ConnCreated metrics.Counter + ConnReused metrics.Counter + ConnWaitTime metrics.Timer + GDPRRequestBlocked metrics.Meter + TLSHandshakeTimer metrics.Timer } type MarkupDeliveryMetrics struct { @@ -321,6 +322,9 @@ func makeBlankAdapterMetrics(disabledMetrics config.DisabledMetrics) *AdapterMet newAdapter.ConnWaitTime = &metrics.NilTimer{} newAdapter.TLSHandshakeTimer = &metrics.NilTimer{} } + if !disabledMetrics.AdapterGDPRRequestBlocked { + newAdapter.GDPRRequestBlocked = blankMeter + } for _, err := range AdapterErrors() { newAdapter.ErrorMeters[err] = blankMeter } @@ -366,6 +370,7 @@ func registerAdapterMetrics(registry metrics.Registry, adapterOrAccount string, am.BidsReceivedMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.bids_received", adapterOrAccount, exchange), registry) } am.PanicMeter = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.requests.panic", adapterOrAccount, exchange), registry) + am.GDPRRequestBlocked = metrics.GetOrRegisterMeter(fmt.Sprintf("%[1]s.%[2]s.gdpr_request_blocked", adapterOrAccount, exchange), registry) } func makeDeliveryMetrics(registry metrics.Registry, prefix string, bidType openrtb_ext.BidType) *MarkupDeliveryMetrics { @@ -730,6 +735,20 @@ func (me *Metrics) RecordRequestPrivacy(privacy PrivacyLabels) { return } +func (me *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { + if me.MetricsDisabled.AdapterGDPRRequestBlocked { + return + } + + am, ok := me.AdapterMetrics[adapterName] + if !ok { + glog.Errorf("Trying to log adapter GDPR request blocked metric for %s: adapter not found", string(adapterName)) + return + } + + am.GDPRRequestBlocked.Mark(1) +} + // RecordAdapterDuplicateBidID as noop func (me *Metrics) RecordAdapterDuplicateBidID(adaptor string, collisions int) { } diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 7ebe2f3c2fe..ed9df52a51e 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -60,7 +60,6 @@ func TestNewMetrics(t *testing.T) { ensureContains(t, registry, "privacy.request.ccpa.opt-out", m.PrivacyCCPARequestOptOut) ensureContains(t, registry, "privacy.request.coppa", m.PrivacyCOPPARequest) ensureContains(t, registry, "privacy.request.lmt", m.PrivacyLMTRequest) - ensureContains(t, registry, "privacy.request.tcf.v1", m.PrivacyTCFRequestVersion[TCFVersionV1]) ensureContains(t, registry, "privacy.request.tcf.v2", m.PrivacyTCFRequestVersion[TCFVersionV2]) ensureContains(t, registry, "privacy.request.tcf.err", m.PrivacyTCFRequestVersion[TCFVersionErr]) } @@ -570,28 +569,58 @@ func TestRecordRequestPrivacy(t *testing.T) { GDPREnforced: true, GDPRTCFVersion: TCFVersionErr, }) - m.RecordRequestPrivacy(PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: TCFVersionV1, - }) m.RecordRequestPrivacy(PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: TCFVersionV2, }) - m.RecordRequestPrivacy(PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: TCFVersionV1, - }) assert.Equal(t, m.PrivacyCCPARequest.Count(), int64(2), "CCPA") assert.Equal(t, m.PrivacyCCPARequestOptOut.Count(), int64(1), "CCPA Opt Out") assert.Equal(t, m.PrivacyCOPPARequest.Count(), int64(1), "COPPA") assert.Equal(t, m.PrivacyLMTRequest.Count(), int64(1), "LMT") assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionErr].Count(), int64(1), "TCF Err") - assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV1].Count(), int64(2), "TCF V1") assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2") } +func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { + var fakeBidder openrtb_ext.BidderName = "fooAdvertising" + + tests := []struct { + description string + metricsDisabled bool + adapterName openrtb_ext.BidderName + expectedCount int64 + }{ + { + description: "", + metricsDisabled: false, + adapterName: openrtb_ext.BidderAppnexus, + expectedCount: 1, + }, + { + description: "", + metricsDisabled: false, + adapterName: fakeBidder, + expectedCount: 0, + }, + { + description: "", + metricsDisabled: true, + adapterName: openrtb_ext.BidderAppnexus, + expectedCount: 0, + }, + } + + for _, tt := range tests { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}) + + m.RecordAdapterGDPRRequestBlocked(tt.adapterName) + + assert.Equal(t, tt.expectedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), tt.description) + } +} + func ensureContainsBidTypeMetrics(t *testing.T, registry metrics.Registry, prefix string, mdm map[openrtb_ext.BidType]*MarkupDeliveryMetrics) { ensureContains(t, registry, prefix+".banner.adm_bids_received", mdm[openrtb_ext.BidTypeBanner].AdmMeter) ensureContains(t, registry, prefix+".banner.nurl_bids_received", mdm[openrtb_ext.BidTypeBanner].NurlMeter) diff --git a/metrics/metrics.go b/metrics/metrics.go index 5966b7716f3..7632ab1812d 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -308,7 +308,6 @@ type TCFVersionValue string const ( TCFVersionErr TCFVersionValue = "err" - TCFVersionV1 TCFVersionValue = "v1" TCFVersionV2 TCFVersionValue = "v2" ) @@ -316,7 +315,6 @@ const ( func TCFVersions() []TCFVersionValue { return []TCFVersionValue{ TCFVersionErr, - TCFVersionV1, TCFVersionV2, } } @@ -324,8 +322,6 @@ func TCFVersions() []TCFVersionValue { // TCFVersionToValue takes an integer TCF version and returns the corresponding TCFVersionValue func TCFVersionToValue(version int) TCFVersionValue { switch { - case version == 1: - return TCFVersionV1 case version == 2: return TCFVersionV2 } @@ -367,6 +363,7 @@ type MetricsEngine interface { RecordRequestQueueTime(success bool, requestType RequestType, length time.Duration) RecordTimeoutNotice(sucess bool) RecordRequestPrivacy(privacy PrivacyLabels) + RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) // RecordAdapterDuplicateBidID captures the bid.ID collisions when adaptor // gives the bid response with multiple bids containing same bid.ID diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index b211b2faa22..8cc91b31c8a 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -141,6 +141,11 @@ func (me *MetricsEngineMock) RecordRequestPrivacy(privacy PrivacyLabels) { me.Called(privacy) } +// RecordAdapterGDPRRequestBlocked mock +func (me *MetricsEngineMock) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { + me.Called(adapterName) +} + // RecordAdapterDuplicateBidID mock func (me *MetricsEngineMock) RecordAdapterDuplicateBidID(adaptor string, collisions int) { me.Called(adaptor, collisions) @@ -169,4 +174,4 @@ func (me *MetricsEngineMock) RecordPodCompititveExclusionTime(labels PodLabels, //RecordAdapterVideoBidDuration mock func (me *MetricsEngineMock) RecordAdapterVideoBidDuration(labels AdapterLabels, videoBidDuration int) { me.Called(labels, videoBidDuration) -} +} \ No newline at end of file diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index f4dfe43469d..621ad808619 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -178,6 +178,12 @@ func preloadLabelValues(m *Metrics) { sourceLabel: sourceValues, versionLabel: tcfVersionsAsString(), }) + + if !m.metricsDisabled.AdapterGDPRRequestBlocked { + preloadLabelValuesForCounter(m.adapterGDPRBlockedRequests, map[string][]string{ + adapterLabel: adapterValues, + }) + } } func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) { diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index a0a13455493..1a854621372 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -41,7 +41,7 @@ type Metrics struct { storedVideoErrors *prometheus.CounterVec timeoutNotifications *prometheus.CounterVec dnsLookupTimer prometheus.Histogram - //tlsHandhakeTimer prometheus.Histogram + //tlsHandhakeTimer prometheus.Histogram privacyCCPA *prometheus.CounterVec privacyCOPPA *prometheus.CounterVec privacyLMT *prometheus.CounterVec @@ -63,6 +63,7 @@ type Metrics struct { adapterDuplicateBidIDCounter *prometheus.CounterVec adapterVideoBidDuration *prometheus.HistogramVec tlsHandhakeTimer *prometheus.HistogramVec + adapterGDPRBlockedRequests *prometheus.CounterVec // Account Metrics accountRequests *prometheus.CounterVec @@ -309,6 +310,13 @@ func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMet "Count of total requests to Prebid Server where the LMT flag was set by source", []string{sourceLabel}) + if !metrics.metricsDisabled.AdapterGDPRRequestBlocked { + metrics.adapterGDPRBlockedRequests = newCounter(cfg, metrics.Registry, + "adapter_gdpr_requests_blocked", + "Count of total bidder requests blocked due to unsatisfied GDPR purpose 2 legal basis", + []string{adapterLabel}) + } + metrics.adapterBids = newCounter(cfg, metrics.Registry, "adapter_bids", "Count of bids labeled by adapter and markup delivery type (adm or nurl).", @@ -764,6 +772,16 @@ func (m *Metrics) RecordRequestPrivacy(privacy metrics.PrivacyLabels) { } } +func (m *Metrics) RecordAdapterGDPRRequestBlocked(adapterName openrtb_ext.BidderName) { + if m.metricsDisabled.AdapterGDPRRequestBlocked { + return + } + + m.adapterGDPRBlockedRequests.With(prometheus.Labels{ + adapterLabel: string(adapterName), + }).Inc() +} + // 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 diff --git a/metrics/prometheus/prometheus_test.go b/metrics/prometheus/prometheus_test.go index 2cdf8702364..b2e383140ce 100644 --- a/metrics/prometheus/prometheus_test.go +++ b/metrics/prometheus/prometheus_test.go @@ -1364,18 +1364,22 @@ func TestRecordAdapterConnections(t *testing.T) { } } -func TestDisableAdapterConnections(t *testing.T) { +func TestDisabledMetrics(t *testing.T) { prometheusMetrics := NewMetrics(config.PrometheusMetrics{ Port: 8080, Namespace: "prebid", Subsystem: "server", - }, config.DisabledMetrics{AdapterConnectionMetrics: true}) + }, config.DisabledMetrics{ + AdapterConnectionMetrics: true, + AdapterGDPRRequestBlocked: true, + }) // 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") } func TestRecordRequestPrivacy(t *testing.T) { @@ -1410,18 +1414,10 @@ func TestRecordRequestPrivacy(t *testing.T) { GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionErr, }) - m.RecordRequestPrivacy(metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }) m.RecordRequestPrivacy(metrics.PrivacyLabels{ GDPREnforced: true, GDPRTCFVersion: metrics.TCFVersionV2, }) - m.RecordRequestPrivacy(metrics.PrivacyLabels{ - GDPREnforced: true, - GDPRTCFVersion: metrics.TCFVersionV1, - }) assertCounterVecValue(t, "", "privacy_ccpa", m.privacyCCPA, float64(1), @@ -1456,13 +1452,6 @@ func TestRecordRequestPrivacy(t *testing.T) { versionLabel: "err", }) - assertCounterVecValue(t, "", "privacy_tcf:v1", m.privacyTCF, - float64(2), - prometheus.Labels{ - sourceLabel: sourceRequest, - versionLabel: "v1", - }) - assertCounterVecValue(t, "", "privacy_tcf:v2", m.privacyTCF, float64(1), prometheus.Labels{ @@ -1715,3 +1704,18 @@ func assertHistogram(t *testing.T, name string, histogram dto.Histogram, expecte assert.Equal(t, expectedCount, histogram.GetSampleCount(), name+":count") assert.Equal(t, expectedSum, histogram.GetSampleSum(), name+":sum") } + +func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { + m := createMetricsForTesting() + + m.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus) + + assertCounterVecValue(t, + "Increment adapter GDPR request blocked counter", + "adapter_gdpr_requests_blocked", + m.adapterGDPRBlockedRequests, + 1, + prometheus.Labels{ + adapterLabel: string(openrtb_ext.BidderAppnexus), + }) +} diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go old mode 100755 new mode 100644 index 7d5684600bb..71b2146f4c3 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -73,115 +73,129 @@ func IsBidderNameReserved(name string) bool { // // Please keep this list alphabetized to minimize merge conflicts. const ( - Bidder33Across BidderName = "33across" - BidderAcuityAds BidderName = "acuityads" - BidderAdform BidderName = "adform" - BidderAdgeneration BidderName = "adgeneration" - BidderAdhese BidderName = "adhese" - BidderAdkernel BidderName = "adkernel" - BidderAdkernelAdn BidderName = "adkernelAdn" - BidderAdman BidderName = "adman" - BidderAdmixer BidderName = "admixer" - BidderAdOcean BidderName = "adocean" - BidderAdoppler BidderName = "adoppler" - BidderAdot BidderName = "adot" - BidderAdpone BidderName = "adpone" - BidderAdprime BidderName = "adprime" - BidderAdtarget BidderName = "adtarget" - BidderAdtelligent BidderName = "adtelligent" - BidderAdvangelists BidderName = "advangelists" - BidderAdxcg BidderName = "adxcg" - BidderAdyoulike BidderName = "adyoulike" - BidderAJA BidderName = "aja" - BidderAMX BidderName = "amx" - BidderApplogy BidderName = "applogy" - BidderAppnexus BidderName = "appnexus" - BidderAudienceNetwork BidderName = "audienceNetwork" - BidderAvocet BidderName = "avocet" - BidderBeachfront BidderName = "beachfront" - BidderBeintoo BidderName = "beintoo" - BidderBetween BidderName = "between" - BidderBidmachine BidderName = "bidmachine" - BidderBrightroll BidderName = "brightroll" - BidderColossus BidderName = "colossus" - BidderConnectAd BidderName = "connectad" - BidderConsumable BidderName = "consumable" - BidderConversant BidderName = "conversant" - BidderCpmstar BidderName = "cpmstar" - BidderCriteo BidderName = "criteo" - BidderDatablocks BidderName = "datablocks" - BidderDmx BidderName = "dmx" - BidderDecenterAds BidderName = "decenterads" - BidderDeepintent BidderName = "deepintent" - BidderEmxDigital BidderName = "emx_digital" - BidderEngageBDR BidderName = "engagebdr" - BidderEPlanning BidderName = "eplanning" - BidderEpom BidderName = "epom" - BidderGamma BidderName = "gamma" - BidderGamoshi BidderName = "gamoshi" - BidderGrid BidderName = "grid" - BidderGumGum BidderName = "gumgum" - BidderImprovedigital BidderName = "improvedigital" - BidderInMobi BidderName = "inmobi" - BidderInvibes BidderName = "invibes" - BidderIx BidderName = "ix" - BidderJixie BidderName = "jixie" - BidderKidoz BidderName = "kidoz" - BidderKrushmedia BidderName = "krushmedia" - BidderKubient BidderName = "kubient" - BidderLifestreet BidderName = "lifestreet" - BidderLockerDome BidderName = "lockerdome" - BidderLogicad BidderName = "logicad" - BidderLunaMedia BidderName = "lunamedia" - BidderMarsmedia BidderName = "marsmedia" - BidderMediafuse BidderName = "mediafuse" - BidderMgid BidderName = "mgid" - BidderMobfoxpb BidderName = "mobfoxpb" - BidderMobileFuse BidderName = "mobilefuse" - BidderNanoInteractive BidderName = "nanointeractive" - BidderNinthDecimal BidderName = "ninthdecimal" - BidderNoBid BidderName = "nobid" - BidderOneTag BidderName = "onetag" - BidderOpenx BidderName = "openx" - BidderOrbidder BidderName = "orbidder" - BidderOutbrain BidderName = "outbrain" - BidderPangle BidderName = "pangle" - BidderPubmatic BidderName = "pubmatic" - BidderPubnative BidderName = "pubnative" - BidderPulsepoint BidderName = "pulsepoint" - BidderRevcontent BidderName = "revcontent" - BidderRhythmone BidderName = "rhythmone" - BidderRTBHouse BidderName = "rtbhouse" - BidderRubicon BidderName = "rubicon" - BidderSharethrough BidderName = "sharethrough" - BidderSilverMob BidderName = "silvermob" - BidderSmaato BidderName = "smaato" - BidderSmartAdserver BidderName = "smartadserver" - BidderSmartRTB BidderName = "smartrtb" - BidderSmartyAds BidderName = "smartyads" - BidderSomoaudience BidderName = "somoaudience" - BidderSonobi BidderName = "sonobi" - BidderSovrn BidderName = "sovrn" - BidderSpotX BidderName = "spotx" - BidderSynacormedia BidderName = "synacormedia" - BidderTappx BidderName = "tappx" - BidderTelaria BidderName = "telaria" - BidderTriplelift BidderName = "triplelift" - BidderTripleliftNative BidderName = "triplelift_native" - BidderTrustX BidderName = "trustx" - BidderUcfunnel BidderName = "ucfunnel" - BidderUnicorn BidderName = "unicorn" - BidderUnruly BidderName = "unruly" - BidderValueImpression BidderName = "valueimpression" - BidderVASTBidder BidderName = "vastbidder" - BidderVerizonMedia BidderName = "verizonmedia" - BidderVisx BidderName = "visx" - BidderVrtcal BidderName = "vrtcal" - BidderYeahmobi BidderName = "yeahmobi" - BidderYieldlab BidderName = "yieldlab" - BidderYieldmo BidderName = "yieldmo" - BidderYieldone BidderName = "yieldone" - BidderZeroClickFraud BidderName = "zeroclickfraud" + Bidder33Across BidderName = "33across" + BidderAcuityAds BidderName = "acuityads" + BidderAdagio BidderName = "adagio" + BidderAdf BidderName = "adf" + BidderAdform BidderName = "adform" + BidderAdgeneration BidderName = "adgeneration" + BidderAdhese BidderName = "adhese" + BidderAdkernel BidderName = "adkernel" + BidderAdkernelAdn BidderName = "adkernelAdn" + BidderAdman BidderName = "adman" + BidderAdmixer BidderName = "admixer" + BidderAdOcean BidderName = "adocean" + BidderAdoppler BidderName = "adoppler" + BidderAdot BidderName = "adot" + BidderAdpone BidderName = "adpone" + BidderAdprime BidderName = "adprime" + BidderAdtarget BidderName = "adtarget" + BidderAdtelligent BidderName = "adtelligent" + BidderAdvangelists BidderName = "advangelists" + BidderAdxcg BidderName = "adxcg" + BidderAdyoulike BidderName = "adyoulike" + BidderAJA BidderName = "aja" + BidderAlgorix BidderName = "algorix" + BidderAMX BidderName = "amx" + BidderApplogy BidderName = "applogy" + BidderAppnexus BidderName = "appnexus" + BidderAudienceNetwork BidderName = "audienceNetwork" + BidderAvocet BidderName = "avocet" + BidderAxonix BidderName = "axonix" + BidderBeachfront BidderName = "beachfront" + BidderBeintoo BidderName = "beintoo" + BidderBetween BidderName = "between" + BidderBidmachine BidderName = "bidmachine" + BidderBidmyadz BidderName = "bidmyadz" + BidderBidsCube BidderName = "bidscube" + BidderBmtm BidderName = "bmtm" + BidderBrightroll BidderName = "brightroll" + BidderColossus BidderName = "colossus" + BidderConnectAd BidderName = "connectad" + BidderConsumable BidderName = "consumable" + BidderConversant BidderName = "conversant" + BidderCpmstar BidderName = "cpmstar" + BidderCriteo BidderName = "criteo" + BidderDatablocks BidderName = "datablocks" + BidderDmx BidderName = "dmx" + BidderDecenterAds BidderName = "decenterads" + BidderDeepintent BidderName = "deepintent" + BidderEmxDigital BidderName = "emx_digital" + BidderEngageBDR BidderName = "engagebdr" + BidderEPlanning BidderName = "eplanning" + BidderEpom BidderName = "epom" + BidderEVolution BidderName = "e_volution" + BidderGamma BidderName = "gamma" + BidderGamoshi BidderName = "gamoshi" + BidderGrid BidderName = "grid" + BidderGumGum BidderName = "gumgum" + BidderImprovedigital BidderName = "improvedigital" + BidderInMobi BidderName = "inmobi" + BidderInteractiveoffers BidderName = "interactiveoffers" + BidderInvibes BidderName = "invibes" + BidderIx BidderName = "ix" + BidderJixie BidderName = "jixie" + BidderKayzen BidderName = "kayzen" + BidderKidoz BidderName = "kidoz" + BidderKrushmedia BidderName = "krushmedia" + BidderKubient BidderName = "kubient" + BidderLockerDome BidderName = "lockerdome" + BidderLogicad BidderName = "logicad" + BidderLunaMedia BidderName = "lunamedia" + BidderSaLunaMedia BidderName = "sa_lunamedia" + BidderMadvertise BidderName = "madvertise" + BidderMarsmedia BidderName = "marsmedia" + BidderMediafuse BidderName = "mediafuse" + BidderMgid BidderName = "mgid" + BidderMobfoxpb BidderName = "mobfoxpb" + BidderMobileFuse BidderName = "mobilefuse" + BidderNanoInteractive BidderName = "nanointeractive" + BidderNinthDecimal BidderName = "ninthdecimal" + BidderNoBid BidderName = "nobid" + BidderOneTag BidderName = "onetag" + BidderOpenx BidderName = "openx" + BidderOperaads BidderName = "operaads" + BidderOrbidder BidderName = "orbidder" + BidderOutbrain BidderName = "outbrain" + BidderPangle BidderName = "pangle" + BidderPubmatic BidderName = "pubmatic" + BidderPubnative BidderName = "pubnative" + BidderPulsepoint BidderName = "pulsepoint" + BidderRevcontent BidderName = "revcontent" + BidderRhythmone BidderName = "rhythmone" + BidderRTBHouse BidderName = "rtbhouse" + BidderRubicon BidderName = "rubicon" + BidderSharethrough BidderName = "sharethrough" + BidderSilverMob BidderName = "silvermob" + BidderSmaato BidderName = "smaato" + BidderSmartAdserver BidderName = "smartadserver" + BidderSmartRTB BidderName = "smartrtb" + BidderSmartyAds BidderName = "smartyads" + BidderSmileWanted BidderName = "smilewanted" + BidderSomoaudience BidderName = "somoaudience" + BidderSonobi BidderName = "sonobi" + BidderSovrn BidderName = "sovrn" + BidderSpotX BidderName = "spotx" + BidderSynacormedia BidderName = "synacormedia" + BidderTappx BidderName = "tappx" + BidderTelaria BidderName = "telaria" + BidderTriplelift BidderName = "triplelift" + BidderTripleliftNative BidderName = "triplelift_native" + BidderTrustX BidderName = "trustx" + BidderUcfunnel BidderName = "ucfunnel" + BidderUnicorn BidderName = "unicorn" + BidderUnruly BidderName = "unruly" + BidderValueImpression BidderName = "valueimpression" + BidderVASTBidder BidderName = "vastbidder" + BidderVerizonMedia BidderName = "verizonmedia" + BidderVisx BidderName = "visx" + BidderViewdeos BidderName = "viewdeos" + BidderVrtcal BidderName = "vrtcal" + BidderYeahmobi BidderName = "yeahmobi" + BidderYieldlab BidderName = "yieldlab" + BidderYieldmo BidderName = "yieldmo" + BidderYieldone BidderName = "yieldone" + BidderZeroClickFraud BidderName = "zeroclickfraud" ) // CoreBidderNames returns a slice of all core bidders. @@ -189,6 +203,8 @@ func CoreBidderNames() []BidderName { return []BidderName{ Bidder33Across, BidderAcuityAds, + BidderAdagio, + BidderAdf, BidderAdform, BidderAdgeneration, BidderAdhese, @@ -207,15 +223,20 @@ func CoreBidderNames() []BidderName { BidderAdxcg, BidderAdyoulike, BidderAJA, + BidderAlgorix, BidderAMX, BidderApplogy, BidderAppnexus, BidderAudienceNetwork, BidderAvocet, + BidderAxonix, BidderBeachfront, BidderBeintoo, BidderBetween, BidderBidmachine, + BidderBidmyadz, + BidderBidsCube, + BidderBmtm, BidderBrightroll, BidderColossus, BidderConnectAd, @@ -231,22 +252,26 @@ func CoreBidderNames() []BidderName { BidderEngageBDR, BidderEPlanning, BidderEpom, + BidderEVolution, BidderGamma, BidderGamoshi, BidderGrid, BidderGumGum, BidderImprovedigital, BidderInMobi, + BidderInteractiveoffers, BidderInvibes, BidderIx, BidderJixie, + BidderKayzen, BidderKidoz, BidderKrushmedia, BidderKubient, - BidderLifestreet, BidderLockerDome, BidderLogicad, BidderLunaMedia, + BidderSaLunaMedia, + BidderMadvertise, BidderMarsmedia, BidderMediafuse, BidderMgid, @@ -257,6 +282,7 @@ func CoreBidderNames() []BidderName { BidderNoBid, BidderOneTag, BidderOpenx, + BidderOperaads, BidderOrbidder, BidderOutbrain, BidderPangle, @@ -273,6 +299,7 @@ func CoreBidderNames() []BidderName { BidderSmartAdserver, BidderSmartRTB, BidderSmartyAds, + BidderSmileWanted, BidderSomoaudience, BidderSonobi, BidderSovrn, @@ -289,6 +316,7 @@ func CoreBidderNames() []BidderName { BidderValueImpression, BidderVASTBidder, BidderVerizonMedia, + BidderViewdeos, BidderVisx, BidderVrtcal, BidderYeahmobi, diff --git a/openrtb_ext/device.go b/openrtb_ext/device.go index cc06f3806cf..1e8605562d2 100644 --- a/openrtb_ext/device.go +++ b/openrtb_ext/device.go @@ -70,8 +70,8 @@ type ExtDevicePrebid struct { // ExtDeviceInt defines the contract for bidrequest.device.ext.prebid.interstitial type ExtDeviceInt struct { - MinWidthPerc uint64 `json:"minwidtheperc"` - MinHeightPerc uint64 `json:"minheightperc"` + MinWidthPerc int64 `json:"minwidtheperc"` + MinHeightPerc int64 `json:"minheightperc"` } func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error { @@ -85,7 +85,7 @@ func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error { if err != nil || perc < 0 || perc > 100 { return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minwidthperc must be a number between 0 and 100"} } - edi.MinWidthPerc = uint64(perc) + edi.MinWidthPerc = int64(perc) } if value, dataType, _, _ := jsonparser.Get(b, "minheightperc"); dataType != jsonparser.Number { return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100"} @@ -94,7 +94,7 @@ func (edi *ExtDeviceInt) UnmarshalJSON(b []byte) error { if err != nil || perc < 0 || perc > 100 { return &errortypes.BadInput{Message: "request.device.ext.prebid.interstitial.minheightperc must be a number between 0 and 100"} } - edi.MinHeightPerc = uint64(perc) + edi.MinHeightPerc = int64(perc) } return nil } diff --git a/openrtb_ext/imp_adf.go b/openrtb_ext/imp_adf.go new file mode 100644 index 00000000000..8ef02a460b2 --- /dev/null +++ b/openrtb_ext/imp_adf.go @@ -0,0 +1,9 @@ +package openrtb_ext + +import ( + "encoding/json" +) + +type ExtImpAdf struct { + MasterTagID json.Number `json:"mid"` +} diff --git a/openrtb_ext/imp_algorix.go b/openrtb_ext/imp_algorix.go new file mode 100644 index 00000000000..e63a45ee11e --- /dev/null +++ b/openrtb_ext/imp_algorix.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtImpAlgoriX defines the contract for bidrequest.imp[i].ext.algorix +type ExtImpAlgorix struct { + Sid string `json:"sid"` + Token string `json:"token"` +} diff --git a/openrtb_ext/imp_appnexus.go b/openrtb_ext/imp_appnexus.go index 9ef69d06458..83d762b3c19 100644 --- a/openrtb_ext/imp_appnexus.go +++ b/openrtb_ext/imp_appnexus.go @@ -17,6 +17,7 @@ type ExtImpAppnexus struct { UsePmtRule *bool `json:"use_pmt_rule"` // At this time we do no processing on the private sizes, so just leaving it as a JSON blob. PrivateSizes json.RawMessage `json:"private_sizes"` + AdPodId bool `json:"generate_ad_pod_id"` } // ExtImpAppnexusKeyVal defines the contract for bidrequest.imp[i].ext.appnexus.keywords[i] diff --git a/openrtb_ext/imp_axonix.go b/openrtb_ext/imp_axonix.go new file mode 100644 index 00000000000..7dd9f68418d --- /dev/null +++ b/openrtb_ext/imp_axonix.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpAxonix struct { + SupplyId string `json:"supplyId"` +} diff --git a/openrtb_ext/imp_between.go b/openrtb_ext/imp_between.go index c868baeb9da..9fe912a8618 100644 --- a/openrtb_ext/imp_between.go +++ b/openrtb_ext/imp_between.go @@ -1,8 +1,6 @@ package openrtb_ext type ExtImpBetween struct { - Host string `json:"host"` - PublisherID string `json:"publisher_id"` - BidFloor float64 `json:"bid_floor"` - BidFloorCur string `json:"bid_floor_cur"` + Host string `json:"host"` + PublisherID string `json:"publisher_id"` } diff --git a/openrtb_ext/imp_bidscube.go b/openrtb_ext/imp_bidscube.go new file mode 100644 index 00000000000..638cad760aa --- /dev/null +++ b/openrtb_ext/imp_bidscube.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpBidsCube struct { + PlacementID string `json:"placementId"` +} diff --git a/openrtb_ext/imp_bmtm.go b/openrtb_ext/imp_bmtm.go new file mode 100644 index 00000000000..a9fd7a52c5e --- /dev/null +++ b/openrtb_ext/imp_bmtm.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ImpExtBmtm struct { + PlacementID int `json:"placement_id"` +} diff --git a/openrtb_ext/imp_interactiveoffers.go b/openrtb_ext/imp_interactiveoffers.go new file mode 100644 index 00000000000..2f8fdf0324e --- /dev/null +++ b/openrtb_ext/imp_interactiveoffers.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpInteractiveoffers struct { + PubID int `json:"pubid"` +} diff --git a/openrtb_ext/imp_kayzen.go b/openrtb_ext/imp_kayzen.go new file mode 100644 index 00000000000..35dece64036 --- /dev/null +++ b/openrtb_ext/imp_kayzen.go @@ -0,0 +1,7 @@ +package openrtb_ext + +// ExtKayzen defines the contract for bidrequest.imp[i].ext.kayzen +type ExtKayzen struct { + Zone string `json:"zone"` + Exchange string `json:"exchange"` +} diff --git a/openrtb_ext/imp_madvertise.go b/openrtb_ext/imp_madvertise.go new file mode 100644 index 00000000000..30bd88dbf51 --- /dev/null +++ b/openrtb_ext/imp_madvertise.go @@ -0,0 +1,6 @@ +package openrtb_ext + +// ExtImpMadvertise defines the contract for bidrequest.imp[i].ext.madvertise +type ExtImpMadvertise struct { + ZoneID string `json:"zoneId"` +} diff --git a/openrtb_ext/imp_operaads.go b/openrtb_ext/imp_operaads.go new file mode 100644 index 00000000000..99ccd7c431b --- /dev/null +++ b/openrtb_ext/imp_operaads.go @@ -0,0 +1,7 @@ +package openrtb_ext + +type ImpExtOperaads struct { + PlacementID string `json:"placementId"` + EndpointID string `json:"endpointId"` + PublisherID string `json:"publisherId"` +} diff --git a/openrtb_ext/imp_pangle.go b/openrtb_ext/imp_pangle.go index cc32dcf1d01..cf3b7e74f41 100644 --- a/openrtb_ext/imp_pangle.go +++ b/openrtb_ext/imp_pangle.go @@ -1,5 +1,7 @@ package openrtb_ext type ImpExtPangle struct { - Token string `json:"token"` + Token string `json:"token"` + AppID string `json:"appid,omitempty"` + PlacementID string `json:"placementid,omitempty"` } diff --git a/openrtb_ext/imp_sa_lunamedia.go b/openrtb_ext/imp_sa_lunamedia.go new file mode 100644 index 00000000000..cb99b0ac561 --- /dev/null +++ b/openrtb_ext/imp_sa_lunamedia.go @@ -0,0 +1,6 @@ +package openrtb_ext + +type ExtImpSaLunamedia struct { + Key string `json:"key"` + Type string `json:"type,omitempty"` +} diff --git a/openrtb_ext/imp_sharethrough.go b/openrtb_ext/imp_sharethrough.go index 3f3780334e6..7c3f1f6781d 100644 --- a/openrtb_ext/imp_sharethrough.go +++ b/openrtb_ext/imp_sharethrough.go @@ -1,13 +1,18 @@ package openrtb_ext -type ExtImpSharethrough struct { - Pkey string `json:"pkey"` - Iframe bool `json:"iframe"` - IframeSize []int `json:"iframeSize"` - BidFloor float64 `json:"bidfloor"` +type ExtData struct { + PBAdSlot string `json:"pbadslot"` } // ExtImpSharethrough defines the contract for bidrequest.imp[i].ext.sharethrough +type ExtImpSharethrough struct { + Pkey string `json:"pkey"` + Iframe bool `json:"iframe"` + IframeSize []int `json:"iframeSize"` + BidFloor float64 `json:"bidfloor"` + Data *ExtData `json:"data,omitempty"` +} + type ExtImpSharethroughResponse struct { AdServerRequestID string `json:"adserverRequestId"` BidID string `json:"bidId"` diff --git a/openrtb_ext/imp_smaato.go b/openrtb_ext/imp_smaato.go index 10de97fb017..14dcb73bdf3 100644 --- a/openrtb_ext/imp_smaato.go +++ b/openrtb_ext/imp_smaato.go @@ -1,9 +1,12 @@ package openrtb_ext // ExtImpSmaato defines the contract for bidrequest.imp[i].ext.smaato -// PublisherId and AdSpaceId are mandatory parameters, others are optional parameters +// PublisherId and AdSpaceId are mandatory parameters for non adpod (long-form video) requests, others are optional parameters +// PublisherId and AdBreakId are mandatory parameters for adpod (long-form video) requests, others are optional parameters // AdSpaceId is identifier for specific ad placement or ad tag +// AdBreakId is identifier for specific ad placement or ad tag type ExtImpSmaato struct { PublisherID string `json:"publisherId"` AdSpaceID string `json:"adspaceId"` + AdBreakID string `json:"adbreakId"` } diff --git a/openrtb_ext/imp_tappx.go b/openrtb_ext/imp_tappx.go index c1ca77bf632..ca8693abd9f 100644 --- a/openrtb_ext/imp_tappx.go +++ b/openrtb_ext/imp_tappx.go @@ -1,8 +1,11 @@ package openrtb_ext type ExtImpTappx struct { - Host string `json:"host"` - TappxKey string `json:"tappxkey"` - Endpoint string `json:"endpoint"` - BidFloor float64 `json:"bidfloor,omitempty"` + Host string `json:"host"` + TappxKey string `json:"tappxkey"` + Endpoint string `json:"endpoint"` + BidFloor float64 `json:"bidfloor,omitempty"` + Mktag string `json:"mktag,omitempty"` + Bcid []string `json:"bcid,omitempty"` + Bcrid []string `json:"bcrid,omitempty"` } diff --git a/openrtb_ext/request.go b/openrtb_ext/request.go index 8d6e6d13546..24766ab1603 100644 --- a/openrtb_ext/request.go +++ b/openrtb_ext/request.go @@ -14,6 +14,9 @@ const FirstPartyDataContextExtKey = "context" // SKAdNExtKey defines the field name within request.ext reserved for Apple's SKAdNetwork. const SKAdNExtKey = "skadn" +// NativeExchangeSpecificLowerBound defines the lower threshold of exchange specific types for native ads. There is no upper bound. +const NativeExchangeSpecificLowerBound = 500 + const MaxDecimalFigures int = 15 // ExtRequest defines the contract for bidrequest.ext @@ -43,6 +46,13 @@ type ExtRequestPrebid struct { // Macros specifies list of custom macros along with the values. This is used while forming // the tracker URLs, where PBS will replace the Custom Macro with its value with url-encoding Macros map[string]string `json:"macros,omitempty"` + + CurrencyConversions *ExtRequestCurrency `json:"currency,omitempty"` +} + +type ExtRequestCurrency struct { + ConversionRates map[string]map[string]float64 `json:"rates"` + UsePBSRates *bool `json:"usepbsrates"` } // ExtRequestPrebid defines the contract for bidrequest.ext.prebid.schains diff --git a/openrtb_ext/request_wrapper.go b/openrtb_ext/request_wrapper.go new file mode 100644 index 00000000000..276f8f5eebe --- /dev/null +++ b/openrtb_ext/request_wrapper.go @@ -0,0 +1,787 @@ +package openrtb_ext + +import ( + "encoding/json" + "errors" + + "github.com/mxmCherry/openrtb/v15/openrtb2" +) + +// RequestWrapper wraps the OpenRTB request to provide a storage location for unmarshalled ext fields, so they +// will not need to be unmarshalled multiple times. +// +// To start with, the wrapper can be created for a request 'req' via: +// reqWrapper := openrtb_ext.RequestWrapper{BidRequest: req} +// +// In order to access an object's ext field, fetch it via: +// userExt, err := reqWrapper.GetUserExt() +// or other Get method as appropriate. +// +// To read or write values, use the Ext objects Get and Set methods. If you need to write to a field that has its own Set +// method, use that to set the value rather than using SetExt() with that change done in the map; when rewritting the +// ext JSON the code will overwrite the the values in the map with the values stored in the seperate fields. +// +// userPrebid := userExt.GetPrebid() +// userExt.SetConsent(consentString) +// +// The GetExt() and SetExt() should only be used to access fields that have not already been resolved in the object. +// Using SetExt() at all is a strong hint that the ext object should be extended to support the new fields being set +// in the map. +// +// NOTE: The RequestWrapper methods (particularly the ones calling (un)Marshal are not thread safe) + +type RequestWrapper struct { + *openrtb2.BidRequest + userExt *UserExt + deviceExt *DeviceExt + requestExt *RequestExt + appExt *AppExt + regExt *RegExt + siteExt *SiteExt +} + +func (rw *RequestWrapper) GetUserExt() (*UserExt, error) { + if rw.userExt != nil { + return rw.userExt, nil + } + rw.userExt = &UserExt{} + if rw.BidRequest == nil || rw.User == nil || rw.User.Ext == nil { + return rw.userExt, rw.userExt.unmarshal(json.RawMessage{}) + } + + return rw.userExt, rw.userExt.unmarshal(rw.User.Ext) +} + +func (rw *RequestWrapper) GetDeviceExt() (*DeviceExt, error) { + if rw.deviceExt != nil { + return rw.deviceExt, nil + } + rw.deviceExt = &DeviceExt{} + if rw.BidRequest == nil || rw.Device == nil || rw.Device.Ext == nil { + return rw.deviceExt, rw.deviceExt.unmarshal(json.RawMessage{}) + } + return rw.deviceExt, rw.deviceExt.unmarshal(rw.Device.Ext) +} + +func (rw *RequestWrapper) GetRequestExt() (*RequestExt, error) { + if rw.requestExt != nil { + return rw.requestExt, nil + } + rw.requestExt = &RequestExt{} + if rw.BidRequest == nil || rw.Ext == nil { + return rw.requestExt, rw.requestExt.unmarshal(json.RawMessage{}) + } + return rw.requestExt, rw.requestExt.unmarshal(rw.Ext) +} + +func (rw *RequestWrapper) GetAppExt() (*AppExt, error) { + if rw.appExt != nil { + return rw.appExt, nil + } + rw.appExt = &AppExt{} + if rw.BidRequest == nil || rw.App == nil || rw.App.Ext == nil { + return rw.appExt, rw.appExt.unmarshal(json.RawMessage{}) + } + return rw.appExt, rw.appExt.unmarshal(rw.App.Ext) +} + +func (rw *RequestWrapper) GetRegExt() (*RegExt, error) { + if rw.regExt != nil { + return rw.regExt, nil + } + rw.regExt = &RegExt{} + if rw.BidRequest == nil || rw.Regs == nil || rw.Regs.Ext == nil { + return rw.regExt, rw.regExt.unmarshal(json.RawMessage{}) + } + return rw.regExt, rw.regExt.unmarshal(rw.Regs.Ext) +} + +func (rw *RequestWrapper) GetSiteExt() (*SiteExt, error) { + if rw.siteExt != nil { + return rw.siteExt, nil + } + rw.siteExt = &SiteExt{} + if rw.BidRequest == nil || rw.Site == nil || rw.Site.Ext == nil { + return rw.siteExt, rw.siteExt.unmarshal(json.RawMessage{}) + } + return rw.siteExt, rw.siteExt.unmarshal(rw.Site.Ext) +} + +func (rw *RequestWrapper) RebuildRequest() error { + if rw.BidRequest == nil { + return errors.New("Requestwrapper Sync called on a nil BidRequest") + } + + if err := rw.rebuildUserExt(); err != nil { + return err + } + if err := rw.rebuildDeviceExt(); err != nil { + return err + } + if err := rw.rebuildRequestExt(); err != nil { + return err + } + if err := rw.rebuildAppExt(); err != nil { + return err + } + if err := rw.rebuildRegExt(); err != nil { + return err + } + if err := rw.rebuildSiteExt(); err != nil { + return err + } + + return nil +} + +func (rw *RequestWrapper) rebuildUserExt() error { + if rw.BidRequest.User == nil && rw.userExt != nil && rw.userExt.Dirty() { + rw.User = &openrtb2.User{} + } + if rw.userExt != nil && rw.userExt.Dirty() { + userJson, err := rw.userExt.marshal() + if err != nil { + return err + } + rw.User.Ext = userJson + } + return nil +} + +func (rw *RequestWrapper) rebuildDeviceExt() error { + if rw.Device == nil && rw.deviceExt != nil && rw.deviceExt.Dirty() { + rw.Device = &openrtb2.Device{} + } + if rw.deviceExt != nil && rw.deviceExt.Dirty() { + deviceJson, err := rw.deviceExt.marshal() + if err != nil { + return err + } + rw.Device.Ext = deviceJson + } + return nil +} + +func (rw *RequestWrapper) rebuildRequestExt() error { + if rw.requestExt != nil && rw.requestExt.Dirty() { + requestJson, err := rw.requestExt.marshal() + if err != nil { + return err + } + rw.Ext = requestJson + } + return nil +} + +func (rw *RequestWrapper) rebuildAppExt() error { + if rw.App == nil && rw.appExt != nil && rw.appExt.Dirty() { + rw.App = &openrtb2.App{} + } + if rw.appExt != nil && rw.appExt.Dirty() { + appJson, err := rw.appExt.marshal() + if err != nil { + return err + } + rw.App.Ext = appJson + } + return nil +} + +func (rw *RequestWrapper) rebuildRegExt() error { + if rw.Regs == nil && rw.regExt != nil && rw.regExt.Dirty() { + rw.Regs = &openrtb2.Regs{} + } + if rw.regExt != nil && rw.regExt.Dirty() { + regsJson, err := rw.regExt.marshal() + if err != nil { + return err + } + rw.Regs.Ext = regsJson + } + return nil +} + +func (rw *RequestWrapper) rebuildSiteExt() error { + if rw.Site == nil && rw.siteExt != nil && rw.siteExt.Dirty() { + rw.Site = &openrtb2.Site{} + } + if rw.siteExt != nil && rw.siteExt.Dirty() { + siteJson, err := rw.siteExt.marshal() + if err != nil { + return err + } + rw.Regs.Ext = siteJson + } + return nil +} + +// --------------------------------------------------------------- +// UserExt provides an interface for request.user.ext +// --------------------------------------------------------------- + +type UserExt struct { + ext map[string]json.RawMessage + extDirty bool + consent *string + consentDirty bool + prebid *ExtUserPrebid + prebidDirty bool + eids *[]ExtUserEid + eidsDirty bool +} + +func (ue *UserExt) unmarshal(extJson json.RawMessage) error { + if len(ue.ext) != 0 || ue.Dirty() { + return nil + } + ue.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + + if err := json.Unmarshal(extJson, &ue.ext); err != nil { + return err + } + + consentJson, hasConsent := ue.ext["consent"] + if hasConsent { + if err := json.Unmarshal(consentJson, &ue.consent); err != nil { + return err + } + } + + prebidJson, hasPrebid := ue.ext["prebid"] + if hasPrebid { + ue.prebid = &ExtUserPrebid{} + if err := json.Unmarshal(prebidJson, ue.prebid); err != nil { + return err + } + } + + eidsJson, hasEids := ue.ext["eids"] + if hasEids { + ue.eids = &[]ExtUserEid{} + if err := json.Unmarshal(eidsJson, ue.eids); err != nil { + return err + } + } + + return nil +} + +func (ue *UserExt) marshal() (json.RawMessage, error) { + if ue.consentDirty { + consentJson, err := json.Marshal(ue.consent) + if err != nil { + return nil, err + } + if len(consentJson) > 2 { + ue.ext["consent"] = json.RawMessage(consentJson) + } else { + delete(ue.ext, "consent") + } + ue.consentDirty = false + } + + if ue.prebidDirty { + prebidJson, err := json.Marshal(ue.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > 2 { + ue.ext["prebid"] = json.RawMessage(prebidJson) + } else { + delete(ue.ext, "prebid") + } + ue.prebidDirty = false + } + + if ue.eidsDirty { + if len(*ue.eids) > 0 { + eidsJson, err := json.Marshal(ue.eids) + if err != nil { + return nil, err + } + ue.ext["eids"] = json.RawMessage(eidsJson) + } else { + delete(ue.ext, "eids") + } + ue.eidsDirty = false + } + + ue.extDirty = false + if len(ue.ext) == 0 { + return nil, nil + } + return json.Marshal(ue.ext) + +} + +func (ue *UserExt) Dirty() bool { + return ue.extDirty || ue.eidsDirty || ue.prebidDirty || ue.consentDirty +} + +func (ue *UserExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range ue.ext { + ext[k] = v + } + return ext +} + +func (ue *UserExt) SetExt(ext map[string]json.RawMessage) { + ue.ext = ext + ue.extDirty = true +} + +func (ue *UserExt) GetConsent() *string { + if ue.consent == nil { + return nil + } + consent := *ue.consent + return &consent +} + +func (ue *UserExt) SetConsent(consent *string) { + ue.consent = consent + ue.consentDirty = true +} + +func (ue *UserExt) GetPrebid() *ExtUserPrebid { + if ue.prebid == nil { + return nil + } + prebid := *ue.prebid + return &prebid +} + +func (ue *UserExt) SetPrebid(prebid *ExtUserPrebid) { + ue.prebid = prebid + ue.prebidDirty = true +} + +func (ue *UserExt) GetEid() *[]ExtUserEid { + if ue.eids == nil { + return nil + } + eids := *ue.eids + return &eids +} + +func (ue *UserExt) SetEid(eid *[]ExtUserEid) { + ue.eids = eid + ue.eidsDirty = true +} + +// --------------------------------------------------------------- +// RequestExt provides an interface for request.ext +// --------------------------------------------------------------- + +type RequestExt struct { + ext map[string]json.RawMessage + extDirty bool + prebid *ExtRequestPrebid + prebidDirty bool +} + +func (re *RequestExt) unmarshal(extJson json.RawMessage) error { + if len(re.ext) != 0 || re.Dirty() { + return nil + } + re.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &re.ext) + if err != nil { + return err + } + prebidJson, hasPrebid := re.ext["prebid"] + if hasPrebid { + re.prebid = &ExtRequestPrebid{} + err = json.Unmarshal(prebidJson, re.prebid) + } + + return err +} + +func (re *RequestExt) marshal() (json.RawMessage, error) { + if re.prebidDirty { + prebidJson, err := json.Marshal(re.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > 2 { + re.ext["prebid"] = json.RawMessage(prebidJson) + } else { + delete(re.ext, "prebid") + } + re.prebidDirty = false + } + + re.extDirty = false + if len(re.ext) == 0 { + return nil, nil + } + return json.Marshal(re.ext) +} + +func (re *RequestExt) Dirty() bool { + return re.extDirty || re.prebidDirty +} + +func (re *RequestExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range re.ext { + ext[k] = v + } + return ext +} + +func (re *RequestExt) SetExt(ext map[string]json.RawMessage) { + re.ext = ext + re.extDirty = true +} + +func (re *RequestExt) GetPrebid() *ExtRequestPrebid { + if re.prebid == nil { + return nil + } + prebid := *re.prebid + return &prebid +} + +func (re *RequestExt) SetPrebid(prebid *ExtRequestPrebid) { + re.prebid = prebid + re.prebidDirty = true +} + +// --------------------------------------------------------------- +// DeviceExt provides an interface for request.device.ext +// --------------------------------------------------------------- +// NOTE: openrtb_ext/device.go:ParseDeviceExtATTS() uses ext.atts, as read only, via jsonparser, only for IOS. +// Doesn't seem like we will see any performance savings by parsing atts at this point, and as it is read only, +// we don't need to worry about write conflicts. Note here in case additional uses of atts evolve as things progress. +// --------------------------------------------------------------- + +type DeviceExt struct { + ext map[string]json.RawMessage + extDirty bool + prebid *ExtDevicePrebid + prebidDirty bool +} + +func (de *DeviceExt) 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 + } + err := json.Unmarshal(extJson, &de.ext) + if err != nil { + return err + } + prebidJson, hasPrebid := de.ext["prebid"] + if hasPrebid { + de.prebid = &ExtDevicePrebid{} + err = json.Unmarshal(prebidJson, de.prebid) + } + + return err +} + +func (de *DeviceExt) marshal() (json.RawMessage, error) { + if de.prebidDirty { + prebidJson, err := json.Marshal(de.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > 2 { + de.ext["prebid"] = json.RawMessage(prebidJson) + } else { + delete(de.ext, "prebid") + } + de.prebidDirty = false + } + + de.extDirty = false + if len(de.ext) == 0 { + return nil, nil + } + return json.Marshal(de.ext) +} + +func (de *DeviceExt) Dirty() bool { + return de.extDirty || de.prebidDirty +} + +func (de *DeviceExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range de.ext { + ext[k] = v + } + return ext +} + +func (de *DeviceExt) SetExt(ext map[string]json.RawMessage) { + de.ext = ext + de.extDirty = true +} + +func (de *DeviceExt) GetPrebid() *ExtDevicePrebid { + if de.prebid == nil { + return nil + } + prebid := *de.prebid + return &prebid +} + +func (de *DeviceExt) SetPrebid(prebid *ExtDevicePrebid) { + de.prebid = prebid + de.prebidDirty = true +} + +// --------------------------------------------------------------- +// AppExt provides an interface for request.app.ext +// --------------------------------------------------------------- + +type AppExt struct { + ext map[string]json.RawMessage + extDirty bool + prebid *ExtAppPrebid + prebidDirty bool +} + +func (ae *AppExt) unmarshal(extJson json.RawMessage) error { + if len(ae.ext) != 0 || ae.Dirty() { + return nil + } + ae.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &ae.ext) + if err != nil { + return err + } + prebidJson, hasPrebid := ae.ext["prebid"] + if hasPrebid { + ae.prebid = &ExtAppPrebid{} + err = json.Unmarshal(prebidJson, ae.prebid) + } + + return err +} + +func (ae *AppExt) marshal() (json.RawMessage, error) { + if ae.prebidDirty { + prebidJson, err := json.Marshal(ae.prebid) + if err != nil { + return nil, err + } + if len(prebidJson) > 2 { + ae.ext["prebid"] = json.RawMessage(prebidJson) + } else { + delete(ae.ext, "prebid") + } + ae.prebidDirty = false + } + + ae.extDirty = false + if len(ae.ext) == 0 { + return nil, nil + } + return json.Marshal(ae.ext) +} + +func (ae *AppExt) Dirty() bool { + return ae.extDirty || ae.prebidDirty +} + +func (ae *AppExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range ae.ext { + ext[k] = v + } + return ext +} + +func (ae *AppExt) SetExt(ext map[string]json.RawMessage) { + ae.ext = ext + ae.extDirty = true +} + +func (ae *AppExt) GetPrebid() *ExtAppPrebid { + if ae.prebid == nil { + return nil + } + prebid := *ae.prebid + return &prebid +} + +func (ae *AppExt) SetPrebid(prebid *ExtAppPrebid) { + ae.prebid = prebid + ae.prebidDirty = true +} + +// --------------------------------------------------------------- +// RegExt provides an interface for request.regs.ext +// --------------------------------------------------------------- + +type RegExt struct { + ext map[string]json.RawMessage + extDirty bool + usPrivacy string + usPrivacyDirty bool +} + +func (re *RegExt) unmarshal(extJson json.RawMessage) error { + if len(re.ext) != 0 || re.Dirty() { + return nil + } + re.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &re.ext) + if err != nil { + return err + } + uspJson, hasUsp := re.ext["us_privacy"] + if hasUsp { + err = json.Unmarshal(uspJson, &re.usPrivacy) + } + + return err +} + +func (re *RegExt) marshal() (json.RawMessage, error) { + if re.usPrivacyDirty { + if len(re.usPrivacy) > 0 { + rawjson, err := json.Marshal(re.usPrivacy) + if err != nil { + return nil, err + } + re.ext["us_privacy"] = rawjson + } else { + delete(re.ext, "us_privacy") + } + re.usPrivacyDirty = false + } + + re.extDirty = false + if len(re.ext) == 0 { + return nil, nil + } + return json.Marshal(re.ext) +} + +func (re *RegExt) Dirty() bool { + return re.extDirty || re.usPrivacyDirty +} + +func (re *RegExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range re.ext { + ext[k] = v + } + return ext +} + +func (re *RegExt) SetExt(ext map[string]json.RawMessage) { + re.ext = ext + re.extDirty = true +} + +func (re *RegExt) GetUSPrivacy() string { + uSPrivacy := re.usPrivacy + return uSPrivacy +} + +func (re *RegExt) SetUSPrivacy(uSPrivacy string) { + re.usPrivacy = uSPrivacy + re.usPrivacyDirty = true +} + +// --------------------------------------------------------------- +// SiteExt provides an interface for request.site.ext +// --------------------------------------------------------------- + +type SiteExt struct { + ext map[string]json.RawMessage + extDirty bool + amp int8 + ampDirty bool +} + +func (se *SiteExt) unmarshal(extJson json.RawMessage) error { + if len(se.ext) != 0 || se.Dirty() { + return nil + } + se.ext = make(map[string]json.RawMessage) + if len(extJson) == 0 { + return nil + } + err := json.Unmarshal(extJson, &se.ext) + if err != nil { + return err + } + AmpJson, hasAmp := se.ext["amp"] + if hasAmp { + err = json.Unmarshal(AmpJson, &se.amp) + if err != nil { + err = errors.New(`request.site.ext.amp must be either 1, 0, or undefined`) + } + } + + return err +} + +func (se *SiteExt) marshal() (json.RawMessage, error) { + if se.ampDirty { + ampJson, err := json.Marshal(se.amp) + if err != nil { + return nil, err + } + if len(ampJson) > 2 { + se.ext["amp"] = json.RawMessage(ampJson) + } else { + delete(se.ext, "amp") + } + se.ampDirty = false + } + + se.extDirty = false + if len(se.ext) == 0 { + return nil, nil + } + return json.Marshal(se.ext) +} + +func (se *SiteExt) Dirty() bool { + return se.extDirty || se.ampDirty +} + +func (se *SiteExt) GetExt() map[string]json.RawMessage { + ext := make(map[string]json.RawMessage) + for k, v := range se.ext { + ext[k] = v + } + return ext +} + +func (se *SiteExt) SetExt(ext map[string]json.RawMessage) { + se.ext = ext + se.extDirty = true +} + +func (se *SiteExt) GetAmp() int8 { + return se.amp +} + +func (se *SiteExt) SetUSPrivacy(amp int8) { + se.amp = amp + se.ampDirty = true +} diff --git a/openrtb_ext/request_wrapper_test.go b/openrtb_ext/request_wrapper_test.go new file mode 100644 index 00000000000..06cad49aedf --- /dev/null +++ b/openrtb_ext/request_wrapper_test.go @@ -0,0 +1,22 @@ +package openrtb_ext + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Some minimal tests to get code coverage above 30%. The real tests are when other modules use these structures. + +func TestUserExt(t *testing.T) { + userExt := &UserExt{} + + userExt.unmarshal(nil) + assert.Equal(t, false, userExt.Dirty(), "New UserExt should not be dirty.") + assert.Nil(t, userExt.GetConsent(), "Empty UserExt should have nil consent") + + newConsent := "NewConsent" + userExt.SetConsent(&newConsent) + assert.Equal(t, "NewConsent", *userExt.GetConsent()) + +} diff --git a/openrtb_ext/user.go b/openrtb_ext/user.go index b83f82330db..d5e6ae678cc 100644 --- a/openrtb_ext/user.go +++ b/openrtb_ext/user.go @@ -10,11 +10,6 @@ type ExtUser struct { Prebid *ExtUserPrebid `json:"prebid,omitempty"` - // DigiTrust breaks the typical Prebid Server convention of namespacing "global" options inside "ext.prebid.*" - // to match the recommendation from the broader digitrust community. - // For more info, see: https://github.com/digi-trust/dt-cdn/wiki/OpenRTB-extension#openrtb-2x - DigiTrust *ExtUserDigiTrust `json:"digitrust,omitempty"` - Eids []ExtUserEid `json:"eids,omitempty"` } @@ -23,14 +18,6 @@ type ExtUserPrebid struct { BuyerUIDs map[string]string `json:"buyeruids,omitempty"` } -// ExtUserDigiTrust defines the contract for bidrequest.user.ext.digitrust -// More info on DigiTrust can be found here: https://github.com/digi-trust/dt-cdn/wiki/Integration-Guide -type ExtUserDigiTrust struct { - ID string `json:"id"` // Unique device identifier - KeyV int `json:"keyv"` // Key version used to encrypt ID - Pref int `json:"pref"` // User optout preference -} - // ExtUserEid defines the contract for bidrequest.user.ext.eids // Responsible for the Universal User ID support: establishing pseudonymous IDs for users. // See https://github.com/prebid/Prebid.js/issues/3900 for details. diff --git a/prebid_cache_client/prebid_cache_test.go b/prebid_cache_client/prebid_cache_test.go index 2b33f2cebd1..65688789fd0 100644 --- a/prebid_cache_client/prebid_cache_test.go +++ b/prebid_cache_client/prebid_cache_test.go @@ -82,19 +82,10 @@ func TestPrebidClient(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(DummyPrebidCacheServer)) defer server.Close() - cobj := make([]*CacheObject, 5) + cobj := make([]*CacheObject, 3) - // example bids from lifestreet, facebook, and appnexus + // example bids cobj[0] = &CacheObject{ - IsVideo: false, - Value: &BidCache{ - Adm: "\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n\n
\n
\n\n\n\n\n
\n\n
\n\n\n\n\n\n\n\n\n\n\n
\n \n \n \n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\"\"\n\"\"\n\n", - NURL: "http://test.com/syspixel?__ads=ip8070-3PJ4q4QyZxnHE6woGe1sQ3&__adt=4122105428549383603&__ade=2&type=tracking&rqc=0w23qR-q7O7MsGkWlR9wOBm8qL7msKBtSKRJV3Pw0a0tZ47xJTnT2JwzqvXgrzPZOLZfI__68S9kCKELawQtZcO6kMyvlPM55uCaRZWng_j5btuPaEuXyA&pab=true", - Width: 300, - Height: 250, - }, - } - cobj[1] = &CacheObject{ IsVideo: false, Value: &BidCache{ Adm: "{\"type\":\"ID\",\"bid_id\":\"8255649814109237089\",\"placement_id\":\"1995257847363113_1997038003851764\",\"resolved_placement_id\":\"1995257847363113_1997038003851764\",\"sdk_version\":\"4.25.0-appnexus.bidding\",\"device_id\":\"87ECBA49-908A-428F-9DE7-4B9CED4F486C\",\"template\":7,\"payload\":\"null\"}", @@ -103,7 +94,7 @@ func TestPrebidClient(t *testing.T) { Height: 250, }, } - cobj[2] = &CacheObject{ + cobj[1] = &CacheObject{ IsVideo: false, Value: &BidCache{ Adm: "", @@ -111,14 +102,10 @@ func TestPrebidClient(t *testing.T) { Height: 250, }, } - cobj[3] = &CacheObject{ + cobj[2] = &CacheObject{ IsVideo: true, Value: "", } - cobj[4] = &CacheObject{ - IsVideo: true, - Value: "\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n\n
\n
\n\n\n\n\n
\n\n \n\n\n\n\n\n\n\n\n\n\n
\n \n \n \n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\"\"\n\"\"\n\n", - } InitPrebidCache(server.URL) ctx := context.TODO() @@ -136,12 +123,6 @@ func TestPrebidClient(t *testing.T) { if cobj[2].UUID != "UUID-3" { t.Errorf("Third object UUID was '%s', should have been 'UUID-3'", cobj[2].UUID) } - if cobj[3].UUID != "UUID-4" { - t.Errorf("Fourth object UUID was '%s', should have been 'UUID-4'", cobj[3].UUID) - } - if cobj[4].UUID != "UUID-5" { - t.Errorf("Fifth object UUID was '%s', should have been 'UUID-5'", cobj[4].UUID) - } delay = 5 * time.Millisecond ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) diff --git a/privacy/ccpa/consentwriter.go b/privacy/ccpa/consentwriter.go index 41f1c39447b..451f2b40238 100644 --- a/privacy/ccpa/consentwriter.go +++ b/privacy/ccpa/consentwriter.go @@ -1,8 +1,12 @@ package ccpa -import "github.com/mxmCherry/openrtb/v15/openrtb2" +import ( + "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" +) -// ConsentWriter implements the PolicyWriter interface for CCPA. +// ConsentWriter implements the old PolicyWriter interface for CCPA. +// This is used where we have not converted to RequestWrapper yet type ConsentWriter struct { Consent string } @@ -12,12 +16,11 @@ func (c ConsentWriter) Write(req *openrtb2.BidRequest) error { if req == nil { return nil } - - regs, err := buildRegs(c.Consent, req.Regs) - if err != nil { + reqWrap := &openrtb_ext.RequestWrapper{BidRequest: req} + if regsExt, err := reqWrap.GetRegExt(); err == nil { + regsExt.SetUSPrivacy(c.Consent) + } else { return err } - req.Regs = regs - - return nil + return reqWrap.RebuildRequest() } diff --git a/privacy/ccpa/consentwriter_test.go b/privacy/ccpa/consentwriter_test.go index d59428626b8..28dfd41785e 100644 --- a/privacy/ccpa/consentwriter_test.go +++ b/privacy/ccpa/consentwriter_test.go @@ -5,10 +5,60 @@ import ( "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) +// RegExt.SetUSPrivacy() is the new ConsentWriter func TestConsentWriter(t *testing.T) { + consent := "anyConsent" + testCases := []struct { + description string + request *openrtb2.BidRequest + expected *openrtb2.BidRequest + expectedError bool + }{ + { + description: "Nil Request", + request: nil, + expected: nil, + }, + { + description: "Success", + request: &openrtb2.BidRequest{}, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + }, + }, + { + description: "Error With Regs.Ext - Does Not Mutate", + request: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, + }, + expectedError: false, + expected: &openrtb2.BidRequest{ + Regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed}`)}, + }, + }, + } + + for _, test := range testCases { + + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request} + var err error + regsExt, err1 := reqWrapper.GetRegExt() + if err1 == nil { + regsExt.SetUSPrivacy(consent) + if reqWrapper.BidRequest != nil { + err = reqWrapper.RebuildRequest() + } + } + assertError(t, test.expectedError, err, test.description) + assert.Equal(t, test.expected, reqWrapper.BidRequest, test.description) + } +} + +func TestConsentWriterLegacy(t *testing.T) { consent := "anyConsent" testCases := []struct { description string diff --git a/privacy/ccpa/policy.go b/privacy/ccpa/policy.go index d57ba8deaa4..39322317df5 100644 --- a/privacy/ccpa/policy.go +++ b/privacy/ccpa/policy.go @@ -1,8 +1,6 @@ package ccpa import ( - "encoding/json" - "errors" "fmt" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -15,8 +13,8 @@ type Policy struct { NoSaleBidders []string } -// ReadFromRequest extracts the CCPA regulatory information from an OpenRTB bid request. -func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { +// ReadFromRequestWrapper extracts the CCPA regulatory information from an OpenRTB bid request. +func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper) (Policy, error) { var consent string var noSaleBidders []string @@ -25,174 +23,80 @@ func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { } // Read consent from request.regs.ext - if req.Regs != nil && len(req.Regs.Ext) > 0 { - var ext openrtb_ext.ExtRegs - if err := json.Unmarshal(req.Regs.Ext, &ext); err != nil { - return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err) - } - consent = ext.USPrivacy + regsExt, err := req.GetRegExt() + if err != nil { + return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err) + } + if regsExt != nil { + consent = regsExt.GetUSPrivacy() } - // Read no sale bidders from request.ext.prebid - if len(req.Ext) > 0 { - var ext openrtb_ext.ExtRequest - if err := json.Unmarshal(req.Ext, &ext); err != nil { - return Policy{}, fmt.Errorf("error reading request.ext.prebid: %s", err) - } - noSaleBidders = ext.Prebid.NoSale + reqExt, err := req.GetRequestExt() + if err != nil { + return Policy{}, fmt.Errorf("error reading request.ext: %s", err) + } + reqPrebid := reqExt.GetPrebid() + if reqPrebid != nil { + noSaleBidders = reqPrebid.NoSale } return Policy{consent, noSaleBidders}, nil } +func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) { + return ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: req}) +} + // Write mutates an OpenRTB bid request with the CCPA regulatory information. -func (p Policy) Write(req *openrtb2.BidRequest) error { +func (p Policy) Write(req *openrtb_ext.RequestWrapper) error { if req == nil { return nil } - regs, err := buildRegs(p.Consent, req.Regs) + regsExt, err := req.GetRegExt() if err != nil { return err } - ext, err := buildExt(p.NoSaleBidders, req.Ext) + + reqExt, err := req.GetRequestExt() if err != nil { return err } - req.Regs = regs - req.Ext = ext + regsExt.SetUSPrivacy(p.Consent) + setPrebidNoSale(p.NoSaleBidders, reqExt) return nil } -func buildRegs(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) { - if consent == "" { - return buildRegsClear(regs) - } - return buildRegsWrite(consent, regs) -} - -func buildRegsClear(regs *openrtb2.Regs) (*openrtb2.Regs, error) { - if regs == nil || len(regs.Ext) == 0 { - return regs, nil - } - - var extMap map[string]interface{} - if err := json.Unmarshal(regs.Ext, &extMap); err != nil { - return nil, err - } - - delete(extMap, "us_privacy") - - // Remove entire ext if it's now empty - if len(extMap) == 0 { - regsResult := *regs - regsResult.Ext = nil - return ®sResult, nil - } - - // Marshal ext if there are still other fields - var regsResult openrtb2.Regs - ext, err := json.Marshal(extMap) - if err == nil { - regsResult = *regs - regsResult.Ext = ext - } - return ®sResult, err -} - -func buildRegsWrite(consent string, regs *openrtb2.Regs) (*openrtb2.Regs, error) { - if regs == nil { - return marshalRegsExt(openrtb2.Regs{}, openrtb_ext.ExtRegs{USPrivacy: consent}) - } - - if regs.Ext == nil { - return marshalRegsExt(*regs, openrtb_ext.ExtRegs{USPrivacy: consent}) - } - - var extMap map[string]interface{} - if err := json.Unmarshal(regs.Ext, &extMap); err != nil { - return nil, err - } - - extMap["us_privacy"] = consent - return marshalRegsExt(*regs, extMap) -} - -func marshalRegsExt(regs openrtb2.Regs, ext interface{}) (*openrtb2.Regs, error) { - extJSON, err := json.Marshal(ext) - if err == nil { - regs.Ext = extJSON - } - return ®s, err -} - -func buildExt(noSaleBidders []string, ext json.RawMessage) (json.RawMessage, error) { +func setPrebidNoSale(noSaleBidders []string, ext *openrtb_ext.RequestExt) { if len(noSaleBidders) == 0 { - return buildExtClear(ext) + setPrebidNoSaleClear(ext) + } else { + setPrebidNoSaleWrite(noSaleBidders, ext) } - return buildExtWrite(noSaleBidders, ext) } -func buildExtClear(ext json.RawMessage) (json.RawMessage, error) { - if len(ext) == 0 { - return ext, nil - } - - var extMap map[string]interface{} - if err := json.Unmarshal(ext, &extMap); err != nil { - return nil, err - } - - prebidExt, exists := extMap["prebid"] - if !exists { - return ext, nil - } - - // Verify prebid is an object - prebidExtMap, ok := prebidExt.(map[string]interface{}) - if !ok { - return nil, errors.New("request.ext.prebid is not a json object") +func setPrebidNoSaleClear(ext *openrtb_ext.RequestExt) { + prebid := ext.GetPrebid() + if prebid == nil { + return } // Remove no sale member - delete(prebidExtMap, "nosale") - if len(prebidExtMap) == 0 { - delete(extMap, "prebid") - } - - // Remove entire ext if it's empty - if len(extMap) == 0 { - return nil, nil - } - - return json.Marshal(extMap) + prebid.NoSale = []string{} + ext.SetPrebid(prebid) } -func buildExtWrite(noSaleBidders []string, ext json.RawMessage) (json.RawMessage, error) { - if len(ext) == 0 { - return json.Marshal(openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{NoSale: noSaleBidders}}) - } - - var extMap map[string]interface{} - if err := json.Unmarshal(ext, &extMap); err != nil { - return nil, err +func setPrebidNoSaleWrite(noSaleBidders []string, ext *openrtb_ext.RequestExt) { + if ext == nil { + // This should hopefully not be possible. The only caller insures that this has been initialized + return } - var prebidExt map[string]interface{} - if prebidExtInterface, exists := extMap["prebid"]; exists { - // Reference Existing Prebid Ext Map - if prebidExtMap, ok := prebidExtInterface.(map[string]interface{}); ok { - prebidExt = prebidExtMap - } else { - return nil, errors.New("request.ext.prebid is not a json object") - } - } else { - // Create New Empty Prebid Ext Map - prebidExt = make(map[string]interface{}) - extMap["prebid"] = prebidExt + prebid := ext.GetPrebid() + if prebid == nil { + prebid = &openrtb_ext.ExtRequestPrebid{} } - - prebidExt["nosale"] = noSaleBidders - return json.Marshal(extMap) + prebid.NoSale = noSaleBidders + ext.SetPrebid(prebid) } diff --git a/privacy/ccpa/policy_test.go b/privacy/ccpa/policy_test.go index 416ebffa31a..ca6d0f8acf2 100644 --- a/privacy/ccpa/policy_test.go +++ b/privacy/ccpa/policy_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/mxmCherry/openrtb/v15/openrtb2" + "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" ) @@ -155,7 +156,8 @@ func TestReadFromRequest(t *testing.T) { } for _, test := range testCases { - result, err := ReadFromRequest(test.request) + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request} + result, err := ReadFromRequestWrapper(reqWrapper) assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expectedPolicy, result, test.description) } @@ -209,9 +211,20 @@ func TestWrite(t *testing.T) { } for _, test := range testCases { - err := test.policy.Write(test.request) + reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: test.request} + var err error + _, err = reqWrapper.GetRegExt() + if err == nil { + _, err = reqWrapper.GetRequestExt() + if err == nil { + err = test.policy.Write(reqWrapper) + if err == nil && reqWrapper.BidRequest != nil { + err = reqWrapper.RebuildRequest() + } + } + } assertError(t, test.expectedError, err, test.description) - assert.Equal(t, test.expected, test.request, test.description) + assert.Equal(t, test.expected, reqWrapper.BidRequest, test.description) } } @@ -237,6 +250,9 @@ func TestBuildRegs(t *testing.T) { regs: &openrtb2.Regs{ Ext: json.RawMessage(`malformed`), }, + expected: &openrtb2.Regs{ + Ext: json.RawMessage(`malformed`), + }, expectedError: true, }, { @@ -253,14 +269,22 @@ func TestBuildRegs(t *testing.T) { regs: &openrtb2.Regs{ Ext: json.RawMessage(`malformed`), }, + expected: &openrtb2.Regs{ + Ext: json.RawMessage(`malformed`), + }, expectedError: true, }, } for _, test := range testCases { - result, err := buildRegs(test.consent, test.regs) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}} + regsExt, err := request.GetRegExt() + if err == nil { + regsExt.SetUSPrivacy(test.consent) + request.RebuildRequest() + } assertError(t, test.expectedError, err, test.description) - assert.Equal(t, test.expected, result, test.description) + assert.Equal(t, test.expected, request.Regs, test.description) } } @@ -274,7 +298,7 @@ func TestBuildRegsClear(t *testing.T) { { description: "Nil Regs", regs: nil, - expected: nil, + expected: &openrtb2.Regs{Ext: nil}, }, { description: "Nil Regs.Ext", @@ -297,21 +321,28 @@ func TestBuildRegsClear(t *testing.T) { expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any"}`)}, }, { - description: "Invalid Regs.Ext Type - Still Cleared", - regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, - expected: &openrtb2.Regs{}, + description: "Invalid Regs.Ext Type - Returns Error, doesn't clear", + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expectedError: true, }, { description: "Malformed Regs.Ext", regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, expectedError: true, }, } for _, test := range testCases { - result, err := buildRegsClear(test.regs) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}} + regsExt, err := request.GetRegExt() + if err == nil { + regsExt.SetUSPrivacy("") + request.RebuildRequest() + } assertError(t, test.expectedError, err, test.description) - assert.Equal(t, test.expected, result, test.description) + assert.Equal(t, test.expected, request.Regs, test.description) } } @@ -354,23 +385,30 @@ func TestBuildRegsWrite(t *testing.T) { expected: &openrtb2.Regs{Ext: json.RawMessage(`{"other":"any","us_privacy":"anyConsent"}`)}, }, { - description: "Invalid Regs.Ext Type - Still Overwrites", - consent: "anyConsent", - regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, - expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":"anyConsent"}`)}, + description: "Invalid Regs.Ext Type - Doesn't Overwrite", + consent: "anyConsent", + regs: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`{"us_privacy":123}`)}, + expectedError: true, }, { description: "Malformed Regs.Ext", consent: "anyConsent", regs: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, + expected: &openrtb2.Regs{Ext: json.RawMessage(`malformed`)}, expectedError: true, }, } for _, test := range testCases { - result, err := buildRegsWrite(test.consent, test.regs) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Regs: test.regs}} + regsExt, err := request.GetRegExt() + if err == nil { + regsExt.SetUSPrivacy(test.consent) + request.RebuildRequest() + } assertError(t, test.expectedError, err, test.description) - assert.Equal(t, test.expected, result, test.description) + assert.Equal(t, test.expected, request.Regs, test.description) } } @@ -415,7 +453,14 @@ func TestBuildExt(t *testing.T) { } for _, test := range testCases { - result, err := buildExt(test.noSaleBidders, test.ext) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}} + reqExt, err := request.GetRequestExt() + var result json.RawMessage + if err == nil { + setPrebidNoSale(test.noSaleBidders, reqExt) + err = request.RebuildRequest() + result = request.Ext + } assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expected, result, test.description) } @@ -460,13 +505,13 @@ func TestBuildExtClear(t *testing.T) { }, { description: "Leaves Other Ext.Prebid Values", - ext: json.RawMessage(`{"prebid":{"nosale":["a","b"],"other":"any"}}`), - expected: json.RawMessage(`{"prebid":{"other":"any"}}`), + ext: json.RawMessage(`{"prebid":{"nosale":["a","b"],"aliases":{"a":"b"}}}`), + expected: json.RawMessage(`{"prebid":{"aliases":{"a":"b"}}}`), }, { description: "Leaves All Other Values", - ext: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"other":"123"}}`), - expected: json.RawMessage(`{"other":"ABC","prebid":{"other":"123"}}`), + ext: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"supportdeals":true}}`), + expected: json.RawMessage(`{"other":"ABC","prebid":{"supportdeals":true}}`), }, { description: "Malformed Ext", @@ -486,7 +531,14 @@ func TestBuildExtClear(t *testing.T) { } for _, test := range testCases { - result, err := buildExtClear(test.ext) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}} + reqExt, err := request.GetRequestExt() + var result json.RawMessage + if err == nil { + setPrebidNoSaleClear(reqExt) + err = request.RebuildRequest() + result = request.Ext + } assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expected, result, test.description) } @@ -539,43 +591,56 @@ func TestBuildExtWrite(t *testing.T) { { description: "Leaves Other Ext.Prebid Values", noSaleBidders: []string{"a", "b"}, - ext: json.RawMessage(`{"prebid":{"other":"any"}}`), - expected: json.RawMessage(`{"prebid":{"nosale":["a","b"],"other":"any"}}`), + ext: json.RawMessage(`{"prebid":{"supportdeals":true}}`), + expected: json.RawMessage(`{"prebid":{"supportdeals":true,"nosale":["a","b"]}}`), }, { description: "Leaves All Other Values", noSaleBidders: []string{"a", "b"}, - ext: json.RawMessage(`{"other":"ABC","prebid":{"other":"123"}}`), - expected: json.RawMessage(`{"other":"ABC","prebid":{"nosale":["a","b"],"other":"123"}}`), + ext: json.RawMessage(`{"other":"ABC","prebid":{"aliases":{"a":"b"}}}`), + expected: json.RawMessage(`{"other":"ABC","prebid":{"aliases":{"a":"b"},"nosale":["a","b"]}}`), }, { description: "Invalid Ext.Prebid No Sale Type - Still Overrides", noSaleBidders: []string{"a", "b"}, ext: json.RawMessage(`{"prebid":{"nosale":123}}`), - expected: json.RawMessage(`{"prebid":{"nosale":["a","b"]}}`), + expected: json.RawMessage(`{"prebid":{"nosale":123}}`), + expectedError: true, }, { description: "Invalid Ext.Prebid Type ", noSaleBidders: []string{"a", "b"}, ext: json.RawMessage(`{"prebid":"wrongtype"}`), + expected: json.RawMessage(`{"prebid":"wrongtype"}`), expectedError: true, }, { description: "Malformed Ext", noSaleBidders: []string{"a", "b"}, ext: json.RawMessage(`{malformed`), + expected: json.RawMessage(`{malformed`), expectedError: true, }, { description: "Malformed Ext.Prebid", noSaleBidders: []string{"a", "b"}, ext: json.RawMessage(`{"prebid":malformed}`), + expected: json.RawMessage(`{"prebid":malformed}`), expectedError: true, }, } for _, test := range testCases { - result, err := buildExtWrite(test.noSaleBidders, test.ext) + request := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.ext}} + reqExt, err := request.GetRequestExt() + var result json.RawMessage + if err == nil { + setPrebidNoSaleWrite(test.noSaleBidders, reqExt) + err = request.RebuildRequest() + result = request.Ext + } else { + result = test.ext + } assertError(t, test.expectedError, err, test.description) assert.Equal(t, test.expected, result, test.description) } diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index dc8f56425c5..9274c5b58be 100644 --- a/privacy/gdpr/policy_test.go +++ b/privacy/gdpr/policy_test.go @@ -17,11 +17,6 @@ func TestValidateConsent(t *testing.T) { consent: "", expected: false, }, - { - description: "TCF1 Valid", - consent: "BONV8oqONXwgmADACHENAO7pqzAAppY", - expected: true, - }, { description: "TCF2 Valid", consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", @@ -33,4 +28,4 @@ func TestValidateConsent(t *testing.T) { result := ValidateConsent(test.consent) assert.Equal(t, test.expected, result, test.description) } -} +} \ No newline at end of file diff --git a/privacy/scrubber.go b/privacy/scrubber.go index edaa5bb07c6..e07ebd0581b 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -225,11 +225,8 @@ func scrubUserExtIDs(userExt json.RawMessage) json.RawMessage { } _, hasEids := userExtParsed["eids"] - _, hasDigitrust := userExtParsed["digitrust"] - if hasEids || hasDigitrust { + if hasEids { delete(userExtParsed, "eids") - delete(userExtParsed, "digitrust") - result, err := json.Marshal(userExtParsed) if err == nil { return result diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 9207315f593..1198d0bbc9c 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -202,7 +202,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, @@ -327,7 +327,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{}, }, scrubUser: ScrubStrategyUserNone, @@ -340,7 +340,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, @@ -359,7 +359,7 @@ func TestScrubUser(t *testing.T) { BuyerUID: "anyBuyerUID", Yob: 42, Gender: "anyGender", - Ext: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + Ext: json.RawMessage(`{}`), Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, @@ -586,18 +586,18 @@ func TestScrubUserExtIDs(t *testing.T) { expected: json.RawMessage(`{"anyExisting":42}}`), }, { - description: "Remove eids + digitrust", - userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids", + userExt: json.RawMessage(`{"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}]}`), expected: json.RawMessage(`{}`), }, { - description: "Remove eids + digitrust - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids - 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 + digitrust - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"eids":[{"source":"anySource","id":"anyId","uids":[{"id":"anyId","ext":{"id":42}}],"ext":{"id":42}}],"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), + description: "Remove eids - 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}}`), }, { @@ -620,21 +620,6 @@ 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 digitrust Only", - userExt: json.RawMessage(`{"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{}`), - }, - { - description: "Remove digitrust Only - With Other Data", - userExt: json.RawMessage(`{"anyExisting":42,"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{"anyExisting":42}`), - }, - { - description: "Remove digitrust Only - With Other Nested Data", - userExt: json.RawMessage(`{"anyExisting":{"existing":42},"digitrust":{"id":"anyId","keyv":4,"pref":8}}`), - expected: json.RawMessage(`{"anyExisting":{"existing":42}}`), - }, } for _, test := range testCases { diff --git a/router/router.go b/router/router.go index 5b8ec014a1c..58bdee057f6 100644 --- a/router/router.go +++ b/router/router.go @@ -7,6 +7,10 @@ import ( "database/sql" "encoding/json" "fmt" + "github.com/prebid/prebid-server/analytics" + "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/usersync" + "github.com/prometheus/client_golang/prometheus" "io/ioutil" "net" "net/http" @@ -14,11 +18,6 @@ import ( "strings" "time" - "github.com/prebid/prebid-server/analytics" - "github.com/prebid/prebid-server/stored_requests" - "github.com/prebid/prebid-server/usersync" - "github.com/prometheus/client_golang/prometheus" - "github.com/prebid/prebid-server/currency" "github.com/prebid/prebid-server/errortypes" @@ -29,7 +28,6 @@ import ( "github.com/prebid/prebid-server/adapters/appnexus" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/ix" - "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/pubmatic" "github.com/prebid/prebid-server/adapters/pulsepoint" "github.com/prebid/prebid-server/adapters/rubicon" @@ -181,7 +179,6 @@ func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { "pulsepoint": pulsepoint.NewPulsePointLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPulsepoint)].Endpoint), "rubicon": rubicon.NewRubiconLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderRubicon)].Endpoint, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Username, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Password, cfg.Adapters[string(openrtb_ext.BidderRubicon)].XAPI.Tracker), - "lifestreet": lifestreet.NewLifestreetLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderLifestreet)].Endpoint), "conversant": conversant.NewConversantLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderConversant)].Endpoint), "adform": adform.NewAdformLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderAdform)].Endpoint), "sovrn": sovrn.NewSovrnLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderSovrn)].Endpoint), diff --git a/static/bidder-info/adagio.yaml b/static/bidder-info/adagio.yaml new file mode 100644 index 00000000000..3661191b3a1 --- /dev/null +++ b/static/bidder-info/adagio.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "dev@adagio.io" +gvlVendorID: 617 +modifyingVastXmlAllowed: false +debug: + allow: true +capabilities: + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/adf.yaml b/static/bidder-info/adf.yaml new file mode 100644 index 00000000000..776e208f562 --- /dev/null +++ b/static/bidder-info/adf.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "scope.sspp@adform.com" +gvlVendorID: 50 +capabilities: + app: + mediaTypes: + - banner + - native + - video + site: + mediaTypes: + - banner + - native + - video diff --git a/static/bidder-info/adhese.yaml b/static/bidder-info/adhese.yaml index 742d78344ce..25058e82409 100644 --- a/static/bidder-info/adhese.yaml +++ b/static/bidder-info/adhese.yaml @@ -1,5 +1,7 @@ maintainer: email: info@adhese.com +gvlVendorID: 553 +modifyingVastXmlAllowed: true capabilities: app: mediaTypes: @@ -8,4 +10,4 @@ capabilities: site: mediaTypes: - banner - - video \ No newline at end of file + - video diff --git a/static/bidder-info/adocean.yaml b/static/bidder-info/adocean.yaml index b3a23718a5a..6374d752c34 100644 --- a/static/bidder-info/adocean.yaml +++ b/static/bidder-info/adocean.yaml @@ -2,6 +2,9 @@ maintainer: email: "aoteam@gemius.com" gvlVendorID: 328 capabilities: + app: + mediaTypes: + - banner site: mediaTypes: - banner diff --git a/static/bidder-info/adtarget.yaml b/static/bidder-info/adtarget.yaml index d52f18ac697..cc064b9ca6b 100644 --- a/static/bidder-info/adtarget.yaml +++ b/static/bidder-info/adtarget.yaml @@ -1,5 +1,6 @@ maintainer: email: "kamil@adtarget.com.tr" +gvlVendorID: 779 capabilities: app: mediaTypes: diff --git a/static/bidder-info/algorix.yaml b/static/bidder-info/algorix.yaml new file mode 100644 index 00000000000..b8301d6cb79 --- /dev/null +++ b/static/bidder-info/algorix.yaml @@ -0,0 +1,8 @@ +maintainer: + email: "xunyunbo@algorix.co" +capabilities: + app: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/axonix.yaml b/static/bidder-info/axonix.yaml new file mode 100644 index 00000000000..3c73501d9cc --- /dev/null +++ b/static/bidder-info/axonix.yaml @@ -0,0 +1,15 @@ +maintainer: + email: support.axonix@emodoinc.com +gvlVendorID: 678 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/bidmyadz.yaml b/static/bidder-info/bidmyadz.yaml new file mode 100644 index 00000000000..70a995a2798 --- /dev/null +++ b/static/bidder-info/bidmyadz.yaml @@ -0,0 +1,13 @@ +maintainer: + email: "contact@bidmyadz.com" +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/bidscube.yaml b/static/bidder-info/bidscube.yaml new file mode 100644 index 00000000000..046eb6224d7 --- /dev/null +++ b/static/bidder-info/bidscube.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "support@bidscube.com" +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/bmtm.yaml b/static/bidder-info/bmtm.yaml new file mode 100644 index 00000000000..c13bf17db73 --- /dev/null +++ b/static/bidder-info/bmtm.yaml @@ -0,0 +1,8 @@ +maintainer: + email: dev@brightmountainmedia.com +modifyingVastXmlAllowed: false +capabilities: + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/criteo.yaml b/static/bidder-info/criteo.yaml index b27e1fae369..bfa098ba39d 100644 --- a/static/bidder-info/criteo.yaml +++ b/static/bidder-info/criteo.yaml @@ -1,5 +1,5 @@ maintainer: - email: "pi-direct@criteo.com" + email: "prebid@criteo.com" gvlVendorID: 91 capabilities: app: diff --git a/static/bidder-info/e_volution.yaml b/static/bidder-info/e_volution.yaml new file mode 100644 index 00000000000..6ea9dc7bac2 --- /dev/null +++ b/static/bidder-info/e_volution.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "admin@e-volution.ai" +gvlVendorID: 957 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/epom.yaml b/static/bidder-info/epom.yaml index 32afa346c9e..991944ea35f 100644 --- a/static/bidder-info/epom.yaml +++ b/static/bidder-info/epom.yaml @@ -1,6 +1,7 @@ maintainer: email: "support@epom.com" modifyingVastXmlAllowed: true +gvlVendorID: 849 capabilities: app: mediaTypes: diff --git a/static/bidder-info/inmobi.yaml b/static/bidder-info/inmobi.yaml index 3f8cdd8cb91..d62a2c9239d 100644 --- a/static/bidder-info/inmobi.yaml +++ b/static/bidder-info/inmobi.yaml @@ -1,8 +1,13 @@ maintainer: - email: "prebid-support@inmobi.com" - + email: "technology-irv@inmobi.com" +gvlVendorID: 333 capabilities: app: mediaTypes: - banner - video + - native + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/interactiveoffers.yaml b/static/bidder-info/interactiveoffers.yaml new file mode 100644 index 00000000000..9a36076bab9 --- /dev/null +++ b/static/bidder-info/interactiveoffers.yaml @@ -0,0 +1,9 @@ +maintainer: + email: "support@interactiveoffers.com" +capabilities: + app: + mediaTypes: + - banner + site: + mediaTypes: + - banner diff --git a/static/bidder-info/kayzen.yaml b/static/bidder-info/kayzen.yaml new file mode 100644 index 00000000000..45fd43c5a11 --- /dev/null +++ b/static/bidder-info/kayzen.yaml @@ -0,0 +1,15 @@ +maintainer: + email: "platform-dev@kayzen.io" +gvlVendorID: 528 +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native diff --git a/static/bidder-info/lifestreet.yaml b/static/bidder-info/madvertise.yaml similarity index 68% rename from static/bidder-info/lifestreet.yaml rename to static/bidder-info/madvertise.yaml index 34dc4eca2d9..6156940e6d5 100644 --- a/static/bidder-info/lifestreet.yaml +++ b/static/bidder-info/madvertise.yaml @@ -1,11 +1,11 @@ maintainer: - email: "mobile.tech@lifestreet.com" -gvlVendorID: 67 + email: "support@madvertise.com" +gvlVendorID: 153 capabilities: app: mediaTypes: - banner + - video site: mediaTypes: - banner - - video diff --git a/static/bidder-info/marsmedia.yaml b/static/bidder-info/marsmedia.yaml index e0267205a27..143b893ed9b 100644 --- a/static/bidder-info/marsmedia.yaml +++ b/static/bidder-info/marsmedia.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@mars.media" +gvlVendorID: 776 capabilities: app: mediaTypes: diff --git a/static/bidder-info/mobilefuse.yaml b/static/bidder-info/mobilefuse.yaml index 178e407d927..18a90a8866e 100644 --- a/static/bidder-info/mobilefuse.yaml +++ b/static/bidder-info/mobilefuse.yaml @@ -1,5 +1,6 @@ maintainer: email: prebid@mobilefuse.com +gvlVendorID: 909 capabilities: app: mediaTypes: diff --git a/static/bidder-info/operaads.yaml b/static/bidder-info/operaads.yaml new file mode 100644 index 00000000000..b95d81155c1 --- /dev/null +++ b/static/bidder-info/operaads.yaml @@ -0,0 +1,14 @@ +maintainer: + email: adtech-prebid-group@opera.com +modifyingVastXmlAllowed: true +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/pubnative.yaml b/static/bidder-info/pubnative.yaml index 65bc2659dfe..44bba23d7f3 100644 --- a/static/bidder-info/pubnative.yaml +++ b/static/bidder-info/pubnative.yaml @@ -1,5 +1,6 @@ maintainer: email: product@pubnative.net +gvlVendorID: 512 capabilities: app: mediaTypes: diff --git a/static/bidder-info/revcontent.yaml b/static/bidder-info/revcontent.yaml index c0216dfca61..57e887565ce 100644 --- a/static/bidder-info/revcontent.yaml +++ b/static/bidder-info/revcontent.yaml @@ -1,5 +1,6 @@ maintainer: email: "developers@revcontent.com" +gvlVendorID: 203 capabilities: app: mediaTypes: @@ -8,4 +9,4 @@ capabilities: site: mediaTypes: - banner - - native \ No newline at end of file + - native diff --git a/static/bidder-info/sa_lunamedia.yaml b/static/bidder-info/sa_lunamedia.yaml new file mode 100644 index 00000000000..181e1fd6c73 --- /dev/null +++ b/static/bidder-info/sa_lunamedia.yaml @@ -0,0 +1,14 @@ +maintainer: + email: "support@lunamedia.io" +gvlVendorID: 998 +capabilities: + app: + mediaTypes: + - banner + - video + - native + site: + mediaTypes: + - banner + - video + - native \ No newline at end of file diff --git a/static/bidder-info/smaato.yaml b/static/bidder-info/smaato.yaml index db3e61e5cc6..b73edb23d18 100644 --- a/static/bidder-info/smaato.yaml +++ b/static/bidder-info/smaato.yaml @@ -1,5 +1,6 @@ maintainer: email: "prebid@smaato.com" +gvlVendorID: 82 capabilities: app: mediaTypes: diff --git a/static/bidder-info/smilewanted.yaml b/static/bidder-info/smilewanted.yaml new file mode 100644 index 00000000000..81b0585bb5e --- /dev/null +++ b/static/bidder-info/smilewanted.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "tech@smilewanted.com" +gvlVendorID: 639 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-info/viewdeos.yaml b/static/bidder-info/viewdeos.yaml new file mode 100644 index 00000000000..9483e281de0 --- /dev/null +++ b/static/bidder-info/viewdeos.yaml @@ -0,0 +1,12 @@ +maintainer: + email: "contact@viewdeos.com" +gvlVendorID: 924 +capabilities: + app: + mediaTypes: + - banner + - video + site: + mediaTypes: + - banner + - video diff --git a/static/bidder-params/adagio.json b/static/bidder-params/adagio.json new file mode 100644 index 00000000000..955c58c73ec --- /dev/null +++ b/static/bidder-params/adagio.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adagio Adapter Params", + "description": "A schema which validates params accepted by the Adagio adapter", + "type": "object", + "required": [ + "organizationId", + "site", + "placement" + ], + "properties": { + "organizationId": { + "type": "string", + "description": "Name to identify the organization", + "minLength": 1 + }, + "site": { + "type": "string", + "description": "Name to identify the site", + "minLength": 1 + }, + "placement": { + "type": "string", + "description": "Name to identify the placement", + "minLength": 1 + }, + "pageviewId": { + "type": "string", + "description": "Name to identify the pageview" + }, + "pagetype": { + "type": "string", + "description": "Name to identify the page type" + }, + "category": { + "type": "string", + "description": "Name to identify the category" + }, + "subcategory": { + "type": "string", + "description": "Name to identify the subcategory" + }, + "environment": { + "type": "string", + "description": "Name to identify the environment" + }, + "features": { + "type": "object", + "patternProperties": { + "^[a-zA-Z_]": { "type": "string" } + } + }, + "prebidVersion:": { + "type": "string", + "description": "Name to identify the version of Prebid.js" + }, + "debug": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "cpm": { + "type": "number" + }, + "lazyLoad": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "threshold": { + "type": "number" + }, + "rootMargin": { + "type": "string" + } + } + } + } + }, + "native": { + "type": "object", + "properties": { + "context": { + "type": "number" + }, + "plcmttype": { + "type": "number" + } + } + } + } +} diff --git a/static/bidder-params/adf.json b/static/bidder-params/adf.json new file mode 100644 index 00000000000..a16df36d681 --- /dev/null +++ b/static/bidder-params/adf.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Adf 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" + } + }, + "required": ["mid"] +} diff --git a/static/bidder-params/admixer.json b/static/bidder-params/admixer.json index 886e33ff2bb..78671931561 100644 --- a/static/bidder-params/admixer.json +++ b/static/bidder-params/admixer.json @@ -8,7 +8,7 @@ "zone": { "type": "string", "description": "Zone ID.", - "pattern": "^([a-fA-F\\d\\-]{36})$" + "pattern": "^([a-fA-F\\d\\-]{32,36})$" }, "customFloor": { "type": "number", @@ -22,4 +22,4 @@ }, "required": ["zone"] -} +} \ No newline at end of file diff --git a/static/bidder-params/algorix.json b/static/bidder-params/algorix.json new file mode 100644 index 00000000000..732625f4549 --- /dev/null +++ b/static/bidder-params/algorix.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "AlgoriX Adapter Params", + "description": "A schema which validates params accepted by the AlgoriX adapter", + "type": "object", + "properties": { + "sid": { + "type": "string", + "description": "Your Sid", + "minLength": 1 + }, + "token": { + "type": "string", + "description": "Your Token", + "minLength": 1 + } + }, + "required": ["sid", "token"] +} \ No newline at end of file diff --git a/static/bidder-params/appnexus.json b/static/bidder-params/appnexus.json index 7da41a67055..ce5ddad0437 100644 --- a/static/bidder-params/appnexus.json +++ b/static/bidder-params/appnexus.json @@ -67,6 +67,10 @@ "type": "boolean", "description": "Boolean to signal AppNexus to apply the relevant payment rule" }, + "generate_ad_pod_id": { + "type": "boolean", + "description": "Boolean to signal AppNexus to add ad pod id to each request" + }, "private_sizes" :{ "type": "array", "items": { diff --git a/static/bidder-params/lifestreet.json b/static/bidder-params/axonix.json similarity index 53% rename from static/bidder-params/lifestreet.json rename to static/bidder-params/axonix.json index 2190d761e69..7a3762ce5e2 100644 --- a/static/bidder-params/lifestreet.json +++ b/static/bidder-params/axonix.json @@ -1,13 +1,14 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Lifestreet Adapter Params", - "description": "A schema which validates params accepted by the Lifestreet adapter", + "title": "Axonix Adapter Params", + "description": "A schema which validates params accepted by the Axonix adapter", "type": "object", "properties": { - "slot_tag": { + "supplyId": { "type": "string", - "description": "A tag which identifies the ad slot" + "minLength": 1, + "description": "Unique supply identifier" } }, - "required": ["slot_tag"] + "required": ["supplyId"] } diff --git a/static/bidder-params/beachfront.json b/static/bidder-params/beachfront.json index a7751b279cc..5b1e71a9ba8 100644 --- a/static/bidder-params/beachfront.json +++ b/static/bidder-params/beachfront.json @@ -4,7 +4,7 @@ "description": "A schema which validates params accepted by the Beachfront adapter", "type": "object", "properties": { - "appId" : { + "appId": { "type": "string", "description": "The id of an inventory target. This can only be used in requests that contain one media type. It will be applied to all imps in the request." }, @@ -14,17 +14,23 @@ "properties": { "video" : { "type": "string", + "title": "Video appId", "description": "An appId string that will be applied to video requests in this imp." }, "banner" : { "type": "string", + "title": "Banner appId", "description": "An appId string that will be applied to banner requests in this imp." } - } + }, + "anyOf":[ + {"required":["video"]}, + {"required":["banner"]} + ] }, "bidfloor": { "type": "number", - "description": "The price floor for the bid." + "description": "The price floor for the bid. Will override the bidfloor set for the impression." }, "videoResponseType": { "type": "string", @@ -32,9 +38,8 @@ } }, - "required": ["bidfloor"], - "oneOf": [{ - "required": ["appId"] }, { - "required": ["appIds"] - }] + "oneOf": [ + { "required": ["appIds"] }, + { "required": ["appId"] } + ] } diff --git a/static/bidder-params/between.json b/static/bidder-params/between.json index 032d38fec4b..64863462697 100644 --- a/static/bidder-params/between.json +++ b/static/bidder-params/between.json @@ -12,15 +12,6 @@ "publisher_id": { "type": "string", "description": "Publisher ID from Between Exchange control panel" - }, - "bid_floor": { - "type": "number", - "description": "The minimum price acceptable for a bid" - }, - "bid_floor_cur": { - "type": "string", - "description": "Currency of bid floor", - "enum": ["USD", "EUR", "RUB"] } }, "required": ["host", "publisher_id"] diff --git a/static/bidder-params/bidmyadz.json b/static/bidder-params/bidmyadz.json new file mode 100644 index 00000000000..4e7b1119e08 --- /dev/null +++ b/static/bidder-params/bidmyadz.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "BidMyAdz Adapter Params", + "description": "A schema which validates params accepted by the BidMyAdz adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string" + } + }, + "required": ["placementId"] + } \ No newline at end of file diff --git a/static/bidder-params/bidscube.json b/static/bidder-params/bidscube.json new file mode 100644 index 00000000000..88dc91ab391 --- /dev/null +++ b/static/bidder-params/bidscube.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "BidsCube Adapter Params", + "description": "A schema which validates params accepted by the BidsCube adapter", + "type": "object", + "properties": { + "placementId": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the BidsCube placement" + }, + "customParams": { + "type": "object", + "description": "User-defined targeting key-value pairs." + } + }, + "required" : [ "placementId" ] +} diff --git a/static/bidder-params/bmtm.json b/static/bidder-params/bmtm.json new file mode 100644 index 00000000000..f1fbbddc7c6 --- /dev/null +++ b/static/bidder-params/bmtm.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Bright Mountain Media Adapter Params", + "description": "A schema which validates params accepted by the Bright Mountain Media adapter", + "type": "object", + "properties": { + "placement_id": { + "type": "number", + "minimum": 1, + "description": "Placement ID from Bright Mountain Media" + } + }, + "required": [ + "placement_id" + ] +} \ No newline at end of file diff --git a/static/bidder-params/criteo.json b/static/bidder-params/criteo.json index 9d348a7eded..88c6fba5d3a 100644 --- a/static/bidder-params/criteo.json +++ b/static/bidder-params/criteo.json @@ -1,30 +1,50 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "Criteo adapter params", - "description": "The schema to validate Criteo specific params accepted by Criteo adapter", - "type": "object", - "properties": { - "zoneid": { - "type": "number", - "description": "Impression's zone ID.", - "minimum": 0 - }, - "networkid": { - "type": "number", - "description": "Impression's network ID.", - "minimum": 0 - } - }, - "anyOf": [ - { - "required": [ - "zoneid" - ] - }, - { - "required": [ - "networkid" - ] - } - ] +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Criteo adapter params", + "description": "The schema to validate Criteo specific params accepted by Criteo adapter", + "type": "object", + "properties": { + "zoneid": { + "type": "integer", + "description": "Impression's zone ID.", + "minimum": 0 + }, + "zoneId": { + "type": "integer", + "description": "Impression's zone ID, preferred.", + "minimum": 0 + }, + "networkid": { + "type": "integer", + "description": "Impression's network ID.", + "minimum": 0 + }, + "networkId": { + "type": "integer", + "description": "Impression's network ID, preferred.", + "minimum": 0 + } + }, + "anyOf": [ + { + "required": [ + "zoneid" + ] + }, + { + "required": [ + "zoneId" + ] + }, + { + "required": [ + "networkid" + ] + }, + { + "required": [ + "networkId" + ] + } + ] } \ No newline at end of file diff --git a/static/bidder-params/e_volution.json b/static/bidder-params/e_volution.json new file mode 100644 index 00000000000..18de2a6062d --- /dev/null +++ b/static/bidder-params/e_volution.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "E-volution Adapter Params", + "description": "A schema which validates params accepted by the E-volution adapter", + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "network or placement key" + } + }, + "required": ["key"] + } \ No newline at end of file diff --git a/static/bidder-params/interactiveoffers.json b/static/bidder-params/interactiveoffers.json new file mode 100644 index 00000000000..79338dcc40a --- /dev/null +++ b/static/bidder-params/interactiveoffers.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Interactive Offers Adapter Params", + "description": "A schema which validates params accepted by Interactive Offers adapter", + "type": "object", + "properties": { + "pubid": { + "type": "integer", + "description": "The publisher id" + } + }, + "required": ["pubid"] +} \ No newline at end of file diff --git a/static/bidder-params/ix.json b/static/bidder-params/ix.json index 155cfa21892..a7a5cb7308a 100644 --- a/static/bidder-params/ix.json +++ b/static/bidder-params/ix.json @@ -4,10 +4,20 @@ "description": "A schema which validates params accepted by the Ix adapter", "type": "object", "properties": { + "siteid": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the site selling the impression, preferred." + }, "siteId": { "type": "string", "minLength": 1, - "description": "An ID which identifies the site selling the impression" + "description": "An ID which identifies the site selling the impression." + }, + "siteID": { + "type": "string", + "minLength": 1, + "description": "An ID which identifies the site selling the impression." }, "size": { "type": "array", @@ -19,5 +29,9 @@ "description": "An array of two integer containing the dimension" } }, - "required": ["siteId"] + "oneOf": [ + {"required": ["siteid"]}, + {"required": ["siteId"]}, + {"required": ["siteID"]} + ] } diff --git a/static/bidder-params/kayzen.json b/static/bidder-params/kayzen.json new file mode 100644 index 00000000000..f2256c6b029 --- /dev/null +++ b/static/bidder-params/kayzen.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Kayzen Adapter Params", + "description": "A schema which validates params accepted by the Kayzen adapter", + "type": "object", + + "properties": { + "zone": { + "type": "string", + "minLength": 1, + "description": "Zone ID" + }, + "exchange": { + "type": "string", + "minLength": 1, + "description": "Exchange/Publisher Name" + } + }, + "required": ["zone", "exchange"] +} + diff --git a/static/bidder-params/madvertise.json b/static/bidder-params/madvertise.json new file mode 100644 index 00000000000..c2fbd941afd --- /dev/null +++ b/static/bidder-params/madvertise.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Madvertise Adapter Params", + "description": "A schema which validates params accepted by the Madvertise adapter", + "type": "object", + "properties": { + "zoneId": { + "type": "string", + "minLength": 7, + "description": "The zone ID provided by Madvertise" + } + }, + "required": [ + "zoneId" + ] +} \ No newline at end of file diff --git a/static/bidder-params/operaads.json b/static/bidder-params/operaads.json new file mode 100644 index 00000000000..5095c5b2d2b --- /dev/null +++ b/static/bidder-params/operaads.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A schema which validates params accepted by the OperaAds adapter", + "properties": { + "placementId": { + "description": "Placement ID", + "type": "string", + "minLength": 1 + }, + "endpointId": { + "description": "Endpoint ID", + "type": "string", + "minLength": 1 + }, + "publisherId": { + "description": "Publisher ID", + "type": "string", + "minLength": 1 + } + }, + "required": [ + "placementId", + "endpointId", + "publisherId" + ], + "title": "OperaAds Adapter Params", + "type": "object" +} \ No newline at end of file diff --git a/static/bidder-params/pangle.json b/static/bidder-params/pangle.json index 74085cb5e65..b36922c31b7 100644 --- a/static/bidder-params/pangle.json +++ b/static/bidder-params/pangle.json @@ -8,9 +8,27 @@ "type": "string", "description": "Access Token", "pattern": ".+" + }, + "appid": { + "type": "string", + "description": "App ID", + "pattern": "[0-9]+" + }, + "placementid": { + "type": "string", + "description": "Placement ID", + "pattern": "[0-9]+" } }, "required": [ "token" - ] -} + ], + "dependencies": { + "placementid": [ + "appid" + ], + "appid": [ + "placementid" + ] + } +} \ No newline at end of file diff --git a/static/bidder-params/sa_lunamedia.json b/static/bidder-params/sa_lunamedia.json new file mode 100644 index 00000000000..51ca09098e2 --- /dev/null +++ b/static/bidder-params/sa_lunamedia.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Sa_Lunamedia Adapter Params", + "description": "A schema which validates params accepted by the Sa_Lunamedia adapter", + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "network or placement key" + }, + "type": { + "type": "string", + "enum": ["network", "publisher"] + } + }, + "required": ["key"] + } \ No newline at end of file diff --git a/static/bidder-params/sharethrough.json b/static/bidder-params/sharethrough.json index ba6580e2a7b..1fe2949bc8f 100644 --- a/static/bidder-params/sharethrough.json +++ b/static/bidder-params/sharethrough.json @@ -26,6 +26,16 @@ "bidfloor": { "type": "number", "description": "The floor price, or minimum amount, a publisher will accept for an impression, given in CPM in USD" + }, + "data": { + "type": "object", + "description": "Ad-specific first party data", + "properties": { + "pbadslot": { + "type": "string", + "description": "Prebid Ad Slot, see: https://docs.prebid.org/features/pbAdSlot.html" + } + } } }, "required": ["pkey"] diff --git a/static/bidder-params/smaato.json b/static/bidder-params/smaato.json index aa91c4bacc5..e4584b86860 100644 --- a/static/bidder-params/smaato.json +++ b/static/bidder-params/smaato.json @@ -11,7 +11,25 @@ "adspaceId": { "type": "string", "description": "Identifier for specific ad placement is SOMA `adspaceId`" + }, + "adbreakId": { + "type": "string", + "description": "Identifier for specific adpod placement is SOMA `adbreakId`" } }, - "required": ["publisherId","adspaceId"] + "required": [ + "publisherId" + ], + "anyOf": [ + { + "required": [ + "adspaceId" + ] + }, + { + "required": [ + "adbreakId" + ] + } + ] } \ No newline at end of file diff --git a/static/bidder-params/smilewanted.json b/static/bidder-params/smilewanted.json new file mode 100644 index 00000000000..be4f9bc142d --- /dev/null +++ b/static/bidder-params/smilewanted.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "SmileWanted Adapter Params", + "description": "A schema which validates params accepted by the SmileWanted adapter", + "type": "object", + "properties": { + "zoneId": { + "type": "string", + "description": "An ID which identifies the SmileWanted zone code", + "minLength": 1 + } + }, + "required": ["zoneId"] +} diff --git a/static/bidder-params/tappx.json b/static/bidder-params/tappx.json index f8feb1913e9..1cf101a44f5 100644 --- a/static/bidder-params/tappx.json +++ b/static/bidder-params/tappx.json @@ -12,6 +12,10 @@ "type": "string", "description": "An ID which identifies the adunit" }, + "mktag": { + "type": "string", + "description": "Minimum bid for this impression expressed in CPM (USD)" + }, "endpoint": { "type": "string", "description": "Endpoint provided to publisher" @@ -19,6 +23,20 @@ "bidfloor": { "type": "number", "description": "Minimum bid for this impression expressed in CPM (USD)" + }, + "bcid": { + "type": "array", + "description": "Block list of CID", + "items": { + "type": "string" + } + }, + "bcrid": { + "type": "array", + "description": "Block list of CRID", + "items": { + "type": "string" + } } }, "required": ["host","tappxkey","endpoint"] diff --git a/static/bidder-params/viewdeos.json b/static/bidder-params/viewdeos.json new file mode 100644 index 00000000000..3e309e4b77a --- /dev/null +++ b/static/bidder-params/viewdeos.json @@ -0,0 +1,26 @@ +{ + "$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"] +} diff --git a/static/tcf1/fallback_gvl.json b/static/tcf1/fallback_gvl.json deleted file mode 100644 index 9f1c8506b32..00000000000 --- a/static/tcf1/fallback_gvl.json +++ /dev/null @@ -1 +0,0 @@ -{"vendorListVersion":215,"lastUpdated":"2020-08-13T16:00:19Z","purposes":[{"id":1,"name":"Information storage and access","description":"The storage of information, or access to information that is already stored, on your device such as advertising identifiers, device identifiers, cookies, and similar technologies."},{"id":2,"name":"Personalisation","description":"The collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as on other websites or apps, over time. Typically, the content of the site or app is used to make inferences about your interests, which inform future selection of advertising and/or content."},{"id":3,"name":"Ad selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver advertisements for you, and to measure the delivery and effectiveness of such advertisements. This includes using previously collected information about your interests to select ads, processing data about what advertisements were shown, how often they were shown, when and where they were shown, and whether you took any action related to the advertisement, including for example clicking an ad or making a purchase. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise advertising and/or content for you in other contexts, such as websites or apps, over time."},{"id":4,"name":"Content selection, delivery, reporting","description":"The collection of information, and combination with previously collected information, to select and deliver content for you, and to measure the delivery and effectiveness of such content. This includes using previously collected information about your interests to select content, processing data about what content was shown, how often or how long it was shown, when and where it was shown, and whether the you took any action related to the content, including for example clicking on content. This does not include personalisation, which is the collection and processing of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, such as websites or apps, over time."},{"id":5,"name":"Measurement","description":"The collection of information about your use of the content, and combination with previously collected information, used to measure, understand, and report on your usage of the service. This does not include personalisation, the collection of information about your use of this service to subsequently personalise content and/or advertising for you in other contexts, i.e. on other service, such as websites or apps, over time."}],"features":[{"id":1,"name":"Matching Data to Offline Sources","description":"Combining data from offline sources that were initially collected in other contexts."},{"id":2,"name":"Linking Devices","description":"Allow processing of a user's data to connect such user across multiple devices."},{"id":3,"name":"Precise Geographic Location Data","description":"Allow processing of a user's precise geographic location data in support of a purpose for which that certain third party has consent."}],"vendors":[{"id":8,"name":"Emerse Sverige AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.emerse.com/privacy-policy/"},{"id":9,"name":"AdMaxim Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.admaxim.com/admaxim-privacy-policy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":12,"name":"BeeswaxIO Corporation","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beeswax.com/privacy/"},{"id":28,"name":"TripleLift, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://triplelift.com/privacy/"},{"id":27,"name":"ADventori SAS","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adventori.com/with-us/legal-notice/"},{"id":25,"name":"Verizon Media EMEA Limited","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.verizonmedia.com/policies/ie/en/verizonmedia/privacy/index.html"},{"id":26,"name":"Venatus Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.venatusmedia.com/privacy/"},{"id":1,"name":"Exponential Interactive, Inc d/b/a VDX.tv","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://vdx.tv/privacy/"},{"id":6,"name":"AdSpirit GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adspirit.de/privacy"},{"id":30,"name":"BidTheatre AB","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.bidtheatre.com/privacy-policy"},{"id":24,"name":"Epsilon","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.conversantmedia.eu/legal/privacy-policy"},{"id":29,"name":"Etarget SE","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.etarget.sk/privacy.php","deletedDate":"2020-06-01T00:00:00Z"},{"id":39,"name":"ADITION technologies AG","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.adition.com/datenschutz"},{"id":11,"name":"Quantcast International Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.quantcast.com/privacy/"},{"id":15,"name":"Adikteev","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adikteev.com/privacy-policy-eng/"},{"id":4,"name":"Roq.ad Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.roq.ad/privacy-policy"},{"id":7,"name":"Vibrant Media Limited","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vibrantmedia.com/en/privacy-policy/"},{"id":2,"name":"Captify Technologies Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.captify.co.uk/privacy-policy/"},{"id":37,"name":"NEURAL.ONE","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://web.neural.one/privacy-policy/"},{"id":13,"name":"Sovrn Holdings Inc","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sovrn.com/sovrn-privacy/"},{"id":34,"name":"NEORY GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.neory.com/privacy.html"},{"id":32,"name":"Xandr, Inc.","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.xandr.com/privacy/platform-privacy-policy/"},{"id":10,"name":"Index Exchange, Inc. ","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[2,3],"policyUrl":"https://www.indexexchange.com/privacy"},{"id":57,"name":"ADARA MEDIA UNLIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://adara.com/privacy-promise/"},{"id":63,"name":"Avocet Systems Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://avocet.io/privacy-portal"},{"id":51,"name":"xAd, Inc. dba GroundTruth","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.groundtruth.com/privacy-policy/"},{"id":49,"name":"TRADELAB","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://tradelab.com/en/privacy/"},{"id":45,"name":"Smart Adserver","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://smartadserver.com/end-user-privacy-policy/"},{"id":52,"name":"The Rubicon Project, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[3],"policyUrl":"http://www.rubiconproject.com/rubicon-project-yield-optimization-privacy-policy/"},{"id":71,"name":"Roku Advertising Services","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://docs.roku.com/published/userprivacypolicy/en/us"},{"id":79,"name":"MediaMath, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.mediamath.com/privacy-policy/"},{"id":91,"name":"Criteo SA","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.criteo.com/privacy/"},{"id":85,"name":"Crimtan Holdings Limited","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[1,3],"policyUrl":"https://crimtan.com/privacy/"},{"id":16,"name":"RTB House S.A.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.rtbhouse.com/privacy-center/services-privacy-policy/"},{"id":86,"name":"Scene Stealer Limited","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"http://scenestealer.tv/privacy-policy/"},{"id":94,"name":"Blis Media Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.blis.com/privacy/"},{"id":73,"name":"Simplifi Holdings Inc.","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2,3],"policyUrl":"https://simpli.fi/site-privacy-policy/"},{"id":33,"name":"ShareThis, Inc","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://sharethis.com/privacy/"},{"id":20,"name":"N Technologies Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://n.rich/privacy-notice"},{"id":55,"name":"Madison Logic, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.madisonlogic.com/privacy/"},{"id":53,"name":"Sirdata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.sirdata.com/privacy/"},{"id":69,"name":"OpenX","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.openx.com/legal/privacy-policy/"},{"id":98,"name":"GroupM UK Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.groupm.com/privacy-notice"},{"id":62,"name":"Justpremium BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://justpremium.com/privacy-policy/"},{"id":19,"name":"Intent Media, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://intentmedia.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":43,"name":"Vdopia DBA Chocolate Platform","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://chocolateplatform.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":36,"name":"RhythmOne DBA Unruly Group Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.rhythmone.com/privacy-policy"},{"id":80,"name":"Sharethrough, Inc","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://platform-cdn.sharethrough.com/privacy-policy"},{"id":81,"name":"PulsePoint, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pulsepoint.com/privacy-policy/website","deletedDate":"2020-07-06T00:00:00Z"},{"id":23,"name":"Amobee, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.amobee.com/trust/privacy-guidelines"},{"id":35,"name":"Purch Group, Inc.","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://www.purch.com/privacy-policy/","deletedDate":"2019-05-30T00:00:00Z"},{"id":3,"name":"affilinet","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.affili.net/de/footeritem/datenschutz","deletedDate":"2019-06-21T00:00:00Z"},{"id":74,"name":"Admotion SRL","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.admotion.com/policy/","deletedDate":"2019-07-24T00:00:00Z"},{"id":191,"name":"realzeit GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://realzeitmedia.com/privacy.html","deletedDate":"2019-04-29T00:00:00Z"},{"id":197,"name":"Switch Concepts Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.switchconcepts.com/privacy-policy","deletedDate":"2019-07-26T00:00:00Z"},{"id":390,"name":"Parsec Media Inc.","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,3],"policyUrl":"www.parsec.media/privacy-policy","deletedDate":"2019-06-27T00:00:00Z"},{"id":459,"name":"uppr GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://netzwerk.uppr.de/privacy-policy.do","deletedDate":"2019-06-17T00:00:00Z"},{"id":221,"name":"LEMO MEDIA GROUP LIMITED","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.lemomedia.com/terms.pdf","deletedDate":"2019-06-28T00:00:00Z"},{"id":478,"name":"RevLifter Ltd","purposeIds":[1],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.revlifter.com/privacy-policy","deletedDate":"2019-07-15T00:00:00Z"},{"id":500,"name":"Turbo","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.turboadv.com/white-rabbit-privacy-policy/","deletedDate":"2019-07-12T00:00:00Z"},{"id":68,"name":"Sizmek by Amazon","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.sizmek.com/privacy-policy/"},{"id":75,"name":"M32 Connect Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://m32.media/privacy-cookie-policy/"},{"id":17,"name":"Greenhouse Group BV (with its trademark LemonPI)","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.lemonpi.io/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":61,"name":"GumGum, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://gumgum.com/privacy-policy"},{"id":40,"name":"Active Agent (ADITION technologies AG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.active-agent.com/de/unternehmen/datenschutzerklaerung/"},{"id":76,"name":"PubMatic, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://pubmatic.com/privacy-policy/"},{"id":89,"name":"Tapad, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://www.tapad.com/eu-privacy-policy"},{"id":46,"name":"Skimbit Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://skimlinks.com/pages/privacy-policy"},{"id":66,"name":"adsquare GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adsquare.com/privacy"},{"id":105,"name":"Impression Desk Technologies Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://impressiondesk.com/privacy-policy/","deletedDate":"2019-08-06T00:00:00Z"},{"id":41,"name":"Adverline","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.adverline.com/privacy/"},{"id":82,"name":"Smaato, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.smaato.com/privacy/"},{"id":60,"name":"Rakuten Marketing LLC","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://rakutenadvertising.com/legal-notices/services-privacy-policy/"},{"id":70,"name":"Yieldlab AG","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[3],"policyUrl":"http://www.yieldlab.de/meta-navigation/datenschutz/"},{"id":50,"name":"Adform","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://site.adform.com/privacy-center/platform-privacy/product-and-services-privacy-policy/"},{"id":48,"name":"NetSuccess, s.r.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inres.sk/pp/"},{"id":100,"name":"Fifty Technology Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://fifty.io/privacy-policy.php"},{"id":21,"name":"The Trade Desk","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2,3],"policyUrl":"https://www.thetradedesk.com/general/privacy-policy"},{"id":110,"name":"Dynata LLC","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.opinionoutpost.co.uk/en-gb/policies/privacy"},{"id":42,"name":"Taboola Europe Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.taboola.com/privacy-policy"},{"id":112,"name":"Maytrics GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://maytrics.com/privacy.php","deletedDate":"2019-09-17T00:00:00Z"},{"id":77,"name":"comScore, Inc.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.scorecardresearch.com/privacy.aspx?newlanguage=1"},{"id":109,"name":"LoopMe Limited","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://loopme.com/privacy-policy/"},{"id":120,"name":"Eyeota Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.eyeota.com/privacy-center"},{"id":93,"name":"Adloox SA","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://adloox.com/disclaimer"},{"id":132,"name":"Teads ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.teads.com/privacy-policy/"},{"id":22,"name":"admetrics GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://admetrics.io/en/privacy_policy/"},{"id":102,"name":"Telaria SAS","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":108,"name":"Rich Audience Technologies SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://richaudience.com/privacy/"},{"id":18,"name":"Widespace AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.widespace.com/legal/privacy-policy-notice/"},{"id":122,"name":"Avid Media Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.avidglobalmedia.eu/privacy-policy.html"},{"id":97,"name":"LiveRamp, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.liveramp.com/service-privacy-policy/"},{"id":138,"name":"ConnectAd Realtime GmbH","purposeIds":[1,2],"legIntPurposeIds":[3,4],"featureIds":[],"policyUrl":"http://connectadrealtime.com/privacy/"},{"id":72,"name":"Nano Interactive GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.nanointeractive.com/privacy"},{"id":127,"name":"PIXIMEDIA SAS","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://piximedia.com/privacy/"},{"id":136,"name":"Str\u00f6er SSP GmbH (SSP)","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[2,3],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":111,"name":"Showheroes SE","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://showheroes.com/privacy/"},{"id":56,"name":"Confiant Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.confiant.com/privacy","deletedDate":"2020-05-18T00:00:00Z"},{"id":124,"name":"Teemo SA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://teemo.co/fr/confidentialite/"},{"id":154,"name":"YOC AG","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://yoc.com/privacy/"},{"id":38,"name":"Beemray Oy","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.beemray.com/privacy-policy/","deletedDate":"2020-06-19T00:00:00Z"},{"id":101,"name":"MiQ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://wearemiq.com/privacy-policy/"},{"id":149,"name":"ADman Interactive SLU","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://admanmedia.com/politica.html?setLng=es"},{"id":151,"name":"Admedo Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[3],"policyUrl":"https://www.admedo.com/privacy-policy","deletedDate":"2020-07-17T00:00:00Z"},{"id":153,"name":"MADVERTISE MEDIA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://madvertise.com/en/gdpr/"},{"id":159,"name":"Underdog Media LLC ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://underdogmedia.com/privacy-policy/"},{"id":157,"name":"Seedtag Advertising S.L","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.seedtag.com/en/privacy-policy/"},{"id":145,"name":"Snapsort Inc., operating as Sortable","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://help.sortable.com/help/privacy-policy"},{"id":131,"name":"ID5 Technology SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.id5.io/privacy"},{"id":158,"name":"Reveal Mobile, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://revealmobile.com/privacy"},{"id":147,"name":"Adacado Technologies Inc. (DBA Adacado)","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adacado.com/privacy-policy-april-25-2018/"},{"id":130,"name":"NextRoll, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.nextroll.com/privacy"},{"id":129,"name":"IPONWEB GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.iponweb.com/privacy-policy/"},{"id":128,"name":"BIDSWITCH GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bidswitch.com/privacy-policy/"},{"id":168,"name":"EASYmedia GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://login.rtbmarket.com/gdpr"},{"id":164,"name":"Outbrain UK Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.outbrain.com/legal/privacy#privacy-policy"},{"id":144,"name":"district m inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://districtm.net/en/page/platforms-data-and-privacy-policy/"},{"id":163,"name":"Bombora Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://bombora.com/privacy"},{"id":173,"name":"Yieldmo, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.yieldmo.com/privacy/"},{"id":88,"name":"TreSensa, Inc.","purposeIds":[1,3],"legIntPurposeIds":[2,5],"featureIds":[1],"policyUrl":"https://www.tresensa.com/eu-privacy"},{"id":78,"name":"Flashtalking, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.flashtalking.com/privacypolicy/"},{"id":59,"name":"Sift Media, Inc","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.sift.co/privacy"},{"id":114,"name":"Sublime","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://ayads.co/privacy.php"},{"id":175,"name":"FORTVISION","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://fortvision.com/POC/index.html","deletedDate":"2019-08-09T00:00:00Z"},{"id":133,"name":"digitalAudience","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://digitalaudience.io/legal/privacy-cookies/"},{"id":14,"name":"Adkernel LLC","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://adkernel.com/privacy-policy/"},{"id":180,"name":"Thirdpresence Oy","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"http://www.thirdpresence.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":183,"name":"EMX Digital LLC","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://emxdigital.com/privacy/"},{"id":58,"name":"33Across","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.33across.com/privacy-policy"},{"id":140,"name":"Platform161","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://platform161.com/cookie-and-privacy-policy/"},{"id":90,"name":"Teroa S.A.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.e-planning.net/en/privacy.html"},{"id":141,"name":"1020, Inc. dba Placecast and Ericsson Emodo","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.emodoinc.com/privacy-policy/"},{"id":142,"name":"Media.net Advertising FZ-LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.media.net/en/privacy-policy"},{"id":209,"name":"Delta Projects AB","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[3],"policyUrl":"https://deltaprojects.com/data-collection-policy"},{"id":195,"name":"advanced store GmbH","purposeIds":[2,3],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.advanced-store.com/de/datenschutz/"},{"id":190,"name":"video intelligence AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.vi.ai/privacy-policy/"},{"id":84,"name":"Semasio GmbH","purposeIds":[],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"http://www.semasio.com/privacy-policy/"},{"id":65,"name":"Location Sciences AI Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.locationsciences.ai/privacy-policy/"},{"id":210,"name":"Zemanta, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1],"policyUrl":"http://www.zemanta.com/legal/privacy"},{"id":200,"name":"Tapjoy, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.tapjoy.com/legal/#privacy-policy"},{"id":188,"name":"Sellpoints Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://retargeter.com/service-privacy-policy/","deletedDate":"2019-09-17T00:00:00Z"},{"id":217,"name":"2KDirect, Inc. (dba iPromote)","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.ipromote.com/privacy-policy/"},{"id":156,"name":"Centro, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.centro.net/privacy-policy/"},{"id":194,"name":"Rezonence Limited","purposeIds":[3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://rezonence.com/privacy-policy/"},{"id":226,"name":"Publicis Media GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.publicismedia.de/datenschutz/"},{"id":198,"name":"SYNC","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://redirect.sync.tv/privacy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":227,"name":"ORTEC B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.ortecadscience.com/privacy-policy/"},{"id":225,"name":"Ligatus GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.ligatus.com/en/privacy-policy","deletedDate":"2020-06-19T00:00:00Z"},{"id":205,"name":"Adssets AB","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"http://adssets.com/policy/"},{"id":179,"name":"Collective Europe Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.collectiveuk.com/privacy.html"},{"id":31,"name":"Ogury Ltd.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://www.ogury.com/privacy-policy/"},{"id":92,"name":"1plusX AG","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.1plusx.com/privacy-policy/"},{"id":155,"name":"AntVoice","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.antvoice.com/en/privacypolicy/"},{"id":115,"name":"smartclip Europe GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[2],"policyUrl":"https://privacy-portal.smartclip.net/"},{"id":126,"name":"DoubleVerify Inc.\u200b","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.doubleverify.com/privacy/"},{"id":193,"name":"Mediasmart Mobile S.L.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://mediasmart.io/privacy/"},{"id":245,"name":"IgnitionOne","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.ignitionone.com/privacy-policy/","deletedDate":"2020-06-30T00:00:00Z"},{"id":213,"name":"emetriq GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.emetriq.com/datenschutz/"},{"id":244,"name":"Temelio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://temelio.com/vie-privee"},{"id":224,"name":"adrule mobile GmbH","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.adrule.net/de/datenschutz/"},{"id":174,"name":"A Million Ads Ltd","purposeIds":[2],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://www.amillionads.com/privacy-policy"},{"id":192,"name":"remerge GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://remerge.io/privacy-policy.html"},{"id":232,"name":"Rockerbox, Inc","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"http://rockerbox.com/privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":256,"name":"Bounce Exchange, Inc","purposeIds":[1],"legIntPurposeIds":[2,4,5],"featureIds":[1,2],"policyUrl":"https://www.bouncex.com/privacy/"},{"id":234,"name":"ZBO Media","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zbo.media/mentions-legales/politique-de-confidentialite-service-publicitaire/"},{"id":246,"name":"Smartology Limited","purposeIds":[3],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://www.smartology.net/privacy-policy/"},{"id":241,"name":"OneTag Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.onetag.com/privacy/"},{"id":254,"name":"LiquidM Technology GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liquidm.com/privacy-policy/"},{"id":215,"name":"ARMIS SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://armis.tech/en/armis-personal-data-privacy-policy/"},{"id":167,"name":"Audiens S.r.l.","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.audiens.com/privacy"},{"id":240,"name":"7Hops.com Inc. (ZergNet)","purposeIds":[],"legIntPurposeIds":[1,4,5],"featureIds":[],"policyUrl":"https://zergnet.com/privacy"},{"id":235,"name":"Bucksense Inc","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.bucksense.com/platform-privacy-policy/"},{"id":185,"name":"Bidtellect, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.bidtellect.com/privacy-policy/"},{"id":258,"name":"Adello Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[3],"policyUrl":"https://www.adello.com/privacy-policy/"},{"id":169,"name":"RTK.IO, Inc","purposeIds":[1,4],"legIntPurposeIds":[2,3,5],"featureIds":[1,3],"policyUrl":"http://www.rtk.io/privacy.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":208,"name":"Spotad","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.spotad.co/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":211,"name":"AdTheorent, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://adtheorent.com/privacy-policy"},{"id":229,"name":"Digitize New Media Ltd","purposeIds":[2,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitize.ie/online-privacy","deletedDate":"2020-07-17T00:00:00Z"},{"id":273,"name":"Bannerflow AB","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.bannerflow.com/privacy "},{"id":104,"name":"Sonobi, Inc","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"http://sonobi.com/privacy-policy/"},{"id":162,"name":"Unruly Group Ltd","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://unruly.co/privacy/"},{"id":249,"name":"Spolecznosci Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.spolecznosci.pl/polityka-prywatnosci"},{"id":125,"name":"Research Now Group, Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.valuedopinions.co.uk/privacy","deletedDate":"2019-09-17T00:00:00Z"},{"id":170,"name":"Goodway Group, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://goodwaygroup.com/privacy-policy/"},{"id":160,"name":"Netsprint SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://netsprint.eu/privacy.html"},{"id":189,"name":"Intowow Innovation Ltd.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.intowow.com/privacy/","deletedDate":"2019-08-12T00:00:00Z"},{"id":279,"name":"Mirando GmbH & Co KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://wwwmirando.de/datenschutz/"},{"id":269,"name":"Sanoma Media Finland","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://sanoma.fi/tietoa-meista/tietosuoja/","deletedDate":"2019-08-07T00:00:00Z"},{"id":276,"name":"Viralize SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://viralize.com/privacy-policy"},{"id":87,"name":"Genius Sports Media Limited","purposeIds":[2,4],"legIntPurposeIds":[1,3,5],"featureIds":[2,3],"policyUrl":"https://www.geniussports.com/privacy-policy"},{"id":182,"name":"Collective, Inc. dba Visto","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vistohub.com/privacy-policy/","deletedDate":"2019-07-26T00:00:00Z"},{"id":255,"name":"Onnetwork Sp. z o.o.","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.onnetwork.tv/pp_services.php"},{"id":203,"name":"Revcontent, LLC","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://intercom.help/revcontent2/en/articles/2290675-revcontent-s-privacy-policy"},{"id":260,"name":"RockYou, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,5],"featureIds":[3],"policyUrl":"https://rockyou.com/privacy-policy/","deletedDate":"2019-08-09T00:00:00Z"},{"id":237,"name":"LKQD, a division of Nexstar Digital, LLC.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.lkqd.com/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":274,"name":"Golden Bees","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.goldenbees.fr/en/privacy-charter/"},{"id":280,"name":"Spot.IM LTD","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.spot.im/privacy/"},{"id":239,"name":"Triton Digital Canada Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.tritondigital.com/privacy-policies"},{"id":177,"name":"plista GmbH","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.plista.com/about/privacy/"},{"id":201,"name":"TimeOne","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://privacy.timeonegroup.com/en/","deletedDate":"2020-05-15T00:00:00Z"},{"id":150,"name":"Inskin Media LTD","purposeIds":[2,3,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"http://www.inskinmedia.com/privacy-policy.html"},{"id":252,"name":"Jaduda GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.jadudamobile.com/datenschutzerklaerung/"},{"id":248,"name":"Converge-Digital","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://converge-digital.com/privacy-policy/"},{"id":161,"name":"Smadex SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://smadex.com/end-user-privacy-policy/"},{"id":285,"name":"Comcast International France SAS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.freewheel.com/privacy-policy"},{"id":228,"name":"McCann Discipline LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.primis.tech/privacy-policy/"},{"id":299,"name":"AdClear GmbH","purposeIds":[1,5],"legIntPurposeIds":[2,3,4],"featureIds":[1,2],"policyUrl":"https://www.adclear.de/datenschutzerklaerung/"},{"id":277,"name":"Codewise VL Sp. z o.o. Sp. k","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://voluumdsp.com/end-user-privacy-policy/"},{"id":259,"name":"ADYOULIKE SA","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.adyoulike.com/privacy_policy.php"},{"id":272,"name":"A.Mob","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.we-are-adot.com/privacy-policy/"},{"id":230,"name":"Steel House, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://steelhouse.com/privacy-policy/"},{"id":253,"name":"Improve Digital BV","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.improvedigital.com/platform-privacy-policy"},{"id":304,"name":"On Device Research Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://s.on-device.com/privacyPolicy"},{"id":314,"name":"Keymantics","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.keymantics.com/assets/privacy-policy.pdf"},{"id":257,"name":"R-TARGET","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"http://www.r-target.com/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":317,"name":"mainADV Srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.mainad.com/privacy-policy/"},{"id":278,"name":"Integral Ad Science, Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://integralads.com/privacy-policy/"},{"id":291,"name":"Qwertize","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.qwertize.com/en/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":295,"name":"Sojern, Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.sojern.com/privacy/product-privacy-policy/"},{"id":315,"name":"Celtra, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.celtra.com/privacy-policy/"},{"id":165,"name":"SpotX, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1],"policyUrl":"https://www.spotx.tv/privacy-policy/"},{"id":47,"name":"ADMAN - Phaistos Networks, S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.adman.gr/privacy"},{"id":134,"name":"SMARTSTREAM.TV GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2],"policyUrl":"https://www.smartstream.tv/en/productprivacy"},{"id":325,"name":"Knorex","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.knorex.com/privacy"},{"id":316,"name":"Gamned","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.gamned.com/privacy-policy/"},{"id":318,"name":"Accorp Sp. z o.o.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"http://www.instytut-pollster.pl/privacy-policy/"},{"id":199,"name":"ADUX","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adux.com/donnees-personelles/"},{"id":236,"name":"PowerLinks Media Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[3],"policyUrl":"https://www.powerlinks.com/privacy-policy/"},{"id":294,"name":"Jivox Corporation","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.jivox.com/privacy"},{"id":143,"name":"Connatix Native Exchange Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://connatix.com/privacy-policy/"},{"id":297,"name":"Polar Mobile Group Inc.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://privacy.polar.me"},{"id":319,"name":"Clipcentric, Inc.","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://clipcentric.com/privacy.bhtml"},{"id":290,"name":"Readpeak Oy","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://readpeak.com/privacy-policy/"},{"id":323,"name":"DAZN Media Services Limited","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://www.goal.com/en-gb/legal/privacy-policy"},{"id":119,"name":"Fusio by S4M","purposeIds":[1,2,5],"legIntPurposeIds":[3],"featureIds":[1,3],"policyUrl":"http://www.s4m.io/privacy-policy/"},{"id":302,"name":"Mobile Professionals BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mobpro.com/privacy.html"},{"id":212,"name":"usemax advertisement (Emego GmbH)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.usemax.de/?l=privacy"},{"id":264,"name":"Adobe Advertising Cloud","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.adobe.com/privacy/experience-cloud.html"},{"id":44,"name":"The ADEX GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://theadex.com/privacy-opt-out/"},{"id":282,"name":"Welect GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.welect.de/datenschutz"},{"id":238,"name":"StackAdapt","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.stackadapt.com/privacy"},{"id":284,"name":"WEBORAMA","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://weborama.com/privacy_en/"},{"id":148,"name":"Liveintent Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://liveintent.com/services-privacy-policy/"},{"id":64,"name":"DigiTrust / IAB Tech Lab","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.digitru.st/privacy-policy/"},{"id":301,"name":"zeotap GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://zeotap.com/privacy_policy"},{"id":275,"name":"TabMo SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://static.tabmo.io.s3.amazonaws.com/privacy-policy/index.html"},{"id":310,"name":"Adevinta Spain S.L.U.","purposeIds":[],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"https://www.adevinta.com/about/privacy/"},{"id":139,"name":"Permodo GmbH","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://permodo.com/de/privacy.html"},{"id":326,"name":"AdTiming Technology Company Limited","purposeIds":[3,5],"legIntPurposeIds":[1,2,4],"featureIds":[],"policyUrl":"http://www.adtiming.com/en/privacypolicy.html"},{"id":262,"name":"Fyber ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.fyber.com/legal/privacy-policy/"},{"id":331,"name":"ad6media","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.ad6media.fr/privacy"},{"id":345,"name":"The Kantar Group Limited","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.kantar.com/cookies-policies"},{"id":308,"name":"Rockabox Media Ltd","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[],"policyUrl":"http://scoota.com/privacy-policy"},{"id":270,"name":"Marfeel Solutions, SL","purposeIds":[],"legIntPurposeIds":[2],"featureIds":[],"policyUrl":"https://www.marfeel.com/privacy-policy/"},{"id":333,"name":"InMobi Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":202,"name":"Telaria, Inc","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://telaria.com/privacy-policy/"},{"id":328,"name":"Gemius SA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.gemius.com/cookie-policy.html"},{"id":281,"name":"Wizaly","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.wizaly.com/terms-of-use#privacy-policy"},{"id":354,"name":"Apester Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://apester.com/privacy-policy/"},{"id":320,"name":"Adelphic LLC","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://adelphic.com/platform/privacy/"},{"id":359,"name":"AerServ LLC","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.inmobi.com/privacy-policy-for-eea"},{"id":265,"name":"Instinctive, Inc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://instinctive.io/privacy"},{"id":349,"name":"Optomaton UG","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://optomaton.com/privacy.html"},{"id":288,"name":"Video Media Groep B.V.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://www.videomediagroup.com/wp-content/uploads/2016/01/Privacy-policy-VMG.pdf","deletedDate":"2019-09-17T00:00:00Z"},{"id":266,"name":"Digilant Spain, SLU","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.digilant.com/es/politica-privacidad/"},{"id":339,"name":"Vuble","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.vuble.tv/us/privacy","deletedDate":"2019-08-26T00:00:00Z"},{"id":303,"name":"Orion Semantics","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://static.orion-semantics.com/privacy.html"},{"id":261,"name":"Signal Digital Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.signal.co/privacy-policy/"},{"id":83,"name":"Visarity Technologies GmbH","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://primo.design/docs/PrivacyPolicyPrimo.html"},{"id":343,"name":"DIGITEKA Technologies","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.ultimedia.com/POLICY.html"},{"id":330,"name":"Linicom","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://www.linicom.com/privacy/","deletedDate":"2020-06-08T00:00:00Z"},{"id":231,"name":"AcuityAds Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.acuityads.com/corporate-privacy-policy.html"},{"id":216,"name":"Mindlytix SAS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://mindlytix.com/privacy/"},{"id":311,"name":"Mobfox US LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobfox.com/privacy-policy/"},{"id":358,"name":"MGID Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mgid.com/privacy-policy"},{"id":152,"name":"Meetrics GmbH","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.meetrics.com/en/data-privacy/"},{"id":251,"name":"Yieldlove GmbH","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"http://www.yieldlove.com/cookie-policy"},{"id":344,"name":"My6sense Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[2,4],"featureIds":[],"policyUrl":"https://my6sense.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":347,"name":"Ezoic Inc.","purposeIds":[2,4,5],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.ezoic.com/terms/"},{"id":218,"name":"Bigabid Media ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://www.bigabid.com/privacy-policy"},{"id":350,"name":"Free Stream Media Corp. dba Samba TV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":351,"name":"Samba TV UK Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://samba.tv/legal/privacy-policy/"},{"id":341,"name":"Somo Audience Corp","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[1,2,3],"policyUrl":"https://somoaudience.com/legal/","deletedDate":"2020-07-06T00:00:00Z"},{"id":380,"name":"Vidoomy Media SL","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"http://vidoomy.com/privacy-policy.html"},{"id":378,"name":"communicationAds GmbH & Co. KG","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.communicationads.net/aboutus/privacy/"},{"id":369,"name":"Getintent USA, inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://getintent.com/privacy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":184,"name":"mediarithmics SAS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mediarithmics.com/en-us/content/privacy-policy"},{"id":368,"name":"VECTAURY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.vectaury.io/en/personal-data"},{"id":373,"name":"Nielsen Marketing Cloud","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"http://www.nielsen.com/us/en/privacy-statement/exelate-privacy-policy.html"},{"id":214,"name":"Digital Control GmbH & Co. KG","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://advolution.de/privacy.php","deletedDate":"2020-05-06T00:00:00Z"},{"id":388,"name":"numberly","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://numberly.com/en/privacy/"},{"id":250,"name":"Qriously Ltd","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.brandwatch.com/legal/qriously-privacy-notice/"},{"id":223,"name":"Audience Trading Platform Ltd.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://atp.io/privacy-policy"},{"id":387,"name":"Triapodi Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appreciate.mobi/page.html#/end-user-privacy-policy"},{"id":312,"name":"Exactag GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.exactag.com/en/data-privacy/"},{"id":178,"name":"Hybrid Theory","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://hybridtheory.com/privacy-policy/"},{"id":377,"name":"AddApptr GmbH","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.addapptr.com/data-privacy"},{"id":382,"name":"The Reach Group GmbH","purposeIds":[1,2,4,5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://trg.de/en/privacy-statement/"},{"id":206,"name":"Hybrid Adtech GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://hybrid.ai/data_protection_policy"},{"id":403,"name":"Mobusi Mobile Advertising S.L.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mobusi.com/privacy.en.html","deletedDate":"2020-07-17T00:00:00Z"},{"id":385,"name":"Oracle Data Cloud","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://www.oracle.com/legal/privacy/marketing-cloud-data-cloud-privacy-policy.html"},{"id":404,"name":"Duplo Media AS","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.easy-ads.com/privacypolicy.htm"},{"id":242,"name":"twiago GmbH","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.twiago.com/datenschutz/"},{"id":376,"name":"Pocketmath Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.pocketmath.com/privacy-policy"},{"id":402,"name":"Effiliation","purposeIds":[2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://inter.effiliation.com/politique-confidentialite.html"},{"id":413,"name":"Eulerian Technologies","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.eulerian.com/en/privacy/"},{"id":400,"name":"Whenever Media Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.whenevermedia.com/privacy-policy","deletedDate":"2019-07-29T00:00:00Z"},{"id":171,"name":"Webedia","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webedia-group.com/site/privacy-policy","deletedDate":"2020-07-01T00:00:00Z"},{"id":398,"name":"Yormedia Solutions Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.yormedia.com/privacy-and-cookies-notice/","deletedDate":"2019-08-06T00:00:00Z"},{"id":415,"name":"Seenthis AB","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://seenthis.co/privacy-notice-2018-04-18.pdf"},{"id":263,"name":"Nativo, Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.nativo.com/interest-based-ads"},{"id":329,"name":"Browsi Mobile Ltd","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://gobrowsi.com/browsi-privacy-policy/"},{"id":389,"name":"Bidmanagement GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adspert.net/en/privacy/","deletedDate":"2020-07-01T00:00:00Z"},{"id":337,"name":"SheMedia, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shemedia.com/ad-services-privacy-policy"},{"id":422,"name":"Brand Metrics Sweden AB","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://collector.brandmetrics.com/brandmetrics_privacypolicy.pdf"},{"id":421,"name":"LeftsnRight, Inc. dba LIQWID","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://liqwid.solutions/privacy-policy","deletedDate":"2020-06-30T00:00:00Z"},{"id":426,"name":"TradeTracker","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[2],"policyUrl":"https://tradetracker.com/privacy-policy/","deletedDate":"2019-08-21T00:00:00Z"},{"id":394,"name":"AudienceProject Aps","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://privacy.audienceproject.com"},{"id":287,"name":"Avazu Inc.","purposeIds":[],"legIntPurposeIds":[1,3,4],"featureIds":[3],"policyUrl":"http://avazuinc.com/opt-out/","deletedDate":"2020-08-03T00:00:00Z"},{"id":243,"name":"Cloud Technologies S.A.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cloudtechnologies.pl/en/internet-advertising-privacy-policy"},{"id":113,"name":"iotec global Ltd.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.iotecglobal.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":338,"name":"dunnhumby Germany GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.sociomantic.com/privacy/en/","deletedDate":"2020-07-17T00:00:00Z"},{"id":405,"name":"IgnitionAi Ltd","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[2],"policyUrl":"https://www.isitelab.io/default.aspx","deletedDate":"2020-07-03T00:00:00Z"},{"id":416,"name":"Commanders Act","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.commandersact.com/en/privacy/"},{"id":434,"name":"DynAdmic","purposeIds":[1,3],"legIntPurposeIds":[2,4],"featureIds":[1,3],"policyUrl":"http://eu.dynadmic.com/privacy-policy/"},{"id":435,"name":"SINGLESPOT SAS ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.singlespot.com/privacy_policy?locale=fr"},{"id":409,"name":"Arrivalist Co.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[1,2],"policyUrl":"https://www.arrivalist.com/privacy"},{"id":321,"name":"Ziff Davis LLC","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.ziffdavis.com/privacy-policy"},{"id":436,"name":"INVIBES GROUP","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[1,2,3],"policyUrl":"http://www.invibes.com/terms"},{"id":442,"name":"R-Advertising","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-20T00:00:00Z"},{"id":362,"name":"Myntelligence S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://myntelligence.com/privacy-page/"},{"id":418,"name":"PROXISTORE","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://www.proxistore.com/common/en/cgv"},{"id":449,"name":"Mobile Journey B.V.","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://mobilejourney.com/Privacy-Policy","deletedDate":"2019-09-05T00:00:00Z"},{"id":443,"name":"Tradedoubler AB","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[2],"policyUrl":"https://www.tradedoubler.com/en/privacy-policy/","deletedDate":"2019-08-13T00:00:00Z"},{"id":429,"name":"Signals","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://signalsdata.com/platform-cookie-policy/"},{"id":335,"name":"Beachfront Media LLC","purposeIds":[],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"http://beachfront.com/privacy-policy/"},{"id":407,"name":"Publishers Internationale Pty Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pi-rate.com.au/privacy.html","deletedDate":"2019-11-08T00:00:00Z"},{"id":427,"name":"Proxi.cloud Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://proxi.cloud/info/privacy-policy/"},{"id":374,"name":"Bmind a Sales Maker Company, S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.bmind.es/legal-notice/"},{"id":438,"name":"INVIDI technologies AB","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"http://www.invidi.com/wp-content/uploads/2020/02/ad-tech-services-privacy-policy.pdf"},{"id":450,"name":"Neodata Group srl","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.neodatagroup.com/en/security-policy"},{"id":452,"name":"Innovid Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.innovid.com/privacy-policy"},{"id":444,"name":"Playbuzz Ltd (aka EX.CO)","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://ex.co/privacy-policy/"},{"id":412,"name":"Cxense ASA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.cxense.com/about-us/privacy-policy"},{"id":454,"name":"Adimo","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://adimo.co/privacy-policy/","deletedDate":"2019-09-12T00:00:00Z"},{"id":455,"name":"GDMServices, Inc. d/b/a FiksuDSP","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://fiksu.com/privacy-policy/"},{"id":298,"name":"Cuebiq Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.cuebiq.com/privacypolicy/","deletedDate":"2019-08-30T00:00:00Z"},{"id":423,"name":"travel audience GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://travelaudience.com/product-privacy-policy/"},{"id":397,"name":"Demandbase, Inc. ","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.demandbase.com/privacy-policy/"},{"id":381,"name":"Solocal","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://frontend.adhslx.com/privacy.html?"},{"id":425,"name":"ADRINO Sp. z o.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.adrino.pl/ciasteczkowa-polityka/","deletedDate":"2019-09-05T00:00:00Z"},{"id":365,"name":"Forensiq LLC","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1,3],"policyUrl":"https://impact.com/privacy-policy/"},{"id":447,"name":"Adludio Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.adludio.com/privacy-policy/"},{"id":410,"name":"Adtelligent Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtelligent.com/privacy-policy/"},{"id":137,"name":"Str\u00f6er SSP GmbH (DSP)","purposeIds":[],"legIntPurposeIds":[1,2,3],"featureIds":[],"policyUrl":"https://www.stroeer.de/fileadmin/de/Konvergenz_und_Konzepte/Daten_und_Technologien/Stroeer_SSP/Downloads/Datenschutz_Stroeer_SSP.pdf"},{"id":395,"name":"PREX Programmatic Exchange GmbH&Co KG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[],"policyUrl":"http://www.programmatic-exchange.com/privacy","deletedDate":"2020-07-03T00:00:00Z"},{"id":462,"name":"Bidstack Limited","purposeIds":[1,2,5],"legIntPurposeIds":[3,4],"featureIds":[2],"policyUrl":"https://www.bidstack.com/privacy-policy/"},{"id":466,"name":"TACTIC\u2122 Real-Time Marketing AS","purposeIds":[],"legIntPurposeIds":[4,5],"featureIds":[],"policyUrl":"https://tacticrealtime.com/privacy/"},{"id":340,"name":"Yieldr UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.yieldr.com/privacy"},{"id":336,"name":"Telecoming S.A.","purposeIds":[3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.telecoming.com/privacy-policy/"},{"id":430,"name":"Ad Unity Ltd","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"http://www.adunity.com/privacy-policy.html","deletedDate":"2019-08-13T00:00:00Z"},{"id":346,"name":"Cybba, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://cybba.com/about/legal/data-processing-agreement/","deletedDate":"2020-08-03T00:00:00Z"},{"id":469,"name":"Zeta Global","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://zetaglobal.com/privacy-policy/"},{"id":440,"name":"DEFINE MEDIA GMBH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.definemedia.de/datenschutz-conative/"},{"id":375,"name":"Affle International","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://affle.com/privacy-policy "},{"id":196,"name":"AdElement Media Solutions Pvt Ltd","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"http://adelement.com/privacy-policy.html"},{"id":268,"name":"Social Tokens Ltd. ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://woobi.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":475,"name":"TAPTAP Digital SL","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1,2,3],"policyUrl":"http://www.taptapnetworks.com/privacy_policy/"},{"id":474,"name":"hbfsTech","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.hbfstech.com/fr/privacy.html"},{"id":448,"name":"Targetspot Belgium SPRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://marketing.targetspot.com/Targetspot/Legal/TargetSpot%20Privacy%20Policy%20-%20June%202018.pdf"},{"id":428,"name":"Internet BillBoard a.s.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"http://www.ibillboard.com/en/privacy-information/"},{"id":461,"name":"B2B Media Group EMEA GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selfcampaign.com/static/privacy","deletedDate":"2019-08-14T00:00:00Z"},{"id":476,"name":"HIRO Media Ltd","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"http://hiro-media.com/privacy.php"},{"id":480,"name":"pilotx.tv","purposeIds":[2,3],"legIntPurposeIds":[1,4,5],"featureIds":[1,2,3],"policyUrl":"https://pilotx.tv/privacy/"},{"id":366,"name":"CerebroAd.com s.r.o.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.cerebroad.com/privacy-policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":392,"name":"Str\u00f6er Mobile Performance GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[3],"policyUrl":"https://stroeermobileperformance.com/?dl=privacy"},{"id":357,"name":"Totaljobs Group Ltd ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.totaljobs.com/privacy-policy"},{"id":486,"name":"Madington","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://delivered-by-madington.com/dat-privacy-policy/"},{"id":468,"name":"NeuStar, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,5],"featureIds":[1,2],"policyUrl":"https://www.home.neustar/privacy"},{"id":458,"name":"AdColony, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1],"policyUrl":"adcolony.com/privacy-policy/"},{"id":489,"name":"YellowHammer Media Group","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.yhmg.com/privacy-policy/","deletedDate":"2019-11-27T00:00:00Z"},{"id":293,"name":"SpringServe, LLC","purposeIds":[],"legIntPurposeIds":[1,3],"featureIds":[],"policyUrl":"https://springserve.com/privacy-policy/"},{"id":484,"name":"STRIATUM SAS","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://adledge.com/data-privacy/"},{"id":493,"name":"Carbon (AI) Limited","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"https://carbonrmp.com/privacy.html"},{"id":495,"name":"Arcspire Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://public.arcspire.io/privacy.pdf"},{"id":496,"name":"Automattic Inc.","purposeIds":[1,2,3,4],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://en.blog.wordpress.com/2017/12/04/updated-privacy-policy/"},{"id":424,"name":"KUPONA GmbH","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.kupona.de/dsgvo/"},{"id":408,"name":"Fidelity Media","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://fidelity-media.com/privacy-policy/"},{"id":473,"name":"Sub2 Technologies Ltd","purposeIds":[3,4,5],"legIntPurposeIds":[1,2],"featureIds":[],"policyUrl":"http://www.sub2tech.com/privacy-policy/"},{"id":467,"name":"Haensel AMS GmbH","purposeIds":[3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://haensel-ams.com/data-privacy/"},{"id":490,"name":"PLAYGROUND XYZ EMEA LTD","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://playground.xyz/privacy"},{"id":464,"name":"Oracle AddThis","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[2],"policyUrl":"http://www.addthis.com/privacy/privacy-policy/","deletedDate":"2020-02-12T00:00:00Z"},{"id":491,"name":"Triboo Data Analytics","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shinystat.com/it/informativa_privacy_generale.html"},{"id":499,"name":"PurposeLab, LLC","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://purposelab.com/privacy/","deletedDate":"2019-10-02T00:00:00Z"},{"id":502,"name":"NEXD","purposeIds":[5],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://nexd.com/privacy-policy"},{"id":465,"name":"Schibsted Product and Tech UK","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.schibsted.com/","deletedDate":"2019-07-26T00:00:00Z"},{"id":497,"name":"Little Big Data sp.z.o.o.","purposeIds":[1,2,4],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://dtxngr.com/legal/"},{"id":492,"name":"LotaData, Inc.","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[1],"policyUrl":"https://lotadata.com/privacy_policy","deletedDate":"2019-10-02T00:00:00Z"},{"id":512,"name":"PubNative GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://pubnative.net/privacy-notice/"},{"id":471,"name":"FlexOffers.com, LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.flexoffers.com/privacy-policy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":494,"name":"Cablato Limited","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://cablato.com/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":516,"name":"Pexi B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://pexi.nl/privacy-policy/"},{"id":507,"name":"AdsWizz Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://www.adswizz.com/our-privacy-policy/"},{"id":482,"name":"UberMedia, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ubermedia.com/summary-of-privacy-policy/"},{"id":505,"name":"Shopalyst Inc","purposeIds":[1,2],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.shortlyst.com/eu/privacy_terms.html"},{"id":517,"name":"SunMedia ","purposeIds":[1,2],"legIntPurposeIds":[3],"featureIds":[2],"policyUrl":"https://www.sunmedia.tv/en/cookies"},{"id":518,"name":"Accelerize Inc.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[2,3],"policyUrl":"https://getcake.com/privacy-policy/","deletedDate":"2020-07-17T00:00:00Z"},{"id":511,"name":"Admixer EU GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://admixer.com/privacy/"},{"id":479,"name":"INFINIA MOBILE S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.infiniamobile.com/privacy_policy"},{"id":513,"name":"Shopstyle","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.shopstyle.co.uk/privacy","deletedDate":"2019-10-02T00:00:00Z"},{"id":509,"name":"ATG Ad Tech Group GmbH","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://ad-tech-group.com/privacy-policy/"},{"id":521,"name":"netzeffekt GmbH","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://www.netzeffekt.de/en/imprint"},{"id":487,"name":"nugg.ad GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1],"policyUrl":"https://www.nugg.ad/en/privacy/general-information.html","deletedDate":"2019-10-03T00:00:00Z"},{"id":515,"name":"ZighZag","purposeIds":[1,3],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://zighzag.com/privacy"},{"id":520,"name":"ChannelSight ","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.channelsight.com/privacypolicy/"},{"id":524,"name":"The Ozone Project Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://ozoneproject.com/privacy-policy"},{"id":529,"name":"Fidzup","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.fidzup.com/en/privacy/","deletedDate":"2019-11-18T00:00:00Z"},{"id":528,"name":"Kayzen","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://kayzen.io/data-privacy-policy"},{"id":527,"name":"Jampp LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://jampp.com/privacy.html"},{"id":506,"name":"salesforce.com, inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.salesforce.com/company/privacy/"},{"id":534,"name":"SmartyAds Inc.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://smartyads.com/privacy-policy"},{"id":535,"name":"INNITY","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.innity.com/privacy-policy.php"},{"id":514,"name":"Uprival LLC","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://uprival.com/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":522,"name":"Tealium Inc.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://tealium.com/privacy-policy/"},{"id":530,"name":"Near Pte Ltd","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://near.co/privacy"},{"id":539,"name":"AdDefend GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.addefend.com/en/privacy-policy/"},{"id":501,"name":"Alliance Gravity Data Media","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.alliancegravity.com/politiquedeprotectiondesdonneespersonnelles"},{"id":519,"name":"Chargeads","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.chargeplatform.com/privacy"},{"id":523,"name":"X-Mode Social, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://xmode.io/privacy-policy.html"},{"id":537,"name":"RUN, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.runads.com/privacy-policy"},{"id":531,"name":"Smartclip Hispania SL","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://rgpd-smartclip.com/"},{"id":536,"name":"GlobalWebIndex","purposeIds":[1],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"http://legal.trendstream.net/non-panellist_privacy_policy"},{"id":542,"name":"Densou Trading Desk ApS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://densou.dk/Policy.html","deletedDate":"2020-01-21T00:00:00Z"},{"id":525,"name":"PUB OCEAN LIMITED","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://rta.pubocean.com/privacy-policy/","deletedDate":"2019-10-03T00:00:00Z"},{"id":544,"name":"Kochava Inc.","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1,2],"policyUrl":"https://www.kochava.com/support-privacy/"},{"id":543,"name":"PaperG, Inc. dba Thunder Industries","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.makethunder.com/privacy"},{"id":334,"name":"Cydersoft","purposeIds":[],"legIntPurposeIds":[1,2,3,4],"featureIds":[2,3],"policyUrl":"http://www.videmob.com/privacy.html"},{"id":551,"name":"Illuma Technology Limited","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.weareilluma.com/endddd","deletedDate":"2019-11-14T00:00:00Z"},{"id":540,"name":"Tunnl BV","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://tunnl.com/privacy.html","deletedDate":"2019-12-20T00:00:00Z"},{"id":547,"name":"Video Reach","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.videoreach.de/about/privacy-policy/","deletedDate":"2020-01-21T00:00:00Z"},{"id":546,"name":"Smart Traffik","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://okube-attribution.com/politique-de-confidentialite/"},{"id":541,"name":"DeepIntent, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://www.deepintent.com/privacypolicy"},{"id":545,"name":"Reignn Platform Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://reignn.com/user-privacy-policy"},{"id":439,"name":"Bit Q Holdings Limited","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.rippll.com/privacy"},{"id":553,"name":"Adhese","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://adhese.com/privacy-and-cookie-policy"},{"id":556,"name":"adhood.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://v3.adhood.com/en/site/politikavekurallar/gizlilik.php?lang=en"},{"id":550,"name":"Happydemics","purposeIds":[5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.iubenda.com/privacy-policy/69056167/full-legal"},{"id":560,"name":"Leiki Ltd.","purposeIds":[1,2,3],"legIntPurposeIds":[4],"featureIds":[],"policyUrl":"http://www.leiki.com/privacy","deletedDate":"2020-01-07T00:00:00Z"},{"id":554,"name":"RMSi Radio Marketing Service interactive GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.rms.de/datenschutz/"},{"id":498,"name":"Mediakeys Platform","purposeIds":[1],"legIntPurposeIds":[3],"featureIds":[3],"policyUrl":"https://drbanner.com/privacypolicy_en/"},{"id":565,"name":"Adobe Audience Manager","purposeIds":[1,2,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adobe.com/privacy/policy.html"},{"id":118,"name":"Drawbridge, Inc.","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.drawbridge.com/privacy/","deletedDate":"2020-03-06T00:00:00Z"},{"id":572,"name":"CHEQ AI TECHNOLOGIES LTD.","purposeIds":[1],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"http://www.cheq.ai/privacy"},{"id":571,"name":"ViewPay","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://viewpay.tv/mentions-legales/"},{"id":568,"name":"Jointag S.r.l.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.jointag.com/privacy/kariboo/publisher/third/"},{"id":570,"name":"Czech Publisher Exchange z.s.p.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.cpex.cz/pro-uzivatele/ochrana-soukromi/"},{"id":559,"name":"Otto (GmbH & Co KG)","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2],"policyUrl":"https://www.otto.de/shoppages/service/datenschutz"},{"id":548,"name":"LBC France","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.leboncoin.fr/dc/cookies","deletedDate":"2020-04-23T00:00:00Z"},{"id":569,"name":"Kairos Fire","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.kairosfire.com/privacy"},{"id":577,"name":"Neustar on behalf of The Procter & Gamble Company","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.pg.com/privacy/english/privacy_statement.shtml"},{"id":590,"name":"Sourcepoint Technologies, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.sourcepoint.com/privacy-policy"},{"id":587,"name":"Localsensor B.V.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[],"policyUrl":"https://www.localsensor.com/privacy.html"},{"id":578,"name":"MAIRDUMONT NETLETIX GmbH&Co. KG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://mairdumont-netletix.com/datenschutz"},{"id":580,"name":"Goldbach Group AG","purposeIds":[],"legIntPurposeIds":[1,2,3,5],"featureIds":[1,2,3],"policyUrl":"https://goldbach.com/ch/de/datenschutz"},{"id":593,"name":"Programatica de publicidad S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://datmean.com/politica-privacidad/"},{"id":574,"name":"Realeyes OU","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://realview.realeyesit.com/privacy"},{"id":581,"name":"Mobilewalla, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[1,2,3],"policyUrl":"https://www.mobilewalla.com/business-services-privacy-policy"},{"id":598,"name":"audio content & control GmbH","purposeIds":[1],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://www.audio-cc.com/audiocc_privacy_policy.pdf"},{"id":596,"name":"InsurAds Technologies SA.","purposeIds":[3],"legIntPurposeIds":[5],"featureIds":[3],"policyUrl":"https://www.insurads.com/privacy.html"},{"id":576,"name":"StartApp Inc.","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[3],"policyUrl":"https://www.startapp.com/policy/privacy-policy/","deletedDate":"2020-04-23T00:00:00Z"},{"id":592,"name":"Colpirio.com","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy-policy.colpirio.com/en/","deletedDate":"2020-03-18T00:00:00Z"},{"id":549,"name":"Bandsintown Amplified LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://corp.bandsintown.com/privacy"},{"id":597,"name":"Better Banners A/S","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://betterbanners.com/en/privacy"},{"id":601,"name":"WebAds B.V","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://privacy.webads.eu/"},{"id":599,"name":"Maximus Live LLC","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[3],"policyUrl":"https://maximusx.com/privacy-policy/"},{"id":604,"name":"Join","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.teamjoin.fr/privacy.html","deletedDate":"2020-04-23T00:00:00Z"},{"id":606,"name":"Impactify ","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://impactify.io/privacy-policy/"},{"id":608,"name":"News and Media Holding, a.s.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.newsandmedia.sk/gdpr/"},{"id":602,"name":"Online Solution Int Limited","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2],"policyUrl":"https://adsafety.net/privacy.html"},{"id":591,"name":"Consumable, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://consumable.com/privacy-policy.html"},{"id":614,"name":"Market Resource Partners LLC","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.mrpfd.com/privacy-policy/"},{"id":615,"name":"Adsolutions BV","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.adsolutions.com/privacy-policy/"},{"id":607,"name":"ucfunnel Co., Ltd.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.ucfunnel.com/privacy-policy"},{"id":609,"name":"Predicio","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.predic.io/privacy"},{"id":617,"name":"Onfocus (Adagio)","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adagio.io/privacy"},{"id":620,"name":"Blue","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"http://www.getblue.io/privacy/"},{"id":610,"name":"Azerion Holding B.V.","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[2,3],"policyUrl":"https://azerion.com/business/privacy.html"},{"id":621,"name":"Seznam.cz, a.s.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://www.seznam.cz/ochranaudaju"},{"id":624,"name":"Norstat AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.norstatpanel.com/en/data-protection"},{"id":623,"name":"Adprime Media Inc. ","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adprimehealth.com/privacy/","deletedDate":"2020-06-17T00:00:00Z"},{"id":95,"name":"Lotame Solutions, inc","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[2],"policyUrl":"https://www.lotame.com/about-lotame/privacy/lotame-corporate-websites-privacy-policy/"},{"id":618,"name":"BEINTOO SPA","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.beintoo.com/privacy-cookie-policy/"},{"id":619,"name":"Capitaldata","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.capitaldata.fr/privacy"},{"id":625,"name":"BILENDI SA","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.maximiles.com/privacy-policy"},{"id":628,"name":": Tappx","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.tappx.com/en/privacy-policy/"},{"id":626,"name":"Hivestack Inc.","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[3],"policyUrl":"https://hivestack.com/privacy-policy"},{"id":631,"name":"Relay42 Netherlands B.V.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://relay42.com/privacy"},{"id":627,"name":"D-Edge","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.d-edge.com/privacy-policy/","deletedDate":"2020-07-06T00:00:00Z"},{"id":644,"name":"Gamoshi LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.gamoshi.com/privacy-policy"},{"id":639,"name":"Smile Wanted Group","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.smilewanted.com/privacy.php"},{"id":635,"name":"WebMediaRM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.webmediarm.com/vie_privee_et_opposition_en.php"},{"id":579,"name":"Ve Global","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.ve.com/privacy-policy"},{"id":645,"name":"Noster Finance S.L.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.finect.com/terminos-legales/politica-de-cookies"},{"id":653,"name":"Smartme Analytics","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"http://smartmeapp.com/info/smartme/aviso_legal.php","deletedDate":"2020-07-03T00:00:00Z"},{"id":613,"name":"Adserve.zone / Artworx AS","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://adserve.zone/adserveprivacypolicy.html"},{"id":573,"name":"Dailymotion SA","purposeIds":[2,3,4,5],"legIntPurposeIds":[1],"featureIds":[2],"policyUrl":"https://www.dailymotion.com/legal/privacy"},{"id":652,"name":"Skaze","purposeIds":[1,2],"legIntPurposeIds":[3,4,5],"featureIds":[1,2,3],"policyUrl":"http://www.skaze.fr/rgpd/"},{"id":646,"name":"Notify","purposeIds":[1,2],"legIntPurposeIds":[5],"featureIds":[1],"policyUrl":"https://notify-group.com/en/mentions-legales/"},{"id":648,"name":"TrueData Solutions, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.truedata.co/privacy-policy/"},{"id":647,"name":"Axel Springer Teaser Ad GmbH","purposeIds":[2],"legIntPurposeIds":[1,3,5],"featureIds":[],"policyUrl":"https://www.adup-tech.com/privacy"},{"id":654,"name":"GRAPHINIUM","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.graphinium.com/privacy/"},{"id":659,"name":"Research and Analysis of Media in Sweden AB","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www2.rampanel.com/privacy-policy/"},{"id":656,"name":"Think Clever Media","purposeIds":[1,2,3,4],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.contentignite.com/privacy-policy/"},{"id":504,"name":"Alive & Kicking Global Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.mcsaatchiplc.com/legal/privacy-cookies","deletedDate":"2020-07-27T00:00:00Z"},{"id":657,"name":"GP One GmbH","purposeIds":[],"legIntPurposeIds":[1,3,5],"featureIds":[3],"policyUrl":"https://www.gsi-one.org/de/privacy-policy.html"},{"id":655,"name":"Sportradar AG","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.sportradar.com/about-us/privacy/"},{"id":662,"name":"SoundCast","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://soundcast.fm/en/data-privacy"},{"id":665,"name":"Digital East GmbH","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.digitaleast.mobi/en/legal/privacy-policy/"},{"id":650,"name":"Telefonica Investigaci\u00f3n y Desarrollo S.A.U","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://www.cognitivemarketing.tid.es/"},{"id":666,"name":"BeOp","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://beop.io/privacy"},{"id":663,"name":"Mobsuccess","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.mobsuccess.com/en/privacy"},{"id":658,"name":"BLIINK SAS","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://bliink.io/privacy-policy"},{"id":667,"name":"Liftoff Mobile, Inc.","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[3],"policyUrl":"https://liftoff.io/privacy-policy/"},{"id":668,"name":"WhatRocks Inc. ","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.whatrocks.co/en/privacy-policy "},{"id":670,"name":"Timehop, Inc.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.timehop.com/privacy"},{"id":674,"name":"Duration Media, LLC.","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.durationmedia.net/privacy-policy"},{"id":675,"name":"Instreamatic inc.","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://instreamatic.com/privacy-policy/"},{"id":676,"name":"BusinessClick","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.businessclick.com/documents/RegulaminProgramuBusinessClick-2019.pdf"},{"id":677,"name":"Intercept Interactive Inc. dba Undertone","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.undertone.com/privacy/"},{"id":660,"name":"Schibsted Norge AS","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[2,3],"policyUrl":"https://static.vg.no/privacy/","deletedDate":"2019-09-16T00:00:00Z"},{"id":673,"name":"TTNET AS","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"http://www.programattik.com/en/privacy-policy.aspx"},{"id":664,"name":"adMarketplace, Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"https://www.admarketplace.com/privacy-policy/"},{"id":671,"name":"Mediaforce LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,3],"policyUrl":"http://casino.mindthebet.co.uk/themes/mindthebetv2-casino/privacy.php"},{"id":561,"name":"AuDigent","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://audigent.com/platform-privacy-policy"},{"id":682,"name":"Radio Net Media Limited","purposeIds":[1,2,3],"legIntPurposeIds":[5],"featureIds":[1,2,3],"policyUrl":"https://www.adtonos.com/service-privacy-policy/"},{"id":684,"name":"Blue Billywig BV","purposeIds":[],"legIntPurposeIds":[5],"featureIds":[],"policyUrl":"https://www.bluebillywig.com/privacy-statement/"},{"id":686,"name":"The MediaGrid Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://www.themediagrid.com/privacy-policy/"},{"id":685,"name":"Arkeero","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://arkeero.com/privacy-2/"},{"id":687,"name":"MISSENA","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://missena.com/confidentialite/"},{"id":690,"name":"Go.pl sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://go.pl/polityka-prywatnosci/"},{"id":691,"name":"Lifesight Pte. Ltd.","purposeIds":[1,2,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.lifesight.io/privacy-policy/"},{"id":697,"name":"ADWAYS SAS","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://www.adways.com/confidentialite/?lang=en"},{"id":681,"name":"MyTraffic","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://www.mytraffic.io/en/privacy"},{"id":649,"name":"adality GmbH","purposeIds":[],"legIntPurposeIds":[1,2],"featureIds":[1],"policyUrl":"https://adality.de/en/privacy/"},{"id":712,"name":"Inspired Mobile Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://byinspired.com/privacypolicy.pdf"},{"id":688,"name":"Effinity","purposeIds":[],"legIntPurposeIds":[1],"featureIds":[],"policyUrl":"https://www.effiliation.com/politique-de-confidentialite/"},{"id":702,"name":"Kwanko","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.kwanko.com/fr/rgpd/"},{"id":715,"name":"BidBerry SRL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.bidberrymedia.com/privacy-policy/"},{"id":713,"name":"Dataseat Ltd","purposeIds":[2,5],"legIntPurposeIds":[1,3,4],"featureIds":[],"policyUrl":"https://dataseat.com/privacy-policy"},{"id":716,"name":"OnAudience Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.onaudience.com/internet-advertising-privacy-policy"},{"id":708,"name":"Dugout Limited ","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://dugout.com/privacy-policy"},{"id":717,"name":"Audience Network","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.en.audiencenetwork.pl/internet-advertising-privacy-policy"},{"id":718,"name":"AppConsent Xchange","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://appconsent.io/en/privacy-policy"},{"id":720,"name":"AAX LLC","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[3],"policyUrl":"https://aax.media/privacy/"},{"id":678,"name":"Axonix LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://axonix.com/privacy-cookie-policy/"},{"id":719,"name":"Online Advertising Network Sp. z o.o.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://www.oan.pl/en/privacy-policy"},{"id":707,"name":"Dentsu Aegis Network Italia SpA","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.dentsuaegisnetwork.com/it/it/policies/info-cookie"},{"id":721,"name":"Beaconspark Ltd","purposeIds":[1,2,3],"legIntPurposeIds":[4,5],"featureIds":[1],"policyUrl":"https://www.engageya.com/privacy"},{"id":724,"name":"Between Exchange","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[2,3],"policyUrl":"https://en.betweenx.com/pdata.pdf"},{"id":728,"name":"Appier PTE Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.appier.com/privacy-policy/"},{"id":729,"name":"Cavai AS & UK ","purposeIds":[],"legIntPurposeIds":[3],"featureIds":[],"policyUrl":"https://cav.ai/privacy-policy/"},{"id":723,"name":"Adzymic Pte Ltd","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"http://www.adzymic.co/privacy"},{"id":737,"name":"Monet Engine Inc","purposeIds":[1,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://appmonet.com/privacy-policy/"},{"id":740,"name":"6Sense Insights, Inc.","purposeIds":[1],"legIntPurposeIds":[2,3,4,5],"featureIds":[1,2],"policyUrl":"https://6sense.com/privacy-policy/"},{"id":744,"name":"Vidazoo Ltd","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[2],"policyUrl":"https://vidazoo.gitbook.io/vidazoo-legal/privacy-policy"},{"id":731,"name":"GeistM Technologies LTD","purposeIds":[],"legIntPurposeIds":[3,4,5],"featureIds":[],"policyUrl":"https://www.geistm.com/privacy"},{"id":741,"name":"Brand Advance Limited","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.wearebrandadvance.com/website-privacy-policy"},{"id":734,"name":"Cint AB","purposeIds":[1,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://www.cint.com/participant-privacy-notice"},{"id":709,"name":"NC Audience Exchange, LLC (NewsIQ)","purposeIds":[1,2],"legIntPurposeIds":[3,5],"featureIds":[1,2],"policyUrl":"https://www.ncaudienceexchange.com/privacy/"},{"id":739,"name":"Blingby LLC","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://blingby.com/privacy"},{"id":732,"name":"Performax.cz, s.r.o.","purposeIds":[2,4,5],"legIntPurposeIds":[1,3],"featureIds":[2,3],"policyUrl":"https://reg.tiscali.cz/privacy-policy"},{"id":736,"name":"BidMachine Inc.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://explorestack.com/privacy-policy/"},{"id":738,"name":"adbility media GmbH","purposeIds":[2,3],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.adbility-media.com/datenschutzerklaerung/"},{"id":742,"name":"Audiencerate LTD","purposeIds":[],"legIntPurposeIds":[1,2,5],"featureIds":[],"policyUrl":"https://www.audiencerate.com/privacy/"},{"id":743,"name":"MOVIads Sp. z o.o. Sp. k.","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://moviads.pl/polityka-prywatnosci/"},{"id":746,"name":"Adxperience SAS","purposeIds":[2],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://adxperience.com/privacy-policy/"},{"id":747,"name":"Kairion GmbH","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://kairion.de/datenschutzbestimmungen/"},{"id":748,"name":"AUDIOMOB LTD","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[2,3],"policyUrl":"https://www.audiomob.io/privacy"},{"id":749,"name":"Good-Loop Ltd","purposeIds":[2],"legIntPurposeIds":[1,3,4,5],"featureIds":[],"policyUrl":"https://doc.good-loop.com/policy/privacy-policy.html"},{"id":754,"name":"DistroScale, Inc.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"http://www.distroscale.com/privacy-policy/"},{"id":756,"name":"Fandom, Inc.","purposeIds":[3],"legIntPurposeIds":[1,2,4,5],"featureIds":[],"policyUrl":"https://www.fandom.com/privacy-policy"},{"id":758,"name":"GfK Netherlands B.V.","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://gfkpanel.nl/privacy"},{"id":759,"name":"RevJet","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.revjet.com/privacy"},{"id":760,"name":"VEXPRO TECHNOLOGIES LTD","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2],"policyUrl":"https://onedash.com/privacy-policy.html"},{"id":761,"name":"Digiseg ApS","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[1],"policyUrl":"https://digiseg.io/privacy-center/"},{"id":763,"name":"Delidatax SL","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"https://www.delidatax.net/privacy.htm"},{"id":764,"name":"Lucidity","purposeIds":[1,3,4,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://golucidity.com/privacy-policy/"},{"id":765,"name":"Grabit Interactive Media Inc dba KERV Interctive","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[2],"policyUrl":"https://kervit.com/privacy-policy/"},{"id":766,"name":"ADCELL | Firstlead GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.adcell.de/agb#sector_6"},{"id":768,"name":"Global Media & Entertainment Limited","purposeIds":[1,2,3,4,5],"legIntPurposeIds":[],"featureIds":[1,2,3],"policyUrl":"http://global.com/privacy-policy/"},{"id":770,"name":"MARKETPERF CORP","purposeIds":[1,2,4],"legIntPurposeIds":[3,5],"featureIds":[2,3],"policyUrl":"https://www.marketperf.com/assets/images/app/marketperf/pdf/privacy-policy.pdf"},{"id":773,"name":"360e-com Sp. z o.o.","purposeIds":[1,2,3,5],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.clickonometrics.com/optout/"},{"id":775,"name":"SelectMedia International LTD","purposeIds":[1],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.selectmedia.asia/terms-and-privacy/"},{"id":778,"name":"Discover-Tech ltd","purposeIds":[2,5],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://discover-tech.io/dsp-privacy-policy/"},{"id":779,"name":"Adtarget Medya A.S.","purposeIds":[1,3],"legIntPurposeIds":[],"featureIds":[3],"policyUrl":"https://adtarget.com.tr/adtarget-privacy-policy-2020.pdf"},{"id":780,"name":"Aniview LTD","purposeIds":[1,2,3],"legIntPurposeIds":[],"featureIds":[],"policyUrl":"https://www.aniview.com/privacy-policy/"},{"id":781,"name":"FeedAd GmbH","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[2,3],"policyUrl":"https://feedad.com/privacy/"},{"id":784,"name":"Nubo LTD","purposeIds":[],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://www.recod3.com/privacypolicy.php"},{"id":786,"name":"TargetVideo GmbH","purposeIds":[],"legIntPurposeIds":[1,2,3,4,5],"featureIds":[],"policyUrl":"https://www.target-video.com/datenschutz/"},{"id":798,"name":"Adverticum cPlc.","purposeIds":[2,3,4],"legIntPurposeIds":[1,5],"featureIds":[],"policyUrl":"https://adverticum.net/english/privacy-and-data-processing-information/"},{"id":803,"name":"Click Tech Limited","purposeIds":[1],"legIntPurposeIds":[2,3],"featureIds":[1],"policyUrl":"https://en.yeahmobi.com/html/privacypolicy/"},{"id":808,"name":"Pure Local Media GmbH","purposeIds":[],"legIntPurposeIds":[3,5],"featureIds":[],"policyUrl":"https://purelocalmedia.de/?page_id=593"}]} \ No newline at end of file diff --git a/stored_requests/config/config.go b/stored_requests/config/config.go index 7f92f2521cd..f682ff932f4 100644 --- a/stored_requests/config/config.go +++ b/stored_requests/config/config.go @@ -166,6 +166,9 @@ func newFetcher(cfg *config.StoredRequests, client *http.Client, db *sql.DB) (fe if cfg.Postgres.FetcherQueries.QueryTemplate != "" { glog.Infof("Loading Stored %s data via Postgres.\nQuery: %s", cfg.DataType(), cfg.Postgres.FetcherQueries.QueryTemplate) idList = append(idList, db_fetcher.NewFetcher(db, cfg.Postgres.FetcherQueries.MakeQuery)) + } else if cfg.Postgres.CacheInitialization.Query != "" && cfg.Postgres.PollUpdates.Query != "" { + //in this case data will be loaded to cache via poll for updates event + idList = append(idList, empty_fetcher.EmptyFetcher{}) } if cfg.HTTP.Endpoint != "" { glog.Infof("Loading Stored %s data via HTTP. endpoint=%s", cfg.DataType(), cfg.HTTP.Endpoint) diff --git a/stored_requests/config/config_test.go b/stored_requests/config/config_test.go index 6c8cd612299..4a8d10a9382 100644 --- a/stored_requests/config/config_test.go +++ b/stored_requests/config/config_test.go @@ -2,6 +2,7 @@ package config import ( "context" + "database/sql" "encoding/json" "errors" "net/http" @@ -41,12 +42,79 @@ func isMemoryCacheType(cache stored_requests.CacheJSON) bool { } func TestNewEmptyFetcher(t *testing.T) { - fetcher := newFetcher(&config.StoredRequests{}, nil, nil) - if fetcher == nil { - t.Errorf("The fetcher should be non-nil, even with an empty config.") + + type testCase struct { + config *config.StoredRequests + emptyFetcher bool + description string } - if _, ok := fetcher.(empty_fetcher.EmptyFetcher); !ok { - t.Errorf("If the config is empty, and EmptyFetcher should be returned") + testCases := []testCase{ + { + config: &config.StoredRequests{}, + emptyFetcher: true, + description: "If the config is empty, an EmptyFetcher should be returned", + }, + { + config: &config.StoredRequests{ + Postgres: config.PostgresConfig{ + CacheInitialization: config.PostgresCacheInitializer{ + Query: "test query", + }, + PollUpdates: config.PostgresUpdatePolling{ + Query: "test poll query", + }, + FetcherQueries: config.PostgresFetcherQueries{ + QueryTemplate: "", + }, + }, + }, + emptyFetcher: true, + description: "If Postgres fetcher query is not defined, but Postgres Cache init query and Postgres update polling query are defined EmptyFetcher should be returned", + }, + { + config: &config.StoredRequests{ + Postgres: config.PostgresConfig{ + CacheInitialization: config.PostgresCacheInitializer{ + Query: "", + }, + PollUpdates: config.PostgresUpdatePolling{ + Query: "", + }, + FetcherQueries: config.PostgresFetcherQueries{ + QueryTemplate: "test fetcher query", + }, + }, + }, + emptyFetcher: false, + description: "If Postgres fetcher query is defined, but Postgres Cache init query and Postgres update polling query are not defined not EmptyFetcher (DBFetcher) should be returned", + }, + { + config: &config.StoredRequests{ + Postgres: config.PostgresConfig{ + CacheInitialization: config.PostgresCacheInitializer{ + Query: "test cache query", + }, + PollUpdates: config.PostgresUpdatePolling{ + Query: "test poll query", + }, + FetcherQueries: config.PostgresFetcherQueries{ + QueryTemplate: "test fetcher query", + }, + }, + }, + emptyFetcher: false, + description: "If Postgres fetcher query is defined and Postgres Cache init query and Postgres update polling query are defined not EmptyFetcher (DBFetcher) should be returned", + }, + } + + for _, test := range testCases { + fetcher := newFetcher(test.config, nil, &sql.DB{}) + assert.NotNil(t, fetcher, "The fetcher should be non-nil.") + if test.emptyFetcher { + assert.Equal(t, empty_fetcher.EmptyFetcher{}, fetcher, "Empty fetcher should be returned") + } else { + assert.NotEqual(t, empty_fetcher.EmptyFetcher{}, fetcher) + } } } diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 50362ad04ec..8275869f5b2 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -1,12 +1,15 @@ package usersyncers import ( + "github.com/prebid/prebid-server/adapters/operaads" "strings" "text/template" "github.com/golang/glog" ttx "github.com/prebid/prebid-server/adapters/33across" "github.com/prebid/prebid-server/adapters/acuityads" + "github.com/prebid/prebid-server/adapters/adagio" + "github.com/prebid/prebid-server/adapters/adf" "github.com/prebid/prebid-server/adapters/adform" "github.com/prebid/prebid-server/adapters/adkernel" "github.com/prebid/prebid-server/adapters/adkernelAdn" @@ -27,6 +30,8 @@ import ( "github.com/prebid/prebid-server/adapters/beachfront" "github.com/prebid/prebid-server/adapters/beintoo" "github.com/prebid/prebid-server/adapters/between" + "github.com/prebid/prebid-server/adapters/bidmyadz" + "github.com/prebid/prebid-server/adapters/bmtm" "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/colossus" "github.com/prebid/prebid-server/adapters/connectad" @@ -37,6 +42,7 @@ import ( "github.com/prebid/prebid-server/adapters/datablocks" "github.com/prebid/prebid-server/adapters/deepintent" "github.com/prebid/prebid-server/adapters/dmx" + "github.com/prebid/prebid-server/adapters/e_volution" "github.com/prebid/prebid-server/adapters/emx_digital" "github.com/prebid/prebid-server/adapters/engagebdr" "github.com/prebid/prebid-server/adapters/eplanning" @@ -45,11 +51,11 @@ import ( "github.com/prebid/prebid-server/adapters/grid" "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/improvedigital" + "github.com/prebid/prebid-server/adapters/inmobi" "github.com/prebid/prebid-server/adapters/invibes" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/jixie" "github.com/prebid/prebid-server/adapters/krushmedia" - "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/lockerdome" "github.com/prebid/prebid-server/adapters/logicad" "github.com/prebid/prebid-server/adapters/lunamedia" @@ -67,10 +73,12 @@ import ( "github.com/prebid/prebid-server/adapters/rhythmone" "github.com/prebid/prebid-server/adapters/rtbhouse" "github.com/prebid/prebid-server/adapters/rubicon" + "github.com/prebid/prebid-server/adapters/sa_lunamedia" "github.com/prebid/prebid-server/adapters/sharethrough" "github.com/prebid/prebid-server/adapters/smartadserver" "github.com/prebid/prebid-server/adapters/smartrtb" "github.com/prebid/prebid-server/adapters/smartyads" + "github.com/prebid/prebid-server/adapters/smilewanted" "github.com/prebid/prebid-server/adapters/somoaudience" "github.com/prebid/prebid-server/adapters/sonobi" "github.com/prebid/prebid-server/adapters/sovrn" @@ -84,6 +92,7 @@ import ( "github.com/prebid/prebid-server/adapters/unruly" "github.com/prebid/prebid-server/adapters/valueimpression" "github.com/prebid/prebid-server/adapters/verizonmedia" + "github.com/prebid/prebid-server/adapters/viewdeos" "github.com/prebid/prebid-server/adapters/visx" "github.com/prebid/prebid-server/adapters/vrtcal" "github.com/prebid/prebid-server/adapters/yieldlab" @@ -103,6 +112,8 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.Bidder33Across, ttx.New33AcrossSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAcuityAds, acuityads.NewAcuityAdsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdagio, adagio.NewAdagioSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderAdf, adf.NewAdfSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdform, adform.NewAdformSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernel, adkernel.NewAdkernelSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAdkernelAdn, adkernelAdn.NewAdkernelAdnSyncer) @@ -121,7 +132,9 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderAvocet, avocet.NewAvocetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBeachfront, beachfront.NewBeachfrontSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBeintoo, beintoo.NewBeintooSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderBmtm, bmtm.NewBmtmSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderBrightroll, brightroll.NewBrightrollSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderBidmyadz, bidmyadz.NewBidmyadzSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderColossus, colossus.NewColossusSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConnectAd, connectad.NewConnectAdSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderConsumable, consumable.NewConsumableSyncer) @@ -132,6 +145,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderDeepintent, deepintent.NewDeepintentSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderDmx, dmx.NewDmxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEmxDigital, emx_digital.NewEMXDigitalSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderEVolution, evolution.NewEvolutionSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEngageBDR, engagebdr.NewEngageBDRSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderEPlanning, eplanning.NewEPlanningSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderAudienceNetwork, audienceNetwork.NewFacebookSyncer) @@ -140,14 +154,15 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderGrid, grid.NewGridSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderGumGum, gumgum.NewGumGumSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderImprovedigital, improvedigital.NewImprovedigitalSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderInMobi, inmobi.NewInmobiSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderInvibes, invibes.NewInvibesSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderIx, ix.NewIxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderJixie, jixie.NewJixieSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderKrushmedia, krushmedia.NewKrushmediaSyncer) - insertIntoMap(cfg, syncers, openrtb_ext.BidderLifestreet, lifestreet.NewLifestreetSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLockerDome, lockerdome.NewLockerDomeSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLogicad, logicad.NewLogicadSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderLunaMedia, lunamedia.NewLunaMediaSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSaLunaMedia, salunamedia.NewSaLunamediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMarsmedia, marsmedia.NewMarsmediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMediafuse, mediafuse.NewMediafuseSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderMgid, mgid.NewMgidSyncer) @@ -157,6 +172,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderOneTag, onetag.NewSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOutbrain, outbrain.NewOutbrainSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderOpenx, openx.NewOpenxSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderOperaads, operaads.NewOperaadsSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPubmatic, pubmatic.NewPubmaticSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderPulsepoint, pulsepoint.NewPulsepointSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderRhythmone, rhythmone.NewRhythmoneSyncer) @@ -169,6 +185,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartAdserver, smartadserver.NewSmartadserverSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartRTB, smartrtb.NewSmartRTBSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSmartyAds, smartyads.NewSmartyAdsSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderSmileWanted, smilewanted.NewSmileWantedSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderSynacormedia, synacormedia.NewSynacorMediaSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTappx, tappx.NewTappxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderTelaria, telaria.NewTelariaSyncer) @@ -179,6 +196,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync insertIntoMap(cfg, syncers, openrtb_ext.BidderUnruly, unruly.NewUnrulySyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderValueImpression, valueimpression.NewValueImpressionSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVerizonMedia, verizonmedia.NewVerizonMediaSyncer) + insertIntoMap(cfg, syncers, openrtb_ext.BidderViewdeos, viewdeos.NewViewdeosSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVisx, visx.NewVisxSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderVrtcal, vrtcal.NewVrtcalSyncer) insertIntoMap(cfg, syncers, openrtb_ext.BidderYieldlab, yieldlab.NewYieldlabSyncer) diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go old mode 100755 new mode 100644 index 10a95fb4b67..d284ddec035 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -16,6 +16,8 @@ func TestNewSyncerMap(t *testing.T) { Adapters: map[string]config.Adapter{ string(openrtb_ext.Bidder33Across): syncConfig, string(openrtb_ext.BidderAcuityAds): syncConfig, + string(openrtb_ext.BidderAdagio): syncConfig, + string(openrtb_ext.BidderAdf): syncConfig, string(openrtb_ext.BidderAdform): syncConfig, string(openrtb_ext.BidderAdkernel): syncConfig, string(openrtb_ext.BidderAdkernelAdn): syncConfig, @@ -36,6 +38,8 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderBeachfront): syncConfig, string(openrtb_ext.BidderBeintoo): syncConfig, string(openrtb_ext.BidderBetween): syncConfig, + string(openrtb_ext.BidderBidmyadz): syncConfig, + string(openrtb_ext.BidderBmtm): syncConfig, string(openrtb_ext.BidderBrightroll): syncConfig, string(openrtb_ext.BidderColossus): syncConfig, string(openrtb_ext.BidderConnectAd): syncConfig, @@ -49,19 +53,21 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderEmxDigital): syncConfig, string(openrtb_ext.BidderEngageBDR): syncConfig, string(openrtb_ext.BidderEPlanning): syncConfig, + string(openrtb_ext.BidderEVolution): syncConfig, string(openrtb_ext.BidderGamma): syncConfig, string(openrtb_ext.BidderGamoshi): syncConfig, string(openrtb_ext.BidderGrid): syncConfig, string(openrtb_ext.BidderGumGum): syncConfig, string(openrtb_ext.BidderImprovedigital): syncConfig, + string(openrtb_ext.BidderInMobi): syncConfig, string(openrtb_ext.BidderInvibes): syncConfig, string(openrtb_ext.BidderIx): syncConfig, string(openrtb_ext.BidderJixie): syncConfig, string(openrtb_ext.BidderKrushmedia): syncConfig, - string(openrtb_ext.BidderLifestreet): syncConfig, string(openrtb_ext.BidderLockerDome): syncConfig, string(openrtb_ext.BidderLogicad): syncConfig, string(openrtb_ext.BidderLunaMedia): syncConfig, + string(openrtb_ext.BidderSaLunaMedia): syncConfig, string(openrtb_ext.BidderMarsmedia): syncConfig, string(openrtb_ext.BidderMediafuse): syncConfig, string(openrtb_ext.BidderMgid): syncConfig, @@ -70,6 +76,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderNoBid): syncConfig, string(openrtb_ext.BidderOneTag): syncConfig, string(openrtb_ext.BidderOpenx): syncConfig, + string(openrtb_ext.BidderOperaads): syncConfig, string(openrtb_ext.BidderOutbrain): syncConfig, string(openrtb_ext.BidderPubmatic): syncConfig, string(openrtb_ext.BidderPulsepoint): syncConfig, @@ -80,6 +87,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderSmartAdserver): syncConfig, string(openrtb_ext.BidderSmartRTB): syncConfig, string(openrtb_ext.BidderSmartyAds): syncConfig, + string(openrtb_ext.BidderSmileWanted): syncConfig, string(openrtb_ext.BidderSomoaudience): syncConfig, string(openrtb_ext.BidderSonobi): syncConfig, string(openrtb_ext.BidderSovrn): syncConfig, @@ -93,6 +101,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderUnruly): syncConfig, string(openrtb_ext.BidderValueImpression): syncConfig, string(openrtb_ext.BidderVerizonMedia): syncConfig, + string(openrtb_ext.BidderViewdeos): syncConfig, string(openrtb_ext.BidderVisx): syncConfig, string(openrtb_ext.BidderVrtcal): syncConfig, string(openrtb_ext.BidderYieldlab): syncConfig, @@ -103,30 +112,35 @@ func TestNewSyncerMap(t *testing.T) { } adaptersWithoutSyncers := map[openrtb_ext.BidderName]bool{ - openrtb_ext.BidderAdgeneration: true, - openrtb_ext.BidderAdhese: true, - openrtb_ext.BidderAdoppler: true, - openrtb_ext.BidderAdot: true, - openrtb_ext.BidderAdprime: true, - openrtb_ext.BidderApplogy: true, - openrtb_ext.BidderBidmachine: true, - openrtb_ext.BidderEpom: true, - openrtb_ext.BidderDecenterAds: true, - openrtb_ext.BidderInMobi: true, - openrtb_ext.BidderKidoz: true, - openrtb_ext.BidderKubient: true, - openrtb_ext.BidderMobfoxpb: true, - openrtb_ext.BidderMobileFuse: true, - openrtb_ext.BidderOrbidder: true, - openrtb_ext.BidderPangle: true, - openrtb_ext.BidderPubnative: true, - openrtb_ext.BidderRevcontent: true, - openrtb_ext.BidderSilverMob: true, - openrtb_ext.BidderSmaato: true, - openrtb_ext.BidderSpotX: true, - openrtb_ext.BidderVASTBidder: true, - openrtb_ext.BidderUnicorn: true, - openrtb_ext.BidderYeahmobi: true, + openrtb_ext.BidderAdgeneration: true, + openrtb_ext.BidderAdhese: true, + openrtb_ext.BidderAdoppler: true, + openrtb_ext.BidderAdot: true, + openrtb_ext.BidderAdprime: true, + openrtb_ext.BidderAlgorix: true, + openrtb_ext.BidderApplogy: true, + openrtb_ext.BidderAxonix: true, + openrtb_ext.BidderBidmachine: true, + openrtb_ext.BidderBidsCube: true, + openrtb_ext.BidderEpom: true, + openrtb_ext.BidderDecenterAds: true, + openrtb_ext.BidderInteractiveoffers: true, + openrtb_ext.BidderKayzen: true, + openrtb_ext.BidderKidoz: true, + openrtb_ext.BidderKubient: true, + openrtb_ext.BidderMadvertise: true, + openrtb_ext.BidderMobfoxpb: true, + openrtb_ext.BidderMobileFuse: true, + openrtb_ext.BidderOrbidder: true, + openrtb_ext.BidderPangle: true, + openrtb_ext.BidderPubnative: true, + openrtb_ext.BidderRevcontent: true, + openrtb_ext.BidderSilverMob: true, + openrtb_ext.BidderSmaato: true, + openrtb_ext.BidderSpotX: true, + openrtb_ext.BidderUnicorn: true, + openrtb_ext.BidderVASTBidder: true, + openrtb_ext.BidderYeahmobi: true, } for bidder, config := range cfg.Adapters { diff --git a/util/jsonutil/jsonutil.go b/util/jsonutil/jsonutil.go new file mode 100644 index 00000000000..963477de7aa --- /dev/null +++ b/util/jsonutil/jsonutil.go @@ -0,0 +1,57 @@ +package jsonutil + +import ( + "bytes" + "encoding/json" + "io" +) + +var comma = []byte(",")[0] + +func DropElement(extension []byte, elementName string) ([]byte, error) { + buf := bytes.NewBuffer(extension) + dec := json.NewDecoder(buf) + var startIndex int64 + var i interface{} + for { + token, err := dec.Token() + if err == io.EOF { + // io.EOF is a successful end + break + } + if err != nil { + return nil, err + } + + if token == elementName { + err := dec.Decode(&i) + if err != nil { + return nil, err + } + endIndex := dec.InputOffset() + + if dec.More() { + //if there were other elements before + if extension[startIndex] == comma { + startIndex++ + } + + for { + //structure has more elements, need to find index of comma + if extension[endIndex] == comma { + endIndex++ + break + } + endIndex++ + } + } + + extension = append(extension[:startIndex], extension[endIndex:]...) + break + } else { + startIndex = dec.InputOffset() + } + + } + return extension, nil +} diff --git a/util/jsonutil/jsonutil_test.go b/util/jsonutil/jsonutil_test.go new file mode 100644 index 00000000000..0b6ec34c4ed --- /dev/null +++ b/util/jsonutil/jsonutil_test.go @@ -0,0 +1,122 @@ +package jsonutil + +import ( + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestDropElement(t *testing.T) { + + tests := []struct { + description string + input []byte + elementToRemove string + output []byte + errorExpected bool + errorContains string + }{ + { + description: "Drop Single Element After Another Element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1,"consented_providers": [1608,765,492]}}`), + elementToRemove: "consented_providers", + output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Single Element Before Another Element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492],"test": 1}}`), + elementToRemove: "consented_providers", + output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"test": 1}}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Single Element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1545,2563,1411]}}`), + elementToRemove: "consented_providers", + output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Single Element string", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": "test"}}`), + elementToRemove: "consented_providers", + output: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {}}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Parent Element Between Two Elements", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), + elementToRemove: "consented_providers_settings", + output: []byte(`{"consent": "TESTCONSENT","test": 123}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Parent Element Before Element", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1},"test": 123}`), + elementToRemove: "consented_providers_settings", + output: []byte(`{"test": 123}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Parent Element After Element", + input: []byte(`{"consent": "TESTCONSENT","consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToRemove: "consented_providers_settings", + output: []byte(`{"consent": "TESTCONSENT"}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Parent Element Only", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToRemove: "consented_providers_settings", + output: []byte(`{}`), + errorExpected: false, + errorContains: "", + }, + { + description: "Drop Element That Doesn't Exist", + input: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + elementToRemove: "test2", + output: []byte(`{"consented_providers_settings": {"consented_providers": [1608,765,492], "test": 1}}`), + errorExpected: false, + errorContains: "", + }, + //Errors + { + description: "Error Decode", + input: []byte(`{"consented_providers_settings": {"consented_providers": ["123",1,,1365,5678,1545,2563,1411], "test": 1}}`), + elementToRemove: "consented_providers", + output: []byte(``), + errorExpected: true, + errorContains: "looking for beginning of value", + }, + { + description: "Error Malformed", + input: []byte(`{consented_providers_settings: {"consented_providers": [1365,5678,1545,2563,1411], "test": 1}}`), + elementToRemove: "consented_providers", + output: []byte(``), + errorExpected: true, + errorContains: "invalid character", + }, + } + + for _, tt := range tests { + res, err := DropElement(tt.input, tt.elementToRemove) + + if tt.errorExpected { + assert.Error(t, err, "Error should not be nil") + assert.True(t, strings.Contains(err.Error(), tt.errorContains)) + } else { + assert.NoError(t, err, "Error should be nil") + assert.Equal(t, tt.output, res, "Result is incorrect") + } + + } +} From b14bfcdb436a1a851b6368dc048dc38010e14164 Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Tue, 17 Aug 2021 22:04:08 +0530 Subject: [PATCH 20/31] UOE-6774: Fixed Spotx GDPR issue (#195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix race condition in Yeahmobi adapter (#1761) Co-authored-by: Gus Carreon * Pubnative: Fix Shared Memory Overwriting (#1760) * Add request for registration (#1780) * Update OpenRTB Library (#1733) * Tappx changes - Backward compatible change of version (#1777) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * DMX: Enforcing w and h in imp (#1778) Co-authored-by: steve-a-districtm * Remove Authorization Headers From Debug Response (#1779) * Hide Authorization Headers In Debug Response * MakeExtHeaders Tests * Add Empty Test * Use http.Header Methods * Updates From Code Review * Fix Merge Conflict * New Adapter: Bidmachine (#1769) * New Adapter: Criteo (#1775) * Fix shared memory issue when stripping authorization header from bid requests (#1790) * RTB House: update parameters (#1785) * update parameters required by RTB House adapter * tabs to spaces Co-authored-by: Przemyslaw Iwanczak * Generate seatbid[].bid[].ext.prebid.bidid (#1772) * Basic GDPR enforcement for specific publisher-vendors. (#1782) * New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior * Zemanta: Rename Adapter To Outbrain (#1797) * Update openrtb library to v15 (#1802) * IX: Set bidVideo when category and duration is available (#1794) * Update IX defaults (#1799) Co-authored-by: Mike Burns * Update Adyoulike endpoint to hit production servers (#1805) * Openx: use bidfloor if set - prebid.js adapter behavior (#1795) * [ORBIDDER] add gvlVendorID and set bid response currency (#1798) * New Adapter: ADXCG (#1803) * Update kidoz properties to type string (#1808) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * Update bidmachine properties to type string (#1809) Remove definitions object from schema and define types and other parameters directly in properties objects to ensure compatibility with more downstream systems that use this schema. * transform native eventtrackers to imptrackers and jstracker (#1811) * TheMediaGrid: Added processing of imp[].ext.data (#1807) * New Adapter: adf (adformOpenRTB) (#1815) * initial adformOpenRTB adapter implementation * do not make request copy * rename adfromOpenRTB adapter to adf * fix user sync url * Set Adhese gvl id and vast modification flag (#1821) * Added gvlVendorID for mobilefuse (#1822) * AppNexus: reform bid floor handling (#1814) * PubNative: Add GVL Vendor ID (#1824) * InMobi: adding gvlVendorID to static yaml (#1826) * Epom Adapter: configure vendor id (GVL ID) (#1828) Co-authored-by: Vasyl Zarva * Update Adtarget gvlid (#1829) * Adding site to static yaml, and exemplary tests (#1827) * AdOcean adapter - add support for mobile apps (#1830) * Allow Native Ad Exchange Specific Types (#1810) * PubMatic: Fix Banner Size Assignment When No AdSlot Provided (#1825) * New Adapter: Interactive Offers (#1835) * IX: Set category in bid.cat (#1837) * New Adapter: Madvertise (#1834) * Conversant bid floor handling (#1840) * Adf adapter: banner and video mediatype support (#1841) * Test for data race conditions in adapters (#1756) * Revcontent adapter: add vendor id (GVL ID) (#1849) * Refactor: Removed unused GDPR return value (#1839) * New Adapter : Kayzen (#1838) * Add Kayzen Adapter * Beachfront: Add schain support (#1844) Co-authored-by: jim naumann * Pangle: add appid & placementid to bidder param (#1842) Co-authored-by: hcai * New Adapter: BidsCube (#1843) * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter Co-authored-by: vlad * Add Viewdeos alias (#1846) * [Smaato] Adding TCF 2.0 vendor id (#1852) * Pass Global Privacy Control header to bidders (#1789) * Feature Request: Ability to pass Sec-GPC header to the bidder endpoints (#1712) * making Sec-GPC value check more strict * minor syntax change * gofmt fixes * updates against draft-code-review:one, more to come soon. * adding a unit test * Adding a test and request header clone update * modified one test and related logic * modifying the last test added with slight more modification of the logic * GDPR: Don't Call Bidder If It Lacks Purpose 2 Legal Basis (#1851) * Update go-gdpr package to v0.9.0 (#1856) * Marsmedia - add GVL ID to bidder config file (#1864) Co-authored-by: Vladi Izgayev * PubMatic: Added parameters dctr & pmzoneid (#1865) * Better Support For Go Modules (#1862) * IX: Update usersync default id (#1873) * AppNexus: Make Ad Pod Id Optional (#1792) * Bugfix for applyCategoryMapping (#1857) * Facebook: Drop consented providers (#1867) * Between: Fix for bid floor issue#1787 (#1870) Co-authored-by: Egor Skorokhodov * Beachfront: Fix for bid floor issue#1787 (#1878) Co-authored-by: jim naumann * Updating interactiveoffers contact info (#1881) * Docs metrics configuration (#1850) * Criteo: update maintainer email address (#1884) * New Adapter: BrightMountainMedia (#1855) New Adapter : BrightMountainMedia * New Adapter: AlgoriX (#1861) * Remove LifeStreet + Legacy Cleanup (#1883) * New Adapter: E-Volution (#1868) * [criteo] accept zoneId and networkId alternate case (#1869) * Unit test random map order fix (#1887) Co-authored-by: Veronika Solovei * Request Provided Currency Rates (#1753) * Debug override header (#1853) * Remove GDPR TCF1 (#1854) * Rename GDPR UserSyncIfAmbiguous to DefaultValue (#1858) * Accept bidfloor from impression to fix issue #1787 for sovrn adapter (#1886) * GDPR: require host specify default value (#1859) * New Adapter: Smile Wanted (#1877) * New Adapter: Smile Wanted * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-676968474 * Improvement of test coverage as requested. * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-683853119 * Fix a weak vendor enforcement bug where vendor does not exist (#1890) * Pubmatic: Sending GPT slotname in impression extension (#1880) * Update To Go 1.16 (#1888) * Friendlier Startup Error Messages (#1894) * Second fix for weak vendor enforcement (#1896) * Rubicon: hardcode EUR to USD for floors (#1899) Co-authored-by: Serhii Nahornyi * Outbrain adapter: overwrite tagid only if it exists (#1895) * New Adapter: BidMyAdz (#1882) Co-authored-by: BidMyAdz * Currency Conversion Utility Function (#1901) * New Adapter: SA Lunamedia (#1891) * Removed Digitrust From Prebid Server (#1892) Co-authored-by: avolcy * IX: merge eventtrackers with imptrackers for native bid responses (#1900) * Inmobi: user sync (#1911) * Rubicon: Update segtax logic (#1909) Co-authored-by: Serhii Nahornyi * New Adapter: Axonix (#1912) * New Axonix adapter * Changed endpoint * Rename adapter type * Leave in examplary only the basic test fixtures * PR comments * Rubicon: Fix Nil Reference Panic (#1918) * GDPR: host-level per-purpose vendor exceptions config (#1893) Co-authored-by: Scott Kay * Criteo - Fix fields mapping error when building bid from bidder response (#1917) * Smaato: Rework multi imp support and add adpod support (#1902) * Allowed $0.00 price bids if there are deals (#1910) * GDPR: host-level per-purpose enforce vendor signals config (#1921) * Add GDPR host-level per-purpose enforce vendor signals config * Update config defaults test with TCF2 object compare * Fix for fetcher warning at server startup (#1914) Co-authored-by: Veronika Solovei * Request Wrapper first pass (#1784) * Rubicon: Use currency conversion function (#1924) Co-authored-by: Serhii Nahornyi * New Adapter: operaads (#1916) * Fix Beachfront data race condition (#1915) Co-authored-by: Jim Naumann * Sharethrough: Add support for GPID (#1925) * Admixer: Fix for bid floor issue#1787 (#1872) * InMobi: adding native support (#1928) * Tappx: new bidder params (#1931) Co-authored-by: Albert Grandes * Fix CVE-2020-35381 (#1942) * Smaato: Split multiple media types (#1930) Co-authored-by: Bernhard Pickenbrock * New adapter: Adagio (#1907) * IX: update required site id field to be more flexible (#1934) Co-authored-by: Joshua Gross * Add SmartRTB adapter (#1071) * Adds timeout notifications for Facebook (#1182) * Add Adoppler bidder support. (#1186) * Add Adoppler bidder support. * Address code review comments. Use JSON-templates for testing. * Fix misprint; Add url.PathEscape call for adunit URL parameter. * Kidoz adapter (#1210) Co-authored-by: Ryan Haksi * AMP CCPA Fix (#1187) * Add kidoz bidder info (#1257) got this info from email communication with kidoz * populate the app ID in the FAN timeout notif url with the publisher ID (#1265) and the auction with the request ID Co-authored-by: Aadesh Patel * * Add PubMatic bidder doc file (#1255) * Add app video capability to PubMatic bidder info file * Added OpenX Bidder adapter documentation (#1291) * Restore the AMP privacy exception as an option. (#1311) * Restore the AMP privacy exception as an option. * Adds missing test case * More PR feedback * Remove unused constant * Comment tweak * Add Yieldlab Adapter (#1287) Co-authored-by: Mirko Feddern Signed-off-by: Alex Klinkert Co-authored-by: Alexander Pinnecke Co-authored-by: Alex Klinkert Co-authored-by: Mirko Feddern * Add Pubnative bidder documentation (#1340) * Add Adtarget server adapter (#1319) * Add Adtarget server adapter * Suggested changes for Adtarget * Avoid overriding AMP request original size with mutli-size (#1352) * Adds Avocet adapter (#1354) * Adding Smartadserver adapter (#1346) Co-authored-by: tadam * Metrics for TCF 2 adoption (#1360) * Add support for multiple root schain nodes (#1374) * Facebook Only Supports App Impressions (#1396) * Add Outgoing Connection Metrics (#1343) * OpenX adapter: pass optional platform (PBID-598) (#1421) * Adds keyvalue hb_format support (#1414) * feat: Add new logger module - Pubstack Analytics Module (#1331) * Pubstack Analytics V1 (#11) * V1 Pubstack (#7) * feat: Add Pubstack Logger (#6) * first version of pubstack analytics * bypass viperconfig * commit #1 * gofmt * update configuration and make the tests pass * add readme on how to configure the adapter and update the network calls * update logging and fix intake url definition * feat: Pubstack Analytics Connector * fixing go mod * fix: bad behaviour on appending path to auction url * add buffering * support bootstyrap like configuration * implement route for all the objects * supports termination signal handling for goroutines * move readme to the correct location * wording * enable configuration reload + add tests * fix logs messages * fix tests * fix log line * conclude merge * merge * update go mod Co-authored-by: Amaury Ravanel * fix duplicated channel keys Co-authored-by: Amaury Ravanel * first pass - PR reviews * rename channel* -> eventChannel * dead code * Review (#10) * use json.Decoder * update documentation * use nil instead []byte("") * clean code * do not use http.DefaultClient * fix race condition (need validation) * separate the sender and buffer logics * refactor the default configuration * remove error counter * Review GP + AR * updating default config * add more logs * remove alias fields in json * fix json serializer * close event channels Co-authored-by: Amaury Ravanel * fix race condition * first pass (pr reviews) * refactor: store enabled modules into a dedicated struct * stop goroutine * test: improve coverage * PR Review * Revert "refactor: store enabled modules into a dedicated struct" This reverts commit f57d9d61680c74244effc39a5d96d6cbb2f19f7d. # Conflicts: # analytics/config/config_test.go Co-authored-by: Amaury Ravanel * New bid adapter for Smaato (#1413) Co-authored-by: vikram Co-authored-by: Stephan * New Adprime adapter (#1418) Co-authored-by: Aiholkin * Enable geo activation of GDPR flag (#1427) * moving docs to website repo (#1443) * Add support for Account configuration (PBID-727, #1395) (#1426) * Pass Through First Party Context Data (#1479) * between adapter (#1437) Co-authored-by: Alexey Elymanov * Bidder Uniqueness Gatekeeping Test (#1506) * Smarty ads adapter (#1500) Co-authored-by: Kushneryk Pavlo Co-authored-by: user * Vtrack and event endpoints (#1467) * Add bidder name key support (#1496) * Add metrics to capture stored data fetch all/delta durations with fetch status (#1515) * Acuity ads adapter (#1537) Co-authored-by: Kushneryk Pavlo * Yieldmo app support in yaml file (#1542) Co-authored-by: Winston * Add client/AccountID support into Adoppler adapter. (#1535) * 33Across: Add video support in adapter (#1557) * Fix bug in request.imp.ext Validation (#1575) * First draft * Brian's reivew * Removed leftover comments Co-authored-by: Gus Carreon * New Adapter Initialization Framework (#1532) * Fix 33Across App Handling (#1602) * Fix adapter JSON tests to have the right test structure (#1589) * Fix JSON EMX Digital * Fix JSON Brightroll * Fix JSON Beintoo * Fix JSON Gamoshi * Fix JSON Kubient * Fix JSON Marsmedia * Fix JSON Nanointeractive * Fix JSON Telaria * Fix JSON valueimpression * Fix JSON smartyads * Fix JSON rhythmone * Fix JSON krushmedia * Fix JSON cpmstar * Fix JSON acuityads * Fix JSON avocet * Rename wrongly named acuity ads test file * Fix JSON gamma * Add expected no bid responses * Fixed indentation and asesome-markup * Added usersync support to Between SSP adapter; Major fixes and refactor (#1587) Co-authored-by: Egor Skorokhodov * 33Across: Add support for multi-imp requests (#1609) * Remove legacy GDPR AMP config flag used to prevent buyer ID scrub on AMP requests (#1565) * New Adapter: Mobfox (#1585) Co-authored-by: mobfox * New Adapter: Revcontent (#1622) * Audit beachfront tests and change some videoResponseType details (#1638) * Tappx User Syncer + Site Update (#1674) Co-authored-by: ubuntu Co-authored-by: Albert Grandes * Beachfront Additional tests (#1679) * added place holder files for all but a couple of the intended new tests. I need to grok what those couple mean before being able to name a file. * This covers most of the suggested cases and a couple more that occured to me. I'll look at the couple that I held off on next. * added the unmarshal tests and found a couple problems to address in the process. * removed my __debug_bin. should be in gitignore. * A bit of clean up and commenting. Bumped version number. Added __debug_bin to .gitignore. This is the debugging binary created by Visual Studio Code, or at least version 1.52.1. * missed a bunch of version strings * removed IP faker * If IP is not included in an AdM request, an error is now thrown for the AdM imp instead of faking it. The AdM endpoint is the only one that requires an IP. Also, added several "no-ip" test cases. * Whent back to the fake IP solution instead of the error. Removed most of the "no-ip" test cases, leaving one. * changed ip in adm-video.json to not match the faker ip * removed a debugging comment * Mobfox: Add rout to adexcange (#1702) Co-authored-by: mobfox * Add Support For SkAdN + Refactor Split Imps (#1741) * No Longer Move bid.ext To bid.ext.bidder (#1742) * No Longer Move bid.ext To bid.ext.bidder * Remove Similar Behavior From seatbid.ext * Avoid Second Bid Copy * Removed Unused seatbid.ext * Debug warnings (#1724) Co-authored-by: Veronika Solovei * FPD: Allow imp.ext.data To Passthrough To Adapters (#1765) * Basic GDPR enforcement for specific publisher-vendors. (#1782) * New Adapter: Zemanta (#1774) * add zemanta adapter * update openrtb package for zemanta * fix loop iterator reference bug * fix getMediaTypeForImp to match server behavior * Zemanta: Rename Adapter To Outbrain (#1797) * New Adapter: adf (adformOpenRTB) (#1815) * initial adformOpenRTB adapter implementation * do not make request copy * rename adfromOpenRTB adapter to adf * fix user sync url * Set Adhese gvl id and vast modification flag (#1821) * Added gvlVendorID for mobilefuse (#1822) * AppNexus: reform bid floor handling (#1814) * PubNative: Add GVL Vendor ID (#1824) * InMobi: adding gvlVendorID to static yaml (#1826) * Epom Adapter: configure vendor id (GVL ID) (#1828) Co-authored-by: Vasyl Zarva * Update Adtarget gvlid (#1829) * Adding site to static yaml, and exemplary tests (#1827) * AdOcean adapter - add support for mobile apps (#1830) * Allow Native Ad Exchange Specific Types (#1810) * PubMatic: Fix Banner Size Assignment When No AdSlot Provided (#1825) * New Adapter: Interactive Offers (#1835) * IX: Set category in bid.cat (#1837) * New Adapter: Madvertise (#1834) * Conversant bid floor handling (#1840) * Adf adapter: banner and video mediatype support (#1841) * Test for data race conditions in adapters (#1756) * Revcontent adapter: add vendor id (GVL ID) (#1849) * Refactor: Removed unused GDPR return value (#1839) * New Adapter : Kayzen (#1838) * Add Kayzen Adapter * Beachfront: Add schain support (#1844) Co-authored-by: jim naumann * Pangle: add appid & placementid to bidder param (#1842) Co-authored-by: hcai * New Adapter: BidsCube (#1843) * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter * add BidsCube adapter Co-authored-by: vlad * Add Viewdeos alias (#1846) * [Smaato] Adding TCF 2.0 vendor id (#1852) * Pass Global Privacy Control header to bidders (#1789) * Feature Request: Ability to pass Sec-GPC header to the bidder endpoints (#1712) * making Sec-GPC value check more strict * minor syntax change * gofmt fixes * updates against draft-code-review:one, more to come soon. * adding a unit test * Adding a test and request header clone update * modified one test and related logic * modifying the last test added with slight more modification of the logic * GDPR: Don't Call Bidder If It Lacks Purpose 2 Legal Basis (#1851) * Update go-gdpr package to v0.9.0 (#1856) * Marsmedia - add GVL ID to bidder config file (#1864) Co-authored-by: Vladi Izgayev * PubMatic: Added parameters dctr & pmzoneid (#1865) * Better Support For Go Modules (#1862) * AppNexus: Make Ad Pod Id Optional (#1792) * Facebook: Drop consented providers (#1867) * Between: Fix for bid floor issue#1787 (#1870) Co-authored-by: Egor Skorokhodov * Beachfront: Fix for bid floor issue#1787 (#1878) Co-authored-by: jim naumann * Updating interactiveoffers contact info (#1881) * Docs metrics configuration (#1850) * Criteo: update maintainer email address (#1884) * New Adapter: BrightMountainMedia (#1855) New Adapter : BrightMountainMedia * New Adapter: AlgoriX (#1861) * Remove LifeStreet + Legacy Cleanup (#1883) * New Adapter: E-Volution (#1868) * [criteo] accept zoneId and networkId alternate case (#1869) * Request Provided Currency Rates (#1753) * Debug override header (#1853) * Remove GDPR TCF1 (#1854) * Rename GDPR UserSyncIfAmbiguous to DefaultValue (#1858) * Accept bidfloor from impression to fix issue #1787 for sovrn adapter (#1886) * GDPR: require host specify default value (#1859) * New Adapter: Smile Wanted (#1877) * New Adapter: Smile Wanted * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-676968474 * Improvement of test coverage as requested. * Implementations of changes requested by : https://github.com/prebid/prebid-server/pull/1877#pullrequestreview-683853119 * Fix a weak vendor enforcement bug where vendor does not exist (#1890) * Update To Go 1.16 (#1888) * Friendlier Startup Error Messages (#1894) * Second fix for weak vendor enforcement (#1896) * Rubicon: hardcode EUR to USD for floors (#1899) Co-authored-by: Serhii Nahornyi * Outbrain adapter: overwrite tagid only if it exists (#1895) * New Adapter: BidMyAdz (#1882) Co-authored-by: BidMyAdz * Currency Conversion Utility Function (#1901) * New Adapter: SA Lunamedia (#1891) * Removed Digitrust From Prebid Server (#1892) Co-authored-by: avolcy * IX: merge eventtrackers with imptrackers for native bid responses (#1900) * Inmobi: user sync (#1911) * Rubicon: Update segtax logic (#1909) Co-authored-by: Serhii Nahornyi * New Adapter: Axonix (#1912) * New Axonix adapter * Changed endpoint * Rename adapter type * Leave in examplary only the basic test fixtures * PR comments * Rubicon: Fix Nil Reference Panic (#1918) * git rebase * Reverted some changes after prebid-server upgrade * Fixed ctv_auction.go after merging prebid-0.170.0 * UOE-6774: Fixed GDPR flow for Spotx * Added missing gdpr.default_value * Fixed usersync url for Unruly Co-authored-by: guscarreon Co-authored-by: Gus Carreon Co-authored-by: bretg Co-authored-by: Scott Kay Co-authored-by: prebidtappx <77485538+prebidtappx@users.noreply.github.com> Co-authored-by: ubuntu Co-authored-by: Albert Grandes Co-authored-by: Steve Alliance Co-authored-by: steve-a-districtm Co-authored-by: Pavel Dunyashev Co-authored-by: Benjamin Co-authored-by: Brian Sardo <1168933+bsardo@users.noreply.github.com> Co-authored-by: Przemysław Iwańczak <36727380+piwanczak@users.noreply.github.com> Co-authored-by: Przemyslaw Iwanczak Co-authored-by: Veronika Solovei Co-authored-by: hhhjort <31041505+hhhjort@users.noreply.github.com> Co-authored-by: Rok Sušnik Co-authored-by: Rok Sušnik Co-authored-by: ixjohnny <75964135+ixjohnny@users.noreply.github.com> Co-authored-by: Michael Burns Co-authored-by: Mike Burns Co-authored-by: guiann Co-authored-by: Laurentiu Badea Co-authored-by: Arne Schulz Co-authored-by: adxcgcom <31470944+adxcgcom@users.noreply.github.com> Co-authored-by: agilfix Co-authored-by: TheMediaGrid <44166371+TheMediaGrid@users.noreply.github.com> Co-authored-by: Jurij Sinickij Co-authored-by: mefjush Co-authored-by: dtbarne <7635750+dtbarne@users.noreply.github.com> Co-authored-by: Pillsoo Shin Co-authored-by: Daniel Lawrence Co-authored-by: epomrnd Co-authored-by: Vasyl Zarva Co-authored-by: Gena Co-authored-by: Marcin Muras <47107445+mmuras@users.noreply.github.com> Co-authored-by: IOTiagoFaria <76956619+IOTiagoFaria@users.noreply.github.com> Co-authored-by: notmani Co-authored-by: johnwier <49074029+johnwier@users.noreply.github.com> Co-authored-by: jcamp-revc <68560678+jcamp-revc@users.noreply.github.com> Co-authored-by: Raghu Teja <2473294+raghuteja@users.noreply.github.com> Co-authored-by: Jim Naumann Co-authored-by: jim naumann Co-authored-by: Hengsheng Cai Co-authored-by: hcai Co-authored-by: Vladyslav Laktionov Co-authored-by: vlad Co-authored-by: Ruslan Sibgatullin Co-authored-by: Vivek Narang Co-authored-by: vladi-mmg Co-authored-by: Vladi Izgayev Co-authored-by: egsk Co-authored-by: Egor Skorokhodov Co-authored-by: timoshas Co-authored-by: Léonard Labat Co-authored-by: BrightMountainMedia <69471268+BrightMountainMediaInc@users.noreply.github.com> Co-authored-by: Bugxyb Co-authored-by: e-volution-tech <61746103+e-volution-tech@users.noreply.github.com> Co-authored-by: Léonard Labat Co-authored-by: Veronika Solovei Co-authored-by: Rachel Joyce Co-authored-by: Maxime DEYMÈS <47388595+MaxSmileWanted@users.noreply.github.com> Co-authored-by: Serhii Nahornyi Co-authored-by: Serhii Nahornyi Co-authored-by: bidmyadz <82382704+bidmyadz@users.noreply.github.com> Co-authored-by: BidMyAdz Co-authored-by: lunamedia <73552749+lunamedia@users.noreply.github.com> Co-authored-by: AlexBVolcy <74930484+AlexBVolcy@users.noreply.github.com> Co-authored-by: avolcy Co-authored-by: Mani Gandham Co-authored-by: armon823 <86739148+armon823@users.noreply.github.com> Co-authored-by: César Fernández Co-authored-by: el-chuck Co-authored-by: jizeyopera <70930512+jizeyopera@users.noreply.github.com> Co-authored-by: Mansi Nahar Co-authored-by: Jim Naumann Co-authored-by: Eddy Pechuzal <46331062+epechuzal@users.noreply.github.com> Co-authored-by: avolokha <84977155+avolokha@users.noreply.github.com> Co-authored-by: Bernhard Pickenbrock Co-authored-by: Olivier Co-authored-by: Joshua Gross <820727+grossjo@users.noreply.github.com> Co-authored-by: Joshua Gross Co-authored-by: evanmsmrtb Co-authored-by: Viacheslav Chimishuk Co-authored-by: rhaksi-kidoz <61601767+rhaksi-kidoz@users.noreply.github.com> Co-authored-by: Ryan Haksi Co-authored-by: Aadesh Co-authored-by: Aadesh Patel Co-authored-by: Mike Chowla Co-authored-by: Jimmy Tu Co-authored-by: Mirko Feddern <3244291+mirkorean@users.noreply.github.com> Co-authored-by: Alexander Pinnecke Co-authored-by: Alex Klinkert Co-authored-by: Mirko Feddern Co-authored-by: Artur Aleksanyan Co-authored-by: Richard Lee <14349+dlackty@users.noreply.github.com> Co-authored-by: Simon Critchley Co-authored-by: tadam75 Co-authored-by: tadam Co-authored-by: gpolaert Co-authored-by: Amaury Ravanel Co-authored-by: Vikram Co-authored-by: vikram Co-authored-by: Stephan Co-authored-by: Adprime <64427228+Adprime@users.noreply.github.com> Co-authored-by: Aiholkin Co-authored-by: Alexey Elymanov Co-authored-by: Alexey Elymanov Co-authored-by: Kushneryk Pavel Co-authored-by: Kushneryk Pavlo Co-authored-by: user Co-authored-by: Daniel Barrigas Co-authored-by: Cameron Rice <37162584+camrice@users.noreply.github.com> Co-authored-by: AcuityAdsIntegrations <72594990+AcuityAdsIntegrations@users.noreply.github.com> Co-authored-by: Winston-Yieldmo <46379634+Winston-Yieldmo@users.noreply.github.com> Co-authored-by: Winston Co-authored-by: Aparna Rao Co-authored-by: Gus Carreon Co-authored-by: mobfxoHB <74364234+mobfxoHB@users.noreply.github.com> --- static/bidder-info/spotx.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/static/bidder-info/spotx.yaml b/static/bidder-info/spotx.yaml index 6aa799d9c67..51824561022 100644 --- a/static/bidder-info/spotx.yaml +++ b/static/bidder-info/spotx.yaml @@ -1,5 +1,6 @@ maintainer: email: "teameighties@spotx.tv" +gvlVendorID: 165 capabilities: app: mediaTypes: From 1ef324dbd3d15c3805aebbd97e30a105308484b3 Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Wed, 18 Aug 2021 20:48:36 +0530 Subject: [PATCH 21/31] Handled NPE in interstitial.go (#196) --- endpoints/openrtb2/interstitial.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/endpoints/openrtb2/interstitial.go b/endpoints/openrtb2/interstitial.go index 359bae11d4c..18717766e26 100644 --- a/endpoints/openrtb2/interstitial.go +++ b/endpoints/openrtb2/interstitial.go @@ -24,7 +24,7 @@ func processInterstitials(req *openrtb_ext.RequestWrapper) error { return err } prebid = deviceExt.GetPrebid() - if prebid.Interstitial == nil { + if prebid == nil || prebid.Interstitial == nil { // No special interstitial support requested, so bail as there is nothing to do return nil } From 2d39ba36135d4af471b986d645812d7af015f09c Mon Sep 17 00:00:00 2001 From: PubMatic-OpenWrap Date: Tue, 31 Aug 2021 17:01:14 +0530 Subject: [PATCH 22/31] OTT-217 - Remove loggers for filtered VAST Tags --- endpoints/openrtb2/ctv_auction.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go index 00a2b254b32..612f10f6836 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -387,12 +387,9 @@ func (deps *ctvEndpointDeps) setDefaultValues() { //set request is adpod request or normal request deps.setIsAdPodRequest() - //TODO: OTT-217, OTT-161 commenting code of filtering vast tags - /* - if deps.isAdPodRequest { - deps.readImpExtensionsAndTags() - } - */ + if deps.isAdPodRequest { + deps.readImpExtensionsAndTags() + } } //validateBidRequest will validate AdPod specific mandatory Parameters and returns error @@ -468,8 +465,7 @@ func (deps *ctvEndpointDeps) createBidRequest(req *openrtb2.BidRequest) *openrtb //createImpressions ctvRequest.Imp = deps.createImpressions() - //TODO: OTT-217, OTT-161 commenting code of filtering vast tags - //deps.filterImpsVastTagsByDuration(&ctvRequest) + deps.filterImpsVastTagsByDuration(&ctvRequest) //TODO: remove adpod extension if not required to send further return &ctvRequest From 705323740e23e808a9fc6e8e3c993e0bd6494e63 Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:12:54 +0530 Subject: [PATCH 23/31] UOE-6744: Added code missed in previous prebid-server upgrade (#200) --- endpoints/openrtb2/auction.go | 16 ++++++++++++++- endpoints/openrtb2/auction_test.go | 33 ++++++++++++++++++++++++++++++ errortypes/code.go | 1 + errortypes/errortypes.go | 19 +++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index a6b3479a36c..b6208c66a5d 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -1001,6 +1001,7 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]s /* Process all the bidder exts in the request */ disabledBidders := []string{} otherExtElements := 0 + validationFailedBidders := []string{} for bidder, ext := range bidderExts { if isBidderToValidate(bidder) { coreBidder := bidder @@ -1009,7 +1010,10 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]s } if bidderName, isValid := deps.bidderMap[coreBidder]; isValid { if err := deps.paramsValidator.Validate(bidderName, ext); err != nil { - return []error{fmt.Errorf("request.imp[%d].ext.%s failed validation.\n%v", impIndex, coreBidder, err)} + validationFailedBidders = append(validationFailedBidders, bidder) + msg := fmt.Sprintf("request.imp[%d].ext.%s failed validation.\n%v", impIndex, coreBidder, err) + glog.Errorf("BidderSchemaValidationError: %s", msg) + errL = append(errL, &errortypes.BidderFailedSchemaValidation{Message: msg}) } } else { if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled { @@ -1029,6 +1033,16 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]s for _, bidder := range disabledBidders { delete(bidderExts, bidder) } + } + + // delete bidders with invalid params + if len(validationFailedBidders) > 0 { + for _, bidder := range validationFailedBidders { + delete(bidderExts, bidder) + } + } + + if len(disabledBidders) > 0 || len(validationFailedBidders) > 0 { extJSON, err := json.Marshal(bidderExts) if err != nil { return []error{err} diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 4835dd92943..af4cfc54dba 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1502,6 +1502,39 @@ func TestValidateImpExt(t *testing.T) { }, }, }, + { + "Invalid bidder params tests", + []testCase{ + { + description: "Impression dropped for bidder with invalid bidder params", + impExt: json.RawMessage(`{"appnexus":{"placement_id":"A"}}`), + expectedImpExt: `{}`, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.appnexus failed validation.\nplacement_id: Invalid type. Expected: integer, given: string"}, + fmt.Errorf("request.imp[%d].ext must contain at least one bidder", 0)}, + }, + { + description: "Valid Bidder params + Invalid bidder params", + impExt: json.RawMessage(`{"appnexus":{"placement_id":"A"},"pubmatic":{"publisherId":"156209"}}`), + expectedImpExt: `{"pubmatic":{"publisherId":"156209"}}`, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.appnexus failed validation.\nplacement_id: Invalid type. Expected: integer, given: string"}}, + }, + { + description: "Valid Bidder + Disabled Bidder + Invalid bidder params", + impExt: json.RawMessage(`{"pubmatic":{"publisherId":156209},"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), + expectedImpExt: `{"appnexus":{"placement_id":555}}`, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.pubmatic failed validation.\npublisherId: Invalid type. Expected: string, given: integer"}, + &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, + }, + { + description: "Valid Bidder + Disabled Bidder + Invalid bidder params", + impExt: json.RawMessage(`{"pubmatic":{"publisherId":156209},"disabledbidder":{"foo":"bar"}}`), + expectedImpExt: `{}`, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.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 must contain at least one bidder", 0)}, + }, + }, + }, } deps := &endpointDeps{ diff --git a/errortypes/code.go b/errortypes/code.go index 554357ea88a..637c51e9de0 100644 --- a/errortypes/code.go +++ b/errortypes/code.go @@ -13,6 +13,7 @@ const ( AcctRequiredErrorCode NoConversionRateErrorCode NoBidPriceErrorCode + BidderFailedSchemaValidationErrorCode ) // Defines numeric codes for well-known warnings. diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index 6bac21fcb0d..e86a3cb0f1b 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -198,3 +198,22 @@ func (err *NoBidPrice) Code() int { func (err *NoBidPrice) Severity() Severity { return SeverityWarning } + +// 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 +} From cf8805c5883c80e3eba3fbb95d2b1c102192187c Mon Sep 17 00:00:00 2001 From: pm-isha-bharti Date: Mon, 13 Sep 2021 19:06:49 +0530 Subject: [PATCH 24/31] UOE-6853: Update ExtCTVBid to include skadn --- openrtb_ext/adpod.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openrtb_ext/adpod.go b/openrtb_ext/adpod.go index ac815cda224..281389e9c64 100644 --- a/openrtb_ext/adpod.go +++ b/openrtb_ext/adpod.go @@ -28,7 +28,8 @@ var ( // ExtCTVBid defines the contract for bidresponse.seatbid.bid[i].ext type ExtCTVBid struct { ExtBid - AdPod *BidAdPodExt `json:"adpod,omitempty"` + AdPod *BidAdPodExt `json:"adpod,omitempty"` + SKAdNetwork json.RawMessage `json:"skadn,omitempty"` } // BidAdPodExt defines the prebid adpod response in bidresponse.ext.adpod parameter From 5111eb18a7d7c911fc898c9d8ca3b25f8a2a2b97 Mon Sep 17 00:00:00 2001 From: Isha Bharti Date: Mon, 13 Sep 2021 19:27:01 +0530 Subject: [PATCH 25/31] UOE-6853: Renaming ExtCTVBid to ExtOWBid --- endpoints/openrtb2/ctv_auction.go | 2 +- openrtb_ext/adpod.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/endpoints/openrtb2/ctv_auction.go b/endpoints/openrtb2/ctv_auction.go index 612f10f6836..dffe9f28ba3 100644 --- a/endpoints/openrtb2/ctv_auction.go +++ b/endpoints/openrtb2/ctv_auction.go @@ -1007,7 +1007,7 @@ func getAdPodBidCreative(video *openrtb2.Video, adpod *types.AdPodBid) *string { //getAdPodBidExtension get commulative adpod bid details func getAdPodBidExtension(adpod *types.AdPodBid) json.RawMessage { - bidExt := &openrtb_ext.ExtCTVBid{ + bidExt := &openrtb_ext.ExtOWBid{ ExtBid: openrtb_ext.ExtBid{ Prebid: &openrtb_ext.ExtBidPrebid{ Type: openrtb_ext.BidTypeVideo, diff --git a/openrtb_ext/adpod.go b/openrtb_ext/adpod.go index 281389e9c64..c7caa41eb29 100644 --- a/openrtb_ext/adpod.go +++ b/openrtb_ext/adpod.go @@ -1,6 +1,7 @@ package openrtb_ext import ( + "encoding/json" "errors" "strings" ) @@ -26,7 +27,7 @@ var ( ) // ExtCTVBid defines the contract for bidresponse.seatbid.bid[i].ext -type ExtCTVBid struct { +type ExtOWBid struct { ExtBid AdPod *BidAdPodExt `json:"adpod,omitempty"` SKAdNetwork json.RawMessage `json:"skadn,omitempty"` From 43b271af3ad933d0b607d17912fefd1fe711a06f Mon Sep 17 00:00:00 2001 From: Sachin Survase <60501281+sachin-pubmatic@users.noreply.github.com> Date: Wed, 15 Sep 2021 21:58:44 +0530 Subject: [PATCH 26/31] Run validate.sh to format files & fixed unit test for bidderparams (#204) --- config/config_test.go | 2 +- endpoints/events/vtrack_test.go | 2 +- endpoints/openrtb2/auction_test.go | 10 +++++----- metrics/metrics_mock.go | 2 +- privacy/gdpr/policy_test.go | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config/config_test.go b/config/config_test.go index f5fbbfa341f..a87d65af359 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -115,7 +115,7 @@ func TestExternalCacheURLValidate(t *testing.T) { } } - func TestDefaults(t *testing.T) { +func TestDefaults(t *testing.T) { cfg, _ := newDefaultConfig(t) cmpInts(t, "port", cfg.Port, 8000) diff --git a/endpoints/events/vtrack_test.go b/endpoints/events/vtrack_test.go index 94a81b00d20..6f290b22499 100644 --- a/endpoints/events/vtrack_test.go +++ b/endpoints/events/vtrack_test.go @@ -938,7 +938,7 @@ func TestGetVideoEventTracking(t *testing.T) { name: "valid_scenario", args: args{ trackerURL: "http://company.tracker.com?eventId=[EVENT_ID]&appbundle=[DOMAIN]", - bid: &openrtb2.Bid{ + bid: &openrtb2.Bid{ // AdM: vastXMLWith2Creatives, }, req: &openrtb2.BidRequest{ diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index af4cfc54dba..a36e6cd0838 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -1509,7 +1509,7 @@ func TestValidateImpExt(t *testing.T) { description: "Impression dropped for bidder with invalid bidder params", impExt: json.RawMessage(`{"appnexus":{"placement_id":"A"}}`), expectedImpExt: `{}`, - expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.appnexus failed validation.\nplacement_id: Invalid type. Expected: integer, given: string"}, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.appnexus failed validation.\nplacement_id: Invalid type. Expected: integer, given: string"}, fmt.Errorf("request.imp[%d].ext must contain at least one bidder", 0)}, }, { @@ -1522,14 +1522,14 @@ func TestValidateImpExt(t *testing.T) { description: "Valid Bidder + Disabled Bidder + Invalid bidder params", impExt: json.RawMessage(`{"pubmatic":{"publisherId":156209},"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), expectedImpExt: `{"appnexus":{"placement_id":555}}`, - expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.pubmatic failed validation.\npublisherId: Invalid type. Expected: string, given: integer"}, - &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, + expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, + &errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.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: `{}`, - expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.pubmatic failed validation.\npublisherId: Invalid type. Expected: string, given: integer"}, + expectedErrs: []error{&errortypes.BidderFailedSchemaValidation{Message: "request.imp[0].ext.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 must contain at least one bidder", 0)}, }, @@ -1566,7 +1566,7 @@ func TestValidateImpExt(t *testing.T) { } 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) + assert.ElementsMatch(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) } } } diff --git a/metrics/metrics_mock.go b/metrics/metrics_mock.go index 8cc91b31c8a..62a01e2a08c 100644 --- a/metrics/metrics_mock.go +++ b/metrics/metrics_mock.go @@ -174,4 +174,4 @@ func (me *MetricsEngineMock) RecordPodCompititveExclusionTime(labels PodLabels, //RecordAdapterVideoBidDuration mock func (me *MetricsEngineMock) RecordAdapterVideoBidDuration(labels AdapterLabels, videoBidDuration int) { me.Called(labels, videoBidDuration) -} \ No newline at end of file +} diff --git a/privacy/gdpr/policy_test.go b/privacy/gdpr/policy_test.go index 9274c5b58be..a0fa6241d72 100644 --- a/privacy/gdpr/policy_test.go +++ b/privacy/gdpr/policy_test.go @@ -28,4 +28,4 @@ func TestValidateConsent(t *testing.T) { result := ValidateConsent(test.consent) assert.Equal(t, test.expected, result, test.description) } -} \ No newline at end of file +} From 23c6c51433b0210d1999c820021030d3e4b282df Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Tue, 28 Sep 2021 16:04:01 +0530 Subject: [PATCH 27/31] Added vendor list file fetching scheduler --- gdpr/vendorlist-scheduler.go | 45 ++++++++++++++++++++++++++++++++++++ main.go | 7 ++++++ 2 files changed, 52 insertions(+) create mode 100644 gdpr/vendorlist-scheduler.go diff --git a/gdpr/vendorlist-scheduler.go b/gdpr/vendorlist-scheduler.go new file mode 100644 index 00000000000..299e60f5b6b --- /dev/null +++ b/gdpr/vendorlist-scheduler.go @@ -0,0 +1,45 @@ +package gdpr + +import ( + "fmt" + "time" +) + +type VendorListScheduler struct { + ticker *time.Ticker + interval time.Duration + done chan bool +} + +func NewVendorListScheduler(interval string) (*VendorListScheduler, error) { + d, err := time.ParseDuration(interval) + if err != nil { + return nil, err + } + + scheduler := &VendorListScheduler{ + ticker: nil, + interval: d, + done: make(chan bool), + } + return scheduler, nil +} + +func (scheduler *VendorListScheduler) Start() { + scheduler.ticker = time.NewTicker(scheduler.interval) + go func() { + for { + select { + case <-scheduler.done: + return + case t := <-scheduler.ticker.C: + fmt.Println("Tick at", t) + } + } + }() +} + +func (scheduler *VendorListScheduler) Stop() { + scheduler.ticker.Stop() + scheduler.done <- true +} diff --git a/main.go b/main.go index e73df6272c6..4a829736981 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package prebidServer import ( + "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/usersync" "math/rand" @@ -89,6 +90,12 @@ func serve(revision string, cfg *config.Configuration) error { pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) pbc.InitPrebidCacheURL(cfg.ExternalURL) + vendorListScheduler, err := gdpr.NewVendorListScheduler("1s") + if err != nil { + return err + } + vendorListScheduler.Start() + //corsRouter := router.SupportCORS(r) //server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(revision, currencyConverter, fetchingInterval), r.MetricsEngine) From aa876e23e1ffa7471ae80beb39184b2dfd9c664f Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Wed, 29 Sep 2021 18:55:55 +0530 Subject: [PATCH 28/31] Refactored code --- config/config.go | 8 +++ gdpr/vendorlist-fetching.go | 9 ++-- gdpr/vendorlist-scheduler.go | 102 +++++++++++++++++++++++++++++------ main.go | 7 --- router/router.go | 8 +++ 5 files changed, 107 insertions(+), 27 deletions(-) diff --git a/config/config.go b/config/config.go index efb71adcc31..80c94661b8a 100644 --- a/config/config.go +++ b/config/config.go @@ -83,6 +83,14 @@ type Configuration struct { //When true, new bid id will be generated in seatbid[].bid[].ext.prebid.bidid and used in event urls instead GenerateBidID bool `mapstructure:"generate_bid_id"` TrackerURL string `mapstructure:"tracker_url"` + + VendorListScheduler VendorListScheduler `mapstructure:"vendor_list_scheduler"` +} + +type VendorListScheduler struct { + Enabled bool `mapstructure:"enabled"` + Interval string `mapstructure:"interval"` + Timeout string `mapstructure:"timeout"` } const MIN_COOKIE_SIZE_BYTES = 500 diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index 24489e73265..9837ef33746 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -20,6 +20,9 @@ import ( type saveVendors func(uint16, api.VendorList) +var cacheSave func(vendorListVersion uint16, list api.VendorList) +var cacheLoad func(vendorListVersion 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 @@ -27,7 +30,7 @@ type saveVendors func(uint16, api.VendorList) // 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) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { - cacheSave, cacheLoad := newVendorListCache() + cacheSave, cacheLoad = newVendorListCache() preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout()) defer cancel() @@ -75,9 +78,9 @@ func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16 // this will fetch the latest version. func vendorListURLMaker(vendorListVersion uint16) string { if vendorListVersion == 0 { - return "https://vendor-list.consensu.org/v2/vendor-list.json" + return "http://localhost/v2/vendor-list.json" } - return "https://vendor-list.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json" + return "http://localhost/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json" } // newOccasionalSaver returns a wrapped version of saveOne() which only activates every few minutes. diff --git a/gdpr/vendorlist-scheduler.go b/gdpr/vendorlist-scheduler.go index 299e60f5b6b..7e414783f0f 100644 --- a/gdpr/vendorlist-scheduler.go +++ b/gdpr/vendorlist-scheduler.go @@ -1,45 +1,113 @@ package gdpr import ( + "context" + "errors" "fmt" + "net/http" "time" ) -type VendorListScheduler struct { - ticker *time.Ticker - interval time.Duration - done chan bool +type vendorListScheduler struct { + ticker *time.Ticker + interval time.Duration + done chan bool + isRunning bool + lastRun time.Time + + httpClient *http.Client + timeout time.Duration } -func NewVendorListScheduler(interval string) (*VendorListScheduler, error) { - d, err := time.ParseDuration(interval) +//Only single instance must be created +var _instance *vendorListScheduler + +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, err + return nil, errors.New("error parsing vendor list scheduler timeout: " + err.Error()) } - scheduler := &VendorListScheduler{ - ticker: nil, - interval: d, - done: make(chan bool), + _instance := &vendorListScheduler{ + ticker: nil, + interval: intervalDuration, + done: make(chan bool), + httpClient: httpClient, + timeout: timeoutDuration, } - return scheduler, nil + return _instance, nil } -func (scheduler *VendorListScheduler) Start() { +func (scheduler *vendorListScheduler) Start() { scheduler.ticker = time.NewTicker(scheduler.interval) go func() { for { select { case <-scheduler.done: + scheduler.isRunning = false return case t := <-scheduler.ticker.C: - fmt.Println("Tick at", t) + if !scheduler.isRunning { + scheduler.isRunning = true + + fmt.Println("Tick at", t) + scheduler.runLoadCache() + + scheduler.lastRun = t + scheduler.isRunning = false + } } } }() } -func (scheduler *VendorListScheduler) Stop() { - scheduler.ticker.Stop() - scheduler.done <- true +func (scheduler *vendorListScheduler) Stop() { + if scheduler.isRunning { + scheduler.ticker.Stop() + scheduler.done <- true + } +} + +func (scheduler *vendorListScheduler) runLoadCache() { + preloadContext, cancel := context.WithTimeout(context.Background(), scheduler.timeout) + defer cancel() + //loadCache(preloadContext, scheduler.httpClient, vendorListURLMaker, cacheSave) + + latestVersion := saveOne(preloadContext, scheduler.httpClient, vendorListURLMaker(0), cacheSave) + + // The GVL for TCF2 has no vendors defined in its first version. It's very unlikely to be used, so don't preload it. + firstVersionToLoad := uint16(2) + + for i := latestVersion; i > firstVersionToLoad; i-- { + // Check if version is present in cache + if list := cacheLoad(i); list != nil { + continue + } + saveOne(preloadContext, scheduler.httpClient, vendorListURLMaker(i), cacheSave) + } +} + +// loadCache saves newly available versions of the vendor list for future use. +func loadCache(ctx context.Context, client *http.Client, urlMaker func(uint16) string, saver saveVendors) { + latestVersion := saveOne(ctx, client, urlMaker(0), saver) + + // The GVL for TCF2 has no vendors defined in its first version. It's very unlikely to be used, so don't preload it. + firstVersionToLoad := uint16(2) + + for i := latestVersion; i > firstVersionToLoad; i-- { + // Check if version is present in cache + if list := cacheLoad(i); list != nil { + continue + } + saveOne(ctx, client, urlMaker(i), saver) + } } diff --git a/main.go b/main.go index 4a829736981..e73df6272c6 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package prebidServer import ( - "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/usersync" "math/rand" @@ -90,12 +89,6 @@ func serve(revision string, cfg *config.Configuration) error { pbc.InitPrebidCache(cfg.CacheURL.GetBaseURL()) pbc.InitPrebidCacheURL(cfg.ExternalURL) - vendorListScheduler, err := gdpr.NewVendorListScheduler("1s") - if err != nil { - return err - } - vendorListScheduler.Start() - //corsRouter := router.SupportCORS(r) //server.Listen(cfg, router.NoCache{Handler: corsRouter}, router.Admin(revision, currencyConverter, fetchingInterval), r.MetricsEngine) diff --git a/router/router.go b/router/router.go index 58bdee057f6..441dace8e78 100644 --- a/router/router.go +++ b/router/router.go @@ -320,6 +320,14 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R gvlVendorIDs := bidderInfos.ToGVLVendorIDMap() g_gdprPerms = gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient) + if cfg.VendorListScheduler.Enabled { + vendorListScheduler, err := gdpr.GetVendorListScheduler(cfg.VendorListScheduler.Interval, cfg.VendorListScheduler.Timeout, generalHttpClient) + if err != nil { + glog.Fatal(err) + } + vendorListScheduler.Start() + } + exchanges = newExchangeMap(cfg) g_cacheClient = pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, g_metrics) From a1378f93cafe8d0905df088744c04e5ef1f0ea5d Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Wed, 29 Sep 2021 20:40:21 +0530 Subject: [PATCH 29/31] Added print statements --- gdpr/vendorlist-scheduler.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gdpr/vendorlist-scheduler.go b/gdpr/vendorlist-scheduler.go index 7e414783f0f..780fa56daad 100644 --- a/gdpr/vendorlist-scheduler.go +++ b/gdpr/vendorlist-scheduler.go @@ -92,6 +92,7 @@ func (scheduler *vendorListScheduler) runLoadCache() { if list := cacheLoad(i); list != nil { continue } + fmt.Println("Downloading: " + vendorListURLMaker(i)) saveOne(preloadContext, scheduler.httpClient, vendorListURLMaker(i), cacheSave) } } @@ -101,13 +102,15 @@ func loadCache(ctx context.Context, client *http.Client, urlMaker func(uint16) s latestVersion := saveOne(ctx, client, urlMaker(0), saver) // The GVL for TCF2 has no vendors defined in its first version. It's very unlikely to be used, so don't preload it. - firstVersionToLoad := uint16(2) + //firstVersionToLoad := uint16(2) + firstVersionToLoad := uint16(91) for i := latestVersion; i > firstVersionToLoad; i-- { // Check if version is present in cache if list := cacheLoad(i); list != nil { continue } + fmt.Println("Downloading: " + urlMaker(i)) saveOne(ctx, client, urlMaker(i), saver) } } From a2feafd2ca6f78c04d326fb89eb12fbb2c98b3f3 Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Wed, 29 Sep 2021 23:20:42 +0530 Subject: [PATCH 30/31] Added unit test for vendorlist-scheduler --- gdpr/vendorlist-fetching.go | 4 +- gdpr/vendorlist-scheduler.go | 69 ++++++------ gdpr/vendorlist-scheduler_test.go | 175 ++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 35 deletions(-) create mode 100644 gdpr/vendorlist-scheduler_test.go diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index 9837ef33746..fa8e1ec51c6 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -78,9 +78,9 @@ func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16 // this will fetch the latest version. func vendorListURLMaker(vendorListVersion uint16) string { if vendorListVersion == 0 { - return "http://localhost/v2/vendor-list.json" + return "https://vendor-list.consensu.org/v2/vendor-list.json" } - return "http://localhost/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json" + return "https://vendor-list.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json" } // newOccasionalSaver returns a wrapped version of saveOne() which only activates every few minutes. diff --git a/gdpr/vendorlist-scheduler.go b/gdpr/vendorlist-scheduler.go index 780fa56daad..234fa943540 100644 --- a/gdpr/vendorlist-scheduler.go +++ b/gdpr/vendorlist-scheduler.go @@ -3,8 +3,9 @@ package gdpr import ( "context" "errors" - "fmt" + "github.com/golang/glog" "net/http" + "sync" "time" ) @@ -13,6 +14,7 @@ type vendorListScheduler struct { interval time.Duration done chan bool isRunning bool + isStarted bool lastRun time.Time httpClient *http.Client @@ -21,6 +23,7 @@ type vendorListScheduler struct { //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 { @@ -37,29 +40,43 @@ func GetVendorListScheduler(interval, timeout string, httpClient *http.Client) ( return nil, errors.New("error parsing vendor list scheduler timeout: " + err.Error()) } - _instance := &vendorListScheduler{ - ticker: nil, - interval: intervalDuration, - done: make(chan bool), - httpClient: httpClient, - timeout: timeoutDuration, + 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 - fmt.Println("Tick at", t) + glog.Info("Running vendor list scheduler at ", t) scheduler.runLoadCache() scheduler.lastRun = t @@ -71,46 +88,32 @@ func (scheduler *vendorListScheduler) Start() { } func (scheduler *vendorListScheduler) Stop() { - if scheduler.isRunning { - scheduler.ticker.Stop() - scheduler.done <- true + 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() - //loadCache(preloadContext, scheduler.httpClient, vendorListURLMaker, cacheSave) latestVersion := saveOne(preloadContext, scheduler.httpClient, vendorListURLMaker(0), cacheSave) // The GVL for TCF2 has no vendors defined in its first version. It's very unlikely to be used, so don't preload it. firstVersionToLoad := uint16(2) - for i := latestVersion; i > firstVersionToLoad; i-- { - // Check if version is present in cache + for i := latestVersion; i >= firstVersionToLoad; i-- { + // Check if version is present in the cache if list := cacheLoad(i); list != nil { continue } - fmt.Println("Downloading: " + vendorListURLMaker(i)) + glog.Infof("Downloading: " + vendorListURLMaker(i)) saveOne(preloadContext, scheduler.httpClient, vendorListURLMaker(i), cacheSave) } } - -// loadCache saves newly available versions of the vendor list for future use. -func loadCache(ctx context.Context, client *http.Client, urlMaker func(uint16) string, saver saveVendors) { - latestVersion := saveOne(ctx, client, urlMaker(0), saver) - - // The GVL for TCF2 has no vendors defined in its first version. It's very unlikely to be used, so don't preload it. - //firstVersionToLoad := uint16(2) - firstVersionToLoad := uint16(91) - - for i := latestVersion; i > firstVersionToLoad; i-- { - // Check if version is present in cache - if list := cacheLoad(i); list != nil { - continue - } - fmt.Println("Downloading: " + urlMaker(i)) - saveOne(ctx, client, urlMaker(i), saver) - } -} diff --git a/gdpr/vendorlist-scheduler_test.go b/gdpr/vendorlist-scheduler_test.go new file mode 100644 index 00000000000..eb06bf88bef --- /dev/null +++ b/gdpr/vendorlist-scheduler_test.go @@ -0,0 +1,175 @@ +package gdpr + +import ( + "context" + "github.com/prebid/go-gdpr/api" + "github.com/stretchr/testify/assert" + "net/http" + "testing" + "time" +) + +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, api.VendorList) {} + latestVersion := saveOne(context.Background(), http.DefaultClient, vendorListURLMaker(0), mockCacheSave) + + cacheSave, cacheLoad = newVendorListCache() + tt.fields.scheduler.runLoadCache() + + firstVersionToLoad := uint16(2) + for i := latestVersion; i >= firstVersionToLoad; i-- { + list := cacheLoad(i) + assert.NotNil(t, list, "vendor-list file should be present in cache") + } + }) + } +} + +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() + } + +} From a962ce4ac49fc9ecc77722e4c62d9af2e222f11f Mon Sep 17 00:00:00 2001 From: Sachin Survase Date: Thu, 30 Sep 2021 15:27:20 +0530 Subject: [PATCH 31/31] Fixed formatting --- gdpr/vendorlist-fetching.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go index fa8e1ec51c6..04fbaa659bf 100644 --- a/gdpr/vendorlist-fetching.go +++ b/gdpr/vendorlist-fetching.go @@ -78,7 +78,7 @@ func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16 // this will fetch the latest version. func vendorListURLMaker(vendorListVersion uint16) string { if vendorListVersion == 0 { - return "https://vendor-list.consensu.org/v2/vendor-list.json" + return "https://vendor-list.consensu.org/v2/vendor-list.json" } return "https://vendor-list.consensu.org/v2/archives/vendor-list-v" + strconv.Itoa(int(vendorListVersion)) + ".json" }