Repository: prebid/prebid-server
Pull request #1154 (2019-12-21 03:53:34)

Fix Currency Converter Doesn't Output CUR
This PR fixes the bug explained in issue #1140 

stale[bot]: This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.


Patch:
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 071b4f8d..3d9055ca 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -324,6 +324,7 @@ func (e *exchange) buildBidResponse(ctx context.Context, liveAdapters []openrtb_
 		if adapterBids[a] != nil && len(adapterBids[a].bids) > 0 {
 			sb := e.makeSeatBid(adapterBids[a], a, adapterExtra, auc)
 			seatBids = append(seatBids, *sb)
+			bidResponse.Cur = adapterBids[a].currency
 		}
 	}
 


Test patch:
diff --git a/adapters/adform/adform_test.go b/adapters/adform/adform_test.go
index e59bd951..63646f5f 100644
--- a/adapters/adform/adform_test.go
+++ b/adapters/adform/adform_test.go
@@ -21,6 +21,8 @@ import (
 	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
+
+	"github.com/stretchr/testify/assert"
 )
 
 func TestJsonSamples(t *testing.T) {
@@ -597,3 +599,77 @@ func TestPriceTypeUrlParameterCreation(t *testing.T) {
 		}
 	}
 }
+
+// Asserts that toOpenRtbBidResponse() creates a *adapters.BidderResponse with
+// the currency of the last valid []*adformBid element and the expected number of bids
+func TestToOpenRtbBidResponse(t *testing.T) {
+	expectedBids := 3
+	lastCurrency, anotherCurrency, emptyCurrency := "EUR", "USD", ""
+
+	request := &openrtb.BidRequest{
+		ID: "test-request-id",
+		Imp: []openrtb.Imp{
+			{
+				ID:     "banner-imp-no1",
+				Ext:    json.RawMessage(`{"bidder1": { "mid": "32341" }}`),
+				Banner: &openrtb.Banner{},
+			},
+			{
+				ID:     "banner-imp-no2",
+				Ext:    json.RawMessage(`{"bidder1": { "mid": "32342" }}`),
+				Banner: &openrtb.Banner{},
+			},
+			{
+				ID:     "banner-imp-no3",
+				Ext:    json.RawMessage(`{"bidder1": { "mid": "32343" }}`),
+				Banner: &openrtb.Banner{},
+			},
+			{
+				ID:     "banner-imp-no4",
+				Ext:    json.RawMessage(`{"bidder1": { "mid": "32344" }}`),
+				Banner: &openrtb.Banner{},
+			},
+		},
+		Device: &openrtb.Device{UA: "ua", IP: "ip"},
+		User:   &openrtb.User{BuyerUID: "buyerUID"},
+	}
+
+	testAdformBids := []*adformBid{
+		{
+			ResponseType: "banner",
+			Banner:       "banner-content1",
+			Price:        1.23,
+			Currency:     anotherCurrency,
+			Width:        300,
+			Height:       200,
+			DealId:       "dealId1",
+			CreativeId:   "creativeId1",
+		},
+		{},
+		{
+			ResponseType: "banner",
+			Banner:       "banner-content3",
+			Price:        1.24,
+			Currency:     emptyCurrency,
+			Width:        300,
+			Height:       200,
+			DealId:       "dealId3",
+			CreativeId:   "creativeId3",
+		},
+		{
+			ResponseType: "banner",
+			Banner:       "banner-content4",
+			Price:        1.25,
+			Currency:     lastCurrency,
+			Width:        300,
+			Height:       200,
+			DealId:       "dealId4",
+			CreativeId:   "creativeId4",
+		},
+	}
+
+	actualBidResponse := toOpenRtbBidResponse(testAdformBids, request)
+
+	assert.Equalf(t, expectedBids, len(actualBidResponse.Bids), "bid count")
+	assert.Equalf(t, lastCurrency, actualBidResponse.Currency, "currency")
+}
diff --git a/currencies/rate_converter_test.go b/currencies/rate_converter_test.go
index a1807a63..f9a14895 100644
--- a/currencies/rate_converter_test.go
+++ b/currencies/rate_converter_test.go
@@ -379,6 +379,7 @@ func TestRates(t *testing.T) {
 		{from: "", to: "EUR", expectedRate: 0, hasError: true},
 		{from: "CNY", to: "", expectedRate: 0, hasError: true},
 		{from: "", to: "", expectedRate: 0, hasError: true},
+		{from: "USD", to: "USD", expectedRate: 1, hasError: false},
 	}
 
 	mockedHttpServer := httptest.NewServer(http.HandlerFunc(
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index dc4d7d98..31dddae4 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -314,51 +314,145 @@ func TestGetBidCacheInfo(t *testing.T) {
 	assert.Equal(t, expCacheURL, cacheURL, "[TestGetBidCacheInfo] cacheId field in ext should equal \"%s\" \n", expCacheURL)
 }
 
-func buildBidResponseParams(bidderName openrtb_ext.BidderName, bidRequest *openrtb.BidRequest) ([]openrtb_ext.BidderName, map[openrtb_ext.BidderName]*pbsOrtbSeatBid, json.RawMessage, map[openrtb_ext.BidderName]*seatResponseExtra) {
-	//liveAdapters []openrtb_ext.BidderName,
-	liveAdapters := []openrtb_ext.BidderName{bidderName}
+func TestBidResponseCurrency(t *testing.T) {
+	// Init objects
+	cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)}
+	cfg.Adapters["appnexus"] = config.Adapter{Endpoint: "http://ib.adnxs.com"}
 
-	//adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid,
-	adapterBids := map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
-		bidderName: {
-			bids: []*pbsOrtbBid{
-				{
-					bid: &openrtb.Bid{
-						ID:    "some-imp-id",
-						Price: 9.517803,
-						W:     300,
-						H:     250,
-					},
-					bidType: openrtb_ext.BidTypeBanner,
-					bidTargets: map[string]string{
-						"pricegranularity":  "med",
-						"includewinners":    "true",
-						"includebidderkeys": "false",
-					},
-				},
-			},
-			currency: "USD",
-			//ext:      bidRequest.Ext,
-		},
+	handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
+	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
+	defer server.Close()
+
+	e := NewExchange(server.Client(), nil, cfg, pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList(), config.DisabledMetrics{}), adapters.ParseBidderInfos(cfg.Adapters, "../static/bidder-info", openrtb_ext.BidderList()), gdpr.AlwaysAllow{}, currencies.NewRateConverterDefault()).(*exchange)
+
+	liveAdapters := make([]openrtb_ext.BidderName, 1)
+	liveAdapters[0] = "appnexus"
+
+	bidRequest := &openrtb.BidRequest{
+		ID: "some-request-id",
+		Imp: []openrtb.Imp{{
+			ID:     "some-impression-id",
+			Banner: &openrtb.Banner{Format: []openrtb.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
+			Ext:    json.RawMessage(`{"appnexus": {"placementId": 10433394}}`),
+		}},
+		Site:   &openrtb.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
+		Device: &openrtb.Device{UA: "curl/7.54.0", IP: "::1"},
+		AT:     1,
+		TMax:   500,
+		Ext:    json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 10433394}}}],"tmax": 500}`),
 	}
 
-	//resolvedRequest json.RawMessage
 	resolvedRequest := json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`)
 
-	//adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra,
 	adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{
-		bidderName: {
-			ResponseTimeMillis: 5,
-			Errors: []openrtb_ext.ExtBidderError{
+		"appnexus": {ResponseTimeMillis: 5},
+	}
+
+	var errList []error
+
+	sampleBid := &openrtb.Bid{
+		ID:    "some-imp-id",
+		Price: 9.517803,
+		W:     300,
+		H:     250,
+		Ext:   nil,
+	}
+	aPbsOrtbBidArr := []*pbsOrtbBid{{bid: sampleBid, bidType: openrtb_ext.BidTypeBanner}}
+	sampleSeatBid := []openrtb.SeatBid{
+		{
+			Seat: "appnexus",
+			Bid: []openrtb.Bid{
 				{
-					Code:    999,
-					Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\"",
+					ID:    "some-imp-id",
+					Price: 9.517803,
+					W:     300,
+					H:     250,
+					Ext:   json.RawMessage(`{"prebid":{"type":"banner"}}`),
+				},
+			},
+		},
+	}
+	emptySeatBid := []openrtb.SeatBid{}
+
+	// Test cases
+	type aTest struct {
+		description         string
+		adapterBids         map[openrtb_ext.BidderName]*pbsOrtbSeatBid
+		expectedBidResponse *openrtb.BidResponse
+	}
+	testCases := []aTest{
+		{
+			description: "1) Adapter to bids map comes with a non-empty currency field and non-empty bid array",
+			adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				openrtb_ext.BidderName("appnexus"): {
+					bids:     aPbsOrtbBidArr,
+					currency: "USD",
 				},
 			},
+			expectedBidResponse: &openrtb.BidResponse{
+				ID:      "some-request-id",
+				SeatBid: sampleSeatBid,
+				Cur:     "USD",
+				Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500}
+`),
+			},
+		},
+		{
+			description: "2) Adapter to bids map comes with a non-empty currency field but an empty bid array",
+			adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				openrtb_ext.BidderName("appnexus"): {
+					bids:     nil,
+					currency: "USD",
+				},
+			},
+			expectedBidResponse: &openrtb.BidResponse{
+				ID:      "some-request-id",
+				SeatBid: emptySeatBid,
+				Cur:     "",
+				Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500}
+`),
+			},
+		},
+		{
+			description: "3) Adapter to bids map comes with an empty currency string and a non-empty bid array",
+			adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				openrtb_ext.BidderName("appnexus"): {
+					bids:     aPbsOrtbBidArr,
+					currency: "",
+				},
+			},
+			expectedBidResponse: &openrtb.BidResponse{
+				ID:      "some-request-id",
+				SeatBid: sampleSeatBid,
+				Cur:     "",
+				Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500}
+`),
+			},
+		},
+		{
+			description: "4) Adapter to bids map comes with an empty currency string and an empty bid array",
+			adapterBids: map[openrtb_ext.BidderName]*pbsOrtbSeatBid{
+				openrtb_ext.BidderName("appnexus"): {
+					bids:     nil,
+					currency: "",
+				},
+			},
+			expectedBidResponse: &openrtb.BidResponse{
+				ID:      "some-request-id",
+				SeatBid: emptySeatBid,
+				Cur:     "",
+				Ext: json.RawMessage(`{"responsetimemillis":{"appnexus":5},"tmaxrequest":500}
+`),
+			},
 		},
 	}
 
-	return liveAdapters, adapterBids, resolvedRequest, adapterExtra
+	// Run tests
+	for i := range testCases {
+		actualBidResp, err := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, resolvedRequest, adapterExtra, nil, errList)
+		assert.NoError(t, err, fmt.Sprintf("[TEST_FAILED] e.buildBidResponse resturns error in test: %s Error message: %s \n", testCases[i].description, err))
+		assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext))
+	}
 }
 
 // TestRaceIntegration runs an integration test using all the sample params from