diff --git a/adapters/rubicon/rubicon.go b/adapters/rubicon/rubicon.go index 80c62df16a1..89e4f31d63f 100644 --- a/adapters/rubicon/rubicon.go +++ b/adapters/rubicon/rubicon.go @@ -1,51 +1,50 @@ package rubicon import ( - "bytes" - "context" "encoding/json" "fmt" - "github.com/buger/jsonparser" - "io/ioutil" "net/http" "net/url" "strconv" "strings" - "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/buger/jsonparser" + "github.com/mxmCherry/openrtb/v15/openrtb2" ) const badvLimitSize = 50 type RubiconAdapter struct { - http *adapters.HTTPAdapter URI string XAPIUsername string XAPIPassword string } -func (a *RubiconAdapter) Name() string { - return "rubicon" +type rubiconContext struct { + Data json.RawMessage `json:"data"` } -func (a *RubiconAdapter) SkipNoCookies() bool { - return false +type rubiconData struct { + AdServer rubiconAdServer `json:"adserver"` + PbAdSlot string `json:"pbadslot"` } -type rubiconParams struct { - AccountId int `json:"accountId"` - SiteId int `json:"siteId"` - ZoneId int `json:"zoneId"` - Inventory json.RawMessage `json:"inventory,omitempty"` - Visitor json.RawMessage `json:"visitor,omitempty"` - Video rubiconVideoParams `json:"video"` +type rubiconAdServer struct { + Name string `json:"name"` + AdSlot string `json:"adslot"` +} + +type rubiconExtImpBidder struct { + Prebid *openrtb_ext.ExtImpPrebid `json:"prebid"` + Bidder json.RawMessage `json:"bidder"` + Gpid string `json:"gpid"` + Data json.RawMessage `json:"data"` + Context rubiconContext `json:"context"` } type bidRequestExt struct { @@ -134,15 +133,6 @@ type rubiconBannerExt struct { } // ***** Video Extension ***** -type rubiconVideoParams struct { - Language string `json:"language,omitempty"` - PlayerHeight int `json:"playerHeight,omitempty"` - PlayerWidth int `json:"playerWidth,omitempty"` - VideoSizeID int `json:"size_id,omitempty"` - Skip int `json:"skip,omitempty"` - SkipDelay int `json:"skipdelay,omitempty"` -} - type rubiconVideoExt struct { Skip int `json:"skip,omitempty"` SkipDelay int `json:"skipdelay,omitempty"` @@ -154,19 +144,6 @@ type rubiconVideoExtRP struct { SizeID int `json:"size_id,omitempty"` } -type rubiconTargetingExt struct { - RP rubiconTargetingExtRP `json:"rp"` -} - -type rubiconTargetingExtRP struct { - Targeting []rubiconTargetingObj `json:"targeting"` -} - -type rubiconTargetingObj struct { - Key string `json:"key"` - Values []string `json:"values"` -} - type rubiconDeviceExtRP struct { PixelRatio float64 `json:"pixelratio"` } @@ -175,10 +152,6 @@ type rubiconDeviceExt struct { RP rubiconDeviceExtRP `json:"rp"` } -type rubiconUser struct { - Language string `json:"language"` -} - type rubiconBidResponse struct { openrtb2.BidResponse SeatBid []rubiconSeatBid `json:"seatbid,omitempty"` @@ -303,7 +276,7 @@ type mappedRubiconUidsParam struct { liverampIdl string } -//MAS algorithm +// MAS algorithm func findPrimary(alt []int) (int, []int) { min, pos, primary := 0, 0, 0 for i, size := range alt { @@ -353,273 +326,6 @@ func parseRubiconSizes(sizes []openrtb2.Format) (primary int, alt []int, err err return } -func (a *RubiconAdapter) callOne(ctx context.Context, 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") - httpReq.Header.Add("User-Agent", "prebid-server/1.0") - httpReq.SetBasicAuth(a.XAPIUsername, a.XAPIPassword) - - rubiResp, e := ctxhttp.Do(ctx, a.http.Client, httpReq) - if e != nil { - err = e - return - } - - defer rubiResp.Body.Close() - body, _ := ioutil.ReadAll(rubiResp.Body) - result.ResponseBody = string(body) - - result.StatusCode = rubiResp.StatusCode - - if rubiResp.StatusCode == 204 { - return - } - - if rubiResp.StatusCode == http.StatusBadRequest { - err = &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), - } - } - - if rubiResp.StatusCode != http.StatusOK { - err = &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status %d; body: %s", rubiResp.StatusCode, result.ResponseBody), - } - return - } - - var bidResp openrtb2.BidResponse - err = json.Unmarshal(body, &bidResp) - if err != nil { - err = &errortypes.BadServerResponse{ - Message: err.Error(), - } - return - } - if len(bidResp.SeatBid) == 0 { - return - } - if len(bidResp.SeatBid[0].Bid) == 0 { - return - } - bid := bidResp.SeatBid[0].Bid[0] - - result.Bid = &pbs.PBSBid{ - AdUnitCode: bid.ImpID, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - // for video, the width and height are undefined as there's no corresponding return value from XAPI - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - } - - // Pull out any server-side determined targeting - var rpExtTrg rubiconTargetingExt - - if err := json.Unmarshal([]byte(bid.Ext), &rpExtTrg); err == nil { - // Converting string => array(string) to string => string - targeting := make(map[string]string) - - // Only pick off the first for now - for _, target := range rpExtTrg.RP.Targeting { - targeting[target.Key] = target.Values[0] - } - - result.Bid.AdServerTargeting = targeting - } - - return -} - -type callOneObject struct { - requestJson bytes.Buffer - mediaType pbs.MediaType -} - -func (a *RubiconAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - callOneObjects := make([]callOneObject, 0, len(bidder.AdUnits)) - supportedMediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - - rubiReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), supportedMediaTypes) - if err != nil { - return nil, err - } - - rubiReqImpCopy := rubiReq.Imp - - for i, unit := range bidder.AdUnits { - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(rubiReqImpCopy) <= i { - break - } - // Only grab this ad unit - // Not supporting multi-media-type add-unit yet - thisImp := rubiReqImpCopy[i] - - // Amend it with RP-specific information - var params rubiconParams - err = json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } - } - - var mint, mintVersion string - mint = "prebid" - mintVersion = req.SDK.Source + "_" + req.SDK.Platform + "_" + req.SDK.Version - track := rubiconImpExtRPTrack{Mint: mint, MintVersion: mintVersion} - - impExt := rubiconImpExt{RP: rubiconImpExtRP{ - ZoneID: params.ZoneId, - Target: params.Inventory, - Track: track, - }} - thisImp.Ext, err = json.Marshal(&impExt) - if err != nil { - continue - } - - // Copy the $.user object and amend with $.user.ext.rp.target - // Copy avoids race condition since it points to ref & shared with other adapters - userCopy := *rubiReq.User - userExt := rubiconUserExt{RP: rubiconUserExtRP{Target: params.Visitor}} - userCopy.Ext, err = json.Marshal(&userExt) - // Assign back our copy - rubiReq.User = &userCopy - - deviceCopy := *rubiReq.Device - deviceExt := rubiconDeviceExt{RP: rubiconDeviceExtRP{PixelRatio: rubiReq.Device.PxRatio}} - deviceCopy.Ext, err = json.Marshal(&deviceExt) - rubiReq.Device = &deviceCopy - - if thisImp.Video != nil { - - videoSizeId := params.Video.VideoSizeID - if videoSizeId == 0 { - resolvedSizeId, err := resolveVideoSizeId(thisImp.Video.Placement, thisImp.Instl, thisImp.ID) - if err == nil { - videoSizeId = resolvedSizeId - } else { - continue - } - } - - videoExt := rubiconVideoExt{Skip: params.Video.Skip, SkipDelay: params.Video.SkipDelay, RP: rubiconVideoExtRP{SizeID: videoSizeId}} - thisImp.Video.Ext, err = json.Marshal(&videoExt) - } else { - primarySizeID, altSizeIDs, err := parseRubiconSizes(unit.Sizes) - if err != nil { - continue - } - bannerExt := rubiconBannerExt{RP: rubiconBannerExtRP{SizeID: primarySizeID, AltSizeIDs: altSizeIDs, MIME: "text/html"}} - thisImp.Banner.Ext, err = json.Marshal(&bannerExt) - } - - siteExt := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: params.SiteId}} - pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: params.AccountId}} - var rubiconUser rubiconUser - err = json.Unmarshal(req.PBSUser, &rubiconUser) - - if rubiReq.Site != nil { - siteCopy := *rubiReq.Site - siteCopy.Ext, err = json.Marshal(&siteExt) - siteCopy.Publisher = &openrtb2.Publisher{} - siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) - siteCopy.Content = &openrtb2.Content{} - siteCopy.Content.Language = rubiconUser.Language - rubiReq.Site = &siteCopy - } else { - site := &openrtb2.Site{} - site.Content = &openrtb2.Content{} - site.Content.Language = rubiconUser.Language - rubiReq.Site = site - } - - if rubiReq.App != nil { - appCopy := *rubiReq.App - appCopy.Ext, err = json.Marshal(&siteExt) - appCopy.Publisher = &openrtb2.Publisher{} - appCopy.Publisher.Ext, err = json.Marshal(&pubExt) - rubiReq.App = &appCopy - } - - rubiReq.Imp = []openrtb2.Imp{thisImp} - - var reqBuffer bytes.Buffer - err = json.NewEncoder(&reqBuffer).Encode(rubiReq) - if err != nil { - return nil, err - } - callOneObjects = append(callOneObjects, callOneObject{reqBuffer, unit.MediaTypes[0]}) - } - if len(callOneObjects) == 0 { - return nil, &errortypes.BadInput{ - Message: "Invalid ad unit/imp", - } - } - - ch := make(chan adapters.CallOneResult) - for _, obj := range callOneObjects { - go func(bidder *pbs.PBSBidder, reqJSON bytes.Buffer, mediaType pbs.MediaType) { - result, err := a.callOne(ctx, 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 - } else { - // no need to check whether mediaTypes is nil or length of zero, pbs.ParsePBSRequest will cover - // these cases. - // for media types other than banner and video, pbs.ParseMediaType will throw error. - // we may want to create a map/switch cases to support more media types in the future. - if mediaType == pbs.MEDIA_TYPE_VIDEO { - result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeVideo) - } else { - result.Bid.CreativeMediaType = string(openrtb_ext.BidTypeBanner) - } - } - } - ch <- result - }(bidder, obj.requestJson, obj.mediaType) - } - - bids := make(pbs.PBSBidSlice, 0) - for i := 0; i < len(callOneObjects); i++ { - result := <-ch - if result.Bid != nil && result.Bid.Price != 0 { - bids = append(bids, result.Bid) - } - if req.IsDebug { - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - RequestBody: callOneObjects[i].requestJson.String(), - StatusCode: result.StatusCode, - ResponseBody: result.ResponseBody, - } - bidder.Debug = append(bidder.Debug, debug) - } - if result.Error != nil { - if glog.V(2) { - glog.Infof("Error from rubicon adapter: %v", result.Error) - } - err = result.Error - } - } - - if len(bids) == 0 { - return nil, err - } - return bids, nil -} - func resolveVideoSizeId(placement openrtb2.VideoPlacementType, instl int8, impId string) (sizeID int, err error) { if placement != 0 { if placement == 1 { @@ -665,19 +371,6 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters return bidder, nil } -func NewRubiconLegacyAdapter(httpConfig *adapters.HTTPAdapterConfig, uri string, xuser string, xpass string, tracker string) *RubiconAdapter { - a := adapters.NewHTTPAdapter(httpConfig) - - uri = appendTrackerToUrl(uri, tracker) - - return &RubiconAdapter{ - http: a, - URI: uri, - XAPIUsername: xuser, - XAPIPassword: xpass, - } -} - func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { numRequests := len(request.Imp) errs := make([]error, 0, len(request.Imp)) @@ -693,7 +386,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada rubiconRequest := *request for _, imp := range requestImpCopy { - var bidderExt adapters.ExtImpBidder + var bidderExt rubiconExtImpBidder if err = json.Unmarshal(imp.Ext, &bidderExt); err != nil { errs = append(errs, &errortypes.BadInput{ Message: err.Error(), @@ -709,12 +402,19 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada continue } - target, err := updateImpRpTargetWithFpdAttributes(rubiconExt, imp, request.Site, request.App) + target, err := updateImpRpTargetWithFpdAttributes(bidderExt, rubiconExt, imp, request.Site, request.App) if err != nil { errs = append(errs, err) continue } - adSlot, err := getAdSlot(imp) + + siteId, err := rubiconExt.SiteId.Int64() + if err != nil { + errs = append(errs, err) + continue + } + + zoneId, err := rubiconExt.ZoneId.Int64() if err != nil { errs = append(errs, err) continue @@ -722,12 +422,13 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada impExt := rubiconImpExt{ RP: rubiconImpExtRP{ - ZoneID: rubiconExt.ZoneId, + ZoneID: int(zoneId), Target: target, Track: rubiconImpExtRPTrack{Mint: "", MintVersion: ""}, }, - GPID: adSlot, + GPID: bidderExt.Gpid, } + imp.Ext, err = json.Marshal(&impExt) if err != nil { errs = append(errs, err) @@ -845,11 +546,17 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada imp.Video = nil } - pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: rubiconExt.AccountId}} + accountId, err := rubiconExt.AccountId.Int64() + if err != nil { + errs = append(errs, err) + continue + } + + pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: int(accountId)}} if request.Site != nil { siteCopy := *request.Site - siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}} + siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: int(siteId)}} if siteCopy.Content != nil { siteTarget := make(map[string]interface{}) updateExtWithIabAttribute(siteTarget, siteCopy.Content.Data, []int{1, 2, 5, 6}) @@ -874,7 +581,7 @@ func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ada rubiconRequest.Site = &siteCopy } else { appCopy := *request.App - appCopy.Ext, err = json.Marshal(rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: rubiconExt.SiteId}}) + appCopy.Ext, err = json.Marshal(rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: int(siteId)}}) appCopy.Publisher = &openrtb2.Publisher{} appCopy.Publisher.Ext, err = json.Marshal(&pubExt) rubiconRequest.App = &appCopy @@ -918,8 +625,9 @@ func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.Ext return bidFloor, nil } -func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp openrtb2.Imp, - site *openrtb2.Site, app *openrtb2.App) (json.RawMessage, error) { +func updateImpRpTargetWithFpdAttributes(extImp rubiconExtImpBidder, extImpRubicon openrtb_ext.ExtImpRubicon, + imp openrtb2.Imp, site *openrtb2.Site, app *openrtb2.App) (json.RawMessage, error) { + existingTarget, _, _, err := jsonparser.Get(imp.Ext, "rp", "target") if isNotKeyPathError(err) { return nil, err @@ -928,7 +636,7 @@ func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp op if err != nil { return nil, err } - err = populateFirstPartyDataAttributes(extImp.Inventory, target) + err = populateFirstPartyDataAttributes(extImpRubicon.Inventory, target) if err != nil { return nil, err } @@ -974,28 +682,41 @@ func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp op } } - impExtContextAttributes, _, _, err := jsonparser.Get(imp.Ext, "context", "data") + if len(extImp.Context.Data) > 0 { + err = populateFirstPartyDataAttributes(extImp.Context.Data, target) + } else if len(extImp.Data) > 0 { + err = populateFirstPartyDataAttributes(extImp.Data, target) + } if isNotKeyPathError(err) { return nil, err } - if len(impExtContextAttributes) > 0 { - err = populateFirstPartyDataAttributes(impExtContextAttributes, target) + var data rubiconData + if len(extImp.Data) > 0 { + err := json.Unmarshal(extImp.Data, &data) if err != nil { return nil, err } - } else if impExtDataAttributes, _, _, err := jsonparser.Get(imp.Ext, "data"); err == nil && len(impExtDataAttributes) > 0 { - err = populateFirstPartyDataAttributes(impExtDataAttributes, target) + } + var contextData rubiconData + if len(extImp.Context.Data) > 0 { + err := json.Unmarshal(extImp.Context.Data, &contextData) if err != nil { return nil, err } } - if isNotKeyPathError(err) { - return nil, err + + if data.PbAdSlot != "" { + target["pbadslot"] = data.PbAdSlot + } else { + dfpAdUnitCode := extractDfpAdUnitCode(data, contextData) + if dfpAdUnitCode != "" { + target["dfp_ad_unit_code"] = dfpAdUnitCode + } } - if len(extImp.Keywords) > 0 { - addStringArrayAttribute(extImp.Keywords, target, "keywords") + if len(extImpRubicon.Keywords) > 0 { + addStringArrayAttribute(extImpRubicon.Keywords, target, "keywords") } updatedTarget, err := json.Marshal(target) if err != nil { @@ -1004,6 +725,16 @@ func updateImpRpTargetWithFpdAttributes(extImp openrtb_ext.ExtImpRubicon, imp op return updatedTarget, nil } +func extractDfpAdUnitCode(data rubiconData, contextData rubiconData) string { + if contextData.AdServer.Name == "gam" && contextData.AdServer.AdSlot != "" { + return contextData.AdServer.AdSlot + } else if data.AdServer.Name == "gam" && data.AdServer.AdSlot != "" { + return data.AdServer.AdSlot + } + + return "" +} + func isNotKeyPathError(err error) bool { return err != nil && err != jsonparser.KeyPathNotFoundError } @@ -1016,62 +747,6 @@ func addStringArrayAttribute(attribute []string, target map[string]interface{}, target[attributeName] = attribute } -func getAdSlot(imp openrtb2.Imp) (string, error) { - var adSlot string - var parsingError error - jsonparser.EachKey(imp.Ext, func(idx int, value []byte, vt jsonparser.ValueType, err error) { - switch idx { - case 0: - adServerContextName, err := jsonparser.GetString(value, "name") - if isNotKeyPathError(err) { - parsingError = err - return - } - if adServerContextName == "gam" { - contextAdSlot, err := jsonparser.GetString(value, "adslot") - if isNotKeyPathError(err) { - parsingError = err - return - } - adSlot = contextAdSlot - } - } - }, []string{"context", "data", "adserver"}) - - if parsingError != nil { - return "", parsingError - } - - if adSlot != "" { - return adSlot, nil - } - - jsonparser.EachKey(imp.Ext, func(idx int, value []byte, vt jsonparser.ValueType, err error) { - switch idx { - case 0: - adServerDataName, err := jsonparser.GetString(value, "name") - if isNotKeyPathError(err) { - parsingError = err - return - } - if adServerDataName == "gam" { - dataAdSlot, err := jsonparser.GetString(value, "adslot") - if isNotKeyPathError(err) { - parsingError = err - return - } - adSlot = dataAdSlot - } - } - }, []string{"data", "adserver"}) - - if parsingError != nil { - return "", parsingError - } - - return adSlot, nil -} - func updateUserRpTargetWithFpdAttributes(visitor json.RawMessage, user openrtb2.User) (json.RawMessage, error) { existingTarget, _, _, err := jsonparser.Get(user.Ext, "rp", "target") if isNotKeyPathError(err) { @@ -1177,6 +852,7 @@ func rawJSONToMap(message json.RawMessage) (map[string]interface{}, error) { return mapFromRawJSON(message) } + func mapFromRawJSON(message json.RawMessage) (map[string]interface{}, error) { targetAsMap := make(map[string]interface{}) err := json.Unmarshal(message, &targetAsMap) diff --git a/adapters/rubicon/rubicon_test.go b/adapters/rubicon/rubicon_test.go index 9bfa04fa78f..e847b4fe0a4 100644 --- a/adapters/rubicon/rubicon_test.go +++ b/adapters/rubicon/rubicon_test.go @@ -1,27 +1,17 @@ package rubicon import ( - "bytes" - "context" "encoding/json" "errors" - "fmt" - "io/ioutil" "net/http" - "net/http/httptest" "strconv" - "strings" "testing" - "time" "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/errortypes" "github.com/prebid/prebid-server/openrtb_ext" - "github.com/prebid/prebid-server/pbs" - "github.com/prebid/prebid-server/usersync" "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb/v15/openrtb2" @@ -48,291 +38,17 @@ type rubiSetNetworkIdTestScenario struct { isNetworkIdSet bool } -type rubiTagInfo struct { - code string - zoneID int - bid float64 - content string - adServerTargeting map[string]string - mediaType string -} - type rubiBidInfo struct { - domain string - page string - accountID int - siteID int - tags []rubiTagInfo - deviceIP string - deviceUA string - buyerUID string - xapiuser string - xapipass string - delay time.Duration - visitorTargeting string - inventoryTargeting string - sdkVersion string - sdkPlatform string - sdkSource string - devicePxRatio float64 + domain string + page string + deviceIP string + deviceUA string + buyerUID string + devicePxRatio float64 } var rubidata rubiBidInfo -func DummyRubiconServer(w http.ResponseWriter, r *http.Request) { - defer func() { - err := r.Body.Close() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }() - - 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 len(breq.Imp) > 1 { - http.Error(w, "Rubicon adapter only supports one Imp per request", http.StatusInternalServerError) - return - } - imp := breq.Imp[0] - var rix rubiconImpExt - err = json.Unmarshal(imp.Ext, &rix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - impTargetingString, _ := json.Marshal(&rix.RP.Target) - if string(impTargetingString) != rubidata.inventoryTargeting { - http.Error(w, fmt.Sprintf("Inventory FPD targeting '%s' doesn't match '%s'", string(impTargetingString), rubidata.inventoryTargeting), http.StatusInternalServerError) - return - } - if rix.RP.Track.Mint != "prebid" { - http.Error(w, fmt.Sprintf("Track mint '%s' doesn't match '%s'", rix.RP.Track.Mint, "prebid"), http.StatusInternalServerError) - return - } - mintVersionString := rubidata.sdkSource + "_" + rubidata.sdkPlatform + "_" + rubidata.sdkVersion - if rix.RP.Track.MintVersion != mintVersionString { - http.Error(w, fmt.Sprintf("Track mint version '%s' doesn't match '%s'", rix.RP.Track.MintVersion, mintVersionString), http.StatusInternalServerError) - return - } - - ix := -1 - - for i, tag := range rubidata.tags { - if rix.RP.ZoneID == tag.zoneID { - ix = i - } - } - if ix == -1 { - http.Error(w, fmt.Sprintf("Zone %d not found", rix.RP.ZoneID), http.StatusInternalServerError) - return - } - - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 2), - }, - }, - } - - if imp.Banner != nil { - var bix rubiconBannerExt - err = json.Unmarshal(imp.Banner.Ext, &bix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if bix.RP.SizeID != 15 { // 300x250 - http.Error(w, fmt.Sprintf("Primary size ID isn't 15"), http.StatusInternalServerError) - return - } - if len(bix.RP.AltSizeIDs) != 1 || bix.RP.AltSizeIDs[0] != 10 { // 300x600 - http.Error(w, fmt.Sprintf("Alt size ID isn't 10"), http.StatusInternalServerError) - return - } - if bix.RP.MIME != "text/html" { - http.Error(w, fmt.Sprintf("MIME isn't text/html"), http.StatusInternalServerError) - return - } - } - - if imp.Video != nil { - var vix rubiconVideoExt - err = json.Unmarshal(imp.Video.Ext, &vix) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if len(imp.Video.MIMEs) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.mimes array"), http.StatusInternalServerError) - return - } - if len(imp.Video.Protocols) == 0 { - http.Error(w, fmt.Sprintf("Empty imp.video.protocols array"), http.StatusInternalServerError) - return - } - for _, protocol := range imp.Video.Protocols { - if protocol < 1 || protocol > 8 { - http.Error(w, fmt.Sprintf("Invalid video protocol %d", protocol), http.StatusInternalServerError) - return - } - } - } - - targeting := "{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}" - rawTargeting := json.RawMessage(targeting) - - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "random-id", - ImpID: imp.ID, - Price: rubidata.tags[ix].bid, - AdM: rubidata.tags[ix].content, - Ext: rawTargeting, - } - - if breq.Site == nil { - http.Error(w, fmt.Sprintf("No site object sent"), http.StatusInternalServerError) - return - } - if breq.Site.Domain != rubidata.domain { - http.Error(w, fmt.Sprintf("Domain '%s' doesn't match '%s", breq.Site.Domain, rubidata.domain), http.StatusInternalServerError) - return - } - if breq.Site.Page != rubidata.page { - http.Error(w, fmt.Sprintf("Page '%s' doesn't match '%s", breq.Site.Page, rubidata.page), http.StatusInternalServerError) - return - } - var rsx rubiconSiteExt - err = json.Unmarshal(breq.Site.Ext, &rsx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if rsx.RP.SiteID != rubidata.siteID { - http.Error(w, fmt.Sprintf("SiteID '%d' doesn't match '%d", rsx.RP.SiteID, rubidata.siteID), http.StatusInternalServerError) - return - } - if breq.Site.Publisher == nil { - http.Error(w, fmt.Sprintf("No site.publisher object sent"), http.StatusInternalServerError) - return - } - var rpx rubiconPubExt - err = json.Unmarshal(breq.Site.Publisher.Ext, &rpx) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if rpx.RP.AccountID != rubidata.accountID { - http.Error(w, fmt.Sprintf("AccountID '%d' doesn't match '%d'", rpx.RP.AccountID, rubidata.accountID), http.StatusInternalServerError) - return - } - if breq.Device.UA != rubidata.deviceUA { - http.Error(w, fmt.Sprintf("UA '%s' doesn't match '%s'", breq.Device.UA, rubidata.deviceUA), http.StatusInternalServerError) - return - } - if breq.Device.IP != rubidata.deviceIP { - http.Error(w, fmt.Sprintf("IP '%s' doesn't match '%s'", breq.Device.IP, rubidata.deviceIP), http.StatusInternalServerError) - return - } - if breq.Device.PxRatio != rubidata.devicePxRatio { - http.Error(w, fmt.Sprintf("Pixel ratio '%f' doesn't match '%f'", breq.Device.PxRatio, rubidata.devicePxRatio), http.StatusInternalServerError) - return - } - if breq.User.BuyerUID != rubidata.buyerUID { - http.Error(w, fmt.Sprintf("User ID '%s' doesn't match '%s'", breq.User.BuyerUID, rubidata.buyerUID), http.StatusInternalServerError) - return - } - - var rux rubiconUserExt - err = json.Unmarshal(breq.User.Ext, &rux) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - userTargetingString, _ := json.Marshal(&rux.RP.Target) - if string(userTargetingString) != rubidata.visitorTargeting { - http.Error(w, fmt.Sprintf("User FPD targeting '%s' doesn't match '%s'", string(userTargetingString), rubidata.visitorTargeting), http.StatusInternalServerError) - return - } - - if rubidata.delay > 0 { - <-time.After(rubidata.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 TestRubiconBasicResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.Nil(t, err, "Should not have gotten an error: %v", err) - assert.Equal(t, 2, len(bids), "Received %d bids instead of 3", len(bids)) - - for _, bid := range bids { - matched := false - for _, tag := range rubidata.tags { - if bid.AdUnitCode == tag.code { - matched = true - - assert.Equal(t, "rubicon", bid.BidderCode, "Incorrect BidderCode '%s'", bid.BidderCode) - - assert.Equal(t, tag.bid, bid.Price, "Incorrect bid price '%.2f' expected '%.2f'", bid.Price, tag.bid) - - assert.Equal(t, tag.content, bid.Adm, "Incorrect bid markup '%s' expected '%s'", bid.Adm, tag.content) - - assert.Equal(t, bid.AdServerTargeting, tag.adServerTargeting, - "Incorrect targeting '%+v' expected '%+v'", bid.AdServerTargeting, tag.adServerTargeting) - - assert.Equal(t, tag.mediaType, bid.CreativeMediaType, "Incorrect media type '%s' expected '%s'", bid.CreativeMediaType, tag.mediaType) - } - } - assert.True(t, matched, "Received bid for unknown ad unit '%s'", bid.AdUnitCode) - } - - // same test but with request timing out - rubidata.delay = 20 * time.Millisecond - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Millisecond) - defer cancel() - - bids, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten a timeout error: %v", err) -} - -func TestRubiconUserSyncInfo(t *testing.T) { - conf := *adapters.DefaultHTTPAdapterConfig - an := NewRubiconLegacyAdapter(&conf, "uri", "xuser", "xpass", "pbs-test-tracker") - - assert.Equal(t, "rubicon", an.Name(), "Name '%s' != 'rubicon'", an.Name()) - - assert.False(t, an.SkipNoCookies(), "SkipNoCookies should be false") -} - func getTestSizes() map[int]openrtb2.Format { return map[int]openrtb2.Format{ 15: {W: 300, H: 250}, @@ -703,368 +419,6 @@ func (m mockCurrencyConversion) GetRates() *map[string]map[string]float64 { 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) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Equal(t, 204, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 204 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestNotFoundResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Equal(t, 404, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 404 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "HTTP status 404"), - "Should start with 'HTTP status' instead of: %v", err.Error()) -} - -func TestWrongFormatResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("This is text.")) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Equal(t, 200, pbReq.Bidders[0].Debug[0].StatusCode, - "StatusCode should be 200 instead of: %v", pbReq.Bidders[0].Debug[0].StatusCode) - - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "invalid character"), - "Should start with 'invalid character' instead of: %v", err) -} - -func TestZeroSeatBidResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{}, - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestEmptyBidResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 0), - }, - }, - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.Nil(t, err, "Should not have gotten an error: %v", err) -} - -func TestWrongBidIdResponse(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 2), - }, - }, - } - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "random-id", - ImpID: "zma", - Price: 1.67, - AdM: "zma", - Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - bids, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Empty(t, bids, "Length of bids should be 0 instead of: %v", len(bids)) - - assert.NotNil(t, err, "Should not have gotten an error: %v", err) - - assert.True(t, strings.HasPrefix(err.Error(), "Unknown ad unit code"), - "Should start with 'Unknown ad unit code' instead of: %v", err) -} - -func TestZeroPriceBidResponse(t *testing.T) { - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb2.BidResponse{ - ID: "test-response-id", - BidID: "test-bid-id", - Cur: "USD", - SeatBid: []openrtb2.SeatBid{ - { - Seat: "RUBICON", - Bid: make([]openrtb2.Bid, 1), - }, - }, - } - resp.SeatBid[0].Bid[0] = openrtb2.Bid{ - ID: "test-bid-id", - ImpID: "first-tag", - Price: 0, - AdM: "zma", - Ext: json.RawMessage("{\"rp\":{\"targeting\":[{\"key\":\"key1\",\"values\":[\"value1\"]},{\"key\":\"key2\",\"values\":[\"value2\"]}]}}"), - } - js, _ := json.Marshal(resp) - w.Write(js) - })) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - b, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - - assert.Nil(t, b, "\n\n\n0 price bids are being included %d, err : %v", len(b), err) -} - -func TestDifferentRequest(t *testing.T) { - SIZE_ID := getTestSizes() - server := httptest.NewServer(http.HandlerFunc(DummyRubiconServer)) - defer server.Close() - - an, ctx, pbReq := CreatePrebidRequest(server, t) - - // test app not nil - pbReq.App = &openrtb2.App{ - ID: "com.test", - Name: "testApp", - } - - _, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set app back to normal - pbReq.App = nil - - // test video media type - pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_VIDEO} - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set media back to normal - pbReq.Bidders[0].AdUnits[0].MediaTypes = []pbs.MediaType{pbs.MEDIA_TYPE_BANNER} - - // test wrong params - pbReq.Bidders[0].AdUnits[0].Params = json.RawMessage(fmt.Sprintf("{\"zoneId\": %s, \"siteId\": %d, \"visitor\": %s, \"inventory\": %s}", "zma", rubidata.siteID, rubidata.visitorTargeting, rubidata.inventoryTargeting)) - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.NotNil(t, err, "Should have gotten an error: %v", err) - - // set params back to normal - 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 = []openrtb2.Format{ - { - W: 2222, - H: 333, - }, - } - pbReq.Bidders[0].AdUnits[1].Sizes = []openrtb2.Format{ - { - W: 222, - H: 3333, - }, - { - W: 350, - H: 270, - }, - } - pbReq.Bidders[0].AdUnits = pbReq.Bidders[0].AdUnits[:len(pbReq.Bidders[0].AdUnits)-1] - 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 = []openrtb2.Format{ - { - W: 222, - H: 3333, - }, - SIZE_ID[10], - SIZE_ID[15], - } - b, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assert.Nil(t, err, "Should have not gotten an error: %v", err) - - assert.Equal(t, 1, len(b), - "Filtering bids based on ad unit sizes failed. Got %d bids instead of 1, error = %v", len(b), err) -} - -func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdapter, ctx context.Context, pbReq *pbs.PBSRequest) { - SIZE_ID := getTestSizes() - rubidata = rubiBidInfo{ - domain: "nytimes.com", - page: "https://www.nytimes.com/2017/05/04/movies/guardians-of-the-galaxy-2-review-chris-pratt.html?hpw&rref=movies&action=click&pgtype=Homepage&module=well-region®ion=bottom-well&WT.nav=bottom-well&_r=0", - accountID: 7891, - siteID: 283282, - tags: make([]rubiTagInfo, 3), - deviceIP: "25.91.96.36", - deviceUA: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30", - buyerUID: "need-an-actual-rp-id", - visitorTargeting: "[\"v1\",\"v2\"]", - inventoryTargeting: "[\"i1\",\"i2\"]", - sdkVersion: "2.0.0", - sdkPlatform: "iOS", - sdkSource: "some-sdk", - devicePxRatio: 4.0, - } - - targeting := make(map[string]string, 2) - targeting["key1"] = "value1" - targeting["key2"] = "value2" - - rubidata.tags[0] = rubiTagInfo{ - code: "first-tag", - zoneID: 8394, - bid: 1.67, - adServerTargeting: targeting, - mediaType: "banner", - } - rubidata.tags[1] = rubiTagInfo{ - code: "second-tag", - zoneID: 8395, - bid: 3.22, - adServerTargeting: targeting, - mediaType: "banner", - } - rubidata.tags[2] = rubiTagInfo{ - code: "video-tag", - zoneID: 7780, - bid: 23.12, - adServerTargeting: targeting, - mediaType: "video", - } - - conf := *adapters.DefaultHTTPAdapterConfig - an = NewRubiconLegacyAdapter(&conf, "uri", rubidata.xapiuser, rubidata.xapipass, "pbs-test-tracker") - an.URI = server.URL - - pbin := pbs.PBSRequest{ - AdUnits: make([]pbs.AdUnit, 3), - Device: &openrtb2.Device{PxRatio: rubidata.devicePxRatio}, - SDK: &pbs.SDK{Source: rubidata.sdkSource, Platform: rubidata.sdkPlatform, Version: rubidata.sdkVersion}, - } - - for i, tag := range rubidata.tags { - pbin.AdUnits[i] = pbs.AdUnit{ - Code: tag.code, - MediaTypes: []string{tag.mediaType}, - Sizes: []openrtb2.Format{ - SIZE_ID[10], - SIZE_ID[15], - }, - Bids: []pbs.Bids{ - { - BidderCode: "rubicon", - BidID: fmt.Sprintf("random-id-from-pbjs-%d", i), - Params: json.RawMessage(fmt.Sprintf("{\"zoneId\": %d, \"siteId\": %d, \"accountId\": %d, \"visitor\": %s, \"inventory\": %s}", tag.zoneID, rubidata.siteID, rubidata.accountID, rubidata.visitorTargeting, rubidata.inventoryTargeting)), - }, - }, - } - if tag.mediaType == "video" { - pbin.AdUnits[i].Video = pbs.PBSVideo{ - Mimes: []string{"video/mp4"}, - Minduration: 15, - Maxduration: 30, - Startdelay: 5, - Skippable: 0, - PlaybackMethod: 1, - Protocols: []int8{1, 2, 3, 4, 5}, - } - } - } - - body := new(bytes.Buffer) - err := json.NewEncoder(body).Encode(pbin) - if err != nil { - t.Fatalf("Json encoding failed: %v", err) - } - - req := httptest.NewRequest("POST", server.URL, body) - req.Header.Add("Referer", rubidata.page) - req.Header.Add("User-Agent", rubidata.deviceUA) - req.Header.Add("X-Real-IP", rubidata.deviceIP) - - pc := usersync.ParseCookieFromRequest(req, &config.HostCookie{}) - pc.TrySync("rubicon", rubidata.buyerUID) - 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) - pbReq.IsDebug = true - - assert.Nil(t, err, "ParsePBSRequest failed: %v", err) - - assert.Equal(t, 1, len(pbReq.Bidders), - "ParsePBSRequest returned %d bidders instead of 1", len(pbReq.Bidders)) - - assert.Equal(t, "rubicon", pbReq.Bidders[0].BidderCode, - "ParsePBSRequest returned invalid bidder") - - ctx = context.TODO() - return -} - func TestOpenRTBRequest(t *testing.T) { SIZE_ID := getTestSizes() bidder := new(RubiconAdapter) @@ -1311,7 +665,12 @@ func TestOpenRTBRequestWithImpAndAdSlotIncluded(t *testing.T) { if err := json.Unmarshal(rubiconReq.Imp[0].Ext, &rpImpExt); err != nil { t.Fatal("Error unmarshalling imp.ext") } - assert.Equal(t, rpImpExt.GPID, "/test-adslot") + + dfpAdUnitCode, err := jsonparser.GetString(rpImpExt.RP.Target, "dfp_ad_unit_code") + if err != nil { + t.Fatal("Error extracting dfp_ad_unit_code") + } + assert.Equal(t, dfpAdUnitCode, "/test-adslot") } func TestOpenRTBFirstPartyDataPopulating(t *testing.T) { @@ -1583,7 +942,10 @@ func TestOpenRTBRequestWithVideoImpAndEnabledRewardedInventoryFlag(t *testing.T) "is_rewarded_inventory": 1 }, "bidder": { - "video": {"size_id": 1} + "video": {"size_id": 1}, + "zoneId": "123", + "siteId": 1234, + "accountId": "444" }}`), }}, App: &openrtb2.App{ diff --git a/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json b/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json index eec396f8873..d688d7beee3 100644 --- a/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json +++ b/adapters/rubicon/rubicontest/exemplary/app-imp-fpd.json @@ -145,19 +145,26 @@ "data": { "adserver": { "name": "gam", - "adslot": "someAdSlot" + "adslot": "adSlotFromContextData" }, "dataAttr1": "dataVal1", "dataAttr2": "dataVal2" } }, + "data": { + "adserver": { + "name": "gam", + "adslot": "adSlotFromData" + } + }, "bidder": { "video": { }, "accountId": 1001, "siteId": 113932, "zoneId": 535510 - } + }, + "gpid": "gpid" } } ] @@ -306,9 +313,9 @@ "h": 576 }, "ext": { - "gpid": "someAdSlot", "rp": { "target": { + "dfp_ad_unit_code": "adSlotFromContextData", "dataAttr1": [ "dataVal1" ], @@ -348,7 +355,8 @@ "mint_version": "" }, "zone_id": 535510 - } + }, + "gpid": "gpid" } } ] diff --git a/adapters/rubicon/rubicontest/exemplary/flexible-schema.json b/adapters/rubicon/rubicontest/exemplary/flexible-schema.json new file mode 100644 index 00000000000..3ac99d56ea4 --- /dev/null +++ b/adapters/rubicon/rubicontest/exemplary/flexible-schema.json @@ -0,0 +1,419 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "device": { + "ip": "123.123.123.123", + "ifa": "zxcjbzxmc-zxcbmz-zxbcz-zxczx" + }, + "app": { + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ], + "ext": { + "data": { + "attr1": "val1", + "attr2": [ + "val21", + "val22" + ], + "attr3": true, + "attr4": [ + true, + false, + true, + true + ], + "attr5": 3, + "attr6": [ + 1, + 2, + 3 + ], + "attr7": 1.23 + } + } + }, + "user": { + "yob": 2000, + "geo": { + "country": "USA", + "lat": 47.627500, + "lon": -122.346200 + }, + "gender": "f", + "data": [ + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy" + } + ] + }, + { + "ext": { + "segtax": "someValue" + }, + "segment": [ + { + "id": "shouldNotBeCopied" + } + ] + }, + { + "ext": { + "segtax": "4" + }, + "segment": [ + { + "id": "shouldNotBeCopied2" + } + ] + }, + { + "ext": { + "segtax": 4 + }, + "segment": [ + { + "id": "idToCopy2" + } + ] + }, + { + "ext": { + "segtax": [ + 4 + ] + }, + "segment": [ + { + "id": "shouldNotBeCopied3" + } + ] + } + ], + "ext": { + "data": { + "attr1": "val1", + "attr2": [ + "val21", + "val22" + ], + "attr3": true, + "attr4": [ + true, + false, + true, + true + ], + "attr5": 3, + "attr6": [ + 1, + 2, + 3 + ], + "attr7": 1.23 + } + } + }, + "imp": [ + { + "id": "test-imp-id", + "instl": 1, + "video": { + "placement": 3, + "mimes": [ + "video/mp4" + ], + "protocols": [ + 2, + 5 + ], + "w": 1024, + "h": 576 + }, + "ext": { + "context": { + "data": { + "adserver": { + "name": "gam", + "adslot": "adSlotFromContextData" + }, + "dataAttr1": "dataVal1", + "dataAttr2": "dataVal2" + } + }, + "data": { + "adserver": { + "name": "gam", + "adslot": "adSlotFromData" + } + }, + "bidder": { + "video": { + }, + "accountId": "1001", + "siteId": "113932", + "zoneId": "535510" + }, + "gpid": "gpid" + } + } + ] + }, + "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" + }, + "app": { + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ], + "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": { + "attr1": [ + "val1" + ], + "attr2": [ + "val21", + "val22" + ], + "attr3": [ + "true" + ], + "attr4": [ + "true", + "false", + "true", + "true" + ], + "attr5": [ + "3" + ], + "iab": [ + "idToCopy", + "idToCopy2" + ] + } + } + } + }, + "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": { + "target": { + "dfp_ad_unit_code": "adSlotFromContextData", + "dataAttr1": [ + "dataVal1" + ], + "dataAttr2": [ + "dataVal2" + ], + "attr1": [ + "val1" + ], + "attr2": [ + "val21", + "val22" + ], + "attr3": [ + "true" + ], + "attr4": [ + "true", + "false", + "true", + "true" + ], + "attr5": [ + "3" + ], + "pagecat": [ + "val1", + "val2" + ], + "sectioncat": [ + "sectionCat1", + "sectionCat2" + ] + }, + "track": { + "mint": "", + "mint_version": "" + }, + "zone_id": 535510 + }, + "gpid": "gpid" + } + } + ] + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "test-request-id", + "seatbid": [ + { + "bid": [ + { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + } + ], + "seat": "adman" + } + ], + "cur": "USD" + } + } + } + ], + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "id": "test_bid_id", + "impid": "test-imp-id", + "price": 0.27543, + "adm": "some-test-ad", + "cid": "test_cid", + "crid": "test_crid", + "dealid": "test_dealid", + "ext": { + "prebid": { + "type": "video" + } + } + }, + "type": "video" + } + ] + } + ] + } diff --git a/adapters/rubicon/rubicontest/exemplary/simple-video.json b/adapters/rubicon/rubicontest/exemplary/simple-video.json index 592f9fb279f..bb0b73d6e58 100644 --- a/adapters/rubicon/rubicontest/exemplary/simple-video.json +++ b/adapters/rubicon/rubicontest/exemplary/simple-video.json @@ -132,6 +132,9 @@ "h": 576 }, "ext": { + "data": { + "pbadslot": "pbadslot" + }, "bidder": { "video": { }, @@ -314,6 +317,7 @@ "ext": { "rp": { "target": { + "pbadslot": "pbadslot", "pagecat": [ "val1", "val2" diff --git a/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json b/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json index 56f5ce6128e..e28244d5dbb 100644 --- a/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json +++ b/adapters/rubicon/rubicontest/exemplary/site-imp-fpd.json @@ -200,7 +200,7 @@ "data": { "adserver": { "name": "gam", - "adslot": "someAdSlot" + "adslot": "adSlotFromData" }, "dataAttr1": "dataVal1", "dataAttr2": "dataVal2" @@ -428,7 +428,6 @@ "h": 576 }, "ext": { - "gpid": "someAdSlot", "rp": { "target": { "dataAttr1": [ @@ -472,7 +471,8 @@ "sectioncat": [ "sectionCat1", "sectionCat2" - ] + ], + "dfp_ad_unit_code": "adSlotFromData" }, "track": { "mint": "", diff --git a/adapters/rubicon/rubicontest/exemplary/user-fpd.json b/adapters/rubicon/rubicontest/exemplary/user-fpd.json index b2be736fefa..9034c62ca34 100644 --- a/adapters/rubicon/rubicontest/exemplary/user-fpd.json +++ b/adapters/rubicon/rubicontest/exemplary/user-fpd.json @@ -257,7 +257,6 @@ "h": 576 }, "ext": { - "gpid": "someAdSlot", "rp": { "target": { "dataAttr1": [ @@ -274,7 +273,8 @@ ], "search": [ "someSearch" - ] + ], + "dfp_ad_unit_code": "someAdSlot" }, "track": { "mint": "", diff --git a/openrtb_ext/imp_rubicon.go b/openrtb_ext/imp_rubicon.go index f9b15ed3223..a09b18ae273 100644 --- a/openrtb_ext/imp_rubicon.go +++ b/openrtb_ext/imp_rubicon.go @@ -6,9 +6,9 @@ import ( // ExtImpRubicon defines the contract for bidrequest.imp[i].ext.rubicon type ExtImpRubicon struct { - AccountId int `json:"accountId"` - SiteId int `json:"siteId"` - ZoneId int `json:"zoneId"` + AccountId json.Number `json:"accountId"` + SiteId json.Number `json:"siteId"` + ZoneId json.Number `json:"zoneId"` Inventory json.RawMessage `json:"inventory,omitempty"` Keywords []string `json:"keywords,omitempty"` Visitor json.RawMessage `json:"visitor,omitempty"` @@ -18,12 +18,12 @@ type ExtImpRubicon struct { // rubiconVideoParams defines the contract for bidrequest.imp[i].ext.rubicon.video type rubiconVideoParams struct { - Language string `json:"language,omitempty"` - PlayerHeight int `json:"playerHeight,omitempty"` - PlayerWidth int `json:"playerWidth,omitempty"` - VideoSizeID int `json:"size_id,omitempty"` - Skip int `json:"skip,omitempty"` - SkipDelay int `json:"skipdelay,omitempty"` + Language string `json:"language,omitempty"` + PlayerHeight json.Number `json:"playerHeight,omitempty"` + PlayerWidth json.Number `json:"playerWidth,omitempty"` + VideoSizeID int `json:"size_id,omitempty"` + Skip int `json:"skip,omitempty"` + SkipDelay int `json:"skipdelay,omitempty"` } // rubiconVideoParams defines the contract for bidrequest.imp[i].ext.rubicon.debug diff --git a/router/router.go b/router/router.go index 17145b5a1e7..45a5100c9be 100644 --- a/router/router.go +++ b/router/router.go @@ -26,7 +26,6 @@ import ( "github.com/prebid/prebid-server/adapters/ix" "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" @@ -159,8 +158,6 @@ func newExchangeMap(cfg *config.Configuration) map[string]adapters.Adapter { "ix": ix.NewIxLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[strings.ToLower(string(openrtb_ext.BidderIx))].Endpoint), "pubmatic": pubmatic.NewPubmaticLegacyAdapter(adapters.DefaultHTTPAdapterConfig, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), "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), "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/router/router_test.go b/router/router_test.go index 24a7709c365..ffc0dd8831d 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -62,18 +62,6 @@ func TestNewJsonDirectoryServer(t *testing.T) { ensureHasKey(t, data, "aliastest") } -func TestExchangeMap(t *testing.T) { - exchanges := newExchangeMap(&config.Configuration{}) - bidderMap := openrtb_ext.BuildBidderMap() - for bidderName := range exchanges { - // OpenRTB doesn't support hardcoded aliases... so this test skips districtm, - // which was the only alias in the legacy adapter map. - if _, ok := bidderMap[bidderName]; bidderName != "districtm" && !ok { - t.Errorf("Bidder %s exists in exchange, but is not a part of the BidderMap.", bidderName) - } - } -} - func TestApplyBidderInfoConfigOverrides(t *testing.T) { var testCases = []struct { description string diff --git a/static/bidder-params/rubicon.json b/static/bidder-params/rubicon.json index a2ba22820b8..e58dbe8a79a 100644 --- a/static/bidder-params/rubicon.json +++ b/static/bidder-params/rubicon.json @@ -5,17 +5,29 @@ "type": "object", "properties": { "accountId": { - "type": "integer", + "type": [ + "integer", + "string" + ], + "pattern": "^\\d+$", "minimum": 1, "description": "An ID which identifies the publisher's account" }, "siteId": { - "type": "integer", + "type": [ + "integer", + "string" + ], + "pattern": "^\\d+$", "minimum": 1, "description": "An ID which identifies the site selling the impression" }, "zoneId": { - "type": "integer", + "type": [ + "integer", + "string" + ], + "pattern": "^\\d+$", "minimum": 1, "description": "An ID which identifies the sub-section of the site where the impression is located" }, @@ -33,6 +45,10 @@ "type": "array" } }, + "pchain": { + "type": "string", + "description": "A payment ID chain string" + }, "video": { "type": "object", "description": "An object defining additional Rubicon video parameters", @@ -42,11 +58,11 @@ "description": "Language of the ad - should match content video" }, "playerHeight": { - "type": "integer", + "type": ["integer", "string"], "description": "Height in pixels of the video player" }, "playerWidth": { - "type": "integer", + "type": ["integer", "string"], "description": "Width in pixels of the video player" }, "size_id": { @@ -55,7 +71,7 @@ }, "skip": { "type": "integer", - "description": "Can this ad be skipped ( 0 = no, 1 = yes)" + "description": "Can this ad be skipped (0 = no, 1 = yes)" }, "skipdelay": { "type": "integer", @@ -64,5 +80,9 @@ } } }, - "required": ["accountId", "siteId", "zoneId"] + "required": [ + "accountId", + "siteId", + "zoneId" + ] }