github.com/prebid/prebid-server@v0.275.0/exchange/bidder_test.go (about)

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