diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go index 45bc6300a5b..48996082a51 100644 --- a/adapters/ix/ix.go +++ b/adapters/ix/ix.go @@ -8,6 +8,8 @@ import ( "io/ioutil" "net/http" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/pbs" "golang.org/x/net/context/ctxhttp" @@ -17,14 +19,17 @@ import ( "github.com/prebid/prebid-server/errortypes" ) +// maximum number of bid requests +const requestLimit = 20 + type IxAdapter struct { http *adapters.HTTPAdapter URI string } -// used for cookies and such +// Name is used for cookies and such func (a *IxAdapter) Name() string { - return "ix" + return string(openrtb_ext.BidderIx) } func (a *IxAdapter) SkipNoCookies() bool { @@ -35,7 +40,23 @@ type indexParams struct { SiteID string `json:"siteId"` } +type ixBidResult struct { + Request *callOneObject + StatusCode int + ResponseBody string + Bid *pbs.PBSBid + Error error +} + +type callOneObject struct { + requestJSON bytes.Buffer + width uint64 + height uint64 +} + func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + var prioritizedRequests, requests []callOneObject + if req.App != nil { return nil, &errortypes.BadInput{ Message: "Index doesn't support apps", @@ -48,70 +69,149 @@ func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.P return nil, err } + indexReqImp := indexReq.Imp for i, unit := range bidder.AdUnits { - var params indexParams - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("unmarshal params '%s' failed: %v", unit.Params, err), - } + + // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply + if len(indexReqImp) <= i { + break } - if params.SiteID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing siteId param", + + for sizeIndex, format := range unit.Sizes { + var params indexParams + err := json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("unmarshal params '%s' failed: %v", unit.Params, err), + } + } + if params.SiteID == "" { + return nil, &errortypes.BadInput{ + Message: "Missing siteId param", + } + } + + // Only grab this ad unit + // Not supporting multi-media-type adunit yet + thisImp := indexReqImp[i] + + thisImp.TagID = unit.Code + thisImp.Banner.Format = []openrtb.Format{format} + thisImp.Banner.W = &format.W + thisImp.Banner.H = &format.H + indexReq.Imp = []openrtb.Imp{thisImp} + // Index spec says "adunit path representing ad server inventory" but we don't have this + // ext is DFP div ID and KV pairs if avail + //indexReq.Imp[i].Ext = json.RawMessage("{}") + + // Any objects pointed to by indexReq *must not be mutated*, or we will get race conditions. + siteCopy := *indexReq.Site + siteCopy.Publisher = &openrtb.Publisher{ID: params.SiteID} + indexReq.Site = &siteCopy + + // spec also asks for publisher id if set + // ext object on request for prefetch + j, _ := json.Marshal(indexReq) + + request := callOneObject{requestJSON: *bytes.NewBuffer(j), width: format.W, height: format.H} + + // prioritize slots over sizes + if sizeIndex == 0 { + prioritizedRequests = append(prioritizedRequests, request) + } else { + requests = append(requests, request) } } + } - // Fixes some segfaults. Since this is legacy code, I'm not looking into it too deeply - if len(indexReq.Imp) <= i { - break + // cap the number of requests to requestLimit + requests = append(prioritizedRequests, requests...) + if len(requests) > requestLimit { + requests = requests[:requestLimit] + } + + if len(requests) == 0 { + return nil, &errortypes.BadInput{ + Message: "Invalid ad unit/imp", } + } - indexReq.Imp[i].TagID = unit.Code - // Index spec says "adunit path representing ad server inventory" but we don't have this - // ext is DFP div ID and KV pairs if avail - //indexReq.Imp[i].Ext = json.RawMessage("{}") - siteCopy := *indexReq.Site - siteCopy.Publisher = &openrtb.Publisher{ID: fmt.Sprintf("%s", params.SiteID)} - indexReq.Site = &siteCopy + ch := make(chan ixBidResult) + for _, request := range requests { + go func(bidder *pbs.PBSBidder, request callOneObject) { + result, err := a.callOne(ctx, request.requestJSON) + result.Request = &request + result.Error = err + if result.Bid != nil { + result.Bid.BidderCode = bidder.BidderCode + result.Bid.BidID = bidder.LookupBidID(result.Bid.AdUnitCode) + result.Bid.Width = request.width + result.Bid.Height = request.height + + 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, request) } - // spec also asks for publisher id if set - // ext object on request for prefetch - j, _ := json.Marshal(indexReq) + bids := make(pbs.PBSBidSlice, 0) + for i := 0; i < len(requests); i++ { + result := <-ch + if result.Bid != nil && result.Bid.Price != 0 { + bids = append(bids, result.Bid) + } - debug := &pbs.BidderDebug{ - RequestURI: a.URI, + if req.IsDebug { + debug := &pbs.BidderDebug{ + RequestURI: a.URI, + RequestBody: result.Request.requestJSON.String(), + StatusCode: result.StatusCode, + ResponseBody: result.ResponseBody, + } + bidder.Debug = append(bidder.Debug, debug) + } + if result.Error != nil { + err = result.Error + } } - if req.IsDebug { - debug.RequestBody = string(j) - bidder.Debug = append(bidder.Debug, debug) + if len(bids) == 0 { + return nil, err } + return bids, nil +} - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) +func (a *IxAdapter) callOne(ctx context.Context, reqJSON bytes.Buffer) (ixBidResult, error) { + var result ixBidResult + + httpReq, _ := http.NewRequest("POST", a.URI, &reqJSON) httpReq.Header.Add("Content-Type", "application/json;charset=utf-8") httpReq.Header.Add("Accept", "application/json") ixResp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) if err != nil { - return nil, err + return result, err } - debug.StatusCode = ixResp.StatusCode + result.StatusCode = ixResp.StatusCode if ixResp.StatusCode == http.StatusNoContent { - return nil, nil + return result, nil } if ixResp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ + return result, &errortypes.BadInput{ Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), } } if ixResp.StatusCode != http.StatusOK { - return nil, &errortypes.BadServerResponse{ + return result, &errortypes.BadServerResponse{ Message: fmt.Sprintf("HTTP status: %d", ixResp.StatusCode), } } @@ -119,52 +219,37 @@ func (a *IxAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.P defer ixResp.Body.Close() body, err := ioutil.ReadAll(ixResp.Body) if err != nil { - return nil, err - } - - if req.IsDebug { - debug.ResponseBody = string(body) + return result, err } + result.ResponseBody = string(body) var bidResp openrtb.BidResponse err = json.Unmarshal(body, &bidResp) if err != nil { - return nil, &errortypes.BadServerResponse{ + return result, &errortypes.BadServerResponse{ Message: fmt.Sprintf("Error parsing response: %v", err), } } - bids := make(pbs.PBSBidSlice, 0) - - for _, sb := range bidResp.SeatBid { - for _, bid := range sb.Bid { - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } + if len(bidResp.SeatBid) == 0 { + return result, nil + } + if len(bidResp.SeatBid[0].Bid) == 0 { + return result, nil + } + bid := bidResp.SeatBid[0].Bid[0] - for _, adunit := range bidder.AdUnits { - if adunit.BidID == bidID { - pbid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: adunit.Code, - BidderCode: bidder.BidderCode, - Price: bid.Price, - Adm: bid.AdM, - Creative_id: bid.CrID, - Width: bid.W, - Height: bid.H, - DealId: bid.DealID, - CreativeMediaType: "banner", - } - bids = append(bids, &pbid) - } - } - } + 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, + CreativeMediaType: string(openrtb_ext.BidTypeBanner), } - return bids, nil + return result, nil } func NewIxAdapter(config *adapters.HTTPAdapterConfig, uri string) *IxAdapter { diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go index 08c86625cc0..7dbbf5a4030 100644 --- a/adapters/ix/ix_test.go +++ b/adapters/ix/ix_test.go @@ -3,6 +3,8 @@ package ix import ( "context" "encoding/json" + "io/ioutil" + "math/rand" "net/http" "net/http/httptest" "testing" @@ -18,6 +20,66 @@ import ( const url string = "http://appnexus-us-east.lb.indexww.com/bidder?p=184932" +func getAdUnit() pbs.PBSAdUnit { + return pbs.PBSAdUnit{ + Code: "unitCode", + MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, + BidID: "bidid", + Sizes: []openrtb.Format{ + { + W: 10, + H: 12, + }, + }, + Params: json.RawMessage("{\"siteId\": \"12\"}"), + } +} + +func getOpenRTBBid(i openrtb.Imp) openrtb.Bid { + return openrtb.Bid{ + ID: fmt.Sprintf("%d", rand.Intn(1000)), + ImpID: i.ID, + Price: 1.0, + AdM: "Content", + CrID: fmt.Sprintf("%d", rand.Intn(1000)), + W: *i.Banner.W, + H: *i.Banner.H, + DealID: "5", + } +} + +func dummyIXServer(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + + var breq openrtb.BidRequest + err = json.Unmarshal(body, &breq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + impression := breq.Imp[0] + + resp := openrtb.BidResponse{ + SeatBid: []openrtb.SeatBid{ + { + Bid: []openrtb.Bid{ + getOpenRTBBid(impression), + }, + }, + }, + } + + 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 TestIxInvalidCall(t *testing.T) { an := NewIxAdapter(adapters.DefaultHTTPAdapterConfig, url) @@ -57,21 +119,13 @@ func TestIxInvalidCallMissingSiteID(t *testing.T) { ctx := context.TODO() pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.Params = json.RawMessage("{}") pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{}"), - }, + adUnit, }, } _, err := an.Call(ctx, &pbReq, &pbBidder) @@ -98,17 +152,7 @@ func TestIxTimeout(t *testing.T) { pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\": \"12\"}"), - }, + getAdUnit(), }, } _, err := an.Call(ctx, &pbReq, &pbBidder) @@ -117,6 +161,90 @@ func TestIxTimeout(t *testing.T) { } } +func TestIxTimeoutMultipleSlots(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + + var breq openrtb.BidRequest + err = json.Unmarshal(body, &breq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + impression := breq.Imp[0] + + resp := openrtb.BidResponse{ + SeatBid: []openrtb.SeatBid{ + { + Bid: []openrtb.Bid{ + getOpenRTBBid(impression), + }, + }, + }, + } + + js, err := json.Marshal(resp) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // cancel the request before 2nd impression is returned + // delay to let 1st impression return successfully + if impression.ID == "unitCode2" { + <-time.After(10 * time.Millisecond) + cancel() + <-r.Context().Done() + } + + w.Header().Set("Content-Type", "application/json") + w.Write(js) + }), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewIxAdapter(&conf, server.URL) + pbReq := pbs.PBSRequest{} + + adUnit1 := getAdUnit() + adUnit2 := getAdUnit() + adUnit2.Code = "unitCode2" + adUnit2.Sizes = []openrtb.Format{ + { + W: 8, + H: 10, + }, + } + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit1, + adUnit2, + }, + } + bids, err := an.Call(ctx, &pbReq, &pbBidder) + + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != 1 { + t.Fatalf("Should have received one bid") + } + + bid := findBidByAdUnitCode(bids, adUnit1.Code) + if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { + t.Fatalf("Recieved the wrong size") + } +} + func TestIxInvalidJsonResponse(t *testing.T) { server := httptest.NewServer( @@ -133,17 +261,7 @@ func TestIxInvalidJsonResponse(t *testing.T) { pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\": \"12\"}"), - }, + getAdUnit(), }, } _, err := an.Call(ctx, &pbReq, &pbBidder) @@ -169,17 +287,7 @@ func TestIxInvalidStatusCode(t *testing.T) { pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\": \"12\"}"), - }, + getAdUnit(), }, } _, err := an.Call(ctx, &pbReq, &pbBidder) @@ -205,17 +313,7 @@ func TestIxBadRequest(t *testing.T) { pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\": \"12\"}"), - }, + getAdUnit(), }, } _, err := an.Call(ctx, &pbReq, &pbBidder) @@ -241,16 +339,7 @@ func TestIxNoContent(t *testing.T) { pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ - { - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - Sizes: []openrtb.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\": \"12\"}"), - }, + getAdUnit(), }, } @@ -263,16 +352,7 @@ func TestIxNoContent(t *testing.T) { func TestIxInvalidCallMissingSize(t *testing.T) { server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := openrtb.BidResponse{} - 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) - }), + http.HandlerFunc(dummyIXServer), ) defer server.Close() @@ -280,34 +360,64 @@ func TestIxInvalidCallMissingSize(t *testing.T) { an := NewIxAdapter(&conf, server.URL) ctx := context.TODO() pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.Sizes = []openrtb.Format{} pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Params: json.RawMessage("{\"siteId\": \"12\"}"), - }, + adUnit, }, } - _, err := an.Call(ctx, &pbReq, &pbBidder) - if err == nil { + if _, err := an.Call(ctx, &pbReq, &pbBidder); err == nil { t.Fatalf("Should not have gotten an error for missing/invalid size: %v", err) } } -func TestIxBasicResponse(t *testing.T) { +func TestIxInvalidCallEmptyBidIDResponse(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewIxAdapter(&conf, server.URL) + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.BidID = "" + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit, + }, + } + if _, err := an.Call(ctx, &pbReq, &pbBidder); err == nil { + t.Fatalf("Should have gotten an error for unknown adunit code") + } +} + +func TestIxMismatchUnitCode(t *testing.T) { server := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + + var breq openrtb.BidRequest + err = json.Unmarshal(body, &breq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + resp := openrtb.BidResponse{ SeatBid: []openrtb.SeatBid{ { Bid: []openrtb.Bid{ { - ID: "1234", - ImpID: "unitCode", + ID: fmt.Sprintf("%d", rand.Intn(1000)), + ImpID: "unitCode_bogus", Price: 1.0, AdM: "Content", CrID: "567", @@ -338,25 +448,196 @@ func TestIxBasicResponse(t *testing.T) { pbBidder := pbs.PBSBidder{ BidderCode: "bannerCode", AdUnits: []pbs.PBSAdUnit{ - { - Code: "unitCode", - MediaTypes: []pbs.MediaType{pbs.MEDIA_TYPE_BANNER}, - BidID: "bidid", - Sizes: []openrtb.Format{ - { - W: 10, - H: 12, - }, - }, - Params: json.RawMessage("{\"siteId\": \"12\"}"), - }, + getAdUnit(), + }, + } + if _, err := an.Call(ctx, &pbReq, &pbBidder); err == nil { + t.Fatalf("Should have gotten an error for unknown adunit code") + } +} + +func TestIxInvalidParam(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewIxAdapter(&conf, server.URL) + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.Params = json.RawMessage("Bogus invalid input") + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit, + }, + } + if _, err := an.Call(ctx, &pbReq, &pbBidder); err == nil { + t.Fatalf("Should have gotten an error for unrecognized params") + } +} + +func TestIxBasicResponse(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewIxAdapter(&conf, server.URL) + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + getAdUnit(), }, } bids, err := an.Call(ctx, &pbReq, &pbBidder) if err != nil { t.Fatalf("Should not have gotten an error: %v", err) } + if len(bids) != 1 { t.Fatalf("Should have received one bid") } } + +func TestIxTwoSlotResponse(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewIxAdapter(&conf, server.URL) + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit1 := getAdUnit() + adUnit2 := getAdUnit() + adUnit2.Code = "unitCode2" + adUnit2.Sizes = []openrtb.Format{ + { + W: 8, + H: 10, + }, + } + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit1, + adUnit2, + }, + } + bids, err := an.Call(ctx, &pbReq, &pbBidder) + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != 2 { + t.Fatalf("Should have received two bid") + } + + bid := findBidByAdUnitCode(bids, adUnit1.Code) + if adUnit1.Sizes[0].H != bid.Height || adUnit1.Sizes[0].W != bid.Width { + t.Fatalf("Recieved the wrong size") + } + + bid = findBidByAdUnitCode(bids, adUnit2.Code) + if adUnit2.Sizes[0].H != bid.Height || adUnit2.Sizes[0].W != bid.Width { + t.Fatalf("Recieved the wrong size") + } +} + +func TestIxMultiSizeResponse(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewIxAdapter(&conf, server.URL) + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnit := getAdUnit() + adUnit.Sizes = append(adUnit.Sizes, openrtb.Format{W: 20, H: 22}) + + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: []pbs.PBSAdUnit{ + adUnit, + }, + } + bids, err := an.Call(ctx, &pbReq, &pbBidder) + + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != 2 { + t.Fatalf("Should have received 2 bids") + } + + for _, v := range adUnit.Sizes { + if !bidResponseForSizeExist(bids, v.H, v.W) { + t.Fatalf("Missing bid for specified size %d and %d", v.W, v.H) + } + } +} + +func bidResponseForSizeExist(bids pbs.PBSBidSlice, h uint64, w uint64) bool { + for _, v := range bids { + if v.Height == h && v.Width == w { + return true + } + } + return false +} + +func findBidByAdUnitCode(bids pbs.PBSBidSlice, c string) *pbs.PBSBid { + for _, v := range bids { + if v.AdUnitCode == c { + return v + } + } + return &pbs.PBSBid{} +} + +func TestIxRequestLimit(t *testing.T) { + + server := httptest.NewServer( + http.HandlerFunc(dummyIXServer), + ) + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewIxAdapter(&conf, server.URL) + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + adUnits := []pbs.PBSAdUnit{} + + for i := 0; i < requestLimit+1; i++ { + adUnits = append(adUnits, getAdUnit()) + } + + pbBidder := pbs.PBSBidder{ + BidderCode: "bannerCode", + AdUnits: adUnits, + } + + bids, err := an.Call(ctx, &pbReq, &pbBidder) + + if err != nil { + t.Fatalf("Should not have gotten an error: %v", err) + } + + if len(bids) != requestLimit { + t.Fatalf("Should have received %d bid", requestLimit) + } +} diff --git a/pbs/pbsrequest.go b/pbs/pbsrequest.go index 3efe742f5c4..6f86ff80977 100644 --- a/pbs/pbsrequest.go +++ b/pbs/pbsrequest.go @@ -319,19 +319,14 @@ func ParsePBSRequest(r *http.Request, cfg *config.AuctionTimeouts, cache cache.C mtypes := ParseMediaTypes(unit.MediaTypes) for _, b := range bidders { var bidder *PBSBidder - // index requires a different request for each ad unit - if b.BidderCode != "ix" { - for _, pb := range pbsReq.Bidders { - if pb.BidderCode == b.BidderCode { - bidder = pb - } + for _, pb := range pbsReq.Bidders { + if pb.BidderCode == b.BidderCode { + bidder = pb } } + if bidder == nil { bidder = &PBSBidder{BidderCode: b.BidderCode} - if b.BidderCode == "ix" { - bidder.AdUnitCode = unit.Code - } pbsReq.Bidders = append(pbsReq.Bidders, bidder) } if b.BidID == "" { diff --git a/pbs/pbsrequest_test.go b/pbs/pbsrequest_test.go index 800845eb793..3e3b37b9fad 100644 --- a/pbs/pbsrequest_test.go +++ b/pbs/pbsrequest_test.go @@ -90,14 +90,14 @@ func TestParseSimpleRequest(t *testing.T) { } // see if our internal representation is intact - if len(pbs_req.Bidders) != 3 { - t.Fatalf("Should have three bidders (2 for index) not %d", len(pbs_req.Bidders)) + if len(pbs_req.Bidders) != 2 { + t.Fatalf("Should have two bidders not %d", len(pbs_req.Bidders)) } if pbs_req.Bidders[0].BidderCode != "ix" { t.Errorf("First bidder not index") } - if len(pbs_req.Bidders[0].AdUnits) != 1 { - t.Errorf("Index bidder should have 1 ad unit") + if len(pbs_req.Bidders[0].AdUnits) != 2 { + t.Errorf("Index bidder should have 2 ad unit") } if pbs_req.Bidders[1].BidderCode != "appnexus" { t.Errorf("Second bidder not appnexus") @@ -105,18 +105,9 @@ func TestParseSimpleRequest(t *testing.T) { if len(pbs_req.Bidders[1].AdUnits) != 2 { t.Errorf("AppNexus bidder should have 2 ad unit") } - if pbs_req.Bidders[2].BidderCode != "ix" { - t.Errorf("Third bidder not index") - } - if len(pbs_req.Bidders[2].AdUnits) != 1 { - t.Errorf("Index bidder should have 1 ad unit") - } if pbs_req.Bidders[1].AdUnits[0].BidID == "" { t.Errorf("ID should have been generated for empty BidID") } - if pbs_req.Bidders[2].AdUnits[0].BidID == "" { - t.Errorf("ID should have been generated for empty BidID") - } if pbs_req.AdUnits[1].MediaTypes[0] != "banner" { t.Errorf("Instead of banner MediaType received %s", pbs_req.AdUnits[1].MediaTypes[0]) } @@ -260,13 +251,13 @@ func TestParseConfig(t *testing.T) { } // see if our internal representation is intact - if len(pbs_req.Bidders) != 5 { - t.Fatalf("Should have 5 bidders (2 for index) not %d", len(pbs_req.Bidders)) + if len(pbs_req.Bidders) != 4 { + t.Fatalf("Should have 4 bidders not %d", len(pbs_req.Bidders)) } if pbs_req.Bidders[0].BidderCode != "ix" { t.Errorf("First bidder not index") } - if len(pbs_req.Bidders[0].AdUnits) != 1 { + if len(pbs_req.Bidders[0].AdUnits) != 2 { t.Errorf("Index bidder should have 1 ad unit") } if pbs_req.Bidders[1].BidderCode != "appnexus" { @@ -275,12 +266,6 @@ func TestParseConfig(t *testing.T) { if len(pbs_req.Bidders[1].AdUnits) != 2 { t.Errorf("AppNexus bidder should have 2 ad unit") } - if pbs_req.Bidders[2].BidderCode != "ix" { - t.Errorf("Third bidder not index") - } - if len(pbs_req.Bidders[2].AdUnits) != 1 { - t.Errorf("Index bidder should have 1 ad unit") - } } func TestParseMobileRequestFirstVersion(t *testing.T) {