github.com/prebid/prebid-server/v2@v2.18.0/exchange/bidder_test.go (about)

     1  package exchange
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"context"
     7  	"crypto/tls"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"net/http/httptrace"
    15  	"sort"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/golang/glog"
    21  	"github.com/prebid/openrtb/v20/adcom1"
    22  	nativeRequests "github.com/prebid/openrtb/v20/native1/request"
    23  	nativeResponse "github.com/prebid/openrtb/v20/native1/response"
    24  	"github.com/prebid/openrtb/v20/openrtb2"
    25  	"github.com/prebid/prebid-server/v2/adapters"
    26  	"github.com/prebid/prebid-server/v2/config"
    27  	"github.com/prebid/prebid-server/v2/currency"
    28  	"github.com/prebid/prebid-server/v2/errortypes"
    29  	"github.com/prebid/prebid-server/v2/exchange/entities"
    30  	"github.com/prebid/prebid-server/v2/experiment/adscert"
    31  	"github.com/prebid/prebid-server/v2/hooks/hookexecution"
    32  	"github.com/prebid/prebid-server/v2/metrics"
    33  	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
    34  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    35  	"github.com/prebid/prebid-server/v2/util/jsonutil"
    36  	"github.com/prebid/prebid-server/v2/util/ptrutil"
    37  	"github.com/prebid/prebid-server/v2/version"
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/mock"
    40  )
    41  
    42  // TestSingleBidder makes sure that the following things work if the Bidder needs only one request.
    43  //
    44  // 1. The Bidder implementation is called with the arguments we expect.
    45  // 2. The returned values are correct for a non-test bid.
    46  func TestSingleBidder(t *testing.T) {
    47  	type aTest struct {
    48  		debugInfo    *config.DebugInfo
    49  		httpCallsLen int
    50  	}
    51  
    52  	testCases := []*aTest{
    53  		{&config.DebugInfo{Allow: false}, 0},
    54  		{&config.DebugInfo{Allow: true}, 1},
    55  	}
    56  
    57  	respStatus := 200
    58  	respBody := "{\"bid\":false}"
    59  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
    60  	defer server.Close()
    61  
    62  	requestHeaders := http.Header{}
    63  	requestHeaders.Add("Content-Type", "application/json")
    64  
    65  	bidAdjustments := map[string]float64{"test": 2.0}
    66  	firstInitialPrice := 3.0
    67  	secondInitialPrice := 4.0
    68  
    69  	bidderImpl := &goodSingleBidder{
    70  		httpRequest: &adapters.RequestData{
    71  			Method:  "POST",
    72  			Uri:     server.URL,
    73  			Body:    []byte("{\"key\":\"val\"}"),
    74  			Headers: http.Header{},
    75  		},
    76  		bidResponse: nil,
    77  	}
    78  
    79  	ctx := context.Background()
    80  
    81  	for _, test := range testCases {
    82  		mockBidderResponse := &adapters.BidderResponse{
    83  			Bids: []*adapters.TypedBid{
    84  				{
    85  					Bid: &openrtb2.Bid{
    86  						Price: firstInitialPrice,
    87  					},
    88  					BidType:      openrtb_ext.BidTypeBanner,
    89  					DealPriority: 4,
    90  				},
    91  				{
    92  					Bid: &openrtb2.Bid{
    93  						Price: secondInitialPrice,
    94  					},
    95  					BidType:      openrtb_ext.BidTypeVideo,
    96  					DealPriority: 5,
    97  				},
    98  			},
    99  		}
   100  		bidderImpl.bidResponse = mockBidderResponse
   101  
   102  		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo, "")
   103  		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   104  
   105  		bidderReq := BidderRequest{
   106  			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
   107  			BidderName: "test",
   108  		}
   109  		bidReqOptions := bidRequestOptions{
   110  			accountDebugAllowed: true,
   111  			headerDebugAllowed:  false,
   112  			addCallSignHeader:   false,
   113  			bidAdjustments:      bidAdjustments,
   114  		}
   115  		extraInfo := &adapters.ExtraRequestInfo{}
   116  		seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   117  
   118  		assert.Len(t, seatBids, 1)
   119  		assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
   120  		seatBid := seatBids[0]
   121  
   122  		// Make sure the goodSingleBidder was called with the expected arguments.
   123  		if bidderImpl.httpResponse == nil {
   124  			t.Errorf("The Bidder should be called with the server's response.")
   125  		}
   126  		if bidderImpl.httpResponse.StatusCode != respStatus {
   127  			t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode)
   128  		}
   129  		if string(bidderImpl.httpResponse.Body) != respBody {
   130  			t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body))
   131  		}
   132  
   133  		// Make sure the returned values are what we expect
   134  		if len(errortypes.FatalOnly(errs)) != 0 {
   135  			t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs))
   136  		}
   137  
   138  		if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 {
   139  			t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs))
   140  		}
   141  		if len(seatBid.Bids) != len(mockBidderResponse.Bids) {
   142  			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.Bids))
   143  		}
   144  		for index, typedBid := range mockBidderResponse.Bids {
   145  			if typedBid.Bid != seatBid.Bids[index].Bid {
   146  				t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index)
   147  			}
   148  			if typedBid.BidType != seatBid.Bids[index].BidType {
   149  				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.Bids[index].BidType)
   150  			}
   151  			if typedBid.DealPriority != seatBid.Bids[index].DealPriority {
   152  				t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.Bids[index].BidType)
   153  			}
   154  		}
   155  		bidAdjustment := bidAdjustments["test"]
   156  		if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice {
   157  			t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price)
   158  		}
   159  		if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice {
   160  			t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price)
   161  		}
   162  		if len(seatBid.HttpCalls) != test.httpCallsLen {
   163  			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.HttpCalls))
   164  		}
   165  		for index, bid := range seatBid.Bids {
   166  			assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.OriginalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different")
   167  		}
   168  	}
   169  }
   170  
   171  func TestSingleBidderGzip(t *testing.T) {
   172  	type aTest struct {
   173  		debugInfo    *config.DebugInfo
   174  		httpCallsLen int
   175  	}
   176  
   177  	testCases := []*aTest{
   178  		{&config.DebugInfo{Allow: false}, 0},
   179  		{&config.DebugInfo{Allow: true}, 1},
   180  	}
   181  
   182  	respStatus := 200
   183  	respBody := "{\"bid\":false}"
   184  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
   185  	defer server.Close()
   186  
   187  	requestHeaders := http.Header{}
   188  	requestHeaders.Add("Content-Type", "application/json")
   189  
   190  	bidAdjustments := map[string]float64{"test": 2.0}
   191  	firstInitialPrice := 3.0
   192  	secondInitialPrice := 4.0
   193  
   194  	bidderImpl := &goodSingleBidder{
   195  		httpRequest: &adapters.RequestData{
   196  			Method:  "POST",
   197  			Uri:     server.URL,
   198  			Body:    []byte(`{"key":"val"}`),
   199  			Headers: http.Header{},
   200  		},
   201  		bidResponse: nil,
   202  	}
   203  
   204  	ctx := context.Background()
   205  
   206  	for _, test := range testCases {
   207  		mockBidderResponse := &adapters.BidderResponse{
   208  			Bids: []*adapters.TypedBid{
   209  				{
   210  					Bid: &openrtb2.Bid{
   211  						Price: firstInitialPrice,
   212  					},
   213  					BidType:      openrtb_ext.BidTypeBanner,
   214  					DealPriority: 4,
   215  				},
   216  				{
   217  					Bid: &openrtb2.Bid{
   218  						Price: secondInitialPrice,
   219  					},
   220  					BidType:      openrtb_ext.BidTypeVideo,
   221  					DealPriority: 5,
   222  				},
   223  			},
   224  		}
   225  		bidderImpl.bidResponse = mockBidderResponse
   226  
   227  		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, test.debugInfo, "GZIP")
   228  		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   229  
   230  		bidderReq := BidderRequest{
   231  			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
   232  			BidderName: "test",
   233  		}
   234  		bidReqOptions := bidRequestOptions{
   235  			accountDebugAllowed: true,
   236  			headerDebugAllowed:  false,
   237  			addCallSignHeader:   false,
   238  			bidAdjustments:      bidAdjustments,
   239  		}
   240  		extraInfo := &adapters.ExtraRequestInfo{}
   241  		seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   242  		assert.Len(t, seatBids, 1)
   243  		assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
   244  		seatBid := seatBids[0]
   245  
   246  		// Make sure the goodSingleBidder was called with the expected arguments.
   247  		if bidderImpl.httpResponse == nil {
   248  			t.Errorf("The Bidder should be called with the server's response.")
   249  		}
   250  		if bidderImpl.httpResponse.StatusCode != respStatus {
   251  			t.Errorf("Bad response status. Expected %d, got %d", respStatus, bidderImpl.httpResponse.StatusCode)
   252  		}
   253  		if string(bidderImpl.httpResponse.Body) != respBody {
   254  			t.Errorf("Bad response body. Expected %s, got %s", respBody, string(bidderImpl.httpResponse.Body))
   255  		}
   256  
   257  		// Make sure the returned values are what we expect
   258  		if len(errortypes.FatalOnly(errs)) != 0 {
   259  			t.Errorf("bidder.Bid returned %d errors. Expected 0", len(errs))
   260  		}
   261  
   262  		if !test.debugInfo.Allow && len(errortypes.WarningOnly(errs)) != 1 {
   263  			t.Errorf("bidder.Bid returned %d warnings. Expected 1", len(errs))
   264  		}
   265  		if len(seatBid.Bids) != len(mockBidderResponse.Bids) {
   266  			t.Fatalf("Expected %d bids. Got %d", len(mockBidderResponse.Bids), len(seatBid.Bids))
   267  		}
   268  		for index, typedBid := range mockBidderResponse.Bids {
   269  			if typedBid.Bid != seatBid.Bids[index].Bid {
   270  				t.Errorf("Bid %d did not point to the same bid returned by the Bidder.", index)
   271  			}
   272  			if typedBid.BidType != seatBid.Bids[index].BidType {
   273  				t.Errorf("Bid %d did not have the right type. Expected %s, got %s", index, typedBid.BidType, seatBid.Bids[index].BidType)
   274  			}
   275  			if typedBid.DealPriority != seatBid.Bids[index].DealPriority {
   276  				t.Errorf("Bid %d did not have the right deal priority. Expected %s, got %s", index, typedBid.BidType, seatBid.Bids[index].BidType)
   277  			}
   278  		}
   279  		bidAdjustment := bidAdjustments["test"]
   280  		if mockBidderResponse.Bids[0].Bid.Price != bidAdjustment*firstInitialPrice {
   281  			t.Errorf("Bid[0].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*firstInitialPrice, mockBidderResponse.Bids[0].Bid.Price)
   282  		}
   283  		if mockBidderResponse.Bids[1].Bid.Price != bidAdjustment*secondInitialPrice {
   284  			t.Errorf("Bid[1].Price was not adjusted properly. Expected %f, got %f", bidAdjustment*secondInitialPrice, mockBidderResponse.Bids[1].Bid.Price)
   285  		}
   286  		if len(seatBid.HttpCalls) != test.httpCallsLen {
   287  			t.Errorf("The bidder shouldn't log HttpCalls when request.test == 0. Found %d", len(seatBid.HttpCalls))
   288  		}
   289  		if test.debugInfo.Allow && len(seatBid.HttpCalls) > 0 {
   290  			assert.Equalf(t, "gzip", seatBid.HttpCalls[0].RequestHeaders["Content-Encoding"][0], "Mismatched headers")
   291  			assert.Equalf(t, "{\"key\":\"val\"}", seatBid.HttpCalls[0].RequestBody, "Mismatched request bodies")
   292  		}
   293  		for index, bid := range seatBid.Bids {
   294  			assert.NotEqual(t, mockBidderResponse.Bids[index].Bid.Price, bid.OriginalBidCPM, "The bid price was adjusted, so the originally bid CPM should be different")
   295  		}
   296  	}
   297  }
   298  
   299  func TestRequestBidRemovesSensitiveHeaders(t *testing.T) {
   300  	server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
   301  	defer server.Close()
   302  
   303  	oldVer := version.Ver
   304  	version.Ver = "test-version"
   305  	defer func() {
   306  		version.Ver = oldVer
   307  	}()
   308  
   309  	requestHeaders := http.Header{}
   310  	requestHeaders.Add("Content-Type", "application/json")
   311  	requestHeaders.Add("Authorization", "anySecret")
   312  
   313  	bidderImpl := &goodSingleBidder{
   314  		httpRequest: &adapters.RequestData{
   315  			Method:  "POST",
   316  			Uri:     server.URL,
   317  			Body:    []byte("requestJson"),
   318  			Headers: requestHeaders,
   319  		},
   320  		bidResponse: &adapters.BidderResponse{
   321  			Bids: []*adapters.TypedBid{},
   322  		},
   323  	}
   324  
   325  	debugInfo := &config.DebugInfo{Allow: true}
   326  	ctx := context.Background()
   327  
   328  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
   329  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   330  
   331  	bidderReq := BidderRequest{
   332  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
   333  		BidderName: "test",
   334  	}
   335  	bidAdjustments := map[string]float64{"test": 1}
   336  	bidReqOptions := bidRequestOptions{
   337  		accountDebugAllowed: true,
   338  		headerDebugAllowed:  false,
   339  		addCallSignHeader:   false,
   340  		bidAdjustments:      bidAdjustments,
   341  	}
   342  	extraInfo := &adapters.ExtraRequestInfo{}
   343  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   344  	expectedHttpCalls := []*openrtb_ext.ExtHttpCall{
   345  		{
   346  			Uri:            server.URL,
   347  			RequestBody:    "requestJson",
   348  			RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"pbs-go/test-version"}},
   349  			ResponseBody:   "responseJson",
   350  			Status:         200,
   351  		},
   352  	}
   353  
   354  	assert.Empty(t, errs)
   355  	assert.Len(t, seatBids, 1)
   356  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
   357  	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCalls)
   358  }
   359  
   360  func TestSetGPCHeader(t *testing.T) {
   361  	server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
   362  	defer server.Close()
   363  
   364  	requestHeaders := http.Header{}
   365  	requestHeaders.Add("Content-Type", "application/json")
   366  
   367  	bidderImpl := &goodSingleBidder{
   368  		httpRequest: &adapters.RequestData{
   369  			Method:  "POST",
   370  			Uri:     server.URL,
   371  			Body:    []byte("requestJson"),
   372  			Headers: requestHeaders,
   373  		},
   374  		bidResponse: &adapters.BidderResponse{
   375  			Bids: []*adapters.TypedBid{},
   376  		},
   377  	}
   378  
   379  	debugInfo := &config.DebugInfo{Allow: true}
   380  	ctx := context.Background()
   381  
   382  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
   383  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   384  	bidderReq := BidderRequest{
   385  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
   386  		BidderName: "test",
   387  	}
   388  	bidAdjustments := map[string]float64{"test": 1}
   389  	bidReqOptions := bidRequestOptions{
   390  		accountDebugAllowed: true,
   391  		headerDebugAllowed:  false,
   392  		addCallSignHeader:   false,
   393  		bidAdjustments:      bidAdjustments,
   394  	}
   395  	extraInfo := &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}
   396  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   397  
   398  	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
   399  		{
   400  			Uri:            server.URL,
   401  			RequestBody:    "requestJson",
   402  			RequestHeaders: map[string][]string{"Content-Type": {"application/json"}, "X-Prebid": {"pbs-go/unknown"}, "Sec-Gpc": {"1"}},
   403  			ResponseBody:   "responseJson",
   404  			Status:         200,
   405  		},
   406  	}
   407  
   408  	assert.Empty(t, errs)
   409  	assert.Len(t, seatBids, 1)
   410  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
   411  	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCall)
   412  }
   413  
   414  func TestSetGPCHeaderNil(t *testing.T) {
   415  	server := httptest.NewServer(mockHandler(200, "getBody", "responseJson"))
   416  	defer server.Close()
   417  
   418  	bidderImpl := &goodSingleBidder{
   419  		httpRequest: &adapters.RequestData{
   420  			Method:  "POST",
   421  			Uri:     server.URL,
   422  			Body:    []byte("requestJson"),
   423  			Headers: nil,
   424  		},
   425  		bidResponse: &adapters.BidderResponse{
   426  			Bids: []*adapters.TypedBid{},
   427  		},
   428  	}
   429  
   430  	debugInfo := &config.DebugInfo{Allow: true}
   431  	ctx := context.Background()
   432  
   433  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, debugInfo, "")
   434  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   435  
   436  	bidderReq := BidderRequest{
   437  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
   438  		BidderName: "test",
   439  	}
   440  	bidAdjustments := map[string]float64{"test": 1}
   441  	bidReqOptions := bidRequestOptions{
   442  		accountDebugAllowed: true,
   443  		headerDebugAllowed:  false,
   444  		addCallSignHeader:   false,
   445  		bidAdjustments:      bidAdjustments,
   446  	}
   447  	extraInfo := &adapters.ExtraRequestInfo{GlobalPrivacyControlHeader: "1"}
   448  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   449  
   450  	expectedHttpCall := []*openrtb_ext.ExtHttpCall{
   451  		{
   452  			Uri:            server.URL,
   453  			RequestBody:    "requestJson",
   454  			RequestHeaders: map[string][]string{"X-Prebid": {"pbs-go/unknown"}, "Sec-Gpc": {"1"}},
   455  			ResponseBody:   "responseJson",
   456  			Status:         200,
   457  		},
   458  	}
   459  
   460  	assert.Empty(t, errs)
   461  	assert.Len(t, seatBids, 1)
   462  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
   463  	assert.ElementsMatch(t, seatBids[0].HttpCalls, expectedHttpCall)
   464  }
   465  
   466  // TestMultiBidder makes sure all the requests get sent, and the responses processed.
   467  // Because this is done in parallel, it should be run under the race detector.
   468  func TestMultiBidder(t *testing.T) {
   469  	respStatus := 200
   470  	getRespBody := "{\"wasPost\":false}"
   471  	postRespBody := "{\"wasPost\":true}"
   472  	server := httptest.NewServer(mockHandler(respStatus, getRespBody, postRespBody))
   473  	defer server.Close()
   474  
   475  	requestHeaders := http.Header{}
   476  	requestHeaders.Add("Content-Type", "application/json")
   477  
   478  	mockBidderResponse := &adapters.BidderResponse{
   479  		Bids: []*adapters.TypedBid{
   480  			{
   481  				Bid:     &openrtb2.Bid{},
   482  				BidType: openrtb_ext.BidTypeBanner,
   483  			},
   484  			{
   485  				Bid:     &openrtb2.Bid{},
   486  				BidType: openrtb_ext.BidTypeVideo,
   487  			},
   488  		},
   489  	}
   490  
   491  	bidderImpl := &mixedMultiBidder{
   492  		httpRequests: []*adapters.RequestData{{
   493  			Method:  "POST",
   494  			Uri:     server.URL,
   495  			Body:    []byte("{\"key\":\"val\"}"),
   496  			Headers: http.Header{},
   497  		},
   498  			{
   499  				Method:  "GET",
   500  				Uri:     server.URL,
   501  				Body:    []byte("{\"key\":\"val2\"}"),
   502  				Headers: http.Header{},
   503  			}},
   504  		bidResponse: mockBidderResponse,
   505  	}
   506  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
   507  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   508  	bidderReq := BidderRequest{
   509  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
   510  		BidderName: "test",
   511  	}
   512  	bidAdjustments := map[string]float64{"test": 1.0}
   513  	bidReqOptions := bidRequestOptions{
   514  		accountDebugAllowed: true,
   515  		headerDebugAllowed:  true,
   516  		addCallSignHeader:   false,
   517  		bidAdjustments:      bidAdjustments,
   518  	}
   519  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   520  
   521  	if len(seatBids) != 1 {
   522  		t.Fatalf("SeatBid should exist, because bids exist.")
   523  	}
   524  
   525  	if len(errs) != 1+len(bidderImpl.httpRequests) {
   526  		t.Errorf("Expected %d errors. Got %d", 1+len(bidderImpl.httpRequests), len(errs))
   527  	}
   528  	if len(seatBids[0].Bids) != len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids) {
   529  		t.Errorf("Expected %d bids. Got %d", len(bidderImpl.httpResponses)*len(mockBidderResponse.Bids), len(seatBids[0].Bids))
   530  	}
   531  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
   532  
   533  }
   534  
   535  // TestBidderTimeout makes sure that things work smoothly if the context expires before the Bidder
   536  // manages to complete its task.
   537  func TestBidderTimeout(t *testing.T) {
   538  	// Fixes #369 (hopefully): Define a context which has already expired
   539  	ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(-7*time.Hour))
   540  	cancelFunc()
   541  	<-ctx.Done()
   542  
   543  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   544  		w.WriteHeader(200)
   545  		if r.Method == "GET" {
   546  			w.Write([]byte("getBody"))
   547  		} else {
   548  			w.Write([]byte("postBody"))
   549  		}
   550  	})
   551  
   552  	server := httptest.NewServer(handler)
   553  	defer server.Close()
   554  
   555  	bidder := &bidderAdapter{
   556  		Bidder:     &mixedMultiBidder{},
   557  		BidderName: openrtb_ext.BidderAppnexus,
   558  		Client:     server.Client(),
   559  		me:         &metricsConfig.NilMetricsEngine{},
   560  	}
   561  	tmaxAdjustments := &TmaxAdjustmentsPreprocessed{}
   562  	callInfo := bidder.doRequest(ctx, &adapters.RequestData{
   563  		Method: "POST",
   564  		Uri:    server.URL,
   565  	}, time.Now(), tmaxAdjustments)
   566  	if callInfo.err == nil {
   567  		t.Errorf("The bidder should report an error if the context has expired already.")
   568  	}
   569  	if callInfo.response != nil {
   570  		t.Errorf("There should be no response if the request never completed.")
   571  	}
   572  }
   573  
   574  // TestInvalidRequest makes sure that bidderAdapter.doRequest returns errors on bad requests.
   575  func TestInvalidRequest(t *testing.T) {
   576  	server := httptest.NewServer(mockHandler(200, "getBody", "postBody"))
   577  	bidder := &bidderAdapter{
   578  		Bidder: &mixedMultiBidder{},
   579  		Client: server.Client(),
   580  	}
   581  	tmaxAdjustments := &TmaxAdjustmentsPreprocessed{}
   582  	callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{
   583  		Method: "\"", // force http.NewRequest() to fail
   584  	}, time.Now(), tmaxAdjustments)
   585  	if callInfo.err == nil {
   586  		t.Errorf("bidderAdapter.doRequest should return an error if the request data is malformed.")
   587  	}
   588  }
   589  
   590  // TestConnectionClose makes sure that bidderAdapter.doRequest returns errors if the connection closes unexpectedly.
   591  func TestConnectionClose(t *testing.T) {
   592  	var server *httptest.Server
   593  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   594  		server.CloseClientConnections()
   595  	})
   596  	server = httptest.NewServer(handler)
   597  
   598  	bidder := &bidderAdapter{
   599  		Bidder:     &mixedMultiBidder{},
   600  		Client:     server.Client(),
   601  		BidderName: openrtb_ext.BidderAppnexus,
   602  		me:         &metricsConfig.NilMetricsEngine{},
   603  	}
   604  	tmaxAdjustments := &TmaxAdjustmentsPreprocessed{}
   605  	callInfo := bidder.doRequest(context.Background(), &adapters.RequestData{
   606  		Method: "POST",
   607  		Uri:    server.URL,
   608  	}, time.Now(), tmaxAdjustments)
   609  	if callInfo.err == nil {
   610  		t.Errorf("bidderAdapter.doRequest should return an error if the connection closes unexpectedly.")
   611  	}
   612  }
   613  
   614  type bid struct {
   615  	currency       string
   616  	price          float64
   617  	originalBidCur string
   618  }
   619  
   620  // TestMultiCurrencies rate converter is set / active.
   621  func TestMultiCurrencies(t *testing.T) {
   622  	// Setup:
   623  	respStatus := 200
   624  	getRespBody := "{\"wasPost\":false}"
   625  	postRespBody := "{\"wasPost\":true}"
   626  
   627  	testCases := []struct {
   628  		bids                      []bid
   629  		rates                     currency.Rates
   630  		expectedBids              []bid
   631  		expectedBadCurrencyErrors []error
   632  		description               string
   633  	}{
   634  		{
   635  			bids: []bid{
   636  				{currency: "USD", price: 1.1},
   637  				{currency: "USD", price: 1.2},
   638  				{currency: "USD", price: 1.3},
   639  			},
   640  			rates: currency.Rates{
   641  				Conversions: map[string]map[string]float64{
   642  					"GBP": {
   643  						"USD": 1.3050530256,
   644  					},
   645  					"EUR": {
   646  						"USD": 1.1435678764,
   647  					},
   648  				},
   649  			},
   650  			expectedBids: []bid{
   651  				{currency: "USD", price: 1.1, originalBidCur: "USD"},
   652  				{currency: "USD", price: 1.2, originalBidCur: "USD"},
   653  				{currency: "USD", price: 1.3, originalBidCur: "USD"},
   654  			},
   655  			expectedBadCurrencyErrors: []error{},
   656  			description:               "Case 1 - Bidder respond with the same currency (default one) on all HTTP responses",
   657  		},
   658  		{
   659  			bids: []bid{
   660  				{currency: "", price: 1.1},
   661  				{currency: "", price: 1.2},
   662  				{currency: "", price: 1.3},
   663  			},
   664  			rates: currency.Rates{
   665  				Conversions: map[string]map[string]float64{
   666  					"GBP": {
   667  						"USD": 1.3050530256,
   668  					},
   669  					"EUR": {
   670  						"USD": 1.1435678764,
   671  					},
   672  				},
   673  			},
   674  			expectedBids: []bid{
   675  				{currency: "USD", price: 1.1, originalBidCur: "USD"},
   676  				{currency: "USD", price: 1.2, originalBidCur: "USD"},
   677  				{currency: "USD", price: 1.3, originalBidCur: "USD"},
   678  			},
   679  			expectedBadCurrencyErrors: []error{},
   680  			description:               "Case 2 - Bidder respond with no currency on all HTTP responses",
   681  		},
   682  		{
   683  			bids: []bid{
   684  				{currency: "EUR", price: 1.1},
   685  				{currency: "EUR", price: 1.2},
   686  				{currency: "EUR", price: 1.3},
   687  			},
   688  			rates: currency.Rates{
   689  				Conversions: map[string]map[string]float64{
   690  					"GBP": {
   691  						"USD": 1.3050530256,
   692  					},
   693  					"EUR": {
   694  						"USD": 1.1435678764,
   695  					},
   696  				},
   697  			},
   698  			expectedBids: []bid{
   699  				{currency: "USD", price: 1.1 * 1.1435678764, originalBidCur: "EUR"},
   700  				{currency: "USD", price: 1.2 * 1.1435678764, originalBidCur: "EUR"},
   701  				{currency: "USD", price: 1.3 * 1.1435678764, originalBidCur: "EUR"},
   702  			},
   703  			expectedBadCurrencyErrors: []error{},
   704  			description:               "Case 3 - Bidder respond with the same non default currency on all HTTP responses",
   705  		},
   706  		{
   707  			bids: []bid{
   708  				{currency: "USD", price: 1.1},
   709  				{currency: "EUR", price: 1.2},
   710  				{currency: "GBP", price: 1.3},
   711  			},
   712  			rates: currency.Rates{
   713  				Conversions: map[string]map[string]float64{
   714  					"GBP": {
   715  						"USD": 1.3050530256,
   716  					},
   717  					"EUR": {
   718  						"USD": 1.1435678764,
   719  					},
   720  				},
   721  			},
   722  			expectedBids: []bid{
   723  				{currency: "USD", price: 1.1, originalBidCur: "USD"},
   724  				{currency: "USD", price: 1.2 * 1.1435678764, originalBidCur: "EUR"},
   725  				{currency: "USD", price: 1.3 * 1.3050530256, originalBidCur: "GBP"},
   726  			},
   727  			expectedBadCurrencyErrors: []error{},
   728  			description:               "Case 4 - Bidder respond with a mix of currencies on all HTTP responses",
   729  		},
   730  		{
   731  			bids: []bid{
   732  				{currency: "", price: 1.1},
   733  				{currency: "EUR", price: 1.2},
   734  				{currency: "GBP", price: 1.3},
   735  			},
   736  			rates: currency.Rates{
   737  				Conversions: map[string]map[string]float64{
   738  					"GBP": {
   739  						"USD": 1.3050530256,
   740  					},
   741  					"EUR": {
   742  						"USD": 1.1435678764,
   743  					},
   744  				},
   745  			},
   746  			expectedBids: []bid{
   747  				{currency: "USD", price: 1.1, originalBidCur: "USD"},
   748  				{currency: "USD", price: 1.2 * 1.1435678764, originalBidCur: "EUR"},
   749  				{currency: "USD", price: 1.3 * 1.3050530256, originalBidCur: "GBP"},
   750  			},
   751  			expectedBadCurrencyErrors: []error{},
   752  			description:               "Case 5 - Bidder respond with a mix of currencies and no currency on all HTTP responses",
   753  		},
   754  		{
   755  			bids: []bid{
   756  				{currency: "JPY", price: 1.1},
   757  				{currency: "EUR", price: 1.2},
   758  				{currency: "GBP", price: 1.3},
   759  			},
   760  			rates: currency.Rates{
   761  				Conversions: map[string]map[string]float64{
   762  					"GBP": {
   763  						"USD": 1.3050530256,
   764  					},
   765  					"EUR": {
   766  						"USD": 1.1435678764,
   767  					},
   768  				},
   769  			},
   770  			expectedBids: []bid{
   771  				{currency: "USD", price: 1.2 * 1.1435678764, originalBidCur: "EUR"},
   772  				{currency: "USD", price: 1.3 * 1.3050530256, originalBidCur: "GBP"},
   773  			},
   774  			expectedBadCurrencyErrors: []error{
   775  				currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"},
   776  			},
   777  			description: "Case 6 - Bidder respond with a mix of currencies and one unknown on all HTTP responses",
   778  		},
   779  		{
   780  			bids: []bid{
   781  				{currency: "JPY", price: 1.1},
   782  				{currency: "BZD", price: 1.2},
   783  				{currency: "DKK", price: 1.3},
   784  			},
   785  			rates: currency.Rates{
   786  				Conversions: map[string]map[string]float64{
   787  					"GBP": {
   788  						"USD": 1.3050530256,
   789  					},
   790  					"EUR": {
   791  						"USD": 1.1435678764,
   792  					},
   793  				},
   794  			},
   795  			expectedBids: []bid{},
   796  			expectedBadCurrencyErrors: []error{
   797  				currency.ConversionNotFoundError{FromCur: "JPY", ToCur: "USD"},
   798  				currency.ConversionNotFoundError{FromCur: "BZD", ToCur: "USD"},
   799  				currency.ConversionNotFoundError{FromCur: "DKK", ToCur: "USD"},
   800  			},
   801  			description: "Case 7 - Bidder respond with currencies not having any rate on all HTTP responses",
   802  		},
   803  		{
   804  			bids: []bid{
   805  				{currency: "AAA", price: 1.1},
   806  				{currency: "BBB", price: 1.2},
   807  				{currency: "CCC", price: 1.3},
   808  			},
   809  			rates: currency.Rates{
   810  				Conversions: map[string]map[string]float64{
   811  					"GBP": {
   812  						"USD": 1.3050530256,
   813  					},
   814  					"EUR": {
   815  						"USD": 1.1435678764,
   816  					},
   817  				},
   818  			},
   819  			expectedBids: []bid{},
   820  			expectedBadCurrencyErrors: []error{
   821  				errors.New("currency: tag is not a recognized currency"),
   822  				errors.New("currency: tag is not a recognized currency"),
   823  				errors.New("currency: tag is not a recognized currency"),
   824  			},
   825  			description: "Case 8 - Bidder respond with not existing currencies",
   826  		},
   827  	}
   828  
   829  	server := httptest.NewServer(mockHandler(respStatus, getRespBody, postRespBody))
   830  	defer server.Close()
   831  
   832  	for _, tc := range testCases {
   833  		mockBidderResponses := make([]*adapters.BidderResponse, len(tc.bids))
   834  		bidderImpl := &goodMultiHTTPCallsBidder{
   835  			bidResponses: mockBidderResponses,
   836  		}
   837  		bidderImpl.httpRequest = make([]*adapters.RequestData, len(tc.bids))
   838  
   839  		for i, bid := range tc.bids {
   840  			mockBidderResponses[i] = &adapters.BidderResponse{
   841  				Bids: []*adapters.TypedBid{
   842  					{
   843  						Bid: &openrtb2.Bid{
   844  							Price: bid.price,
   845  						},
   846  						BidType: openrtb_ext.BidTypeBanner,
   847  					},
   848  				},
   849  				Currency: bid.currency,
   850  			}
   851  
   852  			bidderImpl.httpRequest[i] = &adapters.RequestData{
   853  				Method:  "POST",
   854  				Uri:     server.URL,
   855  				Body:    []byte("{\"key\":\"val\"}"),
   856  				Headers: http.Header{},
   857  			}
   858  		}
   859  
   860  		mockedHTTPServer := httptest.NewServer(http.HandlerFunc(
   861  			func(rw http.ResponseWriter, req *http.Request) {
   862  				b, err := jsonutil.Marshal(tc.rates)
   863  				if err == nil {
   864  					rw.WriteHeader(http.StatusOK)
   865  					rw.Write(b)
   866  				} else {
   867  					rw.WriteHeader(http.StatusInternalServerError)
   868  				}
   869  			}),
   870  		)
   871  
   872  		// Execute:
   873  		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
   874  		currencyConverter := currency.NewRateConverter(
   875  			&http.Client{},
   876  			mockedHTTPServer.URL,
   877  			time.Duration(24)*time.Hour,
   878  		)
   879  		time.Sleep(time.Duration(500) * time.Millisecond)
   880  		currencyConverter.Run()
   881  
   882  		bidderReq := BidderRequest{
   883  			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
   884  			BidderName: openrtb_ext.BidderAppnexus,
   885  		}
   886  		bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1}
   887  		seatBids, extraBidderRespInfo, errs := bidder.requestBid(
   888  			context.Background(),
   889  			bidderReq,
   890  			currencyConverter.Rates(),
   891  			&adapters.ExtraRequestInfo{},
   892  			&adscert.NilSigner{},
   893  			bidRequestOptions{
   894  				accountDebugAllowed: true,
   895  				headerDebugAllowed:  true,
   896  				addCallSignHeader:   false,
   897  				bidAdjustments:      bidAdjustments,
   898  			},
   899  			openrtb_ext.ExtAlternateBidderCodes{},
   900  			&hookexecution.EmptyHookExecutor{},
   901  			nil,
   902  		)
   903  		assert.Len(t, seatBids, 1)
   904  		seatBid := seatBids[0]
   905  		assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
   906  
   907  		// Verify:
   908  		resultLightBids := make([]bid, len(seatBid.Bids))
   909  		for i, b := range seatBid.Bids {
   910  			resultLightBids[i] = bid{
   911  				price:          b.Bid.Price,
   912  				currency:       seatBid.Currency,
   913  				originalBidCur: b.OriginalBidCur,
   914  			}
   915  		}
   916  		assert.ElementsMatch(t, tc.expectedBids, resultLightBids, tc.description)
   917  		assert.ElementsMatch(t, tc.expectedBadCurrencyErrors, errs, tc.description)
   918  	}
   919  }
   920  
   921  // TestMultiCurrencies_RateConverterNotSet no rate converter is set / active.
   922  func TestMultiCurrencies_RateConverterNotSet(t *testing.T) {
   923  	// Setup:
   924  	respStatus := 200
   925  	getRespBody := "{\"wasPost\":false}"
   926  	postRespBody := "{\"wasPost\":true}"
   927  
   928  	testCases := []struct {
   929  		bidCurrency               []string
   930  		expectedBidsCount         uint
   931  		expectedBadCurrencyErrors []error
   932  		description               string
   933  	}{
   934  		{
   935  			bidCurrency:               []string{"USD", "USD", "USD"},
   936  			expectedBidsCount:         3,
   937  			expectedBadCurrencyErrors: []error{},
   938  			description:               "Case 1 - Bidder respond with the same currency (default one) on all HTTP responses",
   939  		},
   940  		{
   941  			bidCurrency:       []string{"EUR", "EUR", "EUR"},
   942  			expectedBidsCount: 0,
   943  			expectedBadCurrencyErrors: []error{
   944  				currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"},
   945  				currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"},
   946  				currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"},
   947  			},
   948  			description: "Case 2 - Bidder respond with the same currency (not default one) on all HTTP responses",
   949  		},
   950  		{
   951  			bidCurrency:               []string{"", "", ""},
   952  			expectedBidsCount:         3,
   953  			expectedBadCurrencyErrors: []error{},
   954  			description:               "Case 3 - Bidder responds with currency not set on all HTTP responses",
   955  		},
   956  		{
   957  			bidCurrency:               []string{"", "USD", ""},
   958  			expectedBidsCount:         3,
   959  			expectedBadCurrencyErrors: []error{},
   960  			description:               "Case 4 - Bidder responds with a mix of not set and default currency in HTTP responses",
   961  		},
   962  		{
   963  			bidCurrency:               []string{"USD", "USD", ""},
   964  			expectedBidsCount:         3,
   965  			expectedBadCurrencyErrors: []error{},
   966  			description:               "Case 5 - Bidder responds with a mix of not set and default currency in HTTP responses",
   967  		},
   968  		{
   969  			bidCurrency:               []string{"", "", "USD"},
   970  			expectedBidsCount:         3,
   971  			expectedBadCurrencyErrors: []error{},
   972  			description:               "Case 6 - Bidder responds with a mix of not set and default currency in HTTP responses",
   973  		},
   974  		{
   975  			bidCurrency:       []string{"EUR", "", "USD"},
   976  			expectedBidsCount: 2,
   977  			expectedBadCurrencyErrors: []error{
   978  				currency.ConversionNotFoundError{FromCur: "EUR", ToCur: "USD"},
   979  			},
   980  			description: "Case 7 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses",
   981  		},
   982  		{
   983  			bidCurrency:       []string{"GBP", "", "USD"},
   984  			expectedBidsCount: 2,
   985  			expectedBadCurrencyErrors: []error{
   986  				currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"},
   987  			},
   988  			description: "Case 8 - Bidder responds with a mix of not set, non default currency and default currency in HTTP responses",
   989  		},
   990  		{
   991  			bidCurrency:       []string{"GBP", "", ""},
   992  			expectedBidsCount: 2,
   993  			expectedBadCurrencyErrors: []error{
   994  				currency.ConversionNotFoundError{FromCur: "GBP", ToCur: "USD"},
   995  			},
   996  			description: "Case 9 - Bidder responds with a mix of not set and empty currencies (default currency) in HTTP responses",
   997  		},
   998  		// Bidder respond with not existing currencies
   999  		{
  1000  			bidCurrency:       []string{"AAA", "BBB", "CCC"},
  1001  			expectedBidsCount: 0,
  1002  			expectedBadCurrencyErrors: []error{
  1003  				errors.New("currency: tag is not a recognized currency"),
  1004  				errors.New("currency: tag is not a recognized currency"),
  1005  				errors.New("currency: tag is not a recognized currency"),
  1006  			},
  1007  			description: "Case 10 - Bidder respond with not existing currencies",
  1008  		},
  1009  	}
  1010  
  1011  	server := httptest.NewServer(mockHandler(respStatus, getRespBody, postRespBody))
  1012  	defer server.Close()
  1013  
  1014  	for _, tc := range testCases {
  1015  		mockBidderResponses := make([]*adapters.BidderResponse, len(tc.bidCurrency))
  1016  		bidderImpl := &goodMultiHTTPCallsBidder{
  1017  			bidResponses: mockBidderResponses,
  1018  		}
  1019  		bidderImpl.httpRequest = make([]*adapters.RequestData, len(tc.bidCurrency))
  1020  
  1021  		for i, cur := range tc.bidCurrency {
  1022  			mockBidderResponses[i] = &adapters.BidderResponse{
  1023  				Bids: []*adapters.TypedBid{
  1024  					{
  1025  						Bid:     &openrtb2.Bid{},
  1026  						BidType: openrtb_ext.BidTypeBanner,
  1027  					},
  1028  				},
  1029  				Currency: cur,
  1030  			}
  1031  
  1032  			bidderImpl.httpRequest[i] = &adapters.RequestData{
  1033  				Method:  "POST",
  1034  				Uri:     server.URL,
  1035  				Body:    []byte("{\"key\":\"val\"}"),
  1036  				Headers: http.Header{},
  1037  			}
  1038  		}
  1039  
  1040  		// Execute:
  1041  		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
  1042  		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1043  		bidderReq := BidderRequest{
  1044  			BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
  1045  			BidderName: "test",
  1046  		}
  1047  		bidAdjustments := map[string]float64{"test": 1}
  1048  		seatBids, extraBidderRespInfo, errs := bidder.requestBid(
  1049  			context.Background(),
  1050  			bidderReq,
  1051  			currencyConverter.Rates(),
  1052  			&adapters.ExtraRequestInfo{},
  1053  			&adscert.NilSigner{},
  1054  			bidRequestOptions{
  1055  				accountDebugAllowed: true,
  1056  				headerDebugAllowed:  true,
  1057  				addCallSignHeader:   false,
  1058  				bidAdjustments:      bidAdjustments,
  1059  			},
  1060  			openrtb_ext.ExtAlternateBidderCodes{},
  1061  			&hookexecution.EmptyHookExecutor{},
  1062  			nil,
  1063  		)
  1064  		assert.Len(t, seatBids, 1)
  1065  		seatBid := seatBids[0]
  1066  
  1067  		// Verify:
  1068  		assert.Equal(t, false, (seatBid == nil && tc.expectedBidsCount != 0), tc.description)
  1069  		assert.Equal(t, tc.expectedBidsCount, uint(len(seatBid.Bids)), tc.description)
  1070  		assert.ElementsMatch(t, tc.expectedBadCurrencyErrors, errs, tc.description)
  1071  		assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  1072  	}
  1073  }
  1074  
  1075  // TestMultiCurrencies_RequestCurrencyPick tests request currencies pick.
  1076  func TestMultiCurrencies_RequestCurrencyPick(t *testing.T) {
  1077  	// Setup:
  1078  	respStatus := 200
  1079  	getRespBody := "{\"wasPost\":false}"
  1080  	postRespBody := "{\"wasPost\":true}"
  1081  
  1082  	testCases := []struct {
  1083  		bidRequestCurrencies   []string
  1084  		bidResponsesCurrency   string
  1085  		expectedPickedCurrency string
  1086  		expectedError          bool
  1087  		rates                  currency.Rates
  1088  		description            string
  1089  	}{
  1090  		{
  1091  			bidRequestCurrencies:   []string{"EUR", "USD", "JPY"},
  1092  			bidResponsesCurrency:   "EUR",
  1093  			expectedPickedCurrency: "EUR",
  1094  			expectedError:          false,
  1095  			rates: currency.Rates{
  1096  				Conversions: map[string]map[string]float64{
  1097  					"JPY": {
  1098  						"USD": 0.0089,
  1099  					},
  1100  					"GBP": {
  1101  						"USD": 1.3050530256,
  1102  					},
  1103  					"EUR": {
  1104  						"USD": 1.1435678764,
  1105  					},
  1106  				},
  1107  			},
  1108  			description: "Case 1 - Allowed currencies in bid request are known, first one is picked",
  1109  		},
  1110  		{
  1111  			bidRequestCurrencies:   []string{"JPY"},
  1112  			bidResponsesCurrency:   "JPY",
  1113  			expectedPickedCurrency: "JPY",
  1114  			expectedError:          false,
  1115  			rates: currency.Rates{
  1116  				Conversions: map[string]map[string]float64{
  1117  					"JPY": {
  1118  						"USD": 0.0089,
  1119  					},
  1120  				},
  1121  			},
  1122  			description: "Case 2 - There is only one allowed currencies in bid request, it's a known one, it's picked",
  1123  		},
  1124  		{
  1125  			bidRequestCurrencies:   []string{"CNY", "USD", "EUR", "JPY"},
  1126  			bidResponsesCurrency:   "USD",
  1127  			expectedPickedCurrency: "USD",
  1128  			expectedError:          false,
  1129  			rates: currency.Rates{
  1130  				Conversions: map[string]map[string]float64{
  1131  					"JPY": {
  1132  						"USD": 0.0089,
  1133  					},
  1134  					"GBP": {
  1135  						"USD": 1.3050530256,
  1136  					},
  1137  					"EUR": {
  1138  						"USD": 1.1435678764,
  1139  					},
  1140  				},
  1141  			},
  1142  			description: "Case 3 - First allowed currencies in bid request is not known but the others are, second one is picked",
  1143  		},
  1144  		{
  1145  			bidRequestCurrencies:   []string{"CNY", "EUR", "JPY"},
  1146  			bidResponsesCurrency:   "",
  1147  			expectedPickedCurrency: "",
  1148  			expectedError:          true,
  1149  			rates: currency.Rates{
  1150  				Conversions: map[string]map[string]float64{},
  1151  			},
  1152  			description: "Case 4 - None allowed currencies in bid request are known, an error is returned",
  1153  		},
  1154  		{
  1155  			bidRequestCurrencies:   []string{"CNY", "EUR", "JPY", "USD"},
  1156  			bidResponsesCurrency:   "USD",
  1157  			expectedPickedCurrency: "USD",
  1158  			expectedError:          false,
  1159  			rates: currency.Rates{
  1160  				Conversions: map[string]map[string]float64{},
  1161  			},
  1162  			description: "Case 5 - None allowed currencies in bid request are known but the default one (`USD`), no rates are set but default currency will be picked",
  1163  		},
  1164  		{
  1165  			bidRequestCurrencies:   nil,
  1166  			bidResponsesCurrency:   "USD",
  1167  			expectedPickedCurrency: "USD",
  1168  			expectedError:          false,
  1169  			rates: currency.Rates{
  1170  				Conversions: map[string]map[string]float64{},
  1171  			},
  1172  			description: "Case 6 - No allowed currencies specified in bid request, default one is picked: `USD`",
  1173  		},
  1174  	}
  1175  
  1176  	server := httptest.NewServer(mockHandler(respStatus, getRespBody, postRespBody))
  1177  	defer server.Close()
  1178  
  1179  	for _, tc := range testCases {
  1180  
  1181  		mockedHTTPServer := httptest.NewServer(http.HandlerFunc(
  1182  			func(rw http.ResponseWriter, req *http.Request) {
  1183  				b, err := jsonutil.Marshal(tc.rates)
  1184  				if err == nil {
  1185  					rw.WriteHeader(http.StatusOK)
  1186  					rw.Write(b)
  1187  				} else {
  1188  					rw.WriteHeader(http.StatusInternalServerError)
  1189  				}
  1190  			}),
  1191  		)
  1192  
  1193  		mockBidderResponses := []*adapters.BidderResponse{
  1194  			{
  1195  				Bids: []*adapters.TypedBid{
  1196  					{
  1197  						Bid:     &openrtb2.Bid{},
  1198  						BidType: openrtb_ext.BidTypeBanner,
  1199  					},
  1200  				},
  1201  				Currency: tc.bidResponsesCurrency,
  1202  			},
  1203  		}
  1204  		bidderImpl := &goodMultiHTTPCallsBidder{
  1205  			bidResponses: mockBidderResponses,
  1206  		}
  1207  		bidderImpl.httpRequest = []*adapters.RequestData{
  1208  			{
  1209  				Method:  "POST",
  1210  				Uri:     server.URL,
  1211  				Body:    []byte("{\"key\":\"val\"}"),
  1212  				Headers: http.Header{},
  1213  			},
  1214  		}
  1215  
  1216  		// Execute:
  1217  		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
  1218  		currencyConverter := currency.NewRateConverter(
  1219  			&http.Client{},
  1220  			mockedHTTPServer.URL,
  1221  			time.Duration(24)*time.Hour,
  1222  		)
  1223  		bidderReq := BidderRequest{
  1224  			BidRequest: &openrtb2.BidRequest{Cur: tc.bidRequestCurrencies, Imp: []openrtb2.Imp{{ID: "impId"}}},
  1225  			BidderName: "test",
  1226  		}
  1227  		bidAdjustments := map[string]float64{"test": 1}
  1228  		seatBids, extraBidderRespInfo, errs := bidder.requestBid(
  1229  			context.Background(),
  1230  			bidderReq,
  1231  			currencyConverter.Rates(),
  1232  			&adapters.ExtraRequestInfo{},
  1233  			&adscert.NilSigner{},
  1234  			bidRequestOptions{
  1235  				accountDebugAllowed: true,
  1236  				headerDebugAllowed:  false,
  1237  				addCallSignHeader:   false,
  1238  				bidAdjustments:      bidAdjustments,
  1239  			},
  1240  			openrtb_ext.ExtAlternateBidderCodes{},
  1241  			&hookexecution.EmptyHookExecutor{},
  1242  			nil,
  1243  		)
  1244  		assert.Len(t, seatBids, 1)
  1245  		seatBid := seatBids[0]
  1246  
  1247  		// Verify:
  1248  		if tc.expectedError {
  1249  			assert.NotNil(t, errs, tc.description)
  1250  		} else {
  1251  			assert.Nil(t, errs, tc.description)
  1252  			assert.Equal(t, tc.expectedPickedCurrency, seatBid.Currency, tc.description)
  1253  			assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  1254  		}
  1255  	}
  1256  }
  1257  
  1258  func TestMakeExt(t *testing.T) {
  1259  	testCases := []struct {
  1260  		description string
  1261  		given       *httpCallInfo
  1262  		expected    *openrtb_ext.ExtHttpCall
  1263  	}{
  1264  		{
  1265  			description: "Nil",
  1266  			given:       nil,
  1267  			expected:    &openrtb_ext.ExtHttpCall{},
  1268  		},
  1269  		{
  1270  			description: "Empty",
  1271  			given: &httpCallInfo{
  1272  				err:      nil,
  1273  				response: nil,
  1274  				request:  nil,
  1275  			},
  1276  			expected: &openrtb_ext.ExtHttpCall{},
  1277  		},
  1278  		{
  1279  			description: "Request & Response - No Error",
  1280  			given: &httpCallInfo{
  1281  				err: nil,
  1282  				request: &adapters.RequestData{
  1283  					Uri:     "requestUri",
  1284  					Body:    []byte("requestBody"),
  1285  					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
  1286  				},
  1287  				response: &adapters.ResponseData{
  1288  					Body:       []byte("responseBody"),
  1289  					StatusCode: 999,
  1290  				},
  1291  			},
  1292  			expected: &openrtb_ext.ExtHttpCall{
  1293  				Uri:            "requestUri",
  1294  				RequestBody:    "requestBody",
  1295  				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
  1296  				ResponseBody:   "responseBody",
  1297  				Status:         999,
  1298  			},
  1299  		},
  1300  		{
  1301  			description: "Request & Response - No Error with Authorization removal",
  1302  			given: &httpCallInfo{
  1303  				err: nil,
  1304  				request: &adapters.RequestData{
  1305  					Uri:     "requestUri",
  1306  					Body:    []byte("requestBody"),
  1307  					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}, "Authorization": {"secret"}}),
  1308  				},
  1309  				response: &adapters.ResponseData{
  1310  					Body:       []byte("responseBody"),
  1311  					StatusCode: 999,
  1312  				},
  1313  			},
  1314  			expected: &openrtb_ext.ExtHttpCall{
  1315  				Uri:            "requestUri",
  1316  				RequestBody:    "requestBody",
  1317  				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
  1318  				ResponseBody:   "responseBody",
  1319  				Status:         999,
  1320  			},
  1321  		},
  1322  		{
  1323  			description: "Request & Response - No Error with nil header",
  1324  			given: &httpCallInfo{
  1325  				err: nil,
  1326  				request: &adapters.RequestData{
  1327  					Uri:     "requestUri",
  1328  					Body:    []byte("requestBody"),
  1329  					Headers: nil,
  1330  				},
  1331  				response: &adapters.ResponseData{
  1332  					Body:       []byte("responseBody"),
  1333  					StatusCode: 999,
  1334  				},
  1335  			},
  1336  			expected: &openrtb_ext.ExtHttpCall{
  1337  				Uri:            "requestUri",
  1338  				RequestBody:    "requestBody",
  1339  				RequestHeaders: nil,
  1340  				ResponseBody:   "responseBody",
  1341  				Status:         999,
  1342  			},
  1343  		},
  1344  		{
  1345  			description: "Request & Response - Error",
  1346  			given: &httpCallInfo{
  1347  				err: errors.New("error"),
  1348  				request: &adapters.RequestData{
  1349  					Uri:     "requestUri",
  1350  					Body:    []byte("requestBody"),
  1351  					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
  1352  				},
  1353  				response: &adapters.ResponseData{
  1354  					Body:       []byte("responseBody"),
  1355  					StatusCode: 999,
  1356  				},
  1357  			},
  1358  			expected: &openrtb_ext.ExtHttpCall{
  1359  				Uri:            "requestUri",
  1360  				RequestBody:    "requestBody",
  1361  				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
  1362  			},
  1363  		},
  1364  		{
  1365  			description: "Request Only",
  1366  			given: &httpCallInfo{
  1367  				err: nil,
  1368  				request: &adapters.RequestData{
  1369  					Uri:     "requestUri",
  1370  					Body:    []byte("requestBody"),
  1371  					Headers: makeHeader(map[string][]string{"Key1": {"value1", "value2"}}),
  1372  				},
  1373  				response: nil,
  1374  			},
  1375  			expected: &openrtb_ext.ExtHttpCall{
  1376  				Uri:            "requestUri",
  1377  				RequestBody:    "requestBody",
  1378  				RequestHeaders: map[string][]string{"Key1": {"value1", "value2"}},
  1379  			},
  1380  		}, {
  1381  			description: "Response Only",
  1382  			given: &httpCallInfo{
  1383  				err: nil,
  1384  				response: &adapters.ResponseData{
  1385  					Body:       []byte("responseBody"),
  1386  					StatusCode: 999,
  1387  				},
  1388  			},
  1389  			expected: &openrtb_ext.ExtHttpCall{},
  1390  		},
  1391  	}
  1392  
  1393  	for _, test := range testCases {
  1394  		result := makeExt(test.given)
  1395  		assert.Equal(t, test.expected, result, test.description)
  1396  	}
  1397  }
  1398  
  1399  func TestFilterHeader(t *testing.T) {
  1400  	testCases := []struct {
  1401  		description string
  1402  		given       http.Header
  1403  		expected    http.Header
  1404  	}{
  1405  		{
  1406  			description: "Nil",
  1407  			given:       nil,
  1408  			expected:    nil,
  1409  		},
  1410  		{
  1411  			description: "Empty",
  1412  			given:       http.Header{},
  1413  			expected:    http.Header{},
  1414  		},
  1415  		{
  1416  			description: "One",
  1417  			given:       makeHeader(map[string][]string{"Key1": {"value1"}}),
  1418  			expected:    makeHeader(map[string][]string{"Key1": {"value1"}}),
  1419  		},
  1420  		{
  1421  			description: "Many",
  1422  			given:       makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}),
  1423  			expected:    makeHeader(map[string][]string{"Key1": {"value1"}, "Key2": {"value2a", "value2b"}}),
  1424  		},
  1425  		{
  1426  			description: "Authorization Header Omitted",
  1427  			given:       makeHeader(map[string][]string{"authorization": {"secret"}}),
  1428  			expected:    http.Header{},
  1429  		},
  1430  		{
  1431  			description: "Authorization Header Omitted - Case Insensitive",
  1432  			given:       makeHeader(map[string][]string{"AuThOrIzAtIoN": {"secret"}}),
  1433  			expected:    http.Header{},
  1434  		},
  1435  		{
  1436  			description: "Authorization Header Omitted + Other Keys",
  1437  			given:       makeHeader(map[string][]string{"authorization": {"secret"}, "Key1": {"value1"}}),
  1438  			expected:    makeHeader(map[string][]string{"Key1": {"value1"}}),
  1439  		},
  1440  	}
  1441  
  1442  	for _, test := range testCases {
  1443  		result := filterHeader(test.given)
  1444  		assert.Equal(t, test.expected, result, test.description)
  1445  	}
  1446  }
  1447  
  1448  func makeHeader(v map[string][]string) http.Header {
  1449  	h := http.Header{}
  1450  	for key, values := range v {
  1451  		for _, value := range values {
  1452  			h.Add(key, value)
  1453  		}
  1454  	}
  1455  	return h
  1456  }
  1457  
  1458  func TestMobileNativeTypes(t *testing.T) {
  1459  	respBody := "{\"bid\":false}"
  1460  	respStatus := 200
  1461  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  1462  	defer server.Close()
  1463  
  1464  	reqBody := "{\"key\":\"val\"}"
  1465  	reqURL := server.URL
  1466  
  1467  	testCases := []struct {
  1468  		mockBidderRequest  *openrtb2.BidRequest
  1469  		mockBidderResponse *adapters.BidderResponse
  1470  		expectedValue      string
  1471  		description        string
  1472  	}{
  1473  		{
  1474  			mockBidderRequest: &openrtb2.BidRequest{
  1475  				Imp: []openrtb2.Imp{
  1476  					{
  1477  						ID: "some-imp-id",
  1478  						Native: &openrtb2.Native{
  1479  							Request: "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}}]}",
  1480  						},
  1481  					},
  1482  				},
  1483  				App: &openrtb2.App{},
  1484  			},
  1485  			mockBidderResponse: &adapters.BidderResponse{
  1486  				Bids: []*adapters.TypedBid{
  1487  					{
  1488  						Bid: &openrtb2.Bid{
  1489  							ImpID: "some-imp-id",
  1490  							AdM:   "{\"assets\":[{\"id\":2,\"img\":{\"url\":\"http://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg\",\"w\":989,\"h\":742,\"ext\":{\"appnexus\":{\"prevent_crop\":0}}}},{\"id\":1,\"title\":{\"text\":\"This is a Prebid Native Creative\"}},{\"id\":3,\"data\":{\"value\":\"Prebid.org\"}},{\"id\":4,\"data\":{\"value\":\"This is a Prebid Native Creative.  There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://some-url.com\"},\"imptrackers\":[\"http://someimptracker.com\"],\"jstracker\":\"some-js-tracker\"}",
  1491  							Price: 10,
  1492  						},
  1493  						BidType: openrtb_ext.BidTypeNative,
  1494  					},
  1495  				},
  1496  			},
  1497  			expectedValue: "{\"assets\":[{\"id\":2,\"img\":{\"type\":3,\"url\":\"http://vcdn.adnxs.com/p/creative-image/f8/7f/0f/13/f87f0f13-230c-4f05-8087-db9216e393de.jpg\",\"w\":989,\"h\":742,\"ext\":{\"appnexus\":{\"prevent_crop\":0}}}},{\"id\":1,\"title\":{\"text\":\"This is a Prebid Native Creative\"}},{\"id\":3,\"data\":{\"type\":1,\"value\":\"Prebid.org\"}},{\"id\":4,\"data\":{\"type\":2,\"value\":\"This is a Prebid Native Creative.  There are many like it, but this one is mine.\"}}],\"link\":{\"url\":\"http://some-url.com\"},\"imptrackers\":[\"http://someimptracker.com\"],\"jstracker\":\"some-js-tracker\"}",
  1498  			description:   "Checks types in response",
  1499  		},
  1500  		{
  1501  			mockBidderRequest: &openrtb2.BidRequest{
  1502  				Imp: []openrtb2.Imp{
  1503  					{
  1504  						ID: "some-imp-id",
  1505  						Native: &openrtb2.Native{
  1506  							Request: "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}}]}",
  1507  						},
  1508  					},
  1509  				},
  1510  				App: &openrtb2.App{},
  1511  			},
  1512  			mockBidderResponse: &adapters.BidderResponse{
  1513  				Bids: []*adapters.TypedBid{
  1514  					{
  1515  						Bid: &openrtb2.Bid{
  1516  							ImpID: "some-imp-id",
  1517  							AdM:   "{\"some-diff-markup\":\"creative\"}",
  1518  							Price: 10,
  1519  						},
  1520  						BidType: openrtb_ext.BidTypeNative,
  1521  					},
  1522  				},
  1523  			},
  1524  			expectedValue: "{\"some-diff-markup\":\"creative\"}",
  1525  			description:   "Non IAB compliant markup",
  1526  		},
  1527  	}
  1528  
  1529  	for _, tc := range testCases {
  1530  		bidderImpl := &goodSingleBidder{
  1531  			httpRequest: &adapters.RequestData{
  1532  				Method:  "POST",
  1533  				Uri:     reqURL,
  1534  				Body:    []byte(reqBody),
  1535  				Headers: http.Header{},
  1536  			},
  1537  			bidResponse: tc.mockBidderResponse,
  1538  		}
  1539  		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
  1540  		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1541  
  1542  		bidderReq := BidderRequest{
  1543  			BidRequest: tc.mockBidderRequest,
  1544  			BidderName: "test",
  1545  		}
  1546  		bidAdjustments := map[string]float64{"test": 1.0}
  1547  		seatBids, extraBidderRespInfo, _ := bidder.requestBid(
  1548  			context.Background(),
  1549  			bidderReq,
  1550  			currencyConverter.Rates(),
  1551  			&adapters.ExtraRequestInfo{},
  1552  			&adscert.NilSigner{},
  1553  			bidRequestOptions{
  1554  				accountDebugAllowed: true,
  1555  				headerDebugAllowed:  true,
  1556  				addCallSignHeader:   false,
  1557  				bidAdjustments:      bidAdjustments,
  1558  			},
  1559  			openrtb_ext.ExtAlternateBidderCodes{},
  1560  			&hookexecution.EmptyHookExecutor{},
  1561  			nil,
  1562  		)
  1563  		assert.Len(t, seatBids, 1)
  1564  		assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  1565  		var actualValue string
  1566  		for _, bid := range seatBids[0].Bids {
  1567  			actualValue = bid.Bid.AdM
  1568  			assert.JSONEq(t, tc.expectedValue, actualValue, tc.description)
  1569  		}
  1570  	}
  1571  }
  1572  
  1573  func TestAddNativeTypes(t *testing.T) {
  1574  	testCases := []struct {
  1575  		description      string
  1576  		bidderRequest    *openrtb2.BidRequest
  1577  		bid              *openrtb2.Bid
  1578  		expectedResponse *nativeResponse.Response
  1579  		expectedErrors   []error
  1580  	}{
  1581  		{
  1582  			description: "Null in bid.Adm in response",
  1583  			bidderRequest: &openrtb2.BidRequest{
  1584  				Imp: []openrtb2.Imp{
  1585  					{
  1586  						ID: "some-imp-id",
  1587  						Native: &openrtb2.Native{
  1588  							Request: "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}}]}",
  1589  						},
  1590  					},
  1591  				},
  1592  				App: &openrtb2.App{},
  1593  			},
  1594  			bid: &openrtb2.Bid{
  1595  				ImpID: "some-imp-id",
  1596  				AdM:   "null",
  1597  				Price: 10,
  1598  			},
  1599  			expectedResponse: nil,
  1600  			expectedErrors:   nil,
  1601  		},
  1602  	}
  1603  
  1604  	for _, tt := range testCases {
  1605  		t.Run(tt.description, func(t *testing.T) {
  1606  			resp, errs := addNativeTypes(tt.bid, tt.bidderRequest)
  1607  			assert.Equal(t, tt.expectedResponse, resp, "response")
  1608  			assert.Equal(t, tt.expectedErrors, errs, "errors")
  1609  		})
  1610  	}
  1611  }
  1612  
  1613  func TestRequestBidsStoredBidResponses(t *testing.T) {
  1614  	respBody := "{\"bid\":false}"
  1615  	respStatus := 200
  1616  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  1617  	defer server.Close()
  1618  
  1619  	bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`)
  1620  	bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2_1", "impid": "bid1impid1"},{"id": "bid_id2_2", "impid": "bid2impid2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`)
  1621  
  1622  	testCases := []struct {
  1623  		description           string
  1624  		mockBidderRequest     *openrtb2.BidRequest
  1625  		bidderStoredResponses map[string]json.RawMessage
  1626  		impReplaceImpId       map[string]bool
  1627  		expectedBidIds        []string
  1628  		expectedImpIds        []string
  1629  	}{
  1630  		{
  1631  			description: "Single imp with stored bid response, replace impid is true",
  1632  			mockBidderRequest: &openrtb2.BidRequest{
  1633  				Imp: nil,
  1634  				App: &openrtb2.App{},
  1635  			},
  1636  			bidderStoredResponses: map[string]json.RawMessage{
  1637  				"bidResponseId1": bidRespId1,
  1638  			},
  1639  			impReplaceImpId: map[string]bool{
  1640  				"bidResponseId1": true,
  1641  			},
  1642  			expectedBidIds: []string{"bid_id1"},
  1643  			expectedImpIds: []string{"bidResponseId1"},
  1644  		},
  1645  		{
  1646  			description: "Single imp with multiple stored bid responses, replace impid is true",
  1647  			mockBidderRequest: &openrtb2.BidRequest{
  1648  				Imp: nil,
  1649  				App: &openrtb2.App{},
  1650  			},
  1651  			bidderStoredResponses: map[string]json.RawMessage{
  1652  				"bidResponseId2": bidRespId2,
  1653  			},
  1654  			impReplaceImpId: map[string]bool{
  1655  				"bidResponseId2": true,
  1656  			},
  1657  			expectedBidIds: []string{"bid_id2_1", "bid_id2_2"},
  1658  			expectedImpIds: []string{"bidResponseId2", "bidResponseId2"},
  1659  		},
  1660  		{
  1661  			description: "Single imp with multiple stored bid responses, replace impid is false",
  1662  			mockBidderRequest: &openrtb2.BidRequest{
  1663  				Imp: nil,
  1664  				App: &openrtb2.App{},
  1665  			},
  1666  			bidderStoredResponses: map[string]json.RawMessage{
  1667  				"bidResponseId2": bidRespId2,
  1668  			},
  1669  			impReplaceImpId: map[string]bool{
  1670  				"bidResponseId2": false,
  1671  			},
  1672  			expectedBidIds: []string{"bid_id2_1", "bid_id2_2"},
  1673  			expectedImpIds: []string{"bid1impid1", "bid2impid2"},
  1674  		},
  1675  		{
  1676  			description: "Two imp with multiple stored bid responses, replace impid is true and false",
  1677  			mockBidderRequest: &openrtb2.BidRequest{
  1678  				Imp: nil,
  1679  				App: &openrtb2.App{},
  1680  			},
  1681  			bidderStoredResponses: map[string]json.RawMessage{
  1682  				"bidResponseId1": bidRespId1,
  1683  				"bidResponseId2": bidRespId2,
  1684  			},
  1685  			impReplaceImpId: map[string]bool{
  1686  				"bidResponseId1": true,
  1687  				"bidResponseId2": false,
  1688  			},
  1689  			expectedBidIds: []string{"bid_id2_1", "bid_id2_2", "bid_id1"},
  1690  			expectedImpIds: []string{"bid1impid1", "bid2impid2", "bidResponseId1"},
  1691  		},
  1692  	}
  1693  
  1694  	for _, tc := range testCases {
  1695  
  1696  		bidderImpl := &goodSingleBidderWithStoredBidResp{}
  1697  		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
  1698  		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1699  
  1700  		bidderReq := BidderRequest{
  1701  			BidRequest:            tc.mockBidderRequest,
  1702  			BidderName:            openrtb_ext.BidderAppnexus,
  1703  			BidderStoredResponses: tc.bidderStoredResponses,
  1704  			ImpReplaceImpId:       tc.impReplaceImpId,
  1705  		}
  1706  		bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
  1707  		seatBids, extraBidderRespInfo, _ := bidder.requestBid(
  1708  			context.Background(),
  1709  			bidderReq,
  1710  			currencyConverter.Rates(),
  1711  			&adapters.ExtraRequestInfo{},
  1712  			&adscert.NilSigner{},
  1713  			bidRequestOptions{
  1714  				accountDebugAllowed: true,
  1715  				headerDebugAllowed:  true,
  1716  				addCallSignHeader:   false,
  1717  				bidAdjustments:      bidAdjustments,
  1718  			},
  1719  			openrtb_ext.ExtAlternateBidderCodes{},
  1720  			&hookexecution.EmptyHookExecutor{},
  1721  			nil,
  1722  		)
  1723  		assert.Len(t, seatBids, 1)
  1724  		assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  1725  
  1726  		assert.Len(t, seatBids[0].Bids, len(tc.expectedBidIds), "Incorrect bids number for test case ", tc.description)
  1727  		for _, bid := range seatBids[0].Bids {
  1728  			assert.Contains(t, tc.expectedBidIds, bid.Bid.ID, tc.description)
  1729  			assert.Contains(t, tc.expectedImpIds, bid.Bid.ImpID, tc.description)
  1730  		}
  1731  	}
  1732  
  1733  }
  1734  
  1735  // TestFledge verifies that fledge responses from bidders are collected.
  1736  func TestFledge(t *testing.T) {
  1737  	respStatus := 200
  1738  	respBody := "{\"bid\":false}"
  1739  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  1740  	defer server.Close()
  1741  
  1742  	fledgeAuctionConfig1 := &openrtb_ext.FledgeAuctionConfig{
  1743  		ImpId:  "imp-id-1",
  1744  		Config: json.RawMessage("[1,2,3]"),
  1745  		Bidder: "openx",
  1746  	}
  1747  	fledgeAuctionConfig2 := &openrtb_ext.FledgeAuctionConfig{
  1748  		ImpId:  "imp-id-2",
  1749  		Config: json.RawMessage("[3,2,1]"),
  1750  		Bidder: "openx",
  1751  	}
  1752  
  1753  	testCases := []struct {
  1754  		mockBidderResponse []*adapters.BidderResponse
  1755  		expectedFledge     []*openrtb_ext.FledgeAuctionConfig
  1756  		description        string
  1757  	}{
  1758  		{
  1759  			mockBidderResponse: []*adapters.BidderResponse{
  1760  				{
  1761  					Bids:                 []*adapters.TypedBid{},
  1762  					FledgeAuctionConfigs: []*openrtb_ext.FledgeAuctionConfig{fledgeAuctionConfig1, fledgeAuctionConfig2},
  1763  				},
  1764  				nil,
  1765  			},
  1766  			expectedFledge: []*openrtb_ext.FledgeAuctionConfig{fledgeAuctionConfig1, fledgeAuctionConfig2},
  1767  			description:    "Collects FLEDGE auction configs from single bidder response",
  1768  		},
  1769  		{
  1770  			mockBidderResponse: []*adapters.BidderResponse{
  1771  				{
  1772  					Bids:                 []*adapters.TypedBid{},
  1773  					FledgeAuctionConfigs: []*openrtb_ext.FledgeAuctionConfig{fledgeAuctionConfig2},
  1774  				},
  1775  				{
  1776  					Bids:                 []*adapters.TypedBid{},
  1777  					FledgeAuctionConfigs: []*openrtb_ext.FledgeAuctionConfig{fledgeAuctionConfig1},
  1778  				},
  1779  			},
  1780  			expectedFledge: []*openrtb_ext.FledgeAuctionConfig{fledgeAuctionConfig1, fledgeAuctionConfig2},
  1781  			description:    "Collects FLEDGE auction configs from multiple bidder response",
  1782  		},
  1783  	}
  1784  
  1785  	for _, tc := range testCases {
  1786  		bidderImpl := &goodMultiHTTPCallsBidder{
  1787  			httpRequest: []*adapters.RequestData{
  1788  				{
  1789  					Method:  "POST",
  1790  					Uri:     server.URL,
  1791  					Body:    []byte("{\"key\":\"val1\"}"),
  1792  					Headers: http.Header{},
  1793  				},
  1794  				{
  1795  					Method:  "POST",
  1796  					Uri:     server.URL,
  1797  					Body:    []byte("{\"key\":\"val2\"}"),
  1798  					Headers: http.Header{},
  1799  				},
  1800  			},
  1801  			bidResponses: tc.mockBidderResponse,
  1802  		}
  1803  		bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderOpenx, nil, "")
  1804  		currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1805  
  1806  		bidderReq := BidderRequest{
  1807  			BidRequest: &openrtb2.BidRequest{
  1808  				Imp: []openrtb2.Imp{
  1809  					{
  1810  						ID:     "imp-id-1",
  1811  						Banner: &openrtb2.Banner{},
  1812  						Ext:    json.RawMessage(`{"ae": 1}`),
  1813  					},
  1814  					{
  1815  						ID:     "imp-id-2",
  1816  						Banner: &openrtb2.Banner{},
  1817  						Ext:    json.RawMessage(`{"ae": 1}`),
  1818  					},
  1819  				},
  1820  			},
  1821  			BidderName: "openx",
  1822  		}
  1823  		seatBids, extraBidderRespInfo, _ := bidder.requestBid(
  1824  			context.Background(),
  1825  			bidderReq,
  1826  			currencyConverter.Rates(),
  1827  			&adapters.ExtraRequestInfo{},
  1828  			&adscert.NilSigner{},
  1829  			bidRequestOptions{
  1830  				accountDebugAllowed: true,
  1831  				headerDebugAllowed:  true,
  1832  				addCallSignHeader:   false,
  1833  				bidAdjustments:      map[string]float64{"test": 1.0},
  1834  			},
  1835  			openrtb_ext.ExtAlternateBidderCodes{},
  1836  			&hookexecution.EmptyHookExecutor{},
  1837  			nil,
  1838  		)
  1839  		assert.Len(t, seatBids, 1)
  1840  		assert.NotNil(t, seatBids[0].FledgeAuctionConfigs)
  1841  		assert.Len(t, seatBids[0].FledgeAuctionConfigs, len(tc.expectedFledge))
  1842  
  1843  		assert.ElementsMatch(t, seatBids[0].FledgeAuctionConfigs, tc.expectedFledge)
  1844  		assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  1845  	}
  1846  }
  1847  
  1848  func TestErrorReporting(t *testing.T) {
  1849  	bidder := AdaptBidder(&bidRejector{}, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
  1850  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1851  	bidderReq := BidderRequest{
  1852  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
  1853  		BidderName: "test",
  1854  	}
  1855  	bidAdjustments := map[string]float64{"test": 1.0}
  1856  	bidReqOptions := bidRequestOptions{
  1857  		accountDebugAllowed: true,
  1858  		headerDebugAllowed:  false,
  1859  		addCallSignHeader:   false,
  1860  		bidAdjustments:      bidAdjustments,
  1861  	}
  1862  	bids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
  1863  	if bids != nil {
  1864  		t.Errorf("There should be no seatbid if no http requests are returned.")
  1865  	}
  1866  	if len(errs) != 1 {
  1867  		t.Fatalf("Expected 1 error. got %d", len(errs))
  1868  	}
  1869  	if errs[0].Error() != "Invalid params on BidRequest." {
  1870  		t.Errorf(`Error message was mutated. Expected "%s", Got "%s"`, "Invalid params on BidRequest.", errs[0].Error())
  1871  	}
  1872  	assert.True(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  1873  }
  1874  
  1875  func TestSetAssetTypes(t *testing.T) {
  1876  	testCases := []struct {
  1877  		respAsset   nativeResponse.Asset
  1878  		nativeReq   nativeRequests.Request
  1879  		expectedErr string
  1880  		desc        string
  1881  	}{
  1882  		{
  1883  			respAsset: nativeResponse.Asset{
  1884  				ID: ptrutil.ToPtr[int64](1),
  1885  				Img: &nativeResponse.Image{
  1886  					URL: "http://some-url",
  1887  				},
  1888  			},
  1889  			nativeReq: nativeRequests.Request{
  1890  				Assets: []nativeRequests.Asset{
  1891  					{
  1892  						ID: 1,
  1893  						Img: &nativeRequests.Image{
  1894  							Type: 2,
  1895  						},
  1896  					},
  1897  					{
  1898  						ID: 2,
  1899  						Data: &nativeRequests.Data{
  1900  							Type: 4,
  1901  						},
  1902  					},
  1903  				},
  1904  			},
  1905  			expectedErr: "",
  1906  			desc:        "Matching image asset exists in the request and asset type is set correctly",
  1907  		},
  1908  		{
  1909  			respAsset: nativeResponse.Asset{
  1910  				ID: ptrutil.ToPtr[int64](2),
  1911  				Data: &nativeResponse.Data{
  1912  					Label: "some label",
  1913  				},
  1914  			},
  1915  			nativeReq: nativeRequests.Request{
  1916  				Assets: []nativeRequests.Asset{
  1917  					{
  1918  						ID: 1,
  1919  						Img: &nativeRequests.Image{
  1920  							Type: 2,
  1921  						},
  1922  					},
  1923  					{
  1924  						ID: 2,
  1925  						Data: &nativeRequests.Data{
  1926  							Type: 4,
  1927  						},
  1928  					},
  1929  				},
  1930  			},
  1931  			expectedErr: "",
  1932  			desc:        "Matching data asset exists in the request and asset type is set correctly",
  1933  		},
  1934  		{
  1935  			respAsset: nativeResponse.Asset{
  1936  				ID: ptrutil.ToPtr[int64](1),
  1937  				Img: &nativeResponse.Image{
  1938  					URL: "http://some-url",
  1939  				},
  1940  			},
  1941  			nativeReq: nativeRequests.Request{
  1942  				Assets: []nativeRequests.Asset{
  1943  					{
  1944  						ID: 2,
  1945  						Img: &nativeRequests.Image{
  1946  							Type: 2,
  1947  						},
  1948  					},
  1949  				},
  1950  			},
  1951  			expectedErr: "Unable to find asset with ID:1 in the request",
  1952  			desc:        "Matching image asset with the same ID doesn't exist in the request",
  1953  		},
  1954  		{
  1955  			respAsset: nativeResponse.Asset{
  1956  				ID: ptrutil.ToPtr[int64](2),
  1957  				Data: &nativeResponse.Data{
  1958  					Label: "some label",
  1959  				},
  1960  			},
  1961  			nativeReq: nativeRequests.Request{
  1962  				Assets: []nativeRequests.Asset{
  1963  					{
  1964  						ID: 2,
  1965  						Img: &nativeRequests.Image{
  1966  							Type: 2,
  1967  						},
  1968  					},
  1969  				},
  1970  			},
  1971  			expectedErr: "Response has a Data asset with ID:2 present that doesn't exist in the request",
  1972  			desc:        "Assets with same ID in the req and resp are of different types",
  1973  		},
  1974  		{
  1975  			respAsset: nativeResponse.Asset{
  1976  				ID: ptrutil.ToPtr[int64](1),
  1977  				Img: &nativeResponse.Image{
  1978  					URL: "http://some-url",
  1979  				},
  1980  			},
  1981  			nativeReq: nativeRequests.Request{
  1982  				Assets: []nativeRequests.Asset{
  1983  					{
  1984  						ID: 1,
  1985  						Data: &nativeRequests.Data{
  1986  							Type: 2,
  1987  						},
  1988  					},
  1989  				},
  1990  			},
  1991  			expectedErr: "Response has an Image asset with ID:1 present that doesn't exist in the request",
  1992  			desc:        "Assets with same ID in the req and resp are of different types",
  1993  		},
  1994  		{
  1995  			respAsset: nativeResponse.Asset{
  1996  				Img: &nativeResponse.Image{
  1997  					URL: "http://some-url",
  1998  				},
  1999  			},
  2000  			nativeReq: nativeRequests.Request{
  2001  				Assets: []nativeRequests.Asset{
  2002  					{
  2003  						ID: 1,
  2004  						Img: &nativeRequests.Image{
  2005  							Type: 2,
  2006  						},
  2007  					},
  2008  				},
  2009  			},
  2010  			expectedErr: "Response Image asset doesn't have an ID",
  2011  			desc:        "Response Image without an ID",
  2012  		},
  2013  		{
  2014  			respAsset: nativeResponse.Asset{
  2015  				Data: &nativeResponse.Data{
  2016  					Label: "some label",
  2017  				},
  2018  			},
  2019  			nativeReq: nativeRequests.Request{
  2020  				Assets: []nativeRequests.Asset{
  2021  					{
  2022  						ID: 1,
  2023  						Data: &nativeRequests.Data{
  2024  							Type: 2,
  2025  						},
  2026  					},
  2027  				},
  2028  			},
  2029  			expectedErr: "Response Data asset doesn't have an ID",
  2030  			desc:        "Response Data asset without an ID",
  2031  		},
  2032  	}
  2033  
  2034  	for _, test := range testCases {
  2035  		err := setAssetTypes(test.respAsset, test.nativeReq)
  2036  		if len(test.expectedErr) != 0 {
  2037  			assert.EqualError(t, err, test.expectedErr, "Test Case: %s", test.desc)
  2038  			continue
  2039  		} else {
  2040  			assert.NoError(t, err, "Test Case: %s", test.desc)
  2041  		}
  2042  
  2043  		for _, asset := range test.nativeReq.Assets {
  2044  			if asset.Img != nil && test.respAsset.Img != nil {
  2045  				assert.Equal(t, asset.Img.Type, test.respAsset.Img.Type, "Asset type not set correctly. Test Case: %s", test.desc)
  2046  			}
  2047  			if asset.Data != nil && test.respAsset.Data != nil {
  2048  				assert.Equal(t, asset.Data.Type, test.respAsset.Data.Type, "Asset type not set correctly. Test Case: %s", test.desc)
  2049  			}
  2050  		}
  2051  	}
  2052  }
  2053  
  2054  func TestCallRecordAdapterConnections(t *testing.T) {
  2055  	// Setup mock server
  2056  	respStatus := 200
  2057  	respBody := "{\"bid\":false}"
  2058  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  2059  	defer server.Close()
  2060  
  2061  	// declare requestBid parameters
  2062  	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0}
  2063  
  2064  	bidderImpl := &goodSingleBidder{
  2065  		httpRequest: &adapters.RequestData{
  2066  			Method:  "POST",
  2067  			Uri:     server.URL,
  2068  			Body:    []byte("{\"key\":\"val\"}"),
  2069  			Headers: http.Header{},
  2070  		},
  2071  		bidResponse: &adapters.BidderResponse{},
  2072  	}
  2073  
  2074  	// setup a mock mockMetricEngine engine and its expectation
  2075  	mockMetricEngine := &metrics.MetricsEngineMock{}
  2076  	expectedAdapterName := openrtb_ext.BidderAppnexus
  2077  	compareConnWaitTime := func(dur time.Duration) bool { return dur.Nanoseconds() > 0 }
  2078  
  2079  	mockMetricEngine.On("RecordAdapterConnections", expectedAdapterName, false, mock.MatchedBy(compareConnWaitTime)).Once()
  2080  	mockMetricEngine.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once()
  2081  	mockMetricEngine.On("RecordBidderServerResponseTime", mock.Anything).Once()
  2082  
  2083  	// Run requestBid using an http.Client with a mock handler
  2084  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, mockMetricEngine, openrtb_ext.BidderAppnexus, nil, "")
  2085  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2086  
  2087  	bidderReq := BidderRequest{
  2088  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
  2089  		BidderName: openrtb_ext.BidderAppnexus,
  2090  	}
  2091  	bidReqOptions := bidRequestOptions{
  2092  		accountDebugAllowed: true,
  2093  		headerDebugAllowed:  true,
  2094  		addCallSignHeader:   false,
  2095  		bidAdjustments:      bidAdjustments,
  2096  	}
  2097  	_, _, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{PbsEntryPoint: metrics.ReqTypeORTB2Web}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
  2098  
  2099  	// Assert no errors
  2100  	assert.Equal(t, 0, len(errs), "bidder.requestBid returned errors %v \n", errs)
  2101  
  2102  	// Assert RecordAdapterConnections() was called with the parameters we expected
  2103  	mockMetricEngine.AssertExpectations(t)
  2104  }
  2105  
  2106  type DNSDoneTripper struct{}
  2107  
  2108  func (DNSDoneTripper) RoundTrip(req *http.Request) (*http.Response, error) {
  2109  	// Access the httptrace.ClientTrace
  2110  	trace := httptrace.ContextClientTrace(req.Context())
  2111  	// Call the DNSDone method on the client trace
  2112  	trace.DNSDone(httptrace.DNSDoneInfo{})
  2113  
  2114  	resp := &http.Response{
  2115  		StatusCode: 200,
  2116  		Body:       io.NopCloser(strings.NewReader("postBody")),
  2117  	}
  2118  
  2119  	return resp, nil
  2120  }
  2121  
  2122  type TLSHandshakeTripper struct{}
  2123  
  2124  func (TLSHandshakeTripper) RoundTrip(req *http.Request) (*http.Response, error) {
  2125  	// Access the httptrace.ClientTrace
  2126  	trace := httptrace.ContextClientTrace(req.Context())
  2127  	// Call the TLSHandshakeDone method on the client trace
  2128  	trace.TLSHandshakeDone(tls.ConnectionState{}, nil)
  2129  
  2130  	resp := &http.Response{
  2131  		StatusCode: 200,
  2132  		Body:       io.NopCloser(strings.NewReader("postBody")),
  2133  	}
  2134  
  2135  	return resp, nil
  2136  }
  2137  
  2138  func TestCallRecordDNSTime(t *testing.T) {
  2139  	// setup a mock metrics engine and its expectation
  2140  	metricsMock := &metrics.MetricsEngineMock{}
  2141  	metricsMock.Mock.On("RecordDNSTime", mock.Anything).Return()
  2142  	metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once()
  2143  	metricsMock.On("RecordBidderServerResponseTime", mock.Anything).Once()
  2144  
  2145  	// Instantiate the bidder that will send the request. We'll make sure to use an
  2146  	// http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{})
  2147  	// gets called
  2148  	bidder := &bidderAdapter{
  2149  		Bidder: &mixedMultiBidder{},
  2150  		Client: &http.Client{Transport: DNSDoneTripper{}},
  2151  		me:     metricsMock,
  2152  	}
  2153  	tmaxAdjustments := &TmaxAdjustmentsPreprocessed{}
  2154  
  2155  	// Run test
  2156  	bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}, time.Now(), tmaxAdjustments)
  2157  
  2158  	// Tried one or another, none seem to work without panicking
  2159  	metricsMock.AssertExpectations(t)
  2160  }
  2161  
  2162  func TestCallRecordTLSHandshakeTime(t *testing.T) {
  2163  	// setup a mock metrics engine and its expectation
  2164  	metricsMock := &metrics.MetricsEngineMock{}
  2165  	metricsMock.Mock.On("RecordTLSHandshakeTime", mock.Anything).Return()
  2166  	metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once()
  2167  	metricsMock.On("RecordBidderServerResponseTime", mock.Anything).Once()
  2168  
  2169  	// Instantiate the bidder that will send the request. We'll make sure to use an
  2170  	// http.Client that runs our mock RoundTripper so DNSDone(httptrace.DNSDoneInfo{})
  2171  	// gets called
  2172  	bidder := &bidderAdapter{
  2173  		Bidder: &mixedMultiBidder{},
  2174  		Client: &http.Client{Transport: TLSHandshakeTripper{}},
  2175  		me:     metricsMock,
  2176  	}
  2177  	tmaxAdjustments := &TmaxAdjustmentsPreprocessed{}
  2178  
  2179  	// Run test
  2180  	bidder.doRequest(context.Background(), &adapters.RequestData{Method: "POST", Uri: "http://www.example.com/"}, time.Now(), tmaxAdjustments)
  2181  
  2182  	// Tried one or another, none seem to work without panicking
  2183  	metricsMock.AssertExpectations(t)
  2184  }
  2185  
  2186  func TestTimeoutNotificationOff(t *testing.T) {
  2187  	respBody := "{\"bid\":false}"
  2188  	respStatus := 200
  2189  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  2190  	defer server.Close()
  2191  
  2192  	bidderImpl := &notifyingBidder{
  2193  		notifyRequest: adapters.RequestData{
  2194  			Method:  "GET",
  2195  			Uri:     server.URL + "/notify/me",
  2196  			Body:    nil,
  2197  			Headers: http.Header{},
  2198  		},
  2199  	}
  2200  	bidder := &bidderAdapter{
  2201  		Bidder: bidderImpl,
  2202  		Client: server.Client(),
  2203  		config: bidderAdapterConfig{Debug: config.Debug{}},
  2204  		me:     &metricsConfig.NilMetricsEngine{},
  2205  	}
  2206  	if tb, ok := bidder.Bidder.(adapters.TimeoutBidder); !ok {
  2207  		t.Error("Failed to cast bidder to a TimeoutBidder")
  2208  	} else {
  2209  		bidder.doTimeoutNotification(tb, &adapters.RequestData{}, glog.Warningf)
  2210  	}
  2211  }
  2212  
  2213  func TestTimeoutNotificationOn(t *testing.T) {
  2214  	// Expire context immediately to force timeout handler.
  2215  	ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now())
  2216  	cancelFunc()
  2217  
  2218  	// Notification logic is hardcoded for 200ms. We need to wait for a little longer than that.
  2219  	server := httptest.NewServer(mockSlowHandler(205*time.Millisecond, 200, `{"bid":false}`))
  2220  	defer server.Close()
  2221  
  2222  	bidder := &notifyingBidder{
  2223  		notifyRequest: adapters.RequestData{
  2224  			Method:  "GET",
  2225  			Uri:     server.URL + "/notify/me",
  2226  			Body:    nil,
  2227  			Headers: http.Header{},
  2228  		},
  2229  	}
  2230  
  2231  	// Wrap with BidderInfo to mimic exchange.go flow.
  2232  	bidderWrappedWithInfo := wrapWithBidderInfo(bidder)
  2233  
  2234  	bidderAdapter := &bidderAdapter{
  2235  		Bidder: bidderWrappedWithInfo,
  2236  		Client: server.Client(),
  2237  		config: bidderAdapterConfig{
  2238  			Debug: config.Debug{
  2239  				TimeoutNotification: config.TimeoutNotification{
  2240  					Log:          true,
  2241  					SamplingRate: 1.0,
  2242  				},
  2243  			},
  2244  		},
  2245  		me: &metricsConfig.NilMetricsEngine{},
  2246  	}
  2247  
  2248  	// Unwrap To Mimic exchange.go Casting Code
  2249  	var coreBidder adapters.Bidder = bidderAdapter.Bidder
  2250  	if b, ok := coreBidder.(*adapters.InfoAwareBidder); ok {
  2251  		coreBidder = b.Bidder
  2252  	}
  2253  	if _, ok := coreBidder.(adapters.TimeoutBidder); !ok {
  2254  		t.Fatal("Failed to cast bidder to a TimeoutBidder")
  2255  	}
  2256  
  2257  	bidRequest := adapters.RequestData{
  2258  		Method: "POST",
  2259  		Uri:    server.URL,
  2260  		Body:   []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`),
  2261  	}
  2262  
  2263  	var loggerBuffer bytes.Buffer
  2264  	logger := func(msg string, args ...interface{}) {
  2265  		loggerBuffer.WriteString(fmt.Sprintf(fmt.Sprintln(msg), args...))
  2266  	}
  2267  	tmaxAdjustments := &TmaxAdjustmentsPreprocessed{}
  2268  	bidderAdapter.doRequestImpl(ctx, &bidRequest, logger, time.Now(), tmaxAdjustments)
  2269  
  2270  	// Wait a little longer than the 205ms mock server sleep.
  2271  	time.Sleep(210 * time.Millisecond)
  2272  
  2273  	logExpected := "TimeoutNotification: error:(context deadline exceeded) body:\n"
  2274  	logActual := loggerBuffer.String()
  2275  	assert.EqualValues(t, logExpected, logActual)
  2276  }
  2277  
  2278  func TestParseDebugInfoTrue(t *testing.T) {
  2279  	debugInfo := &config.DebugInfo{Allow: true}
  2280  	resDebugInfo := parseDebugInfo(debugInfo)
  2281  	assert.True(t, resDebugInfo, "Debug Allow value should be true")
  2282  }
  2283  
  2284  func TestParseDebugInfoFalse(t *testing.T) {
  2285  	debugInfo := &config.DebugInfo{Allow: false}
  2286  	resDebugInfo := parseDebugInfo(debugInfo)
  2287  	assert.False(t, resDebugInfo, "Debug Allow value should be false")
  2288  }
  2289  
  2290  func TestParseDebugInfoIsNil(t *testing.T) {
  2291  	resDebugInfo := parseDebugInfo(nil)
  2292  	assert.True(t, resDebugInfo, "Debug Allow value should be true")
  2293  }
  2294  
  2295  func TestPrepareStoredResponse(t *testing.T) {
  2296  	result := prepareStoredResponse("imp_id1", json.RawMessage(`{"id": "resp_id1"}`))
  2297  	assert.Equal(t, []byte(ImpIdReqBody+"imp_id1"), result.request.Body, "incorrect request body")
  2298  	assert.Equal(t, []byte(`{"id": "resp_id1"}`), result.response.Body, "incorrect response body")
  2299  }
  2300  
  2301  func TestRequestBidsWithAdsCertsSigner(t *testing.T) {
  2302  	respStatus := 200
  2303  	respBody := `{"bid":false}`
  2304  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  2305  	defer server.Close()
  2306  
  2307  	requestHeaders := http.Header{}
  2308  	requestHeaders.Add("Content-Type", "application/json")
  2309  
  2310  	bidderImpl := &goodSingleBidder{
  2311  		httpRequest: &adapters.RequestData{
  2312  			Method:  "POST",
  2313  			Uri:     server.URL,
  2314  			Body:    []byte(`{"key":"val"}`),
  2315  			Headers: http.Header{},
  2316  		},
  2317  		bidResponse: nil,
  2318  	}
  2319  	bidderImpl.bidResponse = &adapters.BidderResponse{
  2320  		Bids: []*adapters.TypedBid{
  2321  			{
  2322  				Bid: &openrtb2.Bid{
  2323  					ID: "bidId",
  2324  				},
  2325  				BidType:      openrtb_ext.BidTypeBanner,
  2326  				DealPriority: 4,
  2327  			},
  2328  		},
  2329  	}
  2330  
  2331  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false}, "")
  2332  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2333  
  2334  	bidderReq := BidderRequest{
  2335  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
  2336  		BidderName: "test",
  2337  	}
  2338  	ctx := context.Background()
  2339  	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0}
  2340  	bidReqOptions := bidRequestOptions{
  2341  		accountDebugAllowed: false,
  2342  		headerDebugAllowed:  false,
  2343  		addCallSignHeader:   true,
  2344  		bidAdjustments:      bidAdjustments,
  2345  	}
  2346  	_, _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
  2347  
  2348  	assert.Empty(t, errs, "no errors should be returned")
  2349  }
  2350  
  2351  func wrapWithBidderInfo(bidder adapters.Bidder) adapters.Bidder {
  2352  	bidderInfo := config.BidderInfo{
  2353  		Disabled: false,
  2354  		Capabilities: &config.CapabilitiesInfo{
  2355  			App: &config.PlatformInfo{
  2356  				MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner},
  2357  			},
  2358  		},
  2359  	}
  2360  	return adapters.BuildInfoAwareBidder(bidder, bidderInfo)
  2361  }
  2362  
  2363  type goodSingleBidder struct {
  2364  	bidRequest            *openrtb2.BidRequest
  2365  	httpRequest           *adapters.RequestData
  2366  	httpResponse          *adapters.ResponseData
  2367  	bidResponse           *adapters.BidderResponse
  2368  	hasStoredBidResponses bool
  2369  }
  2370  
  2371  func (bidder *goodSingleBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  2372  	bidder.bidRequest = request
  2373  	return []*adapters.RequestData{bidder.httpRequest}, nil
  2374  }
  2375  
  2376  func (bidder *goodSingleBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
  2377  	bidder.httpResponse = response
  2378  	return bidder.bidResponse, nil
  2379  }
  2380  
  2381  type goodSingleBidderWithStoredBidResp struct {
  2382  }
  2383  
  2384  func (bidder *goodSingleBidderWithStoredBidResp) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  2385  	return nil, nil
  2386  }
  2387  
  2388  func (bidder *goodSingleBidderWithStoredBidResp) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
  2389  	var bidResp openrtb2.BidResponse
  2390  	if err := jsonutil.UnmarshalValid(response.Body, &bidResp); err != nil {
  2391  		return nil, []error{err}
  2392  	}
  2393  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
  2394  
  2395  	for _, sb := range bidResp.SeatBid {
  2396  		for i := range sb.Bid {
  2397  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
  2398  				Bid:     &sb.Bid[i],
  2399  				BidType: openrtb_ext.BidTypeVideo,
  2400  			})
  2401  		}
  2402  	}
  2403  	return bidResponse, nil
  2404  }
  2405  
  2406  type goodMultiHTTPCallsBidder struct {
  2407  	bidRequest        *openrtb2.BidRequest
  2408  	httpRequest       []*adapters.RequestData
  2409  	httpResponses     []*adapters.ResponseData
  2410  	bidResponses      []*adapters.BidderResponse
  2411  	bidResponseNumber int
  2412  }
  2413  
  2414  func (bidder *goodMultiHTTPCallsBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  2415  	bidder.bidRequest = request
  2416  	response := make([]*adapters.RequestData, len(bidder.httpRequest))
  2417  	copy(response, bidder.httpRequest)
  2418  	return response, nil
  2419  }
  2420  
  2421  func (bidder *goodMultiHTTPCallsBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
  2422  	br := bidder.bidResponses[bidder.bidResponseNumber]
  2423  	bidder.bidResponseNumber++
  2424  	bidder.httpResponses = append(bidder.httpResponses, response)
  2425  
  2426  	return br, nil
  2427  }
  2428  
  2429  type mixedMultiBidder struct {
  2430  	bidRequest    *openrtb2.BidRequest
  2431  	httpRequests  []*adapters.RequestData
  2432  	httpResponses []*adapters.ResponseData
  2433  	bidResponse   *adapters.BidderResponse
  2434  }
  2435  
  2436  func (bidder *mixedMultiBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  2437  	bidder.bidRequest = request
  2438  	return bidder.httpRequests, []error{errors.New("The requests weren't ideal.")}
  2439  }
  2440  
  2441  func (bidder *mixedMultiBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
  2442  	bidder.httpResponses = append(bidder.httpResponses, response)
  2443  	return bidder.bidResponse, []error{errors.New("The bidResponse weren't ideal.")}
  2444  }
  2445  
  2446  type bidRejector struct {
  2447  	httpResponse *adapters.ResponseData
  2448  }
  2449  
  2450  func (bidder *bidRejector) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  2451  	return nil, []error{errors.New("Invalid params on BidRequest.")}
  2452  }
  2453  
  2454  func (bidder *bidRejector) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
  2455  	bidder.httpResponse = response
  2456  	return nil, []error{errors.New("Can't make a response.")}
  2457  }
  2458  
  2459  type notifyingBidder struct {
  2460  	requests      []*adapters.RequestData
  2461  	notifyRequest adapters.RequestData
  2462  }
  2463  
  2464  func (bidder *notifyingBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  2465  	return bidder.requests, nil
  2466  }
  2467  
  2468  func (bidder *notifyingBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
  2469  	return nil, nil
  2470  }
  2471  
  2472  func (bidder *notifyingBidder) MakeTimeoutNotification(req *adapters.RequestData) (*adapters.RequestData, []error) {
  2473  	return &bidder.notifyRequest, nil
  2474  }
  2475  
  2476  func TestExtraBid(t *testing.T) {
  2477  	respStatus := 200
  2478  	respBody := "{\"bid\":false}"
  2479  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  2480  	defer server.Close()
  2481  
  2482  	requestHeaders := http.Header{}
  2483  	requestHeaders.Add("Content-Type", "application/json")
  2484  
  2485  	bidderImpl := &goodSingleBidder{
  2486  		httpRequest: &adapters.RequestData{
  2487  			Method:  "POST",
  2488  			Uri:     server.URL,
  2489  			Body:    []byte("{\"key\":\"val\"}"),
  2490  			Headers: http.Header{},
  2491  		},
  2492  		bidResponse: &adapters.BidderResponse{
  2493  			Bids: []*adapters.TypedBid{
  2494  				{
  2495  					Bid: &openrtb2.Bid{
  2496  						ID: "pubmaticImp1",
  2497  					},
  2498  					BidType:      openrtb_ext.BidTypeBanner,
  2499  					DealPriority: 4,
  2500  					Seat:         "pubmatic",
  2501  				},
  2502  				{
  2503  					Bid: &openrtb2.Bid{
  2504  						ID: "groupmImp1",
  2505  					},
  2506  					BidType:      openrtb_ext.BidTypeVideo,
  2507  					DealPriority: 5,
  2508  					Seat:         "groupm",
  2509  				},
  2510  			},
  2511  		},
  2512  	}
  2513  
  2514  	wantSeatBids := []*entities.PbsOrtbSeatBid{
  2515  		{
  2516  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2517  			Bids: []*entities.PbsOrtbBid{{
  2518  				Bid:            &openrtb2.Bid{ID: "groupmImp1"},
  2519  				DealPriority:   5,
  2520  				BidType:        openrtb_ext.BidTypeVideo,
  2521  				OriginalBidCur: "USD",
  2522  			}},
  2523  			Seat:     "groupm",
  2524  			Currency: "USD",
  2525  		},
  2526  		{
  2527  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2528  			Bids: []*entities.PbsOrtbBid{{
  2529  				Bid:            &openrtb2.Bid{ID: "pubmaticImp1"},
  2530  				DealPriority:   4,
  2531  				BidType:        openrtb_ext.BidTypeBanner,
  2532  				OriginalBidCur: "USD",
  2533  			}},
  2534  			Seat:     string(openrtb_ext.BidderPubmatic),
  2535  			Currency: "USD",
  2536  		},
  2537  	}
  2538  
  2539  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
  2540  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2541  
  2542  	bidderReq := BidderRequest{
  2543  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
  2544  		BidderName: openrtb_ext.BidderPubmatic,
  2545  	}
  2546  
  2547  	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0}
  2548  	bidReqOptions := bidRequestOptions{
  2549  		accountDebugAllowed: false,
  2550  		headerDebugAllowed:  false,
  2551  		addCallSignHeader:   true,
  2552  		bidAdjustments:      bidAdjustments,
  2553  	}
  2554  
  2555  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
  2556  		openrtb_ext.ExtAlternateBidderCodes{
  2557  			Enabled: true,
  2558  			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
  2559  				string(openrtb_ext.BidderPubmatic): {
  2560  					Enabled:            true,
  2561  					AllowedBidderCodes: []string{"groupm"},
  2562  				},
  2563  			},
  2564  		},
  2565  		&hookexecution.EmptyHookExecutor{},
  2566  		nil)
  2567  	assert.Nil(t, errs)
  2568  	assert.Len(t, seatBids, 2)
  2569  	sort.Slice(seatBids, func(i, j int) bool {
  2570  		return len(seatBids[i].Seat) < len(seatBids[j].Seat)
  2571  	})
  2572  	assert.Equal(t, wantSeatBids, seatBids)
  2573  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  2574  }
  2575  
  2576  func TestExtraBidWithAlternateBidderCodeDisabled(t *testing.T) {
  2577  	respStatus := 200
  2578  	respBody := "{\"bid\":false}"
  2579  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  2580  	defer server.Close()
  2581  
  2582  	requestHeaders := http.Header{}
  2583  	requestHeaders.Add("Content-Type", "application/json")
  2584  
  2585  	bidderImpl := &goodSingleBidder{
  2586  		httpRequest: &adapters.RequestData{
  2587  			Method:  "POST",
  2588  			Uri:     server.URL,
  2589  			Body:    []byte("{\"key\":\"val\"}"),
  2590  			Headers: http.Header{},
  2591  		},
  2592  		bidResponse: &adapters.BidderResponse{
  2593  			Bids: []*adapters.TypedBid{
  2594  				{
  2595  					Bid: &openrtb2.Bid{
  2596  						ID: "pubmaticImp1",
  2597  					},
  2598  					BidType:      openrtb_ext.BidTypeBanner,
  2599  					DealPriority: 4,
  2600  					Seat:         "pubmatic",
  2601  				},
  2602  				{
  2603  					Bid: &openrtb2.Bid{
  2604  						ID: "groupmImp1",
  2605  					},
  2606  					BidType:      openrtb_ext.BidTypeVideo,
  2607  					DealPriority: 5,
  2608  					Seat:         "groupm-rejected",
  2609  				},
  2610  				{
  2611  					Bid: &openrtb2.Bid{
  2612  						ID: "groupmImp2",
  2613  					},
  2614  					BidType:      openrtb_ext.BidTypeVideo,
  2615  					DealPriority: 5,
  2616  					Seat:         "groupm-allowed",
  2617  				},
  2618  			},
  2619  		},
  2620  	}
  2621  
  2622  	wantSeatBids := []*entities.PbsOrtbSeatBid{
  2623  		{
  2624  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2625  			Bids: []*entities.PbsOrtbBid{{
  2626  				Bid:            &openrtb2.Bid{ID: "groupmImp2"},
  2627  				DealPriority:   5,
  2628  				BidType:        openrtb_ext.BidTypeVideo,
  2629  				OriginalBidCur: "USD",
  2630  			}},
  2631  			Seat:     "groupm-allowed",
  2632  			Currency: "USD",
  2633  		},
  2634  		{
  2635  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2636  			Bids: []*entities.PbsOrtbBid{{
  2637  				Bid:            &openrtb2.Bid{ID: "pubmaticImp1"},
  2638  				DealPriority:   4,
  2639  				BidType:        openrtb_ext.BidTypeBanner,
  2640  				OriginalBidCur: "USD",
  2641  			}},
  2642  			Seat:     string(openrtb_ext.BidderPubmatic),
  2643  			Currency: "USD",
  2644  		},
  2645  	}
  2646  	wantErrs := []error{
  2647  		&errortypes.Warning{
  2648  			WarningCode: errortypes.AlternateBidderCodeWarningCode,
  2649  			Message:     `invalid biddercode "groupm-rejected" sent by adapter "pubmatic"`,
  2650  		},
  2651  	}
  2652  
  2653  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
  2654  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2655  
  2656  	bidderReq := BidderRequest{
  2657  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
  2658  		BidderName: openrtb_ext.BidderPubmatic,
  2659  	}
  2660  	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0}
  2661  	bidReqOptions := bidRequestOptions{
  2662  		accountDebugAllowed: false,
  2663  		headerDebugAllowed:  false,
  2664  		addCallSignHeader:   true,
  2665  		bidAdjustments:      bidAdjustments,
  2666  	}
  2667  
  2668  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
  2669  		openrtb_ext.ExtAlternateBidderCodes{
  2670  			Enabled: true,
  2671  			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
  2672  				string(openrtb_ext.BidderPubmatic): {
  2673  					Enabled:            true,
  2674  					AllowedBidderCodes: []string{"groupm-allowed"},
  2675  				},
  2676  			},
  2677  		},
  2678  		&hookexecution.EmptyHookExecutor{},
  2679  		nil)
  2680  	assert.Equal(t, wantErrs, errs)
  2681  	assert.Len(t, seatBids, 2)
  2682  	assert.ElementsMatch(t, wantSeatBids, seatBids)
  2683  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  2684  }
  2685  
  2686  func TestExtraBidWithBidAdjustments(t *testing.T) {
  2687  	respStatus := 200
  2688  	respBody := "{\"bid\":false}"
  2689  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  2690  	defer server.Close()
  2691  
  2692  	requestHeaders := http.Header{}
  2693  	requestHeaders.Add("Content-Type", "application/json")
  2694  
  2695  	bidderImpl := &goodSingleBidder{
  2696  		httpRequest: &adapters.RequestData{
  2697  			Method:  "POST",
  2698  			Uri:     server.URL,
  2699  			Body:    []byte("{\"key\":\"val\"}"),
  2700  			Headers: http.Header{},
  2701  		},
  2702  		bidResponse: &adapters.BidderResponse{
  2703  			Bids: []*adapters.TypedBid{
  2704  				{
  2705  					Bid: &openrtb2.Bid{
  2706  						ID:    "pubmaticImp1",
  2707  						Price: 3,
  2708  					},
  2709  					BidType:      openrtb_ext.BidTypeBanner,
  2710  					DealPriority: 4,
  2711  					Seat:         "PUBMATIC",
  2712  				},
  2713  				{
  2714  					Bid: &openrtb2.Bid{
  2715  						ID:    "groupmImp1",
  2716  						Price: 7,
  2717  					},
  2718  					BidType:      openrtb_ext.BidTypeVideo,
  2719  					DealPriority: 5,
  2720  					Seat:         "groupm",
  2721  				},
  2722  			},
  2723  		},
  2724  	}
  2725  
  2726  	wantSeatBids := []*entities.PbsOrtbSeatBid{
  2727  		{
  2728  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2729  			Bids: []*entities.PbsOrtbBid{{
  2730  				Bid: &openrtb2.Bid{
  2731  					ID:    "groupmImp1",
  2732  					Price: 21,
  2733  				},
  2734  				DealPriority:   5,
  2735  				BidType:        openrtb_ext.BidTypeVideo,
  2736  				OriginalBidCPM: 7,
  2737  				OriginalBidCur: "USD",
  2738  			}},
  2739  			Seat:     "groupm",
  2740  			Currency: "USD",
  2741  		},
  2742  		{
  2743  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2744  			Bids: []*entities.PbsOrtbBid{{
  2745  				Bid: &openrtb2.Bid{
  2746  					ID:    "pubmaticImp1",
  2747  					Price: 6,
  2748  				},
  2749  				DealPriority:   4,
  2750  				BidType:        openrtb_ext.BidTypeBanner,
  2751  				OriginalBidCur: "USD",
  2752  				OriginalBidCPM: 3,
  2753  			}},
  2754  			Seat:     "PUBMATIC",
  2755  			Currency: "USD",
  2756  		},
  2757  	}
  2758  
  2759  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
  2760  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2761  
  2762  	bidderReq := BidderRequest{
  2763  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
  2764  		BidderName: "PUBMATIC",
  2765  	}
  2766  	bidAdjustments := map[string]float64{
  2767  		string(openrtb_ext.BidderPubmatic): 2, // All lowercase value in bid adjustments to simulate it being case insensitive
  2768  		"groupm":                           3,
  2769  	}
  2770  
  2771  	bidReqOptions := bidRequestOptions{
  2772  		accountDebugAllowed: false,
  2773  		headerDebugAllowed:  false,
  2774  		addCallSignHeader:   true,
  2775  		bidAdjustments:      bidAdjustments,
  2776  	}
  2777  
  2778  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
  2779  		openrtb_ext.ExtAlternateBidderCodes{
  2780  			Enabled: true,
  2781  			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
  2782  				"PUBMATIC": {
  2783  					Enabled:            true,
  2784  					AllowedBidderCodes: []string{"groupm"},
  2785  				},
  2786  			},
  2787  		},
  2788  		&hookexecution.EmptyHookExecutor{},
  2789  		nil)
  2790  	assert.Nil(t, errs)
  2791  	assert.Len(t, seatBids, 2)
  2792  	sort.Slice(seatBids, func(i, j int) bool {
  2793  		return len(seatBids[i].Seat) < len(seatBids[j].Seat)
  2794  	})
  2795  	assert.Equal(t, wantSeatBids, seatBids)
  2796  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  2797  }
  2798  
  2799  func TestExtraBidWithBidAdjustmentsUsingAdapterCode(t *testing.T) {
  2800  	respStatus := 200
  2801  	respBody := "{\"bid\":false}"
  2802  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  2803  	defer server.Close()
  2804  
  2805  	requestHeaders := http.Header{}
  2806  	requestHeaders.Add("Content-Type", "application/json")
  2807  
  2808  	bidderImpl := &goodSingleBidder{
  2809  		httpRequest: &adapters.RequestData{
  2810  			Method:  "POST",
  2811  			Uri:     server.URL,
  2812  			Body:    []byte("{\"key\":\"val\"}"),
  2813  			Headers: http.Header{},
  2814  		},
  2815  		bidResponse: &adapters.BidderResponse{
  2816  			Bids: []*adapters.TypedBid{
  2817  				{
  2818  					Bid: &openrtb2.Bid{
  2819  						ID:    "pubmaticImp1",
  2820  						Price: 3,
  2821  					},
  2822  					BidType:      openrtb_ext.BidTypeBanner,
  2823  					DealPriority: 4,
  2824  					Seat:         "pubmatic",
  2825  				},
  2826  				{
  2827  					Bid: &openrtb2.Bid{
  2828  						ID:    "groupmImp1",
  2829  						Price: 7,
  2830  					},
  2831  					BidType:      openrtb_ext.BidTypeVideo,
  2832  					DealPriority: 5,
  2833  					Seat:         "groupm",
  2834  				},
  2835  			},
  2836  		},
  2837  	}
  2838  
  2839  	wantSeatBids := []*entities.PbsOrtbSeatBid{
  2840  		{
  2841  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2842  			Bids: []*entities.PbsOrtbBid{{
  2843  				Bid: &openrtb2.Bid{
  2844  					ID:    "groupmImp1",
  2845  					Price: 14,
  2846  				},
  2847  				DealPriority:   5,
  2848  				BidType:        openrtb_ext.BidTypeVideo,
  2849  				OriginalBidCPM: 7,
  2850  				OriginalBidCur: "USD",
  2851  			}},
  2852  			Seat:     "groupm",
  2853  			Currency: "USD",
  2854  		},
  2855  		{
  2856  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2857  			Bids: []*entities.PbsOrtbBid{{
  2858  				Bid: &openrtb2.Bid{
  2859  					ID:    "pubmaticImp1",
  2860  					Price: 6,
  2861  				},
  2862  				DealPriority:   4,
  2863  				BidType:        openrtb_ext.BidTypeBanner,
  2864  				OriginalBidCur: "USD",
  2865  				OriginalBidCPM: 3,
  2866  			}},
  2867  			Seat:     string(openrtb_ext.BidderPubmatic),
  2868  			Currency: "USD",
  2869  		},
  2870  	}
  2871  
  2872  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, "")
  2873  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2874  
  2875  	bidderReq := BidderRequest{
  2876  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}},
  2877  		BidderName: openrtb_ext.BidderPubmatic,
  2878  	}
  2879  	bidAdjustments := map[string]float64{
  2880  		string(openrtb_ext.BidderPubmatic): 2,
  2881  	}
  2882  
  2883  	bidReqOptions := bidRequestOptions{
  2884  		accountDebugAllowed: false,
  2885  		headerDebugAllowed:  false,
  2886  		addCallSignHeader:   true,
  2887  		bidAdjustments:      bidAdjustments,
  2888  	}
  2889  
  2890  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
  2891  		openrtb_ext.ExtAlternateBidderCodes{
  2892  			Enabled: true,
  2893  			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
  2894  				string(openrtb_ext.BidderPubmatic): {
  2895  					Enabled:            true,
  2896  					AllowedBidderCodes: []string{"groupm"},
  2897  				},
  2898  			},
  2899  		},
  2900  		&hookexecution.EmptyHookExecutor{},
  2901  		nil)
  2902  	assert.Nil(t, errs)
  2903  	assert.Len(t, seatBids, 2)
  2904  	sort.Slice(seatBids, func(i, j int) bool {
  2905  		return len(seatBids[i].Seat) < len(seatBids[j].Seat)
  2906  	})
  2907  	assert.Equal(t, wantSeatBids, seatBids)
  2908  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  2909  }
  2910  
  2911  func TestExtraBidWithMultiCurrencies(t *testing.T) {
  2912  	respStatus := 200
  2913  	respBody := "{\"bid\":false}"
  2914  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  2915  	defer server.Close()
  2916  
  2917  	requestHeaders := http.Header{}
  2918  	requestHeaders.Add("Content-Type", "application/json")
  2919  
  2920  	bidderImpl := &goodSingleBidder{
  2921  		httpRequest: &adapters.RequestData{
  2922  			Method:  "POST",
  2923  			Uri:     server.URL,
  2924  			Body:    []byte("{\"key\":\"val\"}"),
  2925  			Headers: http.Header{},
  2926  		},
  2927  		bidResponse: &adapters.BidderResponse{
  2928  			Bids: []*adapters.TypedBid{
  2929  				{
  2930  					Bid: &openrtb2.Bid{
  2931  						ID:    "pubmaticImp1",
  2932  						Price: 3,
  2933  					},
  2934  					BidType:      openrtb_ext.BidTypeBanner,
  2935  					DealPriority: 4,
  2936  					Seat:         "pubmatic",
  2937  				},
  2938  				{
  2939  					Bid: &openrtb2.Bid{
  2940  						ID:    "groupmImp1",
  2941  						Price: 7,
  2942  					},
  2943  					BidType:      openrtb_ext.BidTypeVideo,
  2944  					DealPriority: 5,
  2945  					Seat:         "groupm",
  2946  				},
  2947  			},
  2948  		},
  2949  	}
  2950  
  2951  	wantSeatBids := []*entities.PbsOrtbSeatBid{
  2952  		{
  2953  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2954  			Bids: []*entities.PbsOrtbBid{{
  2955  				Bid: &openrtb2.Bid{
  2956  					ID:    "groupmImp1",
  2957  					Price: 571.5994430039375,
  2958  				},
  2959  				DealPriority:   5,
  2960  				BidType:        openrtb_ext.BidTypeVideo,
  2961  				OriginalBidCPM: 7,
  2962  				OriginalBidCur: "USD",
  2963  			}},
  2964  			Seat:     "groupm",
  2965  			Currency: "INR",
  2966  		},
  2967  		{
  2968  			HttpCalls: []*openrtb_ext.ExtHttpCall{},
  2969  			Bids: []*entities.PbsOrtbBid{{
  2970  				Bid: &openrtb2.Bid{
  2971  					ID:    "pubmaticImp1",
  2972  					Price: 244.97118985883034,
  2973  				},
  2974  				DealPriority:   4,
  2975  				BidType:        openrtb_ext.BidTypeBanner,
  2976  				OriginalBidCPM: 3,
  2977  				OriginalBidCur: "USD",
  2978  			}},
  2979  			Seat:     string(openrtb_ext.BidderPubmatic),
  2980  			Currency: "INR",
  2981  		},
  2982  	}
  2983  
  2984  	mockedHTTPServer := httptest.NewServer(http.HandlerFunc(
  2985  		func(rw http.ResponseWriter, req *http.Request) {
  2986  			rw.Write([]byte(`{"dataAsOf":"2022-11-24T00:00:00.000Z","generatedAt":"2022-11-24T15:00:46.363Z","conversions":{"USD":{"USD":1,"INR":81.65706328627678}}}`))
  2987  			rw.WriteHeader(http.StatusOK)
  2988  		}),
  2989  	)
  2990  
  2991  	// Execute:
  2992  	bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "")
  2993  	currencyConverter := currency.NewRateConverter(
  2994  		&http.Client{},
  2995  		mockedHTTPServer.URL,
  2996  		time.Duration(24)*time.Hour,
  2997  	)
  2998  	time.Sleep(time.Duration(500) * time.Millisecond)
  2999  	currencyConverter.Run()
  3000  
  3001  	bidderReq := BidderRequest{
  3002  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}, Cur: []string{"INR"}},
  3003  		BidderName: openrtb_ext.BidderPubmatic,
  3004  	}
  3005  
  3006  	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 2.0}
  3007  	bidReqOptions := bidRequestOptions{
  3008  		accountDebugAllowed: false,
  3009  		headerDebugAllowed:  false,
  3010  		addCallSignHeader:   true,
  3011  		bidAdjustments:      bidAdjustments,
  3012  	}
  3013  
  3014  	seatBids, extraBidderRespInfo, errs := bidder.requestBid(context.Background(), bidderReq, currencyConverter.Rates(), &adapters.ExtraRequestInfo{}, &MockSigner{}, bidReqOptions,
  3015  		openrtb_ext.ExtAlternateBidderCodes{
  3016  			Enabled: true,
  3017  			Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
  3018  				string(openrtb_ext.BidderPubmatic): {
  3019  					Enabled:            true,
  3020  					AllowedBidderCodes: []string{"groupm"},
  3021  				},
  3022  			},
  3023  		},
  3024  		&hookexecution.EmptyHookExecutor{},
  3025  		nil)
  3026  	assert.Nil(t, errs)
  3027  	assert.Len(t, seatBids, 2)
  3028  	sort.Slice(seatBids, func(i, j int) bool {
  3029  		return len(seatBids[i].Seat) < len(seatBids[j].Seat)
  3030  	})
  3031  	assert.Equal(t, wantSeatBids, seatBids)
  3032  	assert.False(t, extraBidderRespInfo.respProcessingStartTime.IsZero())
  3033  }
  3034  
  3035  func TestGetBidType(t *testing.T) {
  3036  	testCases := []struct {
  3037  		name         string
  3038  		givenBidType openrtb_ext.BidType
  3039  		givenImpId   string
  3040  		givenImp     []openrtb2.Imp
  3041  		expected     string
  3042  	}{
  3043  		{
  3044  			name: "VideoInstream",
  3045  			givenImp: []openrtb2.Imp{
  3046  				{
  3047  					ID: "imp-id",
  3048  					Video: &openrtb2.Video{
  3049  						Plcmt: adcom1.VideoPlcmtInstream,
  3050  					},
  3051  				},
  3052  			},
  3053  			givenBidType: openrtb_ext.BidTypeVideo,
  3054  			givenImpId:   "imp-id",
  3055  			expected:     "video-instream",
  3056  		},
  3057  		{
  3058  			name: "VideoOutstream",
  3059  			givenImp: []openrtb2.Imp{
  3060  				{
  3061  					ID: "imp-id",
  3062  					Video: &openrtb2.Video{
  3063  						Plcmt: adcom1.VideoPlcmtAccompanyingContent,
  3064  					},
  3065  				},
  3066  			},
  3067  			givenBidType: openrtb_ext.BidTypeVideo,
  3068  			givenImpId:   "imp-id",
  3069  			expected:     "video-outstream",
  3070  		},
  3071  		{
  3072  			name:         "NonVideoBidType",
  3073  			givenImp:     []openrtb2.Imp{},
  3074  			givenBidType: openrtb_ext.BidTypeBanner,
  3075  			givenImpId:   "imp-id",
  3076  			expected:     string(openrtb_ext.BidTypeBanner),
  3077  		},
  3078  		{
  3079  			name: "VideoBidTypeImpVideoIsNil",
  3080  			givenImp: []openrtb2.Imp{
  3081  				{
  3082  					ID: "imp-id",
  3083  				},
  3084  			},
  3085  			givenBidType: openrtb_ext.BidTypeVideo,
  3086  			givenImpId:   "imp-id",
  3087  			expected:     "video-instream",
  3088  		},
  3089  	}
  3090  
  3091  	for _, test := range testCases {
  3092  		t.Run(test.name, func(t *testing.T) {
  3093  			actual := getBidTypeForAdjustments(test.givenBidType, test.givenImpId, test.givenImp)
  3094  			assert.Equal(t, test.expected, actual, "Bid type doesn't match")
  3095  		})
  3096  	}
  3097  }
  3098  
  3099  type mockBidderTmaxCtx struct {
  3100  	startTime, deadline, now time.Time
  3101  	ok                       bool
  3102  }
  3103  
  3104  func (m *mockBidderTmaxCtx) Deadline() (deadline time.Time, _ bool) {
  3105  	return m.deadline, m.ok
  3106  }
  3107  func (m *mockBidderTmaxCtx) RemainingDurationMS(deadline time.Time) int64 {
  3108  	return deadline.Sub(m.startTime).Milliseconds()
  3109  }
  3110  
  3111  func (m *mockBidderTmaxCtx) Until(t time.Time) time.Duration {
  3112  	return t.Sub(m.now)
  3113  }
  3114  
  3115  func TestUpdateBidderTmax(t *testing.T) {
  3116  	respStatus := 200
  3117  	respBody := "{\"bid\":false}"
  3118  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  3119  	defer server.Close()
  3120  
  3121  	requestHeaders := http.Header{}
  3122  	requestHeaders.Add("Content-Type", "application/json")
  3123  
  3124  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  3125  	var requestTmax int64 = 700
  3126  
  3127  	bidderReq := BidderRequest{
  3128  		BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{ID: "impId"}}, TMax: requestTmax},
  3129  		BidderName: "test",
  3130  	}
  3131  	extraInfo := &adapters.ExtraRequestInfo{}
  3132  
  3133  	tests := []struct {
  3134  		description     string
  3135  		requestTmax     int64
  3136  		tmaxAdjustments *TmaxAdjustmentsPreprocessed
  3137  		assertFn        func(actualTmax int64) bool
  3138  	}{
  3139  		{
  3140  			description:     "tmax-is-not-enabled",
  3141  			requestTmax:     requestTmax,
  3142  			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: false},
  3143  			assertFn: func(actualTmax int64) bool {
  3144  				return requestTmax == actualTmax
  3145  			},
  3146  		},
  3147  		{
  3148  			description:     "updates-bidder-tmax",
  3149  			requestTmax:     requestTmax,
  3150  			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 100, BidderNetworkLatencyBuffer: 50, PBSResponsePreparationDuration: 50},
  3151  			assertFn: func(actualTmax int64) bool {
  3152  				return requestTmax > actualTmax
  3153  			},
  3154  		},
  3155  	}
  3156  	for _, test := range tests {
  3157  		t.Run(test.description, func(t *testing.T) {
  3158  			bidderImpl := &goodSingleBidder{
  3159  				httpRequest: &adapters.RequestData{
  3160  					Method:  "POST",
  3161  					Uri:     server.URL,
  3162  					Body:    []byte("{\"key\":\"val\"}"),
  3163  					Headers: http.Header{},
  3164  				},
  3165  				bidResponse: &adapters.BidderResponse{},
  3166  			}
  3167  
  3168  			now := time.Now()
  3169  			ctx, cancel := context.WithDeadline(context.Background(), now.Add(500*time.Millisecond))
  3170  			defer cancel()
  3171  			bidReqOptions := bidRequestOptions{bidderRequestStartTime: now, tmaxAdjustments: test.tmaxAdjustments}
  3172  			bidder := AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: false}, "")
  3173  			_, _, errs := bidder.requestBid(ctx, bidderReq, currencyConverter.Rates(), extraInfo, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
  3174  			assert.Empty(t, errs)
  3175  			assert.True(t, test.assertFn(bidderImpl.bidRequest.TMax))
  3176  		})
  3177  	}
  3178  }
  3179  
  3180  func TestHasShorterDurationThanTmax(t *testing.T) {
  3181  	var requestTmaxMS int64 = 700
  3182  	requestTmaxNS := requestTmaxMS * int64(time.Millisecond)
  3183  	startTime := time.Date(2023, 5, 30, 1, 0, 0, 0, time.UTC)
  3184  	now := time.Date(2023, 5, 30, 1, 0, 0, int(200*time.Millisecond), time.UTC)
  3185  	deadline := time.Date(2023, 5, 30, 1, 0, 0, int(requestTmaxNS), time.UTC)
  3186  	ctx := &mockBidderTmaxCtx{startTime: startTime, deadline: deadline, now: now, ok: true}
  3187  
  3188  	tests := []struct {
  3189  		description     string
  3190  		ctx             bidderTmaxContext
  3191  		requestTmax     int64
  3192  		tmaxAdjustments TmaxAdjustmentsPreprocessed
  3193  		expected        bool
  3194  	}{
  3195  		{
  3196  			description:     "tmax-disabled",
  3197  			ctx:             ctx,
  3198  			requestTmax:     requestTmaxMS,
  3199  			tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: false},
  3200  			expected:        false,
  3201  		},
  3202  		{
  3203  			description:     "remaing-duration-greater-than-bidder-response-min",
  3204  			ctx:             ctx,
  3205  			requestTmax:     requestTmaxMS,
  3206  			tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 50, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 40},
  3207  			expected:        false,
  3208  		},
  3209  		{
  3210  			description:     "remaing-duration-less-than-bidder-response-min",
  3211  			ctx:             ctx,
  3212  			requestTmax:     requestTmaxMS,
  3213  			tmaxAdjustments: TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 500},
  3214  			expected:        true,
  3215  		},
  3216  	}
  3217  
  3218  	for _, test := range tests {
  3219  		t.Run(test.description, func(t *testing.T) {
  3220  			assert.Equal(t, test.expected, hasShorterDurationThanTmax(test.ctx, test.tmaxAdjustments))
  3221  		})
  3222  	}
  3223  }
  3224  
  3225  func TestDoRequestImplWithTmax(t *testing.T) {
  3226  	respStatus := 200
  3227  	respBody := "{\"bid\":false}"
  3228  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  3229  	defer server.Close()
  3230  	requestStartTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
  3231  
  3232  	bidRequest := adapters.RequestData{
  3233  		Method: "POST",
  3234  		Uri:    server.URL,
  3235  		Body:   []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`),
  3236  	}
  3237  
  3238  	bidderAdapter := bidderAdapter{
  3239  		me:     &metricsConfig.NilMetricsEngine{},
  3240  		Client: server.Client(),
  3241  	}
  3242  	logger := func(msg string, args ...interface{}) {}
  3243  
  3244  	tests := []struct {
  3245  		ctxDeadline     time.Time
  3246  		description     string
  3247  		tmaxAdjustments *TmaxAdjustmentsPreprocessed
  3248  		assertFn        func(err error)
  3249  	}{
  3250  		{
  3251  			ctxDeadline:     time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
  3252  			description:     "returns-tmax-timeout-error",
  3253  			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 5000},
  3254  			assertFn:        func(err error) { assert.Equal(t, &errortypes.TmaxTimeout{Message: "exceeded tmax duration"}, err) },
  3255  		},
  3256  		{
  3257  			ctxDeadline:     time.Now().Add(5 * time.Second),
  3258  			description:     "remaining-duration-greater-than-tmax-min",
  3259  			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 100},
  3260  			assertFn:        func(err error) { assert.Nil(t, err) },
  3261  		},
  3262  		{
  3263  			description:     "tmax-disabled",
  3264  			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: false},
  3265  			assertFn:        func(err error) { assert.Nil(t, err) },
  3266  		},
  3267  		{
  3268  			description:     "tmax-BidderResponseDurationMin-not-set",
  3269  			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, BidderResponseDurationMin: 0},
  3270  			assertFn:        func(err error) { assert.Nil(t, err) },
  3271  		},
  3272  		{
  3273  			description:     "tmax-is-nil",
  3274  			tmaxAdjustments: nil,
  3275  			assertFn:        func(err error) { assert.Nil(t, err) },
  3276  		},
  3277  	}
  3278  	for _, test := range tests {
  3279  		var (
  3280  			ctx      context.Context
  3281  			cancelFn context.CancelFunc
  3282  		)
  3283  
  3284  		if test.ctxDeadline.IsZero() {
  3285  			ctx = context.Background()
  3286  		} else {
  3287  			ctx, cancelFn = context.WithDeadline(context.Background(), test.ctxDeadline)
  3288  			defer cancelFn()
  3289  		}
  3290  
  3291  		httpCallInfo := bidderAdapter.doRequestImpl(ctx, &bidRequest, logger, requestStartTime, test.tmaxAdjustments)
  3292  		test.assertFn(httpCallInfo.err)
  3293  	}
  3294  }
  3295  
  3296  func TestDoRequestImplWithTmaxTimeout(t *testing.T) {
  3297  	respStatus := 200
  3298  	respBody := "{\"bid\":false}"
  3299  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  3300  	defer server.Close()
  3301  	requestStartTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
  3302  
  3303  	bidRequest := adapters.RequestData{
  3304  		Method: "POST",
  3305  		Uri:    server.URL,
  3306  		Body:   []byte(`{"id":"this-id","app":{"publisher":{"id":"pub-id"}}}`),
  3307  	}
  3308  
  3309  	metricsMock := &metrics.MetricsEngineMock{}
  3310  	metricsMock.On("RecordOverheadTime", metrics.PreBidder, mock.Anything).Once()
  3311  	metricsMock.On("RecordTMaxTimeout").Once()
  3312  
  3313  	bidderAdapter := bidderAdapter{
  3314  		me:     metricsMock,
  3315  		Client: server.Client(),
  3316  	}
  3317  	logger := func(msg string, args ...interface{}) {}
  3318  
  3319  	tests := []struct {
  3320  		ctxDeadline     time.Time
  3321  		description     string
  3322  		tmaxAdjustments *TmaxAdjustmentsPreprocessed
  3323  		assertFn        func(err error)
  3324  	}{
  3325  		{
  3326  			ctxDeadline:     time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC),
  3327  			description:     "returns-tmax-timeout-error",
  3328  			tmaxAdjustments: &TmaxAdjustmentsPreprocessed{IsEnforced: true, PBSResponsePreparationDuration: 100, BidderNetworkLatencyBuffer: 10, BidderResponseDurationMin: 5000},
  3329  			assertFn:        func(err error) { assert.Equal(t, &errortypes.TmaxTimeout{Message: "exceeded tmax duration"}, err) },
  3330  		},
  3331  	}
  3332  	for _, test := range tests {
  3333  		var (
  3334  			ctx      context.Context
  3335  			cancelFn context.CancelFunc
  3336  		)
  3337  
  3338  		if test.ctxDeadline.IsZero() {
  3339  			ctx = context.Background()
  3340  		} else {
  3341  			ctx, cancelFn = context.WithDeadline(context.Background(), test.ctxDeadline)
  3342  			defer cancelFn()
  3343  		}
  3344  
  3345  		httpCallInfo := bidderAdapter.doRequestImpl(ctx, &bidRequest, logger, requestStartTime, test.tmaxAdjustments)
  3346  		test.assertFn(httpCallInfo.err)
  3347  	}
  3348  }
  3349  
  3350  func TestGetRequestBody(t *testing.T) {
  3351  	tests := []struct {
  3352  		name                string
  3353  		endpointCompression string
  3354  		givenReqBody        []byte
  3355  	}{
  3356  		{
  3357  			name:                "No-Compression",
  3358  			endpointCompression: "",
  3359  			givenReqBody:        []byte("test body"),
  3360  		},
  3361  		{
  3362  			name:                "GZIP-Compression",
  3363  			endpointCompression: "GZIP",
  3364  			givenReqBody:        []byte("test body"),
  3365  		},
  3366  	}
  3367  
  3368  	for _, test := range tests {
  3369  		t.Run(test.name, func(t *testing.T) {
  3370  			req := &adapters.RequestData{Body: test.givenReqBody, Headers: http.Header{}}
  3371  			requestBody, err := getRequestBody(req, test.endpointCompression)
  3372  			assert.NoError(t, err)
  3373  
  3374  			if test.endpointCompression == "GZIP" {
  3375  				assert.Equal(t, "gzip", req.Headers.Get("Content-Encoding"))
  3376  
  3377  				decompressedReqBody, err := decompressGzip(requestBody.Bytes())
  3378  				assert.NoError(t, err)
  3379  				assert.Equal(t, test.givenReqBody, decompressedReqBody)
  3380  			} else {
  3381  				assert.Equal(t, test.givenReqBody, requestBody.Bytes())
  3382  			}
  3383  		})
  3384  	}
  3385  }
  3386  
  3387  func decompressGzip(input []byte) ([]byte, error) {
  3388  	r, err := gzip.NewReader(bytes.NewReader(input))
  3389  	if err != nil {
  3390  		return nil, err
  3391  	}
  3392  	defer r.Close()
  3393  
  3394  	decompressed, err := io.ReadAll(r)
  3395  	if err != nil {
  3396  		return nil, err
  3397  	}
  3398  
  3399  	return decompressed, nil
  3400  }
  3401  
  3402  func BenchmarkCompressToGZIPOptimized(b *testing.B) {
  3403  	// Setup the mock server
  3404  	respBody := "{\"bid\":false}"
  3405  	respStatus := 200
  3406  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
  3407  	defer server.Close()
  3408  
  3409  	// Prepare the request data
  3410  	req := &adapters.RequestData{
  3411  		Method:  "POST",
  3412  		Uri:     server.URL,
  3413  		Body:    []byte("{\"key\":\"val\"}"),
  3414  		Headers: http.Header{},
  3415  	}
  3416  
  3417  	// Run the benchmark
  3418  	b.ResetTimer()
  3419  	for i := 0; i < b.N; i++ {
  3420  		getRequestBody(req, "GZIP")
  3421  	}
  3422  }