diff --git a/adapters/conversant/cnvr_legacy.go b/adapters/conversant/cnvr_legacy.go new file mode 100644 index 00000000000..bd121f098c0 --- /dev/null +++ b/adapters/conversant/cnvr_legacy.go @@ -0,0 +1,291 @@ +package conversant + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/mxmCherry/openrtb" + "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 ConversantLegacyAdapter struct { + http *adapters.HTTPAdapter + URI string +} + +// Corresponds to the bidder name in cookies and requests +func (a *ConversantLegacyAdapter) Name() string { + return "conversant" +} + +// Return true so no request will be sent unless user has been sync'ed. +func (a *ConversantLegacyAdapter) SkipNoCookies() bool { + return true +} + +type conversantParams struct { + SiteID string `json:"site_id"` + Secure *int8 `json:"secure"` + TagID string `json:"tag_id"` + Position *int8 `json:"position"` + BidFloor float64 `json:"bidfloor"` + Mobile *int8 `json:"mobile"` + MIMEs []string `json:"mimes"` + API []int8 `json:"api"` + Protocols []int8 `json:"protocols"` + MaxDuration *int64 `json:"maxduration"` +} + +func (a *ConversantLegacyAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { + mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} + cnvrReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) + + if err != nil { + return nil, err + } + + // Create a map of impression objects for both request creation + // and response parsing. + + impMap := make(map[string]*openrtb.Imp, len(cnvrReq.Imp)) + for idx := range cnvrReq.Imp { + impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] + } + + // Fill in additional info from custom params + + for _, unit := range bidder.AdUnits { + var params conversantParams + + imp := impMap[unit.Code] + if imp == nil { + // Skip ad units that do not have corresponding impressions. + continue + } + + err := json.Unmarshal(unit.Params, ¶ms) + if err != nil { + return nil, &errortypes.BadInput{ + Message: err.Error(), + } + } + + // Fill in additional Site info + if params.SiteID != "" { + if cnvrReq.Site != nil { + cnvrReq.Site.ID = params.SiteID + } + if cnvrReq.App != nil { + cnvrReq.App.ID = params.SiteID + } + } + + if params.Mobile != nil && !(cnvrReq.Site == nil) { + cnvrReq.Site.Mobile = *params.Mobile + } + + // Fill in additional impression info + + imp.DisplayManager = "prebid-s2s" + imp.DisplayManagerVer = "1.0.1" + imp.BidFloor = params.BidFloor + imp.TagID = params.TagID + + var position *openrtb.AdPosition + if params.Position != nil { + position = openrtb.AdPosition(*params.Position).Ptr() + } + + if imp.Banner != nil { + imp.Banner.Pos = position + } else if imp.Video != nil { + imp.Video.Pos = position + + if len(params.API) > 0 { + imp.Video.API = make([]openrtb.APIFramework, 0, len(params.API)) + for _, api := range params.API { + imp.Video.API = append(imp.Video.API, openrtb.APIFramework(api)) + } + } + + // Include protocols, mimes, and max duration if specified + // These properties can also be specified in ad unit's video object, + // but are overridden if the custom params object also contains them. + + if len(params.Protocols) > 0 { + imp.Video.Protocols = make([]openrtb.Protocol, 0, len(params.Protocols)) + for _, protocol := range params.Protocols { + imp.Video.Protocols = append(imp.Video.Protocols, openrtb.Protocol(protocol)) + } + } + + if len(params.MIMEs) > 0 { + imp.Video.MIMEs = make([]string, len(params.MIMEs)) + copy(imp.Video.MIMEs, params.MIMEs) + } + + if params.MaxDuration != nil { + imp.Video.MaxDuration = *params.MaxDuration + } + } + + // Take care not to override the global secure flag + + if (imp.Secure == nil || *imp.Secure == 0) && params.Secure != nil { + imp.Secure = params.Secure + } + } + + // Do a quick check on required parameters + + if cnvrReq.Site != nil && cnvrReq.Site.ID == "" { + return nil, &errortypes.BadInput{ + Message: "Missing site id", + } + } + + if cnvrReq.App != nil && cnvrReq.App.ID == "" { + return nil, &errortypes.BadInput{ + Message: "Missing app id", + } + } + + // Start capturing debug info + + debug := &pbs.BidderDebug{ + RequestURI: a.URI, + } + + if cnvrReq.Device == nil { + cnvrReq.Device = &openrtb.Device{} + } + + // Convert request to json to be sent over http + + j, _ := json.Marshal(cnvrReq) + + if req.IsDebug { + debug.RequestBody = string(j) + bidder.Debug = append(bidder.Debug, debug) + } + + httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) + httpReq.Header.Add("Content-Type", "application/json") + httpReq.Header.Add("Accept", "application/json") + + resp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + if req.IsDebug { + debug.StatusCode = resp.StatusCode + } + + if resp.StatusCode == 204 { + return nil, nil + } + + body, err := ioutil.ReadAll(resp.Body) + + if err != nil { + return nil, err + } + + if resp.StatusCode == http.StatusBadRequest { + return nil, &errortypes.BadInput{ + Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), + } + } + + if resp.StatusCode != 200 { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), + } + } + + if req.IsDebug { + debug.ResponseBody = string(body) + } + + var bidResp openrtb.BidResponse + + err = json.Unmarshal(body, &bidResp) + if err != nil { + return nil, &errortypes.BadServerResponse{ + Message: err.Error(), + } + } + + bids := make(pbs.PBSBidSlice, 0) + + for _, seatbid := range bidResp.SeatBid { + for _, bid := range seatbid.Bid { + if bid.Price <= 0 { + continue + } + + imp := impMap[bid.ImpID] + if imp == nil { + // All returned bids should have a matching impression + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown impression id '%s'", bid.ImpID), + } + } + + bidID := bidder.LookupBidID(bid.ImpID) + if bidID == "" { + return nil, &errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), + } + } + + pbsBid := pbs.PBSBid{ + BidID: bidID, + AdUnitCode: bid.ImpID, + Price: bid.Price, + Creative_id: bid.CrID, + BidderCode: bidder.BidderCode, + } + + if imp.Video != nil { + pbsBid.CreativeMediaType = "video" + pbsBid.NURL = bid.AdM // Assign to NURL so it'll be interpreted as a vastUrl + pbsBid.Width = imp.Video.W + pbsBid.Height = imp.Video.H + } else { + pbsBid.CreativeMediaType = "banner" + pbsBid.NURL = bid.NURL + pbsBid.Adm = bid.AdM + pbsBid.Width = bid.W + pbsBid.Height = bid.H + } + + bids = append(bids, &pbsBid) + } + } + + if len(bids) == 0 { + return nil, nil + } + + return bids, nil +} + +func NewConversantAdapter(config *adapters.HTTPAdapterConfig, uri string) *ConversantLegacyAdapter { + a := adapters.NewHTTPAdapter(config) + + return &ConversantLegacyAdapter{ + http: a, + URI: uri, + } +} diff --git a/adapters/conversant/cnvr_legacy_test.go b/adapters/conversant/cnvr_legacy_test.go new file mode 100644 index 00000000000..b958e320dc7 --- /dev/null +++ b/adapters/conversant/cnvr_legacy_test.go @@ -0,0 +1,853 @@ +package conversant + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "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" +) + +// Constants + +const ExpectedSiteID string = "12345" +const ExpectedDisplayManager string = "prebid-s2s" +const ExpectedBuyerUID string = "AQECT_o7M1FLbQJK8QFmAQEBAQE" +const ExpectedNURL string = "http://test.dotomi.com" +const ExpectedAdM string = "" +const ExpectedCrID string = "98765" + +const DefaultParam = `{"site_id": "12345"}` + +// Test properties of Adapter interface + +func TestConversantProperties(t *testing.T) { + an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") + + assertNotEqual(t, an.Name(), "", "Missing family name") + assertTrue(t, an.SkipNoCookies(), "SkipNoCookies should be true") +} + +// Test empty bid requests + +func TestConversantEmptyBid(t *testing.T) { + an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") + + ctx := context.TODO() + pbReq := pbs.PBSRequest{} + pbBidder := pbs.PBSBidder{} + _, err := an.Call(ctx, &pbReq, &pbBidder) + assertTrue(t, err != nil, "No error received for an invalid request") +} + +// Test required parameters, which is just the site id for now + +func TestConversantRequiredParameters(t *testing.T) { + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) + }), + ) + defer server.Close() + + an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) + ctx := context.TODO() + + testParams := func(params ...string) (pbs.PBSBidSlice, error) { + req, err := CreateBannerRequest(params...) + if err != nil { + return nil, err + } + return an.Call(ctx, req, req.Bidders[0]) + } + + var err error + + if _, err = testParams(`{}`); err == nil { + t.Fatal("Failed to catch missing site id") + } +} + +// Test handling of 404 + +func TestConversantBadStatus(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + assertTrue(t, err != nil, "Failed to catch 404 error") +} + +// Test handling of HTTP timeout + +func TestConversantTimeout(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + <-time.After(2 * time.Millisecond) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + // Create a context that expires before http returns + + ctx, cancel := context.WithTimeout(context.Background(), 0) + defer cancel() + + // Create a basic request + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + // Attempt to process the request, which should hit a timeout + // immediately + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err == nil || err != context.DeadlineExceeded { + t.Fatal("No timeout recevied for timed out request", err) + } +} + +// Test handling of 204 + +func TestConversantNoBid(t *testing.T) { + // Create a test http server that returns after 2 milliseconds + + server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) + }), + ) + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if resp != nil || err != nil { + t.Fatal("Failed to handle empty bid", err) + } +} + +// Verify an outgoing openrtp request is created correctly + +func TestConversantRequestDefault(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(DefaultParam) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Video == nil, "Request video should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") + assertEqual(t, imp.TagID, "", "Request tag id") + assertTrue(t, imp.Banner.Pos == nil, "Request pos") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify inapp video request +func TestConversantInappVideoRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + requestParam := `{"secure": 1, "site_id": "12345"}` + appParam := `{ "bundle": "com.naver.linewebtoon" }` + videoParam := `{ "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq := CreateRequest(requestParam) + pbReq, err := ConvertToVideoRequest(pbReq, videoParam) + if err != nil { + t.Fatal("failed to parse request") + } + pbReq, err = ConvertToAppRequest(pbReq, appParam) + if err != nil { + t.Fatal("failed to parse request") + } + pbReq, err = ParseRequest(pbReq) + if err != nil { + t.Fatal("failed to parse request") + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + + imp := &lastReq.Imp[0] + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + assertEqual(t, lastReq.App.ID, "12345", "App Id") +} + +// Verify inapp video request +func TestConversantInappBannerRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "secure": 1, + "site_id": "12345", + "tag_id": "top", + "position": 2, + "bidfloor": 1.01 }` + appParam := `{ "bundle": "com.naver.linewebtoon" }` + + ctx := context.TODO() + pbReq, _ := CreateBannerRequest(param) + pbReq, err := ConvertToAppRequest(pbReq, appParam) + if err != nil { + t.Fatal("failed to parse request") + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + + imp := &lastReq.Imp[0] + assertEqual(t, lastReq.App.ID, "12345", "App Id") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify an outgoing openrtp request with additional conversant parameters is +// processed correctly + +func TestConversantRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile": 1 }` + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(param) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 1, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Video == nil, "Request video should be nil") + assertEqual(t, int(*imp.Secure), 1, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "top", "Request tag id") + assertEqual(t, int(*imp.Banner.Pos), 2, "Request pos") + assertEqual(t, int(*imp.Banner.W), 300, "Request width") + assertEqual(t, int(*imp.Banner.H), 250, "Request height") +} + +// Verify openrtp responses are converted correctly + +func TestConversantResponse(t *testing.T) { + prices := []float64{0.01, 0.0, 2.01} + server, lastReq := CreateServer(prices...) + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile" : 1}` + + ctx := context.TODO() + pbReq, err := CreateBannerRequest(param, param, param) + if err != nil { + t.Fatal("Failed to create a banner request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + prices, imps := FilterZeroPrices(prices, lastReq.Imp) + + assertEqual(t, len(resp), len(prices), "Bad number of responses") + + for i, bid := range resp { + assertEqual(t, bid.Price, prices[i], "Bad price in response") + assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") + + if bid.Price > 0 { + assertEqual(t, bid.Adm, ExpectedAdM, "Bad ad markup in response") + assertEqual(t, bid.NURL, ExpectedNURL, "Bad notification url in response") + assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") + assertEqual(t, bid.Width, *imps[i].Banner.W, "Bad width in response") + assertEqual(t, bid.Height, *imps[i].Banner.H, "Bad height in response") + } + } +} + +// Test video request + +func TestConversantBasicVideoRequest(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "tag_id": "bottom left", + "position": 3, + "bidfloor": 1.01 }` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "bottom left", "Request tag id") + assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/mp4", "Requst video MIMEs type") + assertTrue(t, imp.Video.Protocols == nil, "Request video protocols") + assertEqual(t, imp.Video.MaxDuration, int64(0), "Request video 0 max duration") + assertTrue(t, imp.Video.API == nil, "Request video api should be nil") +} + +// Test video request with parameters in custom params object + +func TestConversantVideoRequestWithParams(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "tag_id": "bottom left", + "position": 3, + "bidfloor": 1.01, + "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "api": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") + assertEqual(t, imp.TagID, "bottom left", "Request tag id") + assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") + assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") + assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") + assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") + assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") + assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") + assertEqual(t, imp.Video.API[0], openrtb.APIFramework(1), "Request video api 1") + assertEqual(t, imp.Video.API[1], openrtb.APIFramework(2), "Request video api 2") +} + +// Test video request with parameters in the video object + +func TestConversantVideoRequestWithParams2(t *testing.T) { + server, lastReq := CreateServer() + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345" }` + videoParam := `{ "mimes": ["video/x-ms-wmv"], + "protocols": [1, 2], + "maxduration": 90 }` + + ctx := context.TODO() + pbReq := CreateRequest(param) + pbReq, err := ConvertToVideoRequest(pbReq, videoParam) + if err != nil { + t.Fatal("Failed to convert to a video request", err) + } + pbReq, err = ParseRequest(pbReq) + if err != nil { + t.Fatal("Failed to parse video request", err) + } + + _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") + imp := &lastReq.Imp[0] + + assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") + assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") + assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") + assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") + assertTrue(t, imp.Banner == nil, "Request banner should be nil") + assertEqual(t, int(*imp.Secure), 0, "Request secure") + assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") + assertEqual(t, int(imp.Video.W), 300, "Request width") + assertEqual(t, int(imp.Video.H), 250, "Request height") + + assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") + assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") + assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") + assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") + assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") + assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") +} + +// Test video responses + +func TestConversantVideoResponse(t *testing.T) { + prices := []float64{0.01, 0.0, 2.01} + server, lastReq := CreateServer(prices...) + if server == nil { + t.Fatal("server not created") + } + + defer server.Close() + + // Create a adapter to test + + conf := *adapters.DefaultHTTPAdapterConfig + an := NewConversantAdapter(&conf, server.URL) + + param := `{ "site_id": "12345", + "secure": 1, + "tag_id": "top", + "position": 2, + "bidfloor": 1.01, + "mobile" : 1}` + + ctx := context.TODO() + pbReq, err := CreateVideoRequest(param, param, param) + if err != nil { + t.Fatal("Failed to create a video request", err) + } + + resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) + if err != nil { + t.Fatal("Failed to retrieve bids", err) + } + + prices, imps := FilterZeroPrices(prices, lastReq.Imp) + + assertEqual(t, len(resp), len(prices), "Bad number of responses") + + for i, bid := range resp { + assertEqual(t, bid.Price, prices[i], "Bad price in response") + assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") + + if bid.Price > 0 { + assertEqual(t, bid.Adm, "", "Bad ad markup in response") + assertEqual(t, bid.NURL, ExpectedAdM, "Bad notification url in response") + assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") + assertEqual(t, bid.Width, imps[i].Video.W, "Bad width in response") + assertEqual(t, bid.Height, imps[i].Video.H, "Bad height in response") + } + } +} + +// Helpers to create a banner and video requests + +func CreateRequest(params ...string) *pbs.PBSRequest { + num := len(params) + + req := pbs.PBSRequest{ + Tid: "t-000", + AccountID: "1", + AdUnits: make([]pbs.AdUnit, num), + } + + for i := 0; i < num; i++ { + req.AdUnits[i] = pbs.AdUnit{ + Code: fmt.Sprintf("au-%03d", i), + Sizes: []openrtb.Format{ + { + W: 300, + H: 250, + }, + }, + Bids: []pbs.Bids{ + { + BidderCode: "conversant", + BidID: fmt.Sprintf("b-%03d", i), + Params: json.RawMessage(params[i]), + }, + }, + } + } + + return &req +} + +// Convert a request to a video request by adding required properties + +func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBSRequest, error) { + for i := 0; i < len(req.AdUnits); i++ { + video := pbs.PBSVideo{} + if i < len(videoParams) { + err := json.Unmarshal([]byte(videoParams[i]), &video) + if err != nil { + return nil, err + } + } + + if video.Mimes == nil { + video.Mimes = []string{"video/mp4"} + } + + req.AdUnits[i].Video = video + req.AdUnits[i].MediaTypes = []string{"video"} + } + + return req, nil +} + +// Convert a request to an app request by adding required properties +func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { + app := new(openrtb.App) + err := json.Unmarshal([]byte(appParams), &app) + if err == nil { + req.App = app + } + + return req, nil +} + +// Feed the request thru the prebid parser so user id and +// other private properties are defined + +func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { + body := new(bytes.Buffer) + _ = json.NewEncoder(body).Encode(req) + + // Need to pass the conversant user id thru uid cookie + + httpReq := httptest.NewRequest("POST", "/foo", body) + cookie := usersync.NewPBSCookie() + _ = cookie.TrySync("conversant", ExpectedBuyerUID) + httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) + httpReq.Header.Add("Referer", "http://example.com") + cache, _ := dummycache.New() + hcc := config.HostCookie{} + + parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ + Default: 2000, + Max: 2000, + }, cache, &hcc) + + return parsedReq, err +} + +// A helper to create a banner request + +func CreateBannerRequest(params ...string) (*pbs.PBSRequest, error) { + req := CreateRequest(params...) + req, err := ParseRequest(req) + return req, err +} + +// A helper to create a video request + +func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { + req := CreateRequest(params...) + req, err := ConvertToVideoRequest(req) + if err != nil { + return nil, err + } + req, err = ParseRequest(req) + return req, err +} + +// Helper to create a test http server that receives and generate openrtb requests and responses + +func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { + var lastBidRequest openrtb.BidRequest + server := httptest.NewServer(http.HandlerFunc(func(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 bidReq openrtb.BidRequest + var price float64 + var bids []openrtb.Bid + var bid openrtb.Bid + + err = json.Unmarshal(body, &bidReq) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + lastBidRequest = bidReq + + for i, imp := range bidReq.Imp { + if i < len(prices) { + price = prices[i] + } else { + price = 0 + } + + if price > 0 { + bid = openrtb.Bid{ + ID: imp.ID, + ImpID: imp.ID, + Price: price, + NURL: ExpectedNURL, + AdM: ExpectedAdM, + CrID: ExpectedCrID, + } + + if imp.Banner != nil { + bid.W = *imp.Banner.W + bid.H = *imp.Banner.H + } else if imp.Video != nil { + bid.W = imp.Video.W + bid.H = imp.Video.H + } + } else { + bid = openrtb.Bid{ + ID: imp.ID, + ImpID: imp.ID, + Price: 0, + } + } + + bids = append(bids, bid) + } + + if len(bids) == 0 { + w.WriteHeader(http.StatusNoContent) + } else { + js, _ := json.Marshal(openrtb.BidResponse{ + ID: bidReq.ID, + SeatBid: []openrtb.SeatBid{ + { + Bid: bids, + }, + }, + }) + + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(js) + } + }), + ) + + return server, &lastBidRequest +} + +// Helper to remove impressions with $0 bids + +func FilterZeroPrices(prices []float64, imps []openrtb.Imp) ([]float64, []openrtb.Imp) { + prices2 := make([]float64, 0) + imps2 := make([]openrtb.Imp, 0) + + for i := range prices { + if prices[i] > 0 { + prices2 = append(prices2, prices[i]) + imps2 = append(imps2, imps[i]) + } + } + + return prices2, imps2 +} + +// Helpers to test equality + +func assertEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { + if expected != actual { + msg = fmt.Sprintf("%s: act(%v) != exp(%v)", msg, actual, expected) + t.Fatal(msg) + } +} + +func assertNotEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { + if expected == actual { + msg = fmt.Sprintf("%s: act(%v) == exp(%v)", msg, actual, expected) + t.Fatal(msg) + } +} + +func assertTrue(t *testing.T, val bool, msg string) { + if val == false { + msg = fmt.Sprintf("%s: is false but should be true", msg) + t.Fatal(msg) + } +} + +func assertFalse(t *testing.T, val bool, msg string) { + if val == true { + msg = fmt.Sprintf("%s: is true but should be false", msg) + t.Fatal(msg) + } +} diff --git a/adapters/conversant/conversant.go b/adapters/conversant/conversant.go index b248e2e9dc1..64ddc38bf1a 100644 --- a/adapters/conversant/conversant.go +++ b/adapters/conversant/conversant.go @@ -1,291 +1,176 @@ package conversant import ( - "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" - "net/http" - "github.com/mxmCherry/openrtb" "github.com/prebid/prebid-server/adapters" "github.com/prebid/prebid-server/errortypes" - "github.com/prebid/prebid-server/pbs" - "golang.org/x/net/context/ctxhttp" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" ) type ConversantAdapter struct { - http *adapters.HTTPAdapter - URI string -} - -// Corresponds to the bidder name in cookies and requests -func (a *ConversantAdapter) Name() string { - return "conversant" + URI string } -// Return true so no request will be sent unless user has been sync'ed. -func (a *ConversantAdapter) SkipNoCookies() bool { - return true -} - -type conversantParams struct { - SiteID string `json:"site_id"` - Secure *int8 `json:"secure"` - TagID string `json:"tag_id"` - Position *int8 `json:"position"` - BidFloor float64 `json:"bidfloor"` - Mobile *int8 `json:"mobile"` - MIMEs []string `json:"mimes"` - API []int8 `json:"api"` - Protocols []int8 `json:"protocols"` - MaxDuration *int64 `json:"maxduration"` -} - -func (a *ConversantAdapter) Call(ctx context.Context, req *pbs.PBSRequest, bidder *pbs.PBSBidder) (pbs.PBSBidSlice, error) { - mediaTypes := []pbs.MediaType{pbs.MEDIA_TYPE_BANNER, pbs.MEDIA_TYPE_VIDEO} - cnvrReq, err := adapters.MakeOpenRTBGeneric(req, bidder, a.Name(), mediaTypes) - - if err != nil { - return nil, err - } - - // Create a map of impression objects for both request creation - // and response parsing. - - impMap := make(map[string]*openrtb.Imp, len(cnvrReq.Imp)) - for idx := range cnvrReq.Imp { - impMap[cnvrReq.Imp[idx].ID] = &cnvrReq.Imp[idx] - } - - // Fill in additional info from custom params - - for _, unit := range bidder.AdUnits { - var params conversantParams - - imp := impMap[unit.Code] - if imp == nil { - // Skip ad units that do not have corresponding impressions. - continue - } - - err := json.Unmarshal(unit.Params, ¶ms) - if err != nil { - return nil, &errortypes.BadInput{ - Message: err.Error(), - } +func (c ConversantAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + for i := 0; i < len(request.Imp); i++ { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(request.Imp[i].Ext, &bidderExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Impression[%d] missing ext object", i), + }} } - // Fill in additional Site info - if params.SiteID != "" { - if cnvrReq.Site != nil { - cnvrReq.Site.ID = params.SiteID - } - if cnvrReq.App != nil { - cnvrReq.App.ID = params.SiteID - } + var cnvrExt openrtb_ext.ExtImpConversant + if err := json.Unmarshal(bidderExt.Bidder, &cnvrExt); err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Impression[%d] missing ext.bidder object", i), + }} } - if params.Mobile != nil && !(cnvrReq.Site == nil) { - cnvrReq.Site.Mobile = *params.Mobile + if cnvrExt.SiteID == "" { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Impression[%d] requires ext.bidder.site_id", i), + }} } - // Fill in additional impression info - - imp.DisplayManager = "prebid-s2s" - imp.DisplayManagerVer = "1.0.1" - imp.BidFloor = params.BidFloor - imp.TagID = params.TagID - - var position *openrtb.AdPosition - if params.Position != nil { - position = openrtb.AdPosition(*params.Position).Ptr() + if i == 0 { + if request.Site != nil { + tmpSite := *request.Site + request.Site = &tmpSite + request.Site.ID = cnvrExt.SiteID + } else if request.App != nil { + tmpApp := *request.App + request.App = &tmpApp + request.App.ID = cnvrExt.SiteID + } } + parseCnvrParams(&request.Imp[i], cnvrExt) + } - if imp.Banner != nil { - imp.Banner.Pos = position - } else if imp.Video != nil { - imp.Video.Pos = position + //create the request body + data, err := json.Marshal(request) + if err != nil { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Error in packaging request to JSON"), + }} + } + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + + return []*adapters.RequestData{{ + Method: "POST", + Uri: c.URI, + Body: data, + Headers: headers, + }}, nil +} - if len(params.API) > 0 { - imp.Video.API = make([]openrtb.APIFramework, 0, len(params.API)) - for _, api := range params.API { - imp.Video.API = append(imp.Video.API, openrtb.APIFramework(api)) - } - } +func parseCnvrParams(imp *openrtb.Imp, cnvrExt openrtb_ext.ExtImpConversant) { + imp.DisplayManager = "prebid-s2s" + imp.DisplayManagerVer = "2.0.0" + imp.BidFloor = cnvrExt.BidFloor + imp.TagID = cnvrExt.TagID - // Include protocols, mimes, and max duration if specified - // These properties can also be specified in ad unit's video object, - // but are overridden if the custom params object also contains them. + // Take care not to override the global secure flag + if (imp.Secure == nil || *imp.Secure == 0) && cnvrExt.Secure != nil { + imp.Secure = cnvrExt.Secure + } - if len(params.Protocols) > 0 { - imp.Video.Protocols = make([]openrtb.Protocol, 0, len(params.Protocols)) - for _, protocol := range params.Protocols { - imp.Video.Protocols = append(imp.Video.Protocols, openrtb.Protocol(protocol)) - } - } + var position *openrtb.AdPosition + if cnvrExt.Position != nil { + position = openrtb.AdPosition(*cnvrExt.Position).Ptr() + } + if imp.Banner != nil { + tmpBanner := *imp.Banner + imp.Banner = &tmpBanner + imp.Banner.Pos = position - if len(params.MIMEs) > 0 { - imp.Video.MIMEs = make([]string, len(params.MIMEs)) - copy(imp.Video.MIMEs, params.MIMEs) - } + } else if imp.Video != nil { + tmpVideo := *imp.Video + imp.Video = &tmpVideo + imp.Video.Pos = position - if params.MaxDuration != nil { - imp.Video.MaxDuration = *params.MaxDuration + if len(cnvrExt.API) > 0 { + imp.Video.API = make([]openrtb.APIFramework, 0, len(cnvrExt.API)) + for _, api := range cnvrExt.API { + imp.Video.API = append(imp.Video.API, openrtb.APIFramework(api)) } } - // Take care not to override the global secure flag + // Include protocols, mimes, and max duration if specified + // These properties can also be specified in ad unit's video object, + // but are overridden if the custom params object also contains them. - if (imp.Secure == nil || *imp.Secure == 0) && params.Secure != nil { - imp.Secure = params.Secure + if len(cnvrExt.Protocols) > 0 { + imp.Video.Protocols = make([]openrtb.Protocol, 0, len(cnvrExt.Protocols)) + for _, protocol := range cnvrExt.Protocols { + imp.Video.Protocols = append(imp.Video.Protocols, openrtb.Protocol(protocol)) + } } - } - - // Do a quick check on required parameters - if cnvrReq.Site != nil && cnvrReq.Site.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing site id", + if len(cnvrExt.MIMEs) > 0 { + imp.Video.MIMEs = make([]string, len(cnvrExt.MIMEs)) + copy(imp.Video.MIMEs, cnvrExt.MIMEs) } - } - if cnvrReq.App != nil && cnvrReq.App.ID == "" { - return nil, &errortypes.BadInput{ - Message: "Missing app id", + if cnvrExt.MaxDuration != nil { + imp.Video.MaxDuration = *cnvrExt.MaxDuration } } +} - // Start capturing debug info - - debug := &pbs.BidderDebug{ - RequestURI: a.URI, - } - - if cnvrReq.Device == nil { - cnvrReq.Device = &openrtb.Device{} - } - - // Convert request to json to be sent over http - - j, _ := json.Marshal(cnvrReq) - - if req.IsDebug { - debug.RequestBody = string(j) - bidder.Debug = append(bidder.Debug, debug) - } - - httpReq, err := http.NewRequest("POST", a.URI, bytes.NewBuffer(j)) - httpReq.Header.Add("Content-Type", "application/json") - httpReq.Header.Add("Accept", "application/json") - - resp, err := ctxhttp.Do(ctx, a.http.Client, httpReq) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - - if req.IsDebug { - debug.StatusCode = resp.StatusCode - } - - if resp.StatusCode == 204 { - return nil, nil - } - - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - return nil, err +func (c ConversantAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil // no bid response } - if resp.StatusCode == http.StatusBadRequest { - return nil, &errortypes.BadInput{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Unexpected status code: %d", response.StatusCode), + }} } - if resp.StatusCode != 200 { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("HTTP status: %d, body: %s", resp.StatusCode, string(body)), - } + var resp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &resp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("bad server response: %d. ", err), + }} } - if req.IsDebug { - debug.ResponseBody = string(body) + if len(resp.SeatBid) == 0 { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Empty bid request"), + }} } - var bidResp openrtb.BidResponse - - err = json.Unmarshal(body, &bidResp) - if err != nil { - return nil, &errortypes.BadServerResponse{ - Message: err.Error(), - } + bids := resp.SeatBid[0].Bid + bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bids)) + for i := 0; i < len(bids); i++ { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &bids[i], + BidType: getBidType(bids[i].ImpID, internalRequest.Imp), + }) } + return bidResponse, nil +} - bids := make(pbs.PBSBidSlice, 0) - - for _, seatbid := range bidResp.SeatBid { - for _, bid := range seatbid.Bid { - if bid.Price <= 0 { - continue - } - - imp := impMap[bid.ImpID] - if imp == nil { - // All returned bids should have a matching impression - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown impression id '%s'", bid.ImpID), - } - } - - bidID := bidder.LookupBidID(bid.ImpID) - if bidID == "" { - return nil, &errortypes.BadServerResponse{ - Message: fmt.Sprintf("Unknown ad unit code '%s'", bid.ImpID), - } - } - - pbsBid := pbs.PBSBid{ - BidID: bidID, - AdUnitCode: bid.ImpID, - Price: bid.Price, - Creative_id: bid.CrID, - BidderCode: bidder.BidderCode, - } - +func getBidType(impId string, imps []openrtb.Imp) openrtb_ext.BidType { + bidType := openrtb_ext.BidTypeBanner + for _, imp := range imps { + if imp.ID == impId { if imp.Video != nil { - pbsBid.CreativeMediaType = "video" - pbsBid.NURL = bid.AdM // Assign to NURL so it'll be interpreted as a vastUrl - pbsBid.Width = imp.Video.W - pbsBid.Height = imp.Video.H - } else { - pbsBid.CreativeMediaType = "banner" - pbsBid.NURL = bid.NURL - pbsBid.Adm = bid.AdM - pbsBid.Width = bid.W - pbsBid.Height = bid.H + bidType = openrtb_ext.BidTypeVideo } - - bids = append(bids, &pbsBid) + break } } - - if len(bids) == 0 { - return nil, nil - } - - return bids, nil + return bidType } -func NewConversantAdapter(config *adapters.HTTPAdapterConfig, uri string) *ConversantAdapter { - a := adapters.NewHTTPAdapter(config) - - return &ConversantAdapter{ - http: a, - URI: uri, - } +func NewConversantBidder(endpoint string) *ConversantAdapter { + return &ConversantAdapter{URI: endpoint} } diff --git a/adapters/conversant/conversant_test.go b/adapters/conversant/conversant_test.go index b958e320dc7..e3275030e83 100644 --- a/adapters/conversant/conversant_test.go +++ b/adapters/conversant/conversant_test.go @@ -1,853 +1,10 @@ package conversant import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" + "github.com/prebid/prebid-server/adapters/adapterstest" "testing" - "time" - - "github.com/mxmCherry/openrtb" - "github.com/prebid/prebid-server/adapters" - "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" ) -// Constants - -const ExpectedSiteID string = "12345" -const ExpectedDisplayManager string = "prebid-s2s" -const ExpectedBuyerUID string = "AQECT_o7M1FLbQJK8QFmAQEBAQE" -const ExpectedNURL string = "http://test.dotomi.com" -const ExpectedAdM string = "" -const ExpectedCrID string = "98765" - -const DefaultParam = `{"site_id": "12345"}` - -// Test properties of Adapter interface - -func TestConversantProperties(t *testing.T) { - an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - assertNotEqual(t, an.Name(), "", "Missing family name") - assertTrue(t, an.SkipNoCookies(), "SkipNoCookies should be true") -} - -// Test empty bid requests - -func TestConversantEmptyBid(t *testing.T) { - an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, "someUrl") - - ctx := context.TODO() - pbReq := pbs.PBSRequest{} - pbBidder := pbs.PBSBidder{} - _, err := an.Call(ctx, &pbReq, &pbBidder) - assertTrue(t, err != nil, "No error received for an invalid request") -} - -// Test required parameters, which is just the site id for now - -func TestConversantRequiredParameters(t *testing.T) { - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - an := NewConversantAdapter(adapters.DefaultHTTPAdapterConfig, server.URL) - ctx := context.TODO() - - testParams := func(params ...string) (pbs.PBSBidSlice, error) { - req, err := CreateBannerRequest(params...) - if err != nil { - return nil, err - } - return an.Call(ctx, req, req.Bidders[0]) - } - - var err error - - if _, err = testParams(`{}`); err == nil { - t.Fatal("Failed to catch missing site id") - } -} - -// Test handling of 404 - -func TestConversantBadStatus(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - assertTrue(t, err != nil, "Failed to catch 404 error") -} - -// Test handling of HTTP timeout - -func TestConversantTimeout(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - <-time.After(2 * time.Millisecond) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - // Create a context that expires before http returns - - ctx, cancel := context.WithTimeout(context.Background(), 0) - defer cancel() - - // Create a basic request - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - // Attempt to process the request, which should hit a timeout - // immediately - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err == nil || err != context.DeadlineExceeded { - t.Fatal("No timeout recevied for timed out request", err) - } -} - -// Test handling of 204 - -func TestConversantNoBid(t *testing.T) { - // Create a test http server that returns after 2 milliseconds - - server := httptest.NewServer( - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, http.StatusText(http.StatusNoContent), http.StatusNoContent) - }), - ) - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if resp != nil || err != nil { - t.Fatal("Failed to handle empty bid", err) - } -} - -// Verify an outgoing openrtp request is created correctly - -func TestConversantRequestDefault(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(DefaultParam) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, imp.TagID, "", "Request tag id") - assertTrue(t, imp.Banner.Pos == nil, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify inapp video request -func TestConversantInappVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - requestParam := `{"secure": 1, "site_id": "12345"}` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(requestParam) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - assertEqual(t, lastReq.App.ID, "12345", "App Id") -} - -// Verify inapp video request -func TestConversantInappBannerRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "secure": 1, - "site_id": "12345", - "tag_id": "top", - "position": 2, - "bidfloor": 1.01 }` - appParam := `{ "bundle": "com.naver.linewebtoon" }` - - ctx := context.TODO() - pbReq, _ := CreateBannerRequest(param) - pbReq, err := ConvertToAppRequest(pbReq, appParam) - if err != nil { - t.Fatal("failed to parse request") - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - - imp := &lastReq.Imp[0] - assertEqual(t, lastReq.App.ID, "12345", "App Id") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify an outgoing openrtp request with additional conversant parameters is -// processed correctly - -func TestConversantRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile": 1 }` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 1, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Video == nil, "Request video should be nil") - assertEqual(t, int(*imp.Secure), 1, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "top", "Request tag id") - assertEqual(t, int(*imp.Banner.Pos), 2, "Request pos") - assertEqual(t, int(*imp.Banner.W), 300, "Request width") - assertEqual(t, int(*imp.Banner.H), 250, "Request height") -} - -// Verify openrtp responses are converted correctly - -func TestConversantResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateBannerRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a banner request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, ExpectedAdM, "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedNURL, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, *imps[i].Banner.W, "Bad width in response") - assertEqual(t, bid.Height, *imps[i].Banner.H, "Bad height in response") - } - } -} - -// Test video request - -func TestConversantBasicVideoRequest(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/mp4", "Requst video MIMEs type") - assertTrue(t, imp.Video.Protocols == nil, "Request video protocols") - assertEqual(t, imp.Video.MaxDuration, int64(0), "Request video 0 max duration") - assertTrue(t, imp.Video.API == nil, "Request video api should be nil") -} - -// Test video request with parameters in custom params object - -func TestConversantVideoRequestWithParams(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "tag_id": "bottom left", - "position": 3, - "bidfloor": 1.01, - "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "api": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 1.01, "Request bid floor") - assertEqual(t, imp.TagID, "bottom left", "Request tag id") - assertEqual(t, int(*imp.Video.Pos), 3, "Request pos") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") - assertEqual(t, len(imp.Video.API), 2, "Request video api should be nil") - assertEqual(t, imp.Video.API[0], openrtb.APIFramework(1), "Request video api 1") - assertEqual(t, imp.Video.API[1], openrtb.APIFramework(2), "Request video api 2") -} - -// Test video request with parameters in the video object - -func TestConversantVideoRequestWithParams2(t *testing.T) { - server, lastReq := CreateServer() - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345" }` - videoParam := `{ "mimes": ["video/x-ms-wmv"], - "protocols": [1, 2], - "maxduration": 90 }` - - ctx := context.TODO() - pbReq := CreateRequest(param) - pbReq, err := ConvertToVideoRequest(pbReq, videoParam) - if err != nil { - t.Fatal("Failed to convert to a video request", err) - } - pbReq, err = ParseRequest(pbReq) - if err != nil { - t.Fatal("Failed to parse video request", err) - } - - _, err = an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - assertEqual(t, len(lastReq.Imp), 1, "Request number of impressions") - imp := &lastReq.Imp[0] - - assertEqual(t, imp.DisplayManager, ExpectedDisplayManager, "Request display manager value") - assertEqual(t, lastReq.Site.ID, ExpectedSiteID, "Request site id") - assertEqual(t, int(lastReq.Site.Mobile), 0, "Request site mobile flag") - assertEqual(t, lastReq.User.BuyerUID, ExpectedBuyerUID, "Request buyeruid") - assertTrue(t, imp.Banner == nil, "Request banner should be nil") - assertEqual(t, int(*imp.Secure), 0, "Request secure") - assertEqual(t, imp.BidFloor, 0.0, "Request bid floor") - assertEqual(t, int(imp.Video.W), 300, "Request width") - assertEqual(t, int(imp.Video.H), 250, "Request height") - - assertEqual(t, len(imp.Video.MIMEs), 1, "Request video MIMEs entries") - assertEqual(t, imp.Video.MIMEs[0], "video/x-ms-wmv", "Requst video MIMEs type") - assertEqual(t, len(imp.Video.Protocols), 2, "Request video protocols") - assertEqual(t, imp.Video.Protocols[0], openrtb.Protocol(1), "Request video protocols 1") - assertEqual(t, imp.Video.Protocols[1], openrtb.Protocol(2), "Request video protocols 2") - assertEqual(t, imp.Video.MaxDuration, int64(90), "Request video 0 max duration") -} - -// Test video responses - -func TestConversantVideoResponse(t *testing.T) { - prices := []float64{0.01, 0.0, 2.01} - server, lastReq := CreateServer(prices...) - if server == nil { - t.Fatal("server not created") - } - - defer server.Close() - - // Create a adapter to test - - conf := *adapters.DefaultHTTPAdapterConfig - an := NewConversantAdapter(&conf, server.URL) - - param := `{ "site_id": "12345", - "secure": 1, - "tag_id": "top", - "position": 2, - "bidfloor": 1.01, - "mobile" : 1}` - - ctx := context.TODO() - pbReq, err := CreateVideoRequest(param, param, param) - if err != nil { - t.Fatal("Failed to create a video request", err) - } - - resp, err := an.Call(ctx, pbReq, pbReq.Bidders[0]) - if err != nil { - t.Fatal("Failed to retrieve bids", err) - } - - prices, imps := FilterZeroPrices(prices, lastReq.Imp) - - assertEqual(t, len(resp), len(prices), "Bad number of responses") - - for i, bid := range resp { - assertEqual(t, bid.Price, prices[i], "Bad price in response") - assertEqual(t, bid.AdUnitCode, imps[i].ID, "Bad bid id in response") - - if bid.Price > 0 { - assertEqual(t, bid.Adm, "", "Bad ad markup in response") - assertEqual(t, bid.NURL, ExpectedAdM, "Bad notification url in response") - assertEqual(t, bid.Creative_id, ExpectedCrID, "Bad creative id in response") - assertEqual(t, bid.Width, imps[i].Video.W, "Bad width in response") - assertEqual(t, bid.Height, imps[i].Video.H, "Bad height in response") - } - } -} - -// Helpers to create a banner and video requests - -func CreateRequest(params ...string) *pbs.PBSRequest { - num := len(params) - - req := pbs.PBSRequest{ - Tid: "t-000", - AccountID: "1", - AdUnits: make([]pbs.AdUnit, num), - } - - for i := 0; i < num; i++ { - req.AdUnits[i] = pbs.AdUnit{ - Code: fmt.Sprintf("au-%03d", i), - Sizes: []openrtb.Format{ - { - W: 300, - H: 250, - }, - }, - Bids: []pbs.Bids{ - { - BidderCode: "conversant", - BidID: fmt.Sprintf("b-%03d", i), - Params: json.RawMessage(params[i]), - }, - }, - } - } - - return &req -} - -// Convert a request to a video request by adding required properties - -func ConvertToVideoRequest(req *pbs.PBSRequest, videoParams ...string) (*pbs.PBSRequest, error) { - for i := 0; i < len(req.AdUnits); i++ { - video := pbs.PBSVideo{} - if i < len(videoParams) { - err := json.Unmarshal([]byte(videoParams[i]), &video) - if err != nil { - return nil, err - } - } - - if video.Mimes == nil { - video.Mimes = []string{"video/mp4"} - } - - req.AdUnits[i].Video = video - req.AdUnits[i].MediaTypes = []string{"video"} - } - - return req, nil -} - -// Convert a request to an app request by adding required properties -func ConvertToAppRequest(req *pbs.PBSRequest, appParams string) (*pbs.PBSRequest, error) { - app := new(openrtb.App) - err := json.Unmarshal([]byte(appParams), &app) - if err == nil { - req.App = app - } - - return req, nil -} - -// Feed the request thru the prebid parser so user id and -// other private properties are defined - -func ParseRequest(req *pbs.PBSRequest) (*pbs.PBSRequest, error) { - body := new(bytes.Buffer) - _ = json.NewEncoder(body).Encode(req) - - // Need to pass the conversant user id thru uid cookie - - httpReq := httptest.NewRequest("POST", "/foo", body) - cookie := usersync.NewPBSCookie() - _ = cookie.TrySync("conversant", ExpectedBuyerUID) - httpReq.Header.Set("Cookie", cookie.ToHTTPCookie(90*24*time.Hour).String()) - httpReq.Header.Add("Referer", "http://example.com") - cache, _ := dummycache.New() - hcc := config.HostCookie{} - - parsedReq, err := pbs.ParsePBSRequest(httpReq, &config.AuctionTimeouts{ - Default: 2000, - Max: 2000, - }, cache, &hcc) - - return parsedReq, err -} - -// A helper to create a banner request - -func CreateBannerRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ParseRequest(req) - return req, err -} - -// A helper to create a video request - -func CreateVideoRequest(params ...string) (*pbs.PBSRequest, error) { - req := CreateRequest(params...) - req, err := ConvertToVideoRequest(req) - if err != nil { - return nil, err - } - req, err = ParseRequest(req) - return req, err -} - -// Helper to create a test http server that receives and generate openrtb requests and responses - -func CreateServer(prices ...float64) (*httptest.Server, *openrtb.BidRequest) { - var lastBidRequest openrtb.BidRequest - server := httptest.NewServer(http.HandlerFunc(func(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 bidReq openrtb.BidRequest - var price float64 - var bids []openrtb.Bid - var bid openrtb.Bid - - err = json.Unmarshal(body, &bidReq) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - - lastBidRequest = bidReq - - for i, imp := range bidReq.Imp { - if i < len(prices) { - price = prices[i] - } else { - price = 0 - } - - if price > 0 { - bid = openrtb.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: price, - NURL: ExpectedNURL, - AdM: ExpectedAdM, - CrID: ExpectedCrID, - } - - if imp.Banner != nil { - bid.W = *imp.Banner.W - bid.H = *imp.Banner.H - } else if imp.Video != nil { - bid.W = imp.Video.W - bid.H = imp.Video.H - } - } else { - bid = openrtb.Bid{ - ID: imp.ID, - ImpID: imp.ID, - Price: 0, - } - } - - bids = append(bids, bid) - } - - if len(bids) == 0 { - w.WriteHeader(http.StatusNoContent) - } else { - js, _ := json.Marshal(openrtb.BidResponse{ - ID: bidReq.ID, - SeatBid: []openrtb.SeatBid{ - { - Bid: bids, - }, - }, - }) - - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write(js) - } - }), - ) - - return server, &lastBidRequest -} - -// Helper to remove impressions with $0 bids - -func FilterZeroPrices(prices []float64, imps []openrtb.Imp) ([]float64, []openrtb.Imp) { - prices2 := make([]float64, 0) - imps2 := make([]openrtb.Imp, 0) - - for i := range prices { - if prices[i] > 0 { - prices2 = append(prices2, prices[i]) - imps2 = append(imps2, imps[i]) - } - } - - return prices2, imps2 -} - -// Helpers to test equality - -func assertEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected != actual { - msg = fmt.Sprintf("%s: act(%v) != exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertNotEqual(t *testing.T, actual interface{}, expected interface{}, msg string) { - if expected == actual { - msg = fmt.Sprintf("%s: act(%v) == exp(%v)", msg, actual, expected) - t.Fatal(msg) - } -} - -func assertTrue(t *testing.T, val bool, msg string) { - if val == false { - msg = fmt.Sprintf("%s: is false but should be true", msg) - t.Fatal(msg) - } -} - -func assertFalse(t *testing.T, val bool, msg string) { - if val == true { - msg = fmt.Sprintf("%s: is true but should be false", msg) - t.Fatal(msg) - } +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "conversanttest", NewConversantBidder("")) } diff --git a/adapters/conversant/conversanttest/exemplary/banner.json b/adapters/conversant/conversanttest/exemplary/banner.json new file mode 100644 index 00000000000..472e18f712d --- /dev/null +++ b/adapters/conversant/conversanttest/exemplary/banner.json @@ -0,0 +1,113 @@ +{ + "mockBidRequest": { + "id": "testauction", + "imp": [ + { + "id": "1", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "site_id": "108060", + "bidfloor": 0.01, + "tag_id": "mytag", + "secure": 1 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "192.168.1.1", + "dnt": 1 + }, + "site": { + "domain": "www.mypage.com" + } + }, + "httpCalls": [{ + "expectedRequest": { + "uri": "", + "body": { + "id": "testauction", + "site": { + "id": "108060", + "domain": "www.mypage.com" + }, + "imp": [ + { + "id": "1", + "tagid": "mytag", + "secure": 1, + "bidfloor": 0.01, + "displaymanager": "prebid-s2s", + "displaymanagerver": "2.0.0", + "banner": { + "format": [{"w": 300, "h": 250}] + }, + "ext": { + "bidder": { + "site_id": "108060", + "bidfloor": 0.01, + "tag_id": "mytag", + "secure": 1 + } + } + } + ], + "device": { + "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36", + "ip": "192.168.1.1", + "dnt": 1 + } + } + }, + "mockResponse": { + "status": 200, + "body": { + "id": "testauction", + "bidid": "c8d95f4b-bcbb-4a6c-adbb-4c7f33af3c24", + "cur": "USD", + "seatbid": [ + { + "bid": [ + { + "id": "1", + "impid": "1", + "price": 0.0340, + "nurl": "https:\/\/event.ad.cpe.dotomi.com\/cvx\/event\/imp?enc=eyJ1c2VyaWQiOiI3MTI3MDUzNzM3NTM3MTAzMjIiLCJwYXJ0bmVyVHhpZCI6ImUyZWUzNjZlLWEyMjgtNDI0Mi1hNjJlLTk4ODk3ODhiYzgxNCIsInR4aWQiOiI3MTE1NzQwNDg3NTczODUwMDIiLCJuZXR3b3JrUmVxdWVzdElkIjoiNzExNTc0MDQ4NzU3Mzg1ODc0Iiwic2lkIjoxMTgwOTgsImRpdmlzaW9uSWQiOjgsInRpZCI6OCwibW9iaWxlRGF0YSI6IjU5IiwiYmlkUHJpY2UiOjAuMDY4MCwicHViQ29zdCI6MC4wMzQwLCJwYXJ0bmVyRmVlIjowLjAxMzYsImlwU3RyaW5nIjoiNzMuMTE4LjEzMC4xODYiLCJzdXBwbHlUeXBlIjoxLCJpbnRlZ3JhdGlvblR5cGUiOjQsIm1lZGlhdGlvblR5cGUiOjEyNiwicGxhY2VtZW50SWQiOiIxMTY5ODcwIiwiaGVhZGVyQmlkIjoxLCJpc0RpcmVjdFB1Ymxpc2hlciI6MCwiaGFzQ29uc2VudCI6MSwib3BlcmF0aW9uIjoiQ0xJRU5UX0hFQURFUl8yNSIsImlzQ29yZVNoaWVsZCI6MCwicGFydG5lckNyZWF0aXZlSWQiOiIyNDk2NDRfMzAweDI1MCIsInBhcnRuZXJEb21haW5zIjpbIndhbG1hcnQuY29tIl0sInNlbGxlclJlcXVlc3RJZCI6ImE3ODcyMWQ3LWE2ZmUtNGJiNS1hNjFkLTFhMDg1MzkxZTVlZCIsInNlbGxlckltcElkIjoiMzAwNDIxZDY0NWY2ZjRjOWMifQ&", + "adm": "