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

     1  package exchange
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"math"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"os"
    14  	"reflect"
    15  	"regexp"
    16  	"sort"
    17  	"strconv"
    18  	"strings"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/buger/jsonparser"
    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/gdpr"
    31  	"github.com/prebid/prebid-server/hooks"
    32  	"github.com/prebid/prebid-server/hooks/hookexecution"
    33  	"github.com/prebid/prebid-server/hooks/hookstage"
    34  	"github.com/prebid/prebid-server/macros"
    35  	"github.com/prebid/prebid-server/metrics"
    36  	metricsConf "github.com/prebid/prebid-server/metrics/config"
    37  	metricsConfig "github.com/prebid/prebid-server/metrics/config"
    38  	"github.com/prebid/prebid-server/openrtb_ext"
    39  	pbc "github.com/prebid/prebid-server/prebid_cache_client"
    40  	"github.com/prebid/prebid-server/privacy"
    41  	"github.com/prebid/prebid-server/stored_requests"
    42  	"github.com/prebid/prebid-server/stored_requests/backends/file_fetcher"
    43  	"github.com/prebid/prebid-server/usersync"
    44  	"github.com/prebid/prebid-server/util/ptrutil"
    45  	"github.com/stretchr/testify/assert"
    46  	"github.com/stretchr/testify/mock"
    47  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
    48  )
    49  
    50  func TestNewExchange(t *testing.T) {
    51  	respStatus := 200
    52  	respBody := "{\"bid\":false}"
    53  	server := httptest.NewServer(mockHandler(respStatus, "getBody", respBody))
    54  	defer server.Close()
    55  
    56  	knownAdapters := openrtb_ext.CoreBidderNames()
    57  
    58  	cfg := &config.Configuration{
    59  		CacheURL: config.Cache{
    60  			ExpectedTimeMillis: 20,
    61  		},
    62  		GDPR: config.GDPR{
    63  			EEACountries: []string{"FIN", "FRA", "GUF"},
    64  		},
    65  	}
    66  
    67  	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  
    72  	adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
    73  	if adaptersErr != nil {
    74  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
    75  	}
    76  
    77  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
    78  
    79  	gdprPermsBuilder := fakePermissionsBuilder{
    80  		permissions: &permissionsMock{
    81  			allowAllBidders: true,
    82  		},
    83  	}.Builder
    84  
    85  	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
    86  	for _, bidderName := range knownAdapters {
    87  		if _, ok := e.adapterMap[bidderName]; !ok {
    88  			if biddersInfo[string(bidderName)].IsEnabled() {
    89  				t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName)
    90  			}
    91  		}
    92  	}
    93  	if e.cacheTime != time.Duration(cfg.CacheURL.ExpectedTimeMillis)*time.Millisecond {
    94  		t.Errorf("Bad cacheTime. Expected 20 ms, got %s", e.cacheTime.String())
    95  	}
    96  }
    97  
    98  // The objective is to get to execute e.buildBidResponse(ctx.Background(), liveA... ) (*openrtb2.BidResponse, error)
    99  // and check whether the returned request successfully prints any '&' characters as it should
   100  // To do so, we:
   101  //  1. Write the endpoint adapter URL with an '&' character into a new config,Configuration struct
   102  //     as specified in https://github.com/prebid/prebid-server/issues/465
   103  //  2. Initialize a new exchange with said configuration
   104  //  3. Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs including the
   105  //     sample request as specified in https://github.com/prebid/prebid-server/issues/465
   106  //  4. Build a BidResponse struct using exchange.buildBidResponse(ctx.Background(), liveA... )
   107  //  5. Assert we have no '&' characters in the response that exchange.buildBidResponse returns
   108  func TestCharacterEscape(t *testing.T) {
   109  
   110  	// 1) Adapter with a '& char in its endpoint property
   111  	//    https://github.com/prebid/prebid-server/issues/465
   112  	cfg := &config.Configuration{}
   113  	biddersInfo := config.BidderInfos{"appnexus": config.BidderInfo{Endpoint: "http://ib.adnxs.com/openrtb2?query1&query2"}} //Note the '&' character in there
   114  
   115  	// 	2) Init new exchange with said configuration
   116  	//Other parameters also needed to create exchange
   117  	handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
   118  	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
   119  
   120  	defer server.Close()
   121  
   122  	adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
   123  	if adaptersErr != nil {
   124  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
   125  	}
   126  
   127  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   128  
   129  	gdprPermsBuilder := fakePermissionsBuilder{
   130  		permissions: &permissionsMock{
   131  			allowAllBidders: true,
   132  		},
   133  	}.Builder
   134  
   135  	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
   136  
   137  	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
   138  	//liveAdapters []openrtb_ext.BidderName,
   139  	liveAdapters := make([]openrtb_ext.BidderName, 1)
   140  	liveAdapters[0] = "appnexus"
   141  
   142  	//adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
   143  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid, 1)
   144  	adapterBids["appnexus"] = &entities.PbsOrtbSeatBid{Currency: "USD"}
   145  
   146  	//An openrtb2.BidRequest struct as specified in https://github.com/prebid/prebid-server/issues/465
   147  	bidRequest := &openrtb2.BidRequest{
   148  		ID: "some-request-id",
   149  		Imp: []openrtb2.Imp{{
   150  			ID:     "some-impression-id",
   151  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
   152  			Ext:    json.RawMessage(`{"appnexus": {"placementId": 1}}`),
   153  		}},
   154  		Site:   &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
   155  		Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"},
   156  		AT:     1,
   157  		TMax:   500,
   158  		Ext:    json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 1}}}],"tmax": 500}`),
   159  	}
   160  
   161  	//adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra,
   162  	adapterExtra := make(map[openrtb_ext.BidderName]*seatResponseExtra, 1)
   163  	adapterExtra["appnexus"] = &seatResponseExtra{
   164  		ResponseTimeMillis: 5,
   165  		Errors:             []openrtb_ext.ExtBidderMessage{{Code: 999, Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\""}},
   166  	}
   167  
   168  	var errList []error
   169  
   170  	// 	4) Build bid response
   171  	bidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, nil, nil, true, nil, "", errList)
   172  
   173  	// 	5) Assert we have no errors and one '&' character as we are supposed to
   174  	if len(errList) > 0 {
   175  		t.Errorf("exchange.buildBidResponse returned %d errors", len(errList))
   176  	}
   177  	if bytes.Contains(bidResp.Ext, []byte("u0026")) {
   178  		t.Errorf("exchange.buildBidResponse() did not correctly print the '&' characters %s", string(bidResp.Ext))
   179  	}
   180  }
   181  
   182  // TestDebugBehaviour asserts the HttpCalls object is included inside the json "debug" field of the bidResponse extension when the
   183  // openrtb2.BidRequest "Test" value is set to 1 or the openrtb2.BidRequest.Ext.Debug boolean field is set to true
   184  func TestDebugBehaviour(t *testing.T) {
   185  
   186  	// Define test cases
   187  	type inTest struct {
   188  		test  int8
   189  		debug bool
   190  	}
   191  	type outTest struct {
   192  		debugInfoIncluded bool
   193  	}
   194  
   195  	type debugData struct {
   196  		bidderLevelDebugAllowed    bool
   197  		accountLevelDebugAllowed   bool
   198  		headerOverrideDebugAllowed bool
   199  	}
   200  
   201  	type aTest struct {
   202  		desc             string
   203  		in               inTest
   204  		out              outTest
   205  		debugData        debugData
   206  		generateWarnings bool
   207  	}
   208  	testCases := []aTest{
   209  		{
   210  			desc:             "test flag equals zero, ext debug flag false, no debug info expected",
   211  			in:               inTest{test: 0, debug: false},
   212  			out:              outTest{debugInfoIncluded: false},
   213  			debugData:        debugData{true, true, false},
   214  			generateWarnings: false,
   215  		},
   216  		{
   217  			desc:             "test flag equals zero, ext debug flag true, debug info expected",
   218  			in:               inTest{test: 0, debug: true},
   219  			out:              outTest{debugInfoIncluded: true},
   220  			debugData:        debugData{true, true, false},
   221  			generateWarnings: false,
   222  		},
   223  		{
   224  			desc:             "test flag equals 1, ext debug flag false, debug info expected",
   225  			in:               inTest{test: 1, debug: false},
   226  			out:              outTest{debugInfoIncluded: true},
   227  			debugData:        debugData{true, true, false},
   228  			generateWarnings: false,
   229  		},
   230  		{
   231  			desc:             "test flag equals 1, ext debug flag true, debug info expected",
   232  			in:               inTest{test: 1, debug: true},
   233  			out:              outTest{debugInfoIncluded: true},
   234  			debugData:        debugData{true, true, false},
   235  			generateWarnings: false,
   236  		},
   237  		{
   238  			desc:             "test flag not equal to 0 nor 1, ext debug flag false, no debug info expected",
   239  			in:               inTest{test: 2, debug: false},
   240  			out:              outTest{debugInfoIncluded: false},
   241  			debugData:        debugData{true, true, false},
   242  			generateWarnings: false,
   243  		},
   244  		{
   245  			desc:             "test flag not equal to 0 nor 1, ext debug flag true, debug info expected",
   246  			in:               inTest{test: -1, debug: true},
   247  			out:              outTest{debugInfoIncluded: true},
   248  			debugData:        debugData{true, true, false},
   249  			generateWarnings: true,
   250  		},
   251  		{
   252  			desc:             "test account level debug disabled",
   253  			in:               inTest{test: -1, debug: true},
   254  			out:              outTest{debugInfoIncluded: false},
   255  			debugData:        debugData{true, false, false},
   256  			generateWarnings: true,
   257  		},
   258  		{
   259  			desc:             "test header override enabled when all other debug options are disabled",
   260  			in:               inTest{test: -1, debug: false},
   261  			out:              outTest{debugInfoIncluded: true},
   262  			debugData:        debugData{false, false, true},
   263  			generateWarnings: false,
   264  		},
   265  		{
   266  			desc:             "test header override and url debug options are enabled when all other debug options are disabled",
   267  			in:               inTest{test: -1, debug: true},
   268  			out:              outTest{debugInfoIncluded: true},
   269  			debugData:        debugData{false, false, true},
   270  			generateWarnings: false,
   271  		},
   272  		{
   273  			desc:             "test header override and url and bidder debug options are enabled when account debug option is disabled",
   274  			in:               inTest{test: -1, debug: true},
   275  			out:              outTest{debugInfoIncluded: true},
   276  			debugData:        debugData{true, false, true},
   277  			generateWarnings: false,
   278  		},
   279  		{
   280  			desc:             "test all debug options are enabled",
   281  			in:               inTest{test: -1, debug: true},
   282  			out:              outTest{debugInfoIncluded: true},
   283  			debugData:        debugData{true, true, true},
   284  			generateWarnings: false,
   285  		},
   286  	}
   287  
   288  	// Set up test
   289  	noBidServer := func(w http.ResponseWriter, r *http.Request) {
   290  		w.WriteHeader(204)
   291  	}
   292  	server := httptest.NewServer(http.HandlerFunc(noBidServer))
   293  	defer server.Close()
   294  
   295  	categoriesFetcher, err := newCategoryFetcher("./test/category-mapping")
   296  	if err != nil {
   297  		t.Errorf("Failed to create a category Fetcher: %v", err)
   298  	}
   299  
   300  	bidRequest := &openrtb2.BidRequest{
   301  		ID: "some-request-id",
   302  		Imp: []openrtb2.Imp{{
   303  			ID:     "some-impression-id",
   304  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
   305  			Ext:    json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`),
   306  		}},
   307  		Site:   &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
   308  		Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"},
   309  		AT:     1,
   310  		TMax:   500,
   311  	}
   312  
   313  	bidderImpl := &goodSingleBidder{
   314  		httpRequest: &adapters.RequestData{
   315  			Method:  "POST",
   316  			Uri:     server.URL,
   317  			Body:    []byte("{\"key\":\"val\"}"),
   318  			Headers: http.Header{},
   319  		},
   320  		bidResponse: &adapters.BidderResponse{},
   321  	}
   322  
   323  	e := new(exchange)
   324  
   325  	e.cache = &wellBehavedCache{}
   326  	e.me = &metricsConf.NilMetricsEngine{}
   327  	e.gdprPermsBuilder = fakePermissionsBuilder{
   328  		permissions: &permissionsMock{
   329  			allowAllBidders: true,
   330  		},
   331  	}.Builder
   332  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   333  	e.categoriesFetcher = categoriesFetcher
   334  	e.requestSplitter = requestSplitter{
   335  		me:               &metricsConf.NilMetricsEngine{},
   336  		gdprPermsBuilder: e.gdprPermsBuilder,
   337  	}
   338  	ctx := context.Background()
   339  
   340  	// Run tests
   341  	for _, test := range testCases {
   342  
   343  		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
   344  			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: test.debugData.bidderLevelDebugAllowed}, ""),
   345  		}
   346  
   347  		bidRequest.Test = test.in.test
   348  
   349  		if test.in.debug {
   350  			bidRequest.Ext = json.RawMessage(`{"prebid":{"debug":true}}`)
   351  		} else {
   352  			bidRequest.Ext = nil
   353  		}
   354  
   355  		auctionRequest := &AuctionRequest{
   356  			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest},
   357  			Account:           config.Account{DebugAllow: test.debugData.accountLevelDebugAllowed},
   358  			UserSyncs:         &emptyUsersync{},
   359  			StartTime:         time.Now(),
   360  			HookExecutor:      &hookexecution.EmptyHookExecutor{},
   361  			TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
   362  		}
   363  		if test.generateWarnings {
   364  			var errL []error
   365  			errL = append(errL, &errortypes.Warning{
   366  				Message:     fmt.Sprintf("CCPA consent test warning."),
   367  				WarningCode: errortypes.InvalidPrivacyConsentWarningCode})
   368  			auctionRequest.Warnings = errL
   369  		}
   370  		debugLog := &DebugLog{}
   371  		if test.debugData.headerOverrideDebugAllowed {
   372  			debugLog = &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true}
   373  		}
   374  		// Run test
   375  		outBidResponse, err := e.HoldAuction(ctx, auctionRequest, debugLog)
   376  
   377  		// Assert no HoldAuction error
   378  		assert.NoErrorf(t, err, "%s. ex.HoldAuction returned an error: %v \n", test.desc, err)
   379  		assert.NotNilf(t, outBidResponse.Ext, "%s. outBidResponse.Ext should not be nil \n", test.desc)
   380  		assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
   381  		actualExt := &openrtb_ext.ExtBidResponse{}
   382  		err = json.Unmarshal(outBidResponse.Ext, actualExt)
   383  		assert.NoErrorf(t, err, "%s. \"ext\" JSON field could not be unmarshaled. err: \"%v\" \n outBidResponse.Ext: \"%s\" \n", test.desc, err, outBidResponse.Ext)
   384  
   385  		assert.NotEmpty(t, actualExt.Prebid, "%s. ext.prebid should not be empty")
   386  		assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set")
   387  		assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "%s. ext.prebid.auctiontimestamp has incorrect value")
   388  
   389  		if test.debugData.headerOverrideDebugAllowed {
   390  			assert.Empty(t, actualExt.Warnings, "warnings should be empty")
   391  			assert.Empty(t, actualExt.Errors, "errors should be empty")
   392  		}
   393  
   394  		if test.out.debugInfoIncluded {
   395  			assert.NotNilf(t, actualExt, "%s. ext.debug field is expected to be included in this outBidResponse.Ext and not be nil.  outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug)
   396  
   397  			// Assert "Debug fields
   398  			assert.Greater(t, len(actualExt.Debug.HttpCalls), 0, "%s. ext.debug.httpcalls array should not be empty\n", test.desc)
   399  			assert.Equal(t, server.URL, actualExt.Debug.HttpCalls["appnexus"][0].Uri, "%s. ext.debug.httpcalls array should not be empty\n", test.desc)
   400  			assert.NotNilf(t, actualExt.Debug.ResolvedRequest, "%s. ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil.  outBidResponse.Ext.Debug = %v \n", test.desc, actualExt.Debug)
   401  
   402  			// If not nil, assert bid extension
   403  			if test.in.debug {
   404  				actualResolvedReqExt, _, _, err := jsonparser.Get(actualExt.Debug.ResolvedRequest, "ext")
   405  				assert.NoError(t, err, "Resolved request should have the correct format")
   406  				assert.JSONEq(t, string(bidRequest.Ext), string(actualResolvedReqExt), test.desc)
   407  			}
   408  		} else if !test.debugData.bidderLevelDebugAllowed && test.debugData.accountLevelDebugAllowed {
   409  			assert.Equal(t, len(actualExt.Debug.HttpCalls), 0, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty")
   410  
   411  		} else {
   412  			assert.Nil(t, actualExt.Debug, "%s. ext.debug.httpcalls array should not be empty", "With bidder level debug disable option http calls should be empty")
   413  		}
   414  
   415  		if test.out.debugInfoIncluded && !test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed {
   416  			assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning")
   417  			assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present")
   418  			assert.Equal(t, "debug turned off for account", actualExt.Warnings["general"][0].Message, "account debug disabled message should be present")
   419  		}
   420  
   421  		if !test.out.debugInfoIncluded && test.in.debug && test.debugData.accountLevelDebugAllowed && !test.debugData.headerOverrideDebugAllowed {
   422  			if test.generateWarnings {
   423  				assert.Len(t, actualExt.Warnings, 2, "warnings should have one warning")
   424  			} else {
   425  				assert.Len(t, actualExt.Warnings, 1, "warnings should have one warning")
   426  			}
   427  			assert.NotNil(t, actualExt.Warnings["appnexus"], "bidder warning should be present")
   428  			assert.Equal(t, "debug turned off for bidder", actualExt.Warnings["appnexus"][0].Message, "account debug disabled message should be present")
   429  		}
   430  
   431  		if test.generateWarnings {
   432  			assert.NotNil(t, actualExt.Warnings["general"], "general warning should be present")
   433  			CCPAWarningPresent := false
   434  			for _, warn := range actualExt.Warnings["general"] {
   435  				if warn.Code == errortypes.InvalidPrivacyConsentWarningCode {
   436  					CCPAWarningPresent = true
   437  					break
   438  				}
   439  			}
   440  			assert.True(t, CCPAWarningPresent, "CCPA Warning should be present")
   441  		}
   442  
   443  	}
   444  }
   445  
   446  func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
   447  
   448  	type testCase struct {
   449  		bidder1DebugEnabled bool
   450  		bidder2DebugEnabled bool
   451  	}
   452  
   453  	testCases := []testCase{
   454  		{
   455  			bidder1DebugEnabled: true, bidder2DebugEnabled: true,
   456  		},
   457  		{
   458  			bidder1DebugEnabled: true, bidder2DebugEnabled: false,
   459  		},
   460  		{
   461  			bidder1DebugEnabled: false, bidder2DebugEnabled: true,
   462  		},
   463  		{
   464  			bidder1DebugEnabled: false, bidder2DebugEnabled: false,
   465  		},
   466  	}
   467  
   468  	// Set up test
   469  	noBidServer := func(w http.ResponseWriter, r *http.Request) {
   470  		w.WriteHeader(204)
   471  	}
   472  	server := httptest.NewServer(http.HandlerFunc(noBidServer))
   473  	defer server.Close()
   474  
   475  	categoriesFetcher, err := newCategoryFetcher("./test/category-mapping")
   476  	if err != nil {
   477  		t.Errorf("Failed to create a category Fetcher: %v", err)
   478  	}
   479  
   480  	bidderImpl := &goodSingleBidder{
   481  		httpRequest: &adapters.RequestData{
   482  			Method:  "POST",
   483  			Uri:     server.URL,
   484  			Body:    []byte(`{"key":"val"}`),
   485  			Headers: http.Header{},
   486  		},
   487  		bidResponse: &adapters.BidderResponse{},
   488  	}
   489  
   490  	e := new(exchange)
   491  	e.cache = &wellBehavedCache{}
   492  	e.me = &metricsConf.NilMetricsEngine{}
   493  	e.gdprPermsBuilder = fakePermissionsBuilder{
   494  		permissions: &permissionsMock{
   495  			allowAllBidders: true,
   496  		},
   497  	}.Builder
   498  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
   499  	e.categoriesFetcher = categoriesFetcher
   500  	e.requestSplitter = requestSplitter{
   501  		me:               e.me,
   502  		gdprPermsBuilder: e.gdprPermsBuilder,
   503  	}
   504  
   505  	debugLog := DebugLog{Enabled: true}
   506  
   507  	for _, testCase := range testCases {
   508  		bidRequest := &openrtb2.BidRequest{
   509  			ID: "some-request-id",
   510  			Imp: []openrtb2.Imp{{
   511  				ID:     "some-impression-id",
   512  				Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
   513  				Ext:    json.RawMessage(`{"prebid":{"bidder":{"telaria": {"placementId": 1}, "appnexus": {"placementid": 2}}}}`),
   514  			}},
   515  			Site:   &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
   516  			Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"},
   517  			AT:     1,
   518  			TMax:   500,
   519  		}
   520  
   521  		bidRequest.Ext = json.RawMessage(`{"prebid":{"debug":true}}`)
   522  
   523  		auctionRequest := &AuctionRequest{
   524  			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest},
   525  			Account:           config.Account{DebugAllow: true},
   526  			UserSyncs:         &emptyUsersync{},
   527  			StartTime:         time.Now(),
   528  			HookExecutor:      &hookexecution.EmptyHookExecutor{},
   529  			TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
   530  		}
   531  
   532  		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
   533  			openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder1DebugEnabled}, ""),
   534  			openrtb_ext.BidderTelaria:  AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{Allow: testCase.bidder2DebugEnabled}, ""),
   535  		}
   536  		// Run test
   537  		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog)
   538  		// Assert no HoldAuction err
   539  		assert.NoErrorf(t, err, "ex.HoldAuction returned an err")
   540  		assert.NotNilf(t, outBidResponse.Ext, "outBidResponse.Ext should not be nil")
   541  		assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
   542  
   543  		actualExt := &openrtb_ext.ExtBidResponse{}
   544  		err = json.Unmarshal(outBidResponse.Ext, actualExt)
   545  		assert.NoErrorf(t, err, "JSON field unmarshaling err. ")
   546  
   547  		assert.NotEmpty(t, actualExt.Prebid, "ext.prebid should not be empty")
   548  		assert.NotEmpty(t, actualExt.Prebid.AuctionTimestamp, "ext.prebid.auctiontimestamp should not be empty when AuctionRequest.StartTime is set")
   549  		assert.Equal(t, auctionRequest.StartTime.UnixNano()/1e+6, actualExt.Prebid.AuctionTimestamp, "ext.prebid.auctiontimestamp has incorrect value")
   550  
   551  		assert.NotNilf(t, actualExt, "ext.debug field is expected to be included in this outBidResponse.Ext and not be nil")
   552  
   553  		// Assert "Debug fields
   554  		if testCase.bidder1DebugEnabled {
   555  			assert.Equal(t, server.URL, actualExt.Debug.HttpCalls["appnexus"][0].Uri, "Url for bidder with debug enabled is incorrect")
   556  			assert.NotNilf(t, actualExt.Debug.HttpCalls["appnexus"][0].RequestBody, "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil")
   557  		}
   558  		if testCase.bidder2DebugEnabled {
   559  			assert.Equal(t, server.URL, actualExt.Debug.HttpCalls["telaria"][0].Uri, "Url for bidder with debug enabled is incorrect")
   560  			assert.NotNilf(t, actualExt.Debug.HttpCalls["telaria"][0].RequestBody, "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil")
   561  		}
   562  		if !testCase.bidder1DebugEnabled {
   563  			assert.Nil(t, actualExt.Debug.HttpCalls["appnexus"], "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil")
   564  		}
   565  		if !testCase.bidder2DebugEnabled {
   566  			assert.Nil(t, actualExt.Debug.HttpCalls["telaria"], "ext.debug.resolvedrequest field is expected to be included in this outBidResponse.Ext and not be nil")
   567  		}
   568  		if testCase.bidder1DebugEnabled && testCase.bidder2DebugEnabled {
   569  			assert.Equal(t, 2, len(actualExt.Debug.HttpCalls), "With bidder level debug enable option for both bidders http calls should have 2 elements")
   570  		}
   571  	}
   572  }
   573  
   574  func TestOverrideWithCustomCurrency(t *testing.T) {
   575  
   576  	mockCurrencyClient := &fakeCurrencyRatesHttpClient{
   577  		responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`,
   578  	}
   579  	mockCurrencyConverter := currency.NewRateConverter(
   580  		mockCurrencyClient,
   581  		"currency.fake.com",
   582  		24*time.Hour,
   583  	)
   584  
   585  	type testIn struct {
   586  		customCurrencyRates json.RawMessage
   587  		bidRequestCurrency  string
   588  	}
   589  	type testResults struct {
   590  		numBids         int
   591  		bidRespPrice    float64
   592  		bidRespCurrency string
   593  	}
   594  
   595  	testCases := []struct {
   596  		desc     string
   597  		in       testIn
   598  		expected testResults
   599  	}{
   600  		{
   601  			desc: "Blank currency field in ext. bidRequest comes with a valid currency but conversion rate was not found in PBS. Return no bids",
   602  			in: testIn{
   603  				customCurrencyRates: json.RawMessage(`{ "prebid": { "currency": {} } } `),
   604  				bidRequestCurrency:  "GBP",
   605  			},
   606  			expected: testResults{},
   607  		},
   608  		{
   609  			desc: "valid request.ext.prebid.currency, expect custom rates to override those of the currency rate server",
   610  			in: testIn{
   611  				customCurrencyRates: json.RawMessage(`{
   612  						  "prebid": {
   613  						    "currency": {
   614  						      "rates": {
   615  						        "USD": {
   616  						          "MXN": 20.00,
   617  						          "EUR": 10.95
   618  						        }
   619  						      }
   620  						    }
   621  						  }
   622  						}`),
   623  				bidRequestCurrency: "MXN",
   624  			},
   625  			expected: testResults{
   626  				numBids:         1,
   627  				bidRespPrice:    20.00,
   628  				bidRespCurrency: "MXN",
   629  			},
   630  		},
   631  	}
   632  
   633  	// Init mock currency conversion service
   634  	mockCurrencyConverter.Run()
   635  
   636  	// Init an exchange to run an auction from
   637  	noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
   638  	mockAppnexusBidService := httptest.NewServer(http.HandlerFunc(noBidServer))
   639  	defer mockAppnexusBidService.Close()
   640  
   641  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
   642  	if error != nil {
   643  		t.Errorf("Failed to create a category Fetcher: %v", error)
   644  	}
   645  
   646  	oneDollarBidBidder := &goodSingleBidder{
   647  		httpRequest: &adapters.RequestData{
   648  			Method:  "POST",
   649  			Uri:     mockAppnexusBidService.URL,
   650  			Body:    []byte("{\"key\":\"val\"}"),
   651  			Headers: http.Header{},
   652  		},
   653  	}
   654  
   655  	e := new(exchange)
   656  	e.cache = &wellBehavedCache{}
   657  	e.me = &metricsConf.NilMetricsEngine{}
   658  	e.gdprPermsBuilder = fakePermissionsBuilder{
   659  		permissions: &permissionsMock{
   660  			allowAllBidders: true,
   661  		},
   662  	}.Builder
   663  	e.currencyConverter = mockCurrencyConverter
   664  	e.categoriesFetcher = categoriesFetcher
   665  	e.bidIDGenerator = &mockBidIDGenerator{false, false}
   666  	e.requestSplitter = requestSplitter{
   667  		me:               e.me,
   668  		gdprPermsBuilder: e.gdprPermsBuilder,
   669  	}
   670  
   671  	// Define mock incoming bid requeset
   672  	mockBidRequest := &openrtb2.BidRequest{
   673  		ID: "some-request-id",
   674  		Imp: []openrtb2.Imp{{
   675  			ID:     "some-impression-id",
   676  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
   677  			Ext:    json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`),
   678  		}},
   679  		Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
   680  	}
   681  
   682  	// Run tests
   683  	for _, test := range testCases {
   684  
   685  		oneDollarBidBidder.bidResponse = &adapters.BidderResponse{
   686  			Bids: []*adapters.TypedBid{
   687  				{
   688  					Bid: &openrtb2.Bid{Price: 1.00},
   689  				},
   690  			},
   691  			Currency: "USD",
   692  		}
   693  
   694  		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
   695  			openrtb_ext.BidderAppnexus: AdaptBidder(oneDollarBidBidder, mockAppnexusBidService.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
   696  		}
   697  
   698  		// Set custom rates in extension
   699  		mockBidRequest.Ext = test.in.customCurrencyRates
   700  
   701  		// Set bidRequest currency list
   702  		mockBidRequest.Cur = []string{test.in.bidRequestCurrency}
   703  
   704  		auctionRequest := &AuctionRequest{
   705  			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
   706  			Account:           config.Account{},
   707  			UserSyncs:         &emptyUsersync{},
   708  			HookExecutor:      &hookexecution.EmptyHookExecutor{},
   709  			TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
   710  		}
   711  
   712  		// Run test
   713  		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
   714  
   715  		// Assertions
   716  		assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err)
   717  		assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
   718  
   719  		if test.expected.numBids > 0 {
   720  			// Assert out currency
   721  			assert.Equal(t, test.expected.bidRespCurrency, outBidResponse.Cur, "Bid response currency is wrong: %s \n", test.desc)
   722  
   723  			// Assert returned bid
   724  			if !assert.NotNil(t, outBidResponse, "outBidResponse is nil: %s \n", test.desc) {
   725  				return
   726  			}
   727  			if !assert.NotEmpty(t, outBidResponse.SeatBid, "outBidResponse.SeatBid is empty: %s", test.desc) {
   728  				return
   729  			}
   730  			if !assert.NotEmpty(t, outBidResponse.SeatBid[0].Bid, "outBidResponse.SeatBid[0].Bid is empty: %s", test.desc) {
   731  				return
   732  			}
   733  
   734  			// Assert returned bid price matches the currency conversion
   735  			assert.Equal(t, test.expected.bidRespPrice, outBidResponse.SeatBid[0].Bid[0].Price, "Bid response seatBid price is wrong: %s", test.desc)
   736  		} else {
   737  			assert.Len(t, outBidResponse.SeatBid, 0, "outBidResponse.SeatBid should be empty: %s", test.desc)
   738  		}
   739  	}
   740  }
   741  
   742  func TestAdapterCurrency(t *testing.T) {
   743  	fakeCurrencyClient := &fakeCurrencyRatesHttpClient{
   744  		responseBody: `{"dataAsOf":"2018-09-12","conversions":{"USD":{"MXN":10.00}}}`,
   745  	}
   746  	currencyConverter := currency.NewRateConverter(
   747  		fakeCurrencyClient,
   748  		"currency.fake.com",
   749  		24*time.Hour,
   750  	)
   751  	currencyConverter.Run()
   752  
   753  	// Initialize Mock Bidder
   754  	// - Response purposefully causes PBS-Core to stop processing the request, since this test is only
   755  	//   interested in the call to MakeRequests and nothing after.
   756  	mockBidder := &mockBidder{}
   757  	mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return([]*adapters.RequestData(nil), []error(nil))
   758  
   759  	// Initialize Real Exchange
   760  	e := exchange{
   761  		cache: &wellBehavedCache{},
   762  		me:    &metricsConf.NilMetricsEngine{},
   763  		gdprPermsBuilder: fakePermissionsBuilder{
   764  			permissions: &permissionsMock{
   765  				allowAllBidders: true,
   766  			},
   767  		}.Builder,
   768  		currencyConverter: currencyConverter,
   769  		categoriesFetcher: nilCategoryFetcher{},
   770  		bidIDGenerator:    &mockBidIDGenerator{false, false},
   771  		adapterMap: map[openrtb_ext.BidderName]AdaptedBidder{
   772  			openrtb_ext.BidderName("foo"): AdaptBidder(mockBidder, nil, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderName("foo"), nil, ""),
   773  		},
   774  	}
   775  	e.requestSplitter = requestSplitter{
   776  		me:               e.me,
   777  		gdprPermsBuilder: e.gdprPermsBuilder,
   778  	}
   779  
   780  	// Define Bid Request
   781  	request := &openrtb2.BidRequest{
   782  		ID: "some-request-id",
   783  		Imp: []openrtb2.Imp{{
   784  			ID:     "some-impression-id",
   785  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
   786  			Ext:    json.RawMessage(`{"prebid":{"bidder":{"foo":{"placementId":1}}}}`),
   787  		}},
   788  		Site: &openrtb2.Site{
   789  			Page: "prebid.org",
   790  			Ext:  json.RawMessage(`{"amp":0}`),
   791  		},
   792  		Cur: []string{"USD"},
   793  		Ext: json.RawMessage(`{"prebid": {"currency": {"rates": {"USD": {"MXN": 20.00}}}}}`),
   794  	}
   795  
   796  	// Run Auction
   797  	auctionRequest := &AuctionRequest{
   798  		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request},
   799  		Account:           config.Account{},
   800  		UserSyncs:         &emptyUsersync{},
   801  		HookExecutor:      &hookexecution.EmptyHookExecutor{},
   802  		TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
   803  	}
   804  	response, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
   805  	assert.NoError(t, err)
   806  	assert.Equal(t, "some-request-id", response.ID, "Response ID")
   807  	assert.Empty(t, response.SeatBid, "Response Bids")
   808  	assert.Contains(t, string(response.Ext), `"errors":{"foo":[{"code":5,"message":"The adapter failed to generate any bid requests, but also failed to generate an error explaining why"}]}`, "Response Ext")
   809  
   810  	// Test Currency Converter Properly Passed To Adapter
   811  	if assert.NotNil(t, mockBidder.lastExtraRequestInfo, "Currency Conversion Argument") {
   812  		converted, err := mockBidder.lastExtraRequestInfo.ConvertCurrency(2.0, "USD", "MXN")
   813  		assert.NoError(t, err, "Currency Conversion Error")
   814  		assert.Equal(t, 40.0, converted, "Currency Conversion Response")
   815  	}
   816  }
   817  
   818  func TestFloorsSignalling(t *testing.T) {
   819  
   820  	fakeCurrencyClient := &fakeCurrencyRatesHttpClient{
   821  		responseBody: `{"dataAsOf":"2023-04-10","conversions":{"USD":{"MXN":10.00}}}`,
   822  	}
   823  	currencyConverter := currency.NewRateConverter(
   824  		fakeCurrencyClient,
   825  		"currency.com",
   826  		24*time.Hour,
   827  	)
   828  	currencyConverter.Run()
   829  
   830  	// Initialize Real Exchange
   831  	e := exchange{
   832  		cache: &wellBehavedCache{},
   833  		me:    &metricsConf.NilMetricsEngine{},
   834  		gdprPermsBuilder: fakePermissionsBuilder{
   835  			permissions: &permissionsMock{
   836  				allowAllBidders: true,
   837  			},
   838  		}.Builder,
   839  		currencyConverter: currencyConverter,
   840  		categoriesFetcher: nilCategoryFetcher{},
   841  		bidIDGenerator:    &mockBidIDGenerator{false, false},
   842  		floor:             config.PriceFloors{Enabled: true},
   843  	}
   844  	e.requestSplitter = requestSplitter{
   845  		me:               e.me,
   846  		gdprPermsBuilder: e.gdprPermsBuilder,
   847  	}
   848  
   849  	type testResults struct {
   850  		bidFloor    float64
   851  		bidFloorCur string
   852  		err         error
   853  		resolvedReq string
   854  	}
   855  
   856  	testCases := []struct {
   857  		desc         string
   858  		req          *openrtb_ext.RequestWrapper
   859  		floorsEnable bool
   860  		expected     testResults
   861  	}{
   862  		{
   863  			desc:         "no update in imp.bidfloor, floors disabled in account config",
   864  			floorsEnable: false,
   865  			req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
   866  				ID: "some-request-id",
   867  				Imp: []openrtb2.Imp{{
   868  					ID:          "some-impression-id",
   869  					BidFloor:    15,
   870  					BidFloorCur: "USD",
   871  					Banner:      &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
   872  					Ext:         json.RawMessage(`{"prebid":{}}`),
   873  				}},
   874  				Site: &openrtb2.Site{
   875  					Page:   "prebid.org",
   876  					Ext:    json.RawMessage(`{"amp":0}`),
   877  					Domain: "www.website.com",
   878  				},
   879  				Cur: []string{"USD"},
   880  				Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":11,"*|*|www.test.com":15,"*|*|*":20},"Default":50,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true}}}`),
   881  			}},
   882  			expected: testResults{
   883  				bidFloor:    15.00,
   884  				bidFloorCur: "USD",
   885  			},
   886  		},
   887  		{
   888  			desc:         "no update in imp.bidfloor due to no rule matched",
   889  			floorsEnable: true,
   890  			req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
   891  				ID: "some-request-id",
   892  				Imp: []openrtb2.Imp{{
   893  					ID:          "some-impression-id",
   894  					BidFloor:    15,
   895  					BidFloorCur: "USD",
   896  					Banner:      &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
   897  					Ext:         json.RawMessage(`{"prebid":{}}`),
   898  				}},
   899  				Site: &openrtb2.Site{
   900  					Page:   "prebid.org",
   901  					Ext:    json.RawMessage(`{"amp":0}`),
   902  					Domain: "www.website.com",
   903  				},
   904  				Cur: []string{"USD"},
   905  				Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website123.com":10,"*|*|www.test.com":15},"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true}}}`),
   906  			}},
   907  			expected: testResults{
   908  				bidFloor:    15.00,
   909  				bidFloorCur: "USD",
   910  			},
   911  		},
   912  		{
   913  			desc:         "update imp.bidfloor with matched rule value",
   914  			floorsEnable: true,
   915  			req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
   916  				ID: "some-request-id",
   917  				Imp: []openrtb2.Imp{{
   918  					ID:          "some-impression-id",
   919  					BidFloor:    15,
   920  					BidFloorCur: "USD",
   921  					Banner:      &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
   922  					Ext:         json.RawMessage(`{"prebid":{}}`),
   923  				}},
   924  				Site: &openrtb2.Site{
   925  					Page:   "prebid.org",
   926  					Ext:    json.RawMessage(`{"amp":0}`),
   927  					Domain: "www.website.com",
   928  				},
   929  				Cur: []string{"USD"},
   930  				Ext: json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":10,"*|*|www.test.com":15,"*|*|*":20},"Default":50,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true}}}`),
   931  			}},
   932  			expected: testResults{
   933  				bidFloor:    10.00,
   934  				bidFloorCur: "USD",
   935  			},
   936  		},
   937  		{
   938  			desc:         "update resolved request with floors details",
   939  			floorsEnable: true,
   940  			req: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
   941  				ID: "some-request-id",
   942  				Imp: []openrtb2.Imp{{
   943  					ID:          "some-impression-id",
   944  					BidFloor:    15,
   945  					BidFloorCur: "USD",
   946  					Banner:      &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
   947  					Ext:         json.RawMessage(`{"prebid":{}}`),
   948  				}},
   949  				Site: &openrtb2.Site{
   950  					Page:   "prebid.org",
   951  					Ext:    json.RawMessage(`{"amp":0}`),
   952  					Domain: "www.website.com",
   953  				},
   954  				Test: 1,
   955  				Cur:  []string{"USD"},
   956  				Ext:  json.RawMessage(`{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","values":{"banner|300x250|www.website.com":11,"*|*|www.test.com":15,"*|*|*":20},"Default":50,"schema":{"fields":["mediaType","size","domain"],"delimiter":"|"}}]},"enabled":true}}}`),
   957  			}},
   958  			expected: testResults{
   959  				bidFloor:    11.00,
   960  				bidFloorCur: "USD",
   961  				resolvedReq: `{"id":"some-request-id","imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"bidfloor":11,"bidfloorcur":"USD","ext":{"prebid":{"floors":{"floorrule":"banner|300x250|www.website.com","floorrulevalue":11,"floorvalue":11}}}}],"site":{"domain":"www.website.com","page":"prebid.org","ext":{"amp":0}},"test":1,"cur":["USD"],"ext":{"prebid":{"floors":{"floormin":1,"floormincur":"USD","data":{"currency":"USD","modelgroups":[{"modelversion":"model 1 from req","schema":{"fields":["mediaType","size","domain"],"delimiter":"|"},"values":{"*|*|*":20,"*|*|www.test.com":15,"banner|300x250|www.website.com":11},"default":50}]},"enabled":true,"skipped":false,"fetchstatus":"none","location":"request"}}}}`,
   962  			},
   963  		},
   964  	}
   965  
   966  	for _, test := range testCases {
   967  		auctionRequest := &AuctionRequest{
   968  			BidRequestWrapper: test.req,
   969  			Account:           config.Account{DebugAllow: true, PriceFloors: config.AccountPriceFloors{Enabled: test.floorsEnable, MaxRule: 100, MaxSchemaDims: 5}},
   970  			UserSyncs:         &emptyUsersync{},
   971  			HookExecutor:      &hookexecution.EmptyHookExecutor{},
   972  		}
   973  		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
   974  
   975  		// Assertions
   976  		assert.Equal(t, test.expected.err, err, "Error")
   977  		assert.Equal(t, test.expected.bidFloor, auctionRequest.BidRequestWrapper.Imp[0].BidFloor, "Floor Value")
   978  		assert.Equal(t, test.expected.bidFloorCur, auctionRequest.BidRequestWrapper.Imp[0].BidFloorCur, "Floor Currency")
   979  
   980  		if test.req.Test == 1 {
   981  			actualResolvedRequest, _, _, _ := jsonparser.Get(outBidResponse.Ext, "debug", "resolvedrequest")
   982  			assert.JSONEq(t, test.expected.resolvedReq, string(actualResolvedRequest), "Resolved request is incorrect")
   983  		}
   984  	}
   985  
   986  }
   987  func TestGetAuctionCurrencyRates(t *testing.T) {
   988  
   989  	pbsRates := map[string]map[string]float64{
   990  		"MXN": {
   991  			"USD": 20.13,
   992  			"EUR": 27.82,
   993  			"JPY": 5.09, // "MXN" to "JPY" rate not found in customRates
   994  		},
   995  	}
   996  
   997  	customRates := map[string]map[string]float64{
   998  		"MXN": {
   999  			"USD": 25.00, // different rate than in pbsRates
  1000  			"EUR": 27.82, // same as in pbsRates
  1001  			"GBP": 31.12, // not found in pbsRates at all
  1002  		},
  1003  	}
  1004  
  1005  	expectedRateEngineRates := map[string]map[string]float64{
  1006  		"MXN": {
  1007  			"USD": 25.00, // rates engine will prioritize the value found in custom rates
  1008  			"EUR": 27.82, // same value in both the engine reads the custom entry first
  1009  			"JPY": 5.09,  // the engine will find it in the pbsRates conversions
  1010  			"GBP": 31.12, // the engine will find it in the custom conversions
  1011  		},
  1012  	}
  1013  
  1014  	boolTrue := true
  1015  	boolFalse := false
  1016  
  1017  	type testInput struct {
  1018  		pbsRates       map[string]map[string]float64
  1019  		bidExtCurrency *openrtb_ext.ExtRequestCurrency
  1020  	}
  1021  	type testOutput struct {
  1022  		constantRates  bool
  1023  		resultingRates map[string]map[string]float64
  1024  	}
  1025  	testCases := []struct {
  1026  		desc     string
  1027  		given    testInput
  1028  		expected testOutput
  1029  	}{
  1030  		{
  1031  			"valid pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates",
  1032  			testInput{
  1033  				pbsRates: pbsRates,
  1034  				bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
  1035  					ConversionRates: customRates,
  1036  					UsePBSRates:     &boolFalse,
  1037  				},
  1038  			},
  1039  			testOutput{
  1040  				resultingRates: customRates,
  1041  			},
  1042  		},
  1043  		{
  1044  			"valid pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates are a mix but customRates gets priority",
  1045  			testInput{
  1046  				pbsRates: pbsRates,
  1047  				bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
  1048  					ConversionRates: customRates,
  1049  					UsePBSRates:     &boolTrue,
  1050  				},
  1051  			},
  1052  			testOutput{
  1053  				resultingRates: expectedRateEngineRates,
  1054  			},
  1055  		},
  1056  		{
  1057  			"nil pbsRates, valid ConversionRates, false UsePBSRates. Resulting rates identical to customRates",
  1058  			testInput{
  1059  				pbsRates: nil,
  1060  				bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
  1061  					ConversionRates: customRates,
  1062  					UsePBSRates:     &boolFalse,
  1063  				},
  1064  			},
  1065  			testOutput{
  1066  				resultingRates: customRates,
  1067  			},
  1068  		},
  1069  		{
  1070  			"nil pbsRates, valid ConversionRates, true UsePBSRates. Resulting rates identical to customRates",
  1071  			testInput{
  1072  				pbsRates: nil,
  1073  				bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
  1074  					ConversionRates: customRates,
  1075  					UsePBSRates:     &boolTrue,
  1076  				},
  1077  			},
  1078  			testOutput{
  1079  				resultingRates: customRates,
  1080  			},
  1081  		},
  1082  		{
  1083  			"valid pbsRates, empty ConversionRates, false UsePBSRates. Because pbsRates cannot be used, default to constant rates",
  1084  			testInput{
  1085  				pbsRates: pbsRates,
  1086  				bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
  1087  					// ConversionRates inCustomRates not initialized makes for a zero-length map
  1088  					UsePBSRates: &boolFalse,
  1089  				},
  1090  			},
  1091  			testOutput{
  1092  				constantRates: true,
  1093  			},
  1094  		},
  1095  		{
  1096  			"valid pbsRates, nil ConversionRates, UsePBSRates defaults to true. Resulting rates will be identical to pbsRates",
  1097  			testInput{
  1098  				pbsRates:       pbsRates,
  1099  				bidExtCurrency: nil,
  1100  			},
  1101  			testOutput{
  1102  				resultingRates: pbsRates,
  1103  			},
  1104  		},
  1105  		{
  1106  			"nil pbsRates, empty ConversionRates, false UsePBSRates. Default to constant rates",
  1107  			testInput{
  1108  				pbsRates: nil,
  1109  				bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
  1110  					// ConversionRates inCustomRates not initialized makes for a zero-length map
  1111  					UsePBSRates: &boolFalse,
  1112  				},
  1113  			},
  1114  			testOutput{
  1115  				constantRates: true,
  1116  			},
  1117  		},
  1118  		{
  1119  			"customRates empty, UsePBSRates set to true, pbsRates are nil. Return default constant rates converter",
  1120  			testInput{
  1121  				pbsRates: nil,
  1122  				bidExtCurrency: &openrtb_ext.ExtRequestCurrency{
  1123  					// ConversionRates inCustomRates not initialized makes for a zero-length map
  1124  					UsePBSRates: &boolTrue,
  1125  				},
  1126  			},
  1127  			testOutput{
  1128  				constantRates: true,
  1129  			},
  1130  		},
  1131  		{
  1132  			"nil customRates, nil pbsRates, UsePBSRates defaults to true. Return default constant rates converter",
  1133  			testInput{
  1134  				pbsRates:       nil,
  1135  				bidExtCurrency: nil,
  1136  			},
  1137  			testOutput{
  1138  				constantRates: true,
  1139  			},
  1140  		},
  1141  	}
  1142  
  1143  	for _, tc := range testCases {
  1144  
  1145  		// Test setup:
  1146  		jsonPbsRates, err := json.Marshal(tc.given.pbsRates)
  1147  		if err != nil {
  1148  			t.Fatalf("Failed to marshal PBS rates: %v", err)
  1149  		}
  1150  
  1151  		// Init mock currency conversion service
  1152  		mockCurrencyClient := &fakeCurrencyRatesHttpClient{
  1153  			responseBody: `{"dataAsOf":"2018-09-12","conversions":` + string(jsonPbsRates) + `}`,
  1154  		}
  1155  		mockCurrencyConverter := currency.NewRateConverter(
  1156  			mockCurrencyClient,
  1157  			"currency.fake.com",
  1158  			24*time.Hour,
  1159  		)
  1160  		mockCurrencyConverter.Run()
  1161  
  1162  		e := new(exchange)
  1163  		e.currencyConverter = mockCurrencyConverter
  1164  
  1165  		// Run test
  1166  		auctionRates := e.getAuctionCurrencyRates(tc.given.bidExtCurrency)
  1167  
  1168  		// When fromCurrency and toCurrency are the same, a rate of 1.00 is always expected
  1169  		rate, err := auctionRates.GetRate("USD", "USD")
  1170  		assert.NoError(t, err, tc.desc)
  1171  		assert.Equal(t, float64(1), rate, tc.desc)
  1172  
  1173  		// If we expect an error, assert we have one along with a conversion rate of zero
  1174  		if tc.expected.constantRates {
  1175  			rate, err := auctionRates.GetRate("USD", "MXN")
  1176  			assert.Error(t, err, tc.desc)
  1177  			assert.Equal(t, float64(0), rate, tc.desc)
  1178  		} else {
  1179  			for fromCurrency, rates := range tc.expected.resultingRates {
  1180  				for toCurrency, expectedRate := range rates {
  1181  					actualRate, err := auctionRates.GetRate(fromCurrency, toCurrency)
  1182  					assert.NoError(t, err, tc.desc)
  1183  					assert.Equal(t, expectedRate, actualRate, tc.desc)
  1184  				}
  1185  			}
  1186  		}
  1187  	}
  1188  }
  1189  
  1190  func TestReturnCreativeEndToEnd(t *testing.T) {
  1191  	sampleAd := "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST ...></VAST>"
  1192  
  1193  	// Define test cases
  1194  	type aTest struct {
  1195  		desc   string
  1196  		inExt  json.RawMessage
  1197  		outAdM string
  1198  	}
  1199  	testGroups := []struct {
  1200  		groupDesc   string
  1201  		testCases   []aTest
  1202  		expectError bool
  1203  	}{
  1204  		{
  1205  			groupDesc: "Valid bidRequest Ext but no returnCreative value specified, default to returning creative",
  1206  			testCases: []aTest{
  1207  				{
  1208  					"Nil ext in bidRequest",
  1209  					nil,
  1210  					sampleAd,
  1211  				},
  1212  				{
  1213  					"empty ext",
  1214  					json.RawMessage(``),
  1215  					sampleAd,
  1216  				},
  1217  				{
  1218  					"bids doesn't come with returnCreative value",
  1219  					json.RawMessage(`{"prebid":{"cache":{"bids":{}}}}`),
  1220  					sampleAd,
  1221  				},
  1222  				{
  1223  					"vast doesn't come with returnCreative value",
  1224  					json.RawMessage(`{"prebid":{"cache":{"vastXml":{}}}}`),
  1225  					sampleAd,
  1226  				},
  1227  			},
  1228  		},
  1229  		{
  1230  			groupDesc: "Bids field comes with returnCreative value",
  1231  			testCases: []aTest{
  1232  				{
  1233  					"Bids returnCreative set to true, return ad markup in response",
  1234  					json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":true}}}}`),
  1235  					sampleAd,
  1236  				},
  1237  				{
  1238  					"Bids returnCreative set to false, don't return ad markup in response",
  1239  					json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":false}}}}`),
  1240  					"",
  1241  				},
  1242  			},
  1243  		},
  1244  		{
  1245  			groupDesc: "Vast field comes with returnCreative value",
  1246  			testCases: []aTest{
  1247  				{
  1248  					"Vast returnCreative set to true, return ad markup in response",
  1249  					json.RawMessage(`{"prebid":{"cache":{"vastXml":{"returnCreative":true}}}}`),
  1250  					sampleAd,
  1251  				},
  1252  				{
  1253  					"Vast returnCreative set to false, don't return ad markup in response",
  1254  					json.RawMessage(`{"prebid":{"cache":{"vastXml":{"returnCreative":false}}}}`),
  1255  					"",
  1256  				},
  1257  			},
  1258  		},
  1259  		{
  1260  			groupDesc: "Both Bids and Vast come with their own returnCreative value",
  1261  			testCases: []aTest{
  1262  				{
  1263  					"Both false, expect empty AdM",
  1264  					json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":false},"vastXml":{"returnCreative":false}}}}`),
  1265  					"",
  1266  				},
  1267  				{
  1268  					"Bids returnCreative is true, expect valid AdM",
  1269  					json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":true},"vastXml":{"returnCreative":false}}}}`),
  1270  					sampleAd,
  1271  				},
  1272  				{
  1273  					"Vast returnCreative is true, expect valid AdM",
  1274  					json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":false},"vastXml":{"returnCreative":true}}}}`),
  1275  					sampleAd,
  1276  				},
  1277  				{
  1278  					"Both field's returnCreative set to true, expect valid AdM",
  1279  					json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":true},"vastXml":{"returnCreative":true}}}}`),
  1280  					sampleAd,
  1281  				},
  1282  			},
  1283  		},
  1284  	}
  1285  
  1286  	// Init an exchange to run an auction from
  1287  	noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
  1288  	server := httptest.NewServer(http.HandlerFunc(noBidServer))
  1289  	defer server.Close()
  1290  
  1291  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  1292  	if error != nil {
  1293  		t.Errorf("Failed to create a category Fetcher: %v", error)
  1294  	}
  1295  
  1296  	bidderImpl := &goodSingleBidder{
  1297  		httpRequest: &adapters.RequestData{
  1298  			Method:  "POST",
  1299  			Uri:     server.URL,
  1300  			Body:    []byte("{\"key\":\"val\"}"),
  1301  			Headers: http.Header{},
  1302  		},
  1303  		bidResponse: &adapters.BidderResponse{
  1304  			Bids: []*adapters.TypedBid{
  1305  				{
  1306  					Bid: &openrtb2.Bid{AdM: sampleAd},
  1307  				},
  1308  			},
  1309  		},
  1310  	}
  1311  
  1312  	e := new(exchange)
  1313  	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
  1314  		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
  1315  	}
  1316  	e.cache = &wellBehavedCache{}
  1317  	e.me = &metricsConf.NilMetricsEngine{}
  1318  	e.gdprPermsBuilder = fakePermissionsBuilder{
  1319  		permissions: &permissionsMock{
  1320  			allowAllBidders: true,
  1321  		},
  1322  	}.Builder
  1323  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1324  	e.categoriesFetcher = categoriesFetcher
  1325  	e.bidIDGenerator = &mockBidIDGenerator{false, false}
  1326  	e.requestSplitter = requestSplitter{
  1327  		me:               e.me,
  1328  		gdprPermsBuilder: e.gdprPermsBuilder,
  1329  	}
  1330  
  1331  	// Define mock incoming bid requeset
  1332  	mockBidRequest := &openrtb2.BidRequest{
  1333  		ID: "some-request-id",
  1334  		Imp: []openrtb2.Imp{{
  1335  			ID:     "some-impression-id",
  1336  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
  1337  			Ext:    json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`),
  1338  		}},
  1339  		Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
  1340  	}
  1341  
  1342  	// Run tests
  1343  	for _, testGroup := range testGroups {
  1344  		for _, test := range testGroup.testCases {
  1345  			mockBidRequest.Ext = test.inExt
  1346  
  1347  			auctionRequest := &AuctionRequest{
  1348  				BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
  1349  				Account:           config.Account{},
  1350  				UserSyncs:         &emptyUsersync{},
  1351  				HookExecutor:      &hookexecution.EmptyHookExecutor{},
  1352  				TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  1353  			}
  1354  
  1355  			// Run test
  1356  			debugLog := DebugLog{}
  1357  			outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &debugLog)
  1358  
  1359  			// Assert return error, if any
  1360  			if testGroup.expectError {
  1361  				assert.Errorf(t, err, "HoldAuction expected to throw error for: %s - %s. \n", testGroup.groupDesc, test.desc)
  1362  				continue
  1363  			} else {
  1364  				assert.NoErrorf(t, err, "%s: %s. HoldAuction error: %v \n", testGroup.groupDesc, test.desc, err)
  1365  				assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
  1366  			}
  1367  
  1368  			// Assert returned bid
  1369  			if !assert.NotNil(t, outBidResponse, "%s: %s. outBidResponse is nil \n", testGroup.groupDesc, test.desc) {
  1370  				return
  1371  			}
  1372  			if !assert.NotEmpty(t, outBidResponse.SeatBid, "%s: %s. outBidResponse.SeatBid is empty \n", testGroup.groupDesc, test.desc) {
  1373  				return
  1374  			}
  1375  			if !assert.NotEmpty(t, outBidResponse.SeatBid[0].Bid, "%s: %s. outBidResponse.SeatBid[0].Bid is empty \n", testGroup.groupDesc, test.desc) {
  1376  				return
  1377  			}
  1378  			assert.Equal(t, test.outAdM, outBidResponse.SeatBid[0].Bid[0].AdM, "Ad markup string doesn't match in: %s - %s \n", testGroup.groupDesc, test.desc)
  1379  		}
  1380  	}
  1381  }
  1382  
  1383  func TestGetBidCacheInfoEndToEnd(t *testing.T) {
  1384  	testUUID := "CACHE_UUID_1234"
  1385  	testExternalCacheScheme := "https"
  1386  	testExternalCacheHost := "www.externalprebidcache.net"
  1387  	testExternalCachePath := "endpoints/cache"
  1388  
  1389  	// 1) An adapter
  1390  	bidderName := openrtb_ext.BidderName("appnexus")
  1391  
  1392  	cfg := &config.Configuration{
  1393  		CacheURL: config.Cache{
  1394  			Host: "www.internalprebidcache.net",
  1395  		},
  1396  		ExtCacheURL: config.ExternalCache{
  1397  			Scheme: testExternalCacheScheme,
  1398  			Host:   testExternalCacheHost,
  1399  			Path:   testExternalCachePath,
  1400  		},
  1401  	}
  1402  
  1403  	adapterList := make([]openrtb_ext.BidderName, 0, 2)
  1404  	syncerKeys := []string{}
  1405  	var moduleStageNames map[string][]string
  1406  	testEngine := metricsConf.NewMetricsEngine(cfg, adapterList, syncerKeys, moduleStageNames)
  1407  	//	2) Init new exchange with said configuration
  1408  	handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
  1409  	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
  1410  	defer server.Close()
  1411  
  1412  	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
  1413  	if err != nil {
  1414  		t.Fatal(err)
  1415  	}
  1416  
  1417  	adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
  1418  	if adaptersErr != nil {
  1419  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
  1420  	}
  1421  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1422  	pbc := pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine)
  1423  
  1424  	gdprPermsBuilder := fakePermissionsBuilder{
  1425  		permissions: &permissionsMock{
  1426  			allowAllBidders: true,
  1427  		},
  1428  	}.Builder
  1429  
  1430  	e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
  1431  	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
  1432  	liveAdapters := []openrtb_ext.BidderName{bidderName}
  1433  
  1434  	//adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
  1435  	bids := []*openrtb2.Bid{
  1436  		{
  1437  			ID:             "some-imp-id",
  1438  			ImpID:          "",
  1439  			Price:          9.517803,
  1440  			NURL:           "",
  1441  			BURL:           "",
  1442  			LURL:           "",
  1443  			AdM:            "",
  1444  			AdID:           "",
  1445  			ADomain:        nil,
  1446  			Bundle:         "",
  1447  			IURL:           "",
  1448  			CID:            "",
  1449  			CrID:           "",
  1450  			Tactic:         "",
  1451  			Cat:            nil,
  1452  			Attr:           nil,
  1453  			API:            0,
  1454  			Protocol:       0,
  1455  			QAGMediaRating: 0,
  1456  			Language:       "",
  1457  			DealID:         "",
  1458  			W:              300,
  1459  			H:              250,
  1460  			WRatio:         0,
  1461  			HRatio:         0,
  1462  			Exp:            0,
  1463  			Ext:            nil,
  1464  		},
  1465  	}
  1466  	auc := &auction{
  1467  		cacheIds: map[*openrtb2.Bid]string{
  1468  			bids[0]: testUUID,
  1469  		},
  1470  	}
  1471  	aPbsOrtbBidArr := []*entities.PbsOrtbBid{
  1472  		{
  1473  			Bid:     bids[0],
  1474  			BidType: openrtb_ext.BidTypeBanner,
  1475  			BidTargets: map[string]string{
  1476  				"pricegranularity":  "med",
  1477  				"includewinners":    "true",
  1478  				"includebidderkeys": "false",
  1479  			},
  1480  		},
  1481  	}
  1482  	adapterBids := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  1483  		bidderName: {
  1484  			Bids:     aPbsOrtbBidArr,
  1485  			Currency: "USD",
  1486  		},
  1487  	}
  1488  
  1489  	//adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra,
  1490  	adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{
  1491  		bidderName: {
  1492  			ResponseTimeMillis: 5,
  1493  			Errors: []openrtb_ext.ExtBidderMessage{
  1494  				{
  1495  					Code:    999,
  1496  					Message: "Post ib.adnxs.com/openrtb2?query1&query2: unsupported protocol scheme \"\"",
  1497  				},
  1498  			},
  1499  		},
  1500  	}
  1501  	bidRequest := &openrtb2.BidRequest{
  1502  		ID:   "some-request-id",
  1503  		TMax: 1000,
  1504  		Imp: []openrtb2.Imp{
  1505  			{
  1506  				ID:     "test-div",
  1507  				Secure: openrtb2.Int8Ptr(0),
  1508  				Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}}},
  1509  				Ext: json.RawMessage(` {
  1510      "rubicon": {
  1511          "accountId": 1001,
  1512          "siteId": 113932,
  1513          "zoneId": 535510
  1514      },
  1515      "appnexus": { "placementId": 1 },
  1516      "pubmatic": { "publisherId": "156209", "adSlot": "pubmatic_test2@300x250" },
  1517      "pulsepoint": { "cf": "300X250", "cp": 512379, "ct": 486653 },
  1518      "conversant": { "site_id": "108060" },
  1519      "ix": { "siteId": "287415" }
  1520  }`),
  1521  			},
  1522  		},
  1523  		Site: &openrtb2.Site{
  1524  			Page:      "http://rubitest.com/index.html",
  1525  			Publisher: &openrtb2.Publisher{ID: "1001"},
  1526  		},
  1527  		Test: 1,
  1528  		Ext:  json.RawMessage(`{"prebid": { "cache": { "bids": {}, "vastxml": {} }, "targeting": { "pricegranularity": "med", "includewinners": true, "includebidderkeys": false } }}`),
  1529  	}
  1530  
  1531  	var errList []error
  1532  
  1533  	// 	4) Build bid response
  1534  	bid_resp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, adapterExtra, auc, nil, true, nil, "", errList)
  1535  
  1536  	expectedBidResponse := &openrtb2.BidResponse{
  1537  		SeatBid: []openrtb2.SeatBid{
  1538  			{
  1539  				Seat: string(bidderName),
  1540  				Bid: []openrtb2.Bid{
  1541  					{
  1542  						Ext: json.RawMessage(`{ "prebid": { "cache": { "bids": { "cacheId": "` + testUUID + `", "url": "` + testExternalCacheScheme + `://` + testExternalCacheHost + `/` + testExternalCachePath + `?uuid=` + testUUID + `" }, "key": "", "url": "" }`),
  1543  					},
  1544  				},
  1545  			},
  1546  		},
  1547  	}
  1548  	// compare cache UUID
  1549  	expCacheUUID, err := jsonparser.GetString(expectedBidResponse.SeatBid[0].Bid[0].Ext, "prebid", "cache", "bids", "cacheId")
  1550  	assert.NoErrorf(t, err, "[TestGetBidCacheInfo] Error found while trying to json parse the cacheId field from expected build response. Message: %v \n", err)
  1551  
  1552  	cacheUUID, err := jsonparser.GetString(bid_resp.SeatBid[0].Bid[0].Ext, "prebid", "cache", "bids", "cacheId")
  1553  	assert.NoErrorf(t, err, "[TestGetBidCacheInfo] bid_resp.SeatBid[0].Bid[0].Ext = %s \n", bid_resp.SeatBid[0].Bid[0].Ext)
  1554  
  1555  	assert.Equal(t, expCacheUUID, cacheUUID, "[TestGetBidCacheInfo] cacheId field in ext should equal \"%s\" \n", expCacheUUID)
  1556  
  1557  	// compare cache URL
  1558  	expCacheURL, err := jsonparser.GetString(expectedBidResponse.SeatBid[0].Bid[0].Ext, "prebid", "cache", "bids", "url")
  1559  	assert.NoErrorf(t, err, "[TestGetBidCacheInfo] Error found while trying to json parse the url field from expected build response. Message: %v \n", err)
  1560  
  1561  	cacheURL, err := jsonparser.GetString(bid_resp.SeatBid[0].Bid[0].Ext, "prebid", "cache", "bids", "url")
  1562  	assert.NoErrorf(t, err, "[TestGetBidCacheInfo] Error found while trying to json parse the url field from actual build response. Message: %v \n", err)
  1563  
  1564  	assert.Equal(t, expCacheURL, cacheURL, "[TestGetBidCacheInfo] cacheId field in ext should equal \"%s\" \n", expCacheURL)
  1565  }
  1566  
  1567  func TestBidReturnsCreative(t *testing.T) {
  1568  	sampleAd := "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST ...></VAST>"
  1569  	sampleOpenrtbBid := &openrtb2.Bid{ID: "some-bid-id", AdM: sampleAd}
  1570  
  1571  	// Define test cases
  1572  	testCases := []struct {
  1573  		description            string
  1574  		inReturnCreative       bool
  1575  		expectedCreativeMarkup string
  1576  	}{
  1577  		{
  1578  			"returnCreative set to true, expect a full creative markup string in returned bid",
  1579  			true,
  1580  			sampleAd,
  1581  		},
  1582  		{
  1583  			"returnCreative set to false, expect empty creative markup string in returned bid",
  1584  			false,
  1585  			"",
  1586  		},
  1587  	}
  1588  
  1589  	// Test set up
  1590  	sampleBids := []*entities.PbsOrtbBid{
  1591  		{
  1592  			Bid:            sampleOpenrtbBid,
  1593  			BidType:        openrtb_ext.BidTypeBanner,
  1594  			BidTargets:     map[string]string{},
  1595  			GeneratedBidID: "randomId",
  1596  		},
  1597  	}
  1598  	sampleAuction := &auction{cacheIds: map[*openrtb2.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}}
  1599  
  1600  	noBidHandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
  1601  	server := httptest.NewServer(http.HandlerFunc(noBidHandler))
  1602  	defer server.Close()
  1603  
  1604  	bidderImpl := &goodSingleBidder{
  1605  		httpRequest: &adapters.RequestData{
  1606  			Method:  "POST",
  1607  			Uri:     server.URL,
  1608  			Body:    []byte("{\"key\":\"val\"}"),
  1609  			Headers: http.Header{},
  1610  		},
  1611  		bidResponse: &adapters.BidderResponse{},
  1612  	}
  1613  	e := new(exchange)
  1614  	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
  1615  		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
  1616  	}
  1617  	e.cache = &wellBehavedCache{}
  1618  	e.me = &metricsConf.NilMetricsEngine{}
  1619  
  1620  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1621  
  1622  	//Run tests
  1623  	for _, test := range testCases {
  1624  		resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, test.inReturnCreative, nil, nil, "", "")
  1625  
  1626  		assert.Equal(t, 0, len(resultingErrs), "%s. Test should not return errors \n", test.description)
  1627  		assert.Equal(t, test.expectedCreativeMarkup, resultingBids[0].AdM, "%s. Ad markup string doesn't match expected \n", test.description)
  1628  
  1629  		var bidExt openrtb_ext.ExtBid
  1630  		json.Unmarshal(resultingBids[0].Ext, &bidExt)
  1631  		assert.Equal(t, 0, bidExt.Prebid.DealPriority, "%s. Test should have DealPriority set to 0", test.description)
  1632  		assert.Equal(t, false, bidExt.Prebid.DealTierSatisfied, "%s. Test should have DealTierSatisfied set to false", test.description)
  1633  	}
  1634  }
  1635  
  1636  func TestGetBidCacheInfo(t *testing.T) {
  1637  	bid := &openrtb2.Bid{ID: "42"}
  1638  	testCases := []struct {
  1639  		description      string
  1640  		scheme           string
  1641  		host             string
  1642  		path             string
  1643  		bid              *entities.PbsOrtbBid
  1644  		auction          *auction
  1645  		expectedFound    bool
  1646  		expectedCacheID  string
  1647  		expectedCacheURL string
  1648  	}{
  1649  		{
  1650  			description:      "JSON Cache ID",
  1651  			scheme:           "https",
  1652  			host:             "prebid.org",
  1653  			path:             "cache",
  1654  			bid:              &entities.PbsOrtbBid{Bid: bid},
  1655  			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
  1656  			expectedFound:    true,
  1657  			expectedCacheID:  "anyID",
  1658  			expectedCacheURL: "https://prebid.org/cache?uuid=anyID",
  1659  		},
  1660  		{
  1661  			description:      "VAST Cache ID",
  1662  			scheme:           "https",
  1663  			host:             "prebid.org",
  1664  			path:             "cache",
  1665  			bid:              &entities.PbsOrtbBid{Bid: bid},
  1666  			auction:          &auction{vastCacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
  1667  			expectedFound:    true,
  1668  			expectedCacheID:  "anyID",
  1669  			expectedCacheURL: "https://prebid.org/cache?uuid=anyID",
  1670  		},
  1671  		{
  1672  			description:      "Cache ID Not Found",
  1673  			scheme:           "https",
  1674  			host:             "prebid.org",
  1675  			path:             "cache",
  1676  			bid:              &entities.PbsOrtbBid{Bid: bid},
  1677  			auction:          &auction{},
  1678  			expectedFound:    false,
  1679  			expectedCacheID:  "",
  1680  			expectedCacheURL: "",
  1681  		},
  1682  		{
  1683  			description:      "Scheme Not Provided",
  1684  			host:             "prebid.org",
  1685  			path:             "cache",
  1686  			bid:              &entities.PbsOrtbBid{Bid: bid},
  1687  			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
  1688  			expectedFound:    true,
  1689  			expectedCacheID:  "anyID",
  1690  			expectedCacheURL: "prebid.org/cache?uuid=anyID",
  1691  		},
  1692  		{
  1693  			description:      "Host And Path Not Provided - Without Scheme",
  1694  			bid:              &entities.PbsOrtbBid{Bid: bid},
  1695  			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
  1696  			expectedFound:    true,
  1697  			expectedCacheID:  "anyID",
  1698  			expectedCacheURL: "",
  1699  		},
  1700  		{
  1701  			description:      "Host And Path Not Provided - With Scheme",
  1702  			scheme:           "https",
  1703  			bid:              &entities.PbsOrtbBid{Bid: bid},
  1704  			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
  1705  			expectedFound:    true,
  1706  			expectedCacheID:  "anyID",
  1707  			expectedCacheURL: "",
  1708  		},
  1709  		{
  1710  			description:      "Nil Bid",
  1711  			scheme:           "https",
  1712  			host:             "prebid.org",
  1713  			path:             "cache",
  1714  			bid:              nil,
  1715  			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
  1716  			expectedFound:    false,
  1717  			expectedCacheID:  "",
  1718  			expectedCacheURL: "",
  1719  		},
  1720  		{
  1721  			description:      "Nil Embedded Bid",
  1722  			scheme:           "https",
  1723  			host:             "prebid.org",
  1724  			path:             "cache",
  1725  			bid:              &entities.PbsOrtbBid{Bid: nil},
  1726  			auction:          &auction{cacheIds: map[*openrtb2.Bid]string{bid: "anyID"}},
  1727  			expectedFound:    false,
  1728  			expectedCacheID:  "",
  1729  			expectedCacheURL: "",
  1730  		},
  1731  		{
  1732  			description:      "Nil Auction",
  1733  			scheme:           "https",
  1734  			host:             "prebid.org",
  1735  			path:             "cache",
  1736  			bid:              &entities.PbsOrtbBid{Bid: bid},
  1737  			auction:          nil,
  1738  			expectedFound:    false,
  1739  			expectedCacheID:  "",
  1740  			expectedCacheURL: "",
  1741  		},
  1742  	}
  1743  
  1744  	for _, test := range testCases {
  1745  		exchange := &exchange{
  1746  			cache: &mockCache{
  1747  				scheme: test.scheme,
  1748  				host:   test.host,
  1749  				path:   test.path,
  1750  			},
  1751  		}
  1752  
  1753  		cacheInfo, found := exchange.getBidCacheInfo(test.bid, test.auction)
  1754  
  1755  		assert.Equal(t, test.expectedFound, found, test.description+":found")
  1756  		assert.Equal(t, test.expectedCacheID, cacheInfo.CacheId, test.description+":id")
  1757  		assert.Equal(t, test.expectedCacheURL, cacheInfo.Url, test.description+":url")
  1758  	}
  1759  }
  1760  
  1761  func TestBidResponseCurrency(t *testing.T) {
  1762  	// Init objects
  1763  	cfg := &config.Configuration{}
  1764  
  1765  	handlerNoBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
  1766  	server := httptest.NewServer(http.HandlerFunc(handlerNoBidServer))
  1767  	defer server.Close()
  1768  
  1769  	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
  1770  	if err != nil {
  1771  		t.Fatal(err)
  1772  	}
  1773  
  1774  	adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
  1775  	if adaptersErr != nil {
  1776  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
  1777  	}
  1778  
  1779  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  1780  
  1781  	gdprPermsBuilder := fakePermissionsBuilder{
  1782  		permissions: &permissionsMock{
  1783  			allowAllBidders: true,
  1784  		},
  1785  	}.Builder
  1786  
  1787  	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
  1788  
  1789  	liveAdapters := make([]openrtb_ext.BidderName, 1)
  1790  	liveAdapters[0] = "appnexus"
  1791  
  1792  	bidRequest := &openrtb2.BidRequest{
  1793  		ID: "some-request-id",
  1794  		Imp: []openrtb2.Imp{{
  1795  			ID:     "some-impression-id",
  1796  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
  1797  			Ext:    json.RawMessage(`{"appnexus": {"placementId": 10433394}}`),
  1798  		}},
  1799  		Site:   &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
  1800  		Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"},
  1801  		AT:     1,
  1802  		TMax:   500,
  1803  		Ext:    json.RawMessage(`{"id": "some-request-id","site": {"page": "prebid.org"},"imp": [{"id": "some-impression-id","banner": {"format": [{"w": 300,"h": 250},{"w": 300,"h": 600}]},"ext": {"appnexus": {"placementId": 10433394}}}],"tmax": 500}`),
  1804  	}
  1805  
  1806  	adapterExtra := map[openrtb_ext.BidderName]*seatResponseExtra{
  1807  		"appnexus": {ResponseTimeMillis: 5},
  1808  	}
  1809  
  1810  	var errList []error
  1811  
  1812  	sampleBid := &openrtb2.Bid{
  1813  		ID:    "some-imp-id",
  1814  		Price: 9.517803,
  1815  		W:     300,
  1816  		H:     250,
  1817  		Ext:   nil,
  1818  	}
  1819  	aPbsOrtbBidArr := []*entities.PbsOrtbBid{{Bid: sampleBid, BidType: openrtb_ext.BidTypeBanner, OriginalBidCPM: 9.517803}}
  1820  	sampleSeatBid := []openrtb2.SeatBid{
  1821  		{
  1822  			Seat: "appnexus",
  1823  			Bid: []openrtb2.Bid{
  1824  				{
  1825  					ID:    "some-imp-id",
  1826  					Price: 9.517803,
  1827  					W:     300,
  1828  					H:     250,
  1829  					Ext:   json.RawMessage(`{"origbidcpm":9.517803,"prebid":{"type":"banner"}}`),
  1830  				},
  1831  			},
  1832  		},
  1833  	}
  1834  	emptySeatBid := []openrtb2.SeatBid{}
  1835  
  1836  	// Test cases
  1837  	type aTest struct {
  1838  		description         string
  1839  		adapterBids         map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
  1840  		expectedBidResponse *openrtb2.BidResponse
  1841  	}
  1842  	testCases := []aTest{
  1843  		{
  1844  			description: "1) Adapter to bids map comes with a non-empty currency field and non-empty bid array",
  1845  			adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  1846  				openrtb_ext.BidderName("appnexus"): {
  1847  					Bids:     aPbsOrtbBidArr,
  1848  					Currency: "USD",
  1849  				},
  1850  			},
  1851  			expectedBidResponse: &openrtb2.BidResponse{
  1852  				ID:      "some-request-id",
  1853  				SeatBid: sampleSeatBid,
  1854  				Cur:     "USD",
  1855  			},
  1856  		},
  1857  		{
  1858  			description: "2) Adapter to bids map comes with a non-empty currency field but an empty bid array",
  1859  			adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  1860  				openrtb_ext.BidderName("appnexus"): {
  1861  					Bids:     nil,
  1862  					Currency: "USD",
  1863  				},
  1864  			},
  1865  			expectedBidResponse: &openrtb2.BidResponse{
  1866  				ID:      "some-request-id",
  1867  				SeatBid: emptySeatBid,
  1868  				Cur:     "",
  1869  			},
  1870  		},
  1871  		{
  1872  			description: "3) Adapter to bids map comes with an empty currency string and a non-empty bid array",
  1873  			adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  1874  				openrtb_ext.BidderName("appnexus"): {
  1875  					Bids:     aPbsOrtbBidArr,
  1876  					Currency: "",
  1877  				},
  1878  			},
  1879  			expectedBidResponse: &openrtb2.BidResponse{
  1880  				ID:      "some-request-id",
  1881  				SeatBid: sampleSeatBid,
  1882  				Cur:     "",
  1883  			},
  1884  		},
  1885  		{
  1886  			description: "4) Adapter to bids map comes with an empty currency string and an empty bid array",
  1887  			adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  1888  				openrtb_ext.BidderName("appnexus"): {
  1889  					Bids:     nil,
  1890  					Currency: "",
  1891  				},
  1892  			},
  1893  			expectedBidResponse: &openrtb2.BidResponse{
  1894  				ID:      "some-request-id",
  1895  				SeatBid: emptySeatBid,
  1896  				Cur:     "",
  1897  			},
  1898  		},
  1899  	}
  1900  
  1901  	bidResponseExt := &openrtb_ext.ExtBidResponse{
  1902  		ResponseTimeMillis:   map[openrtb_ext.BidderName]int{openrtb_ext.BidderName("appnexus"): 5},
  1903  		RequestTimeoutMillis: 500,
  1904  	}
  1905  	// Run tests
  1906  	for i := range testCases {
  1907  		actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, testCases[i].adapterBids, bidRequest, adapterExtra, nil, bidResponseExt, true, nil, "", errList)
  1908  		assert.Equalf(t, testCases[i].expectedBidResponse, actualBidResp, fmt.Sprintf("[TEST_FAILED] Objects must be equal for test: %s \n Expected: >>%s<< \n Actual: >>%s<< ", testCases[i].description, testCases[i].expectedBidResponse.Ext, actualBidResp.Ext))
  1909  	}
  1910  }
  1911  
  1912  func TestBidResponseImpExtInfo(t *testing.T) {
  1913  	// Init objects
  1914  	cfg := &config.Configuration{}
  1915  
  1916  	gdprPermsBuilder := fakePermissionsBuilder{
  1917  		permissions: &permissionsMock{
  1918  			allowAllBidders: true,
  1919  		},
  1920  	}.Builder
  1921  
  1922  	noBidHandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
  1923  	server := httptest.NewServer(http.HandlerFunc(noBidHandler))
  1924  	defer server.Close()
  1925  
  1926  	biddersInfo := config.BidderInfos{"appnexus": config.BidderInfo{Endpoint: "http://ib.adnxs.com"}}
  1927  
  1928  	adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
  1929  	if adaptersErr != nil {
  1930  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
  1931  	}
  1932  
  1933  	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdprPermsBuilder, nil, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
  1934  
  1935  	liveAdapters := make([]openrtb_ext.BidderName, 1)
  1936  	liveAdapters[0] = "appnexus"
  1937  
  1938  	bidRequest := &openrtb2.BidRequest{
  1939  		ID: "some-request-id",
  1940  		Imp: []openrtb2.Imp{{
  1941  			ID:    "some-impression-id",
  1942  			Video: &openrtb2.Video{},
  1943  			Ext:   json.RawMessage(`{"appnexus": {"placementId": 10433394}}`),
  1944  		}},
  1945  		Ext: json.RawMessage(``),
  1946  	}
  1947  
  1948  	var errList []error
  1949  
  1950  	sampleBid := &openrtb2.Bid{
  1951  		ID:    "some-imp-id",
  1952  		ImpID: "some-impression-id",
  1953  		W:     300,
  1954  		H:     250,
  1955  		Ext:   nil,
  1956  	}
  1957  	aPbsOrtbBidArr := []*entities.PbsOrtbBid{{Bid: sampleBid, BidType: openrtb_ext.BidTypeVideo}}
  1958  
  1959  	adapterBids := map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  1960  		openrtb_ext.BidderName("appnexus"): {
  1961  			Bids: aPbsOrtbBidArr,
  1962  		},
  1963  	}
  1964  
  1965  	impExtInfo := make(map[string]ImpExtInfo, 1)
  1966  	impExtInfo["some-impression-id"] = ImpExtInfo{
  1967  		true,
  1968  		[]byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`),
  1969  		json.RawMessage(`{"imp_passthrough_val": 1}`)}
  1970  
  1971  	expectedBidResponseExt := `{"origbidcpm":0,"prebid":{"type":"video","passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`
  1972  
  1973  	actualBidResp := e.buildBidResponse(context.Background(), liveAdapters, adapterBids, bidRequest, nil, nil, nil, true, impExtInfo, "", errList)
  1974  
  1975  	resBidExt := string(actualBidResp.SeatBid[0].Bid[0].Ext)
  1976  	assert.Equalf(t, expectedBidResponseExt, resBidExt, "Expected bid response extension is incorrect")
  1977  
  1978  }
  1979  
  1980  // TestRaceIntegration runs an integration test using all the sample params from
  1981  // adapters/{bidder}/{bidder}test/params/race/*.json files.
  1982  //
  1983  // Its primary goal is to catch race conditions, since parts of the BidRequest passed into MakeBids()
  1984  // are shared across many goroutines.
  1985  //
  1986  // The "known" file names right now are "banner.json" and "video.json". These files should hold params
  1987  // which the Bidder would expect on banner or video Imps, respectively.
  1988  func TestRaceIntegration(t *testing.T) {
  1989  	noBidServer := func(w http.ResponseWriter, r *http.Request) {
  1990  		w.WriteHeader(204)
  1991  	}
  1992  	server := httptest.NewServer(http.HandlerFunc(noBidServer))
  1993  	defer server.Close()
  1994  
  1995  	cfg := &config.Configuration{}
  1996  
  1997  	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
  1998  	if err != nil {
  1999  		t.Fatal(err)
  2000  	}
  2001  
  2002  	adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
  2003  	if adaptersErr != nil {
  2004  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
  2005  	}
  2006  
  2007  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2008  
  2009  	auctionRequest := &AuctionRequest{
  2010  		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: getTestBuildRequest(t)},
  2011  		Account:           config.Account{},
  2012  		UserSyncs:         &emptyUsersync{},
  2013  		HookExecutor:      &hookexecution.EmptyHookExecutor{},
  2014  		TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  2015  	}
  2016  
  2017  	debugLog := DebugLog{}
  2018  
  2019  	gdprPermsBuilder := fakePermissionsBuilder{
  2020  		permissions: &permissionsMock{
  2021  			allowAllBidders: true,
  2022  		},
  2023  	}.Builder
  2024  
  2025  	ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, &nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
  2026  	_, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog)
  2027  	if err != nil {
  2028  		t.Errorf("HoldAuction returned unexpected error: %v", err)
  2029  	}
  2030  }
  2031  
  2032  func newCategoryFetcher(directory string) (stored_requests.CategoryFetcher, error) {
  2033  	fetcher, err := file_fetcher.NewFileFetcher(directory)
  2034  	if err != nil {
  2035  		return nil, err
  2036  	}
  2037  	catfetcher, ok := fetcher.(stored_requests.CategoryFetcher)
  2038  	if !ok {
  2039  		return nil, fmt.Errorf("Failed to type cast fetcher to CategoryFetcher")
  2040  	}
  2041  	return catfetcher, nil
  2042  }
  2043  
  2044  func getTestBuildRequest(t *testing.T) *openrtb2.BidRequest {
  2045  	dnt := int8(1)
  2046  	return &openrtb2.BidRequest{
  2047  		Site: &openrtb2.Site{
  2048  			Page:   "www.some.domain.com",
  2049  			Domain: "domain.com",
  2050  			Publisher: &openrtb2.Publisher{
  2051  				ID: "some-publisher-id",
  2052  			},
  2053  		},
  2054  		Device: &openrtb2.Device{
  2055  			UA:       "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36",
  2056  			IFA:      "ifa",
  2057  			IP:       "132.173.230.74",
  2058  			DNT:      &dnt,
  2059  			Language: "EN",
  2060  		},
  2061  		Source: &openrtb2.Source{
  2062  			TID: "61018dc9-fa61-4c41-b7dc-f90b9ae80e87",
  2063  		},
  2064  		User: &openrtb2.User{
  2065  			ID:       "our-id",
  2066  			BuyerUID: "their-id",
  2067  			Ext:      json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`),
  2068  		},
  2069  		Regs: &openrtb2.Regs{
  2070  			COPPA: 1,
  2071  			Ext:   json.RawMessage(`{"gdpr":1}`),
  2072  		},
  2073  		Imp: []openrtb2.Imp{{
  2074  			ID: "some-imp-id",
  2075  			Banner: &openrtb2.Banner{
  2076  				Format: []openrtb2.Format{{
  2077  					W: 300,
  2078  					H: 250,
  2079  				}, {
  2080  					W: 300,
  2081  					H: 600,
  2082  				}},
  2083  			},
  2084  			Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`),
  2085  		}, {
  2086  			Video: &openrtb2.Video{
  2087  				MIMEs:       []string{"video/mp4"},
  2088  				MinDuration: 1,
  2089  				MaxDuration: 300,
  2090  				W:           300,
  2091  				H:           600,
  2092  			},
  2093  			Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`),
  2094  		}},
  2095  	}
  2096  }
  2097  
  2098  func TestPanicRecovery(t *testing.T) {
  2099  	cfg := &config.Configuration{
  2100  		CacheURL: config.Cache{
  2101  			ExpectedTimeMillis: 20,
  2102  		},
  2103  	}
  2104  
  2105  	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
  2106  	if err != nil {
  2107  		t.Fatal(err)
  2108  	}
  2109  
  2110  	adapters, adaptersErr := BuildAdapters(&http.Client{}, cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
  2111  	if adaptersErr != nil {
  2112  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
  2113  	}
  2114  
  2115  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2116  
  2117  	gdprPermsBuilder := fakePermissionsBuilder{
  2118  		permissions: &permissionsMock{
  2119  			allowAllBidders: true,
  2120  		},
  2121  	}.Builder
  2122  
  2123  	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
  2124  
  2125  	chBids := make(chan *bidResponseWrapper, 1)
  2126  	panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) {
  2127  		panic("panic!")
  2128  	}
  2129  
  2130  	apnLabels := metrics.AdapterLabels{
  2131  		Source:      metrics.DemandWeb,
  2132  		RType:       metrics.ReqTypeORTB2Web,
  2133  		Adapter:     openrtb_ext.BidderAppnexus,
  2134  		PubID:       "test1",
  2135  		CookieFlag:  metrics.CookieFlagYes,
  2136  		AdapterBids: metrics.AdapterBidNone,
  2137  	}
  2138  
  2139  	bidderRequests := []BidderRequest{
  2140  		{
  2141  			BidderName:     "bidder1",
  2142  			BidderCoreName: "appnexus",
  2143  			BidderLabels:   apnLabels,
  2144  			BidRequest: &openrtb2.BidRequest{
  2145  				ID: "b-1",
  2146  			},
  2147  		},
  2148  		{
  2149  			BidderName:     "bidder2",
  2150  			BidderCoreName: "bidder2",
  2151  			BidRequest: &openrtb2.BidRequest{
  2152  				ID: "b-2",
  2153  			},
  2154  		},
  2155  	}
  2156  
  2157  	recovered := e.recoverSafely(bidderRequests, panicker, chBids)
  2158  	recovered(bidderRequests[0], nil)
  2159  }
  2160  
  2161  // TestPanicRecoveryHighLevel calls HoldAuction with a panicingAdapter{}
  2162  func TestPanicRecoveryHighLevel(t *testing.T) {
  2163  	noBidServer := func(w http.ResponseWriter, r *http.Request) {
  2164  		w.WriteHeader(204)
  2165  	}
  2166  	server := httptest.NewServer(http.HandlerFunc(noBidServer))
  2167  	defer server.Close()
  2168  
  2169  	cfg := &config.Configuration{}
  2170  
  2171  	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
  2172  	if err != nil {
  2173  		t.Fatal(err)
  2174  	}
  2175  
  2176  	adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
  2177  	if adaptersErr != nil {
  2178  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
  2179  	}
  2180  
  2181  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  2182  
  2183  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  2184  	if error != nil {
  2185  		t.Errorf("Failed to create a category Fetcher: %v", error)
  2186  	}
  2187  
  2188  	gdprPermsBuilder := fakePermissionsBuilder{
  2189  		permissions: &permissionsMock{
  2190  			allowAllBidders: true,
  2191  		},
  2192  	}.Builder
  2193  	e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, categoriesFetcher, &adscert.NilSigner{}, macros.NewStringIndexBasedReplacer()).(*exchange)
  2194  
  2195  	e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{}
  2196  	e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{}
  2197  
  2198  	request := &openrtb2.BidRequest{
  2199  		Site: &openrtb2.Site{
  2200  			Page:   "www.some.domain.com",
  2201  			Domain: "domain.com",
  2202  			Publisher: &openrtb2.Publisher{
  2203  				ID: "some-publisher-id",
  2204  			},
  2205  		},
  2206  		User: &openrtb2.User{
  2207  			ID:       "our-id",
  2208  			BuyerUID: "their-id",
  2209  			Ext:      json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`),
  2210  		},
  2211  		Imp: []openrtb2.Imp{{
  2212  			ID: "some-imp-id",
  2213  			Banner: &openrtb2.Banner{
  2214  				Format: []openrtb2.Format{{
  2215  					W: 300,
  2216  					H: 250,
  2217  				}, {
  2218  					W: 300,
  2219  					H: 600,
  2220  				}},
  2221  			},
  2222  			Ext: json.RawMessage(`{"ext_field": "value"}`),
  2223  		}},
  2224  	}
  2225  
  2226  	auctionRequest := &AuctionRequest{
  2227  		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: request},
  2228  		Account:           config.Account{},
  2229  		UserSyncs:         &emptyUsersync{},
  2230  		HookExecutor:      &hookexecution.EmptyHookExecutor{},
  2231  		TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  2232  	}
  2233  	debugLog := DebugLog{}
  2234  	_, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog)
  2235  	if err != nil {
  2236  		t.Errorf("HoldAuction returned unexpected error: %v", err)
  2237  	}
  2238  
  2239  }
  2240  
  2241  func TestTimeoutComputation(t *testing.T) {
  2242  	cacheTimeMillis := 10
  2243  	ex := exchange{
  2244  		cacheTime: time.Duration(cacheTimeMillis) * time.Millisecond,
  2245  	}
  2246  	deadline := time.Now()
  2247  	ctx, cancel := context.WithDeadline(context.Background(), deadline)
  2248  	defer cancel()
  2249  
  2250  	auctionCtx, cancel := ex.makeAuctionContext(ctx, true)
  2251  	defer cancel()
  2252  
  2253  	if finalDeadline, ok := auctionCtx.Deadline(); !ok || deadline.Add(-time.Duration(cacheTimeMillis)*time.Millisecond) != finalDeadline {
  2254  		t.Errorf("The auction should allocate cacheTime amount of time from the whole request timeout.")
  2255  	}
  2256  }
  2257  
  2258  // TestExchangeJSON executes tests for all the *.json files in exchangetest.
  2259  func TestExchangeJSON(t *testing.T) {
  2260  	if specFiles, err := os.ReadDir("./exchangetest"); err == nil {
  2261  		for _, specFile := range specFiles {
  2262  			fileName := "./exchangetest/" + specFile.Name()
  2263  			fileDisplayName := "exchange/exchangetest/" + specFile.Name()
  2264  			t.Run(fileDisplayName, func(t *testing.T) {
  2265  				specData, err := loadFile(fileName)
  2266  				if assert.NoError(t, err, "Failed to load contents of file %s: %v", fileDisplayName, err) {
  2267  					assert.NotPanics(t, func() { runSpec(t, fileDisplayName, specData) }, fileDisplayName)
  2268  				}
  2269  			})
  2270  		}
  2271  	}
  2272  }
  2273  
  2274  // LoadFile reads and parses a file as a test case. If something goes wrong, it returns an error.
  2275  func loadFile(filename string) (*exchangeSpec, error) {
  2276  	specData, err := os.ReadFile(filename)
  2277  	if err != nil {
  2278  		return nil, fmt.Errorf("Failed to read file %s: %v", filename, err)
  2279  	}
  2280  
  2281  	var spec exchangeSpec
  2282  	if err := json.Unmarshal(specData, &spec); err != nil {
  2283  		return nil, fmt.Errorf("Failed to unmarshal JSON from file: %v", err)
  2284  	}
  2285  
  2286  	return &spec, nil
  2287  }
  2288  
  2289  func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
  2290  	aliases, errs := parseAliases(&spec.IncomingRequest.OrtbRequest)
  2291  	if len(errs) != 0 {
  2292  		t.Fatalf("%s: Failed to parse aliases", filename)
  2293  	}
  2294  
  2295  	var s struct{}
  2296  	eeac := make(map[string]struct{})
  2297  	for _, c := range []string{"FIN", "FRA", "GUF"} {
  2298  		eeac[c] = s
  2299  	}
  2300  
  2301  	var gdprDefaultValue string
  2302  	if spec.AssumeGDPRApplies {
  2303  		gdprDefaultValue = "1"
  2304  	} else {
  2305  		gdprDefaultValue = "0"
  2306  	}
  2307  
  2308  	privacyConfig := config.Privacy{
  2309  		CCPA: config.CCPA{
  2310  			Enforce: spec.EnforceCCPA,
  2311  		},
  2312  		LMT: config.LMT{
  2313  			Enforce: spec.EnforceLMT,
  2314  		},
  2315  		GDPR: config.GDPR{
  2316  			Enabled:         spec.GDPREnabled,
  2317  			DefaultValue:    gdprDefaultValue,
  2318  			EEACountriesMap: eeac,
  2319  			TCF2: config.TCF2{
  2320  				Enabled: spec.GDPREnabled,
  2321  			},
  2322  		},
  2323  	}
  2324  	bidIdGenerator := &mockBidIDGenerator{}
  2325  	if spec.BidIDGenerator != nil {
  2326  		*bidIdGenerator = *spec.BidIDGenerator
  2327  	}
  2328  	ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig, bidIdGenerator, spec.HostSChainFlag, spec.FloorsEnabled, spec.HostConfigBidValidation, spec.Server)
  2329  	biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest)
  2330  	debugLog := &DebugLog{}
  2331  	if spec.DebugLog != nil {
  2332  		*debugLog = *spec.DebugLog
  2333  		debugLog.Regexp = regexp.MustCompile(`[<>]`)
  2334  	}
  2335  
  2336  	// Passthrough JSON Testing
  2337  	impExtInfoMap := make(map[string]ImpExtInfo)
  2338  	if spec.PassthroughFlag {
  2339  		impPassthrough, impID, err := getInfoFromImp(&openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest})
  2340  		if err != nil {
  2341  			t.Errorf("%s: Exchange returned an unexpected error. Got %s", filename, err.Error())
  2342  		}
  2343  		impExtInfoMap[impID] = ImpExtInfo{Passthrough: impPassthrough}
  2344  	}
  2345  
  2346  	// Imp Setting for Bid Validation
  2347  	if spec.HostConfigBidValidation.SecureMarkup == config.ValidationEnforce || spec.HostConfigBidValidation.SecureMarkup == config.ValidationWarn {
  2348  		_, impID, err := getInfoFromImp(&openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest})
  2349  		if err != nil {
  2350  			t.Errorf("%s: Exchange returned an unexpected error. Got %s", filename, err.Error())
  2351  		}
  2352  		impExtInfoMap[impID] = ImpExtInfo{}
  2353  	}
  2354  
  2355  	activityControl := privacy.NewActivityControl(spec.AccountPrivacy)
  2356  
  2357  	auctionRequest := &AuctionRequest{
  2358  		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest},
  2359  		Account: config.Account{
  2360  			ID: "testaccount",
  2361  			Events: config.Events{
  2362  				Enabled: &spec.EventsEnabled,
  2363  			},
  2364  			DebugAllow:  true,
  2365  			PriceFloors: config.AccountPriceFloors{Enabled: spec.AccountFloorsEnabled},
  2366  			Validations: spec.AccountConfigBidValidation,
  2367  		},
  2368  		UserSyncs:     mockIdFetcher(spec.IncomingRequest.Usersyncs),
  2369  		ImpExtInfoMap: impExtInfoMap,
  2370  		HookExecutor:  &hookexecution.EmptyHookExecutor{},
  2371  		TCF2Config:    gdpr.NewTCF2Config(privacyConfig.GDPR.TCF2, config.AccountGDPR{}),
  2372  		Activities:    activityControl,
  2373  	}
  2374  
  2375  	if spec.MultiBid != nil {
  2376  		auctionRequest.Account.DefaultBidLimit = spec.MultiBid.AccountMaxBid
  2377  
  2378  		requestExt := &openrtb_ext.ExtRequest{}
  2379  		err := json.Unmarshal(spec.IncomingRequest.OrtbRequest.Ext, requestExt)
  2380  		assert.NoError(t, err, "invalid request ext")
  2381  		validatedMultiBids, errs := openrtb_ext.ValidateAndBuildExtMultiBid(&requestExt.Prebid)
  2382  		for _, err := range errs { // same as in validateRequestExt().
  2383  			auctionRequest.Warnings = append(auctionRequest.Warnings, &errortypes.Warning{
  2384  				WarningCode: errortypes.MultiBidWarningCode,
  2385  				Message:     err.Error(),
  2386  			})
  2387  		}
  2388  
  2389  		requestExt.Prebid.MultiBid = validatedMultiBids
  2390  		updateReqExt, err := json.Marshal(requestExt)
  2391  		assert.NoError(t, err, "invalid request ext")
  2392  		auctionRequest.BidRequestWrapper.Ext = updateReqExt
  2393  	}
  2394  
  2395  	if spec.StartTime > 0 {
  2396  		auctionRequest.StartTime = time.Unix(0, spec.StartTime*1e+6)
  2397  	}
  2398  	if spec.RequestType != nil {
  2399  		auctionRequest.RequestType = *spec.RequestType
  2400  	}
  2401  	ctx := context.Background()
  2402  
  2403  	aucResponse, err := ex.HoldAuction(ctx, auctionRequest, debugLog)
  2404  	var bid *openrtb2.BidResponse
  2405  	if aucResponse != nil {
  2406  		bid = aucResponse.BidResponse
  2407  	}
  2408  	if len(spec.Response.Error) > 0 && spec.Response.Bids == nil {
  2409  		if err.Error() != spec.Response.Error {
  2410  			t.Errorf("%s: Exchange returned different errors. Expected %s, got %s", filename, spec.Response.Error, err.Error())
  2411  		}
  2412  		return
  2413  	}
  2414  	responseTimes := extractResponseTimes(t, filename, bid)
  2415  	for _, bidderName := range biddersInAuction {
  2416  		if _, ok := responseTimes[bidderName]; !ok {
  2417  			t.Errorf("%s: Response JSON missing expected ext.responsetimemillis.%s", filename, bidderName)
  2418  		}
  2419  	}
  2420  	if spec.Response.Bids != nil {
  2421  		diffOrtbResponses(t, filename, spec.Response.Bids, bid)
  2422  		if err == nil {
  2423  			if spec.Response.Error != "" {
  2424  				t.Errorf("%s: Exchange did not return expected error: %s", filename, spec.Response.Error)
  2425  			}
  2426  		} else {
  2427  			if err.Error() != spec.Response.Error {
  2428  				t.Errorf("%s: Exchange returned different errors. Expected %s, got %s", filename, spec.Response.Error, err.Error())
  2429  			}
  2430  		}
  2431  	}
  2432  	if spec.DebugLog != nil {
  2433  		if spec.DebugLog.Enabled {
  2434  			if len(debugLog.Data.Response) == 0 {
  2435  				t.Errorf("%s: DebugLog response was not modified when it should have been", filename)
  2436  			}
  2437  		} else {
  2438  			if len(debugLog.Data.Response) != 0 {
  2439  				t.Errorf("%s: DebugLog response was modified when it shouldn't have been", filename)
  2440  			}
  2441  		}
  2442  	}
  2443  	if spec.IncomingRequest.OrtbRequest.Test == 1 {
  2444  		//compare debug info
  2445  		assert.JSONEq(t, string(bid.Ext), string(spec.Response.Ext), "Debug info modified")
  2446  	}
  2447  
  2448  	if spec.PassthroughFlag || (spec.MultiBid != nil && spec.MultiBid.AssertMultiBidWarnings) {
  2449  		expectedPassthough := ""
  2450  		actualPassthrough := ""
  2451  		actualBidRespExt := &openrtb_ext.ExtBidResponse{}
  2452  		if bid.Ext != nil {
  2453  			if err := json.Unmarshal(bid.Ext, actualBidRespExt); err != nil {
  2454  				assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err))
  2455  			}
  2456  			if actualBidRespExt.Prebid != nil {
  2457  				actualPassthrough = string(actualBidRespExt.Prebid.Passthrough)
  2458  			}
  2459  		}
  2460  		expectedBidRespExt := &openrtb_ext.ExtBidResponse{}
  2461  		if spec.Response.Ext != nil {
  2462  			if err := json.Unmarshal(spec.Response.Ext, expectedBidRespExt); err != nil {
  2463  				assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err))
  2464  			}
  2465  			if expectedBidRespExt.Prebid != nil {
  2466  				expectedPassthough = string(expectedBidRespExt.Prebid.Passthrough)
  2467  			}
  2468  		}
  2469  
  2470  		if spec.MultiBid != nil && spec.MultiBid.AssertMultiBidWarnings {
  2471  			assert.Equal(t, expectedBidRespExt.Warnings, actualBidRespExt.Warnings, "Expected same multi-bid warnings")
  2472  		}
  2473  
  2474  		if spec.PassthroughFlag {
  2475  			// special handling since JSONEq fails if either parameters is an empty string instead of json
  2476  			if expectedPassthough == "" || actualPassthrough == "" {
  2477  				assert.Equal(t, expectedPassthough, actualPassthrough, "Expected bid response extension is incorrect")
  2478  			} else {
  2479  				assert.JSONEq(t, expectedPassthough, actualPassthrough, "Expected bid response extension is incorrect")
  2480  			}
  2481  		}
  2482  
  2483  	}
  2484  
  2485  	if spec.FledgeEnabled {
  2486  		assert.JSONEq(t, string(spec.Response.Ext), string(bid.Ext), "ext mismatch")
  2487  	}
  2488  
  2489  	if spec.HostConfigBidValidation.BannerCreativeMaxSize == config.ValidationEnforce || spec.HostConfigBidValidation.SecureMarkup == config.ValidationEnforce {
  2490  		actualBidRespExt := &openrtb_ext.ExtBidResponse{}
  2491  		expectedBidRespExt := &openrtb_ext.ExtBidResponse{}
  2492  		if bid.Ext != nil {
  2493  			if err := json.Unmarshal(bid.Ext, actualBidRespExt); err != nil {
  2494  				assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err))
  2495  			}
  2496  		}
  2497  		if err := json.Unmarshal(spec.Response.Ext, expectedBidRespExt); err != nil {
  2498  			assert.NoError(t, err, fmt.Sprintf("Error when unmarshalling: %s", err))
  2499  		}
  2500  
  2501  		assert.Equal(t, expectedBidRespExt.Errors, actualBidRespExt.Errors, "Expected errors from response ext do not match")
  2502  	}
  2503  }
  2504  
  2505  func findBiddersInAuction(t *testing.T, context string, req *openrtb2.BidRequest) []string {
  2506  	if splitImps, err := splitImps(req.Imp); err != nil {
  2507  		t.Errorf("%s: Failed to parse Bidders from request: %v", context, err)
  2508  		return nil
  2509  	} else {
  2510  		bidders := make([]string, 0, len(splitImps))
  2511  		for bidderName := range splitImps {
  2512  			bidders = append(bidders, bidderName)
  2513  		}
  2514  		return bidders
  2515  	}
  2516  }
  2517  
  2518  // extractResponseTimes validates the format of bid.ext.responsetimemillis, and then removes it.
  2519  // This is done because the response time will change from run to run, so it's impossible to hardcode a value
  2520  // into the JSON. The best we can do is make sure that the property exists.
  2521  func extractResponseTimes(t *testing.T, context string, bid *openrtb2.BidResponse) map[string]int {
  2522  	if data, dataType, _, err := jsonparser.Get(bid.Ext, "responsetimemillis"); err != nil || dataType != jsonparser.Object {
  2523  		t.Errorf("%s: Exchange did not return ext.responsetimemillis object: %v", context, err)
  2524  		return nil
  2525  	} else {
  2526  		responseTimes := make(map[string]int)
  2527  		if err := json.Unmarshal(data, &responseTimes); err != nil {
  2528  			t.Errorf("%s: Failed to unmarshal ext.responsetimemillis into map[string]int: %v", context, err)
  2529  			return nil
  2530  		}
  2531  
  2532  		// Delete the response times so that they don't appear in the JSON, because they can't be tested reliably anyway.
  2533  		// If there's no other ext, just delete it altogether.
  2534  		bid.Ext = jsonparser.Delete(bid.Ext, "responsetimemillis")
  2535  		if jsonpatch.Equal(bid.Ext, []byte("{}")) {
  2536  			bid.Ext = nil
  2537  		}
  2538  		return responseTimes
  2539  	}
  2540  }
  2541  
  2542  func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy, bidIDGenerator BidIDGenerator, hostSChainFlag, floorsFlag bool, hostBidValidation config.Validations, server exchangeServer) Exchange {
  2543  	bidderAdapters := make(map[openrtb_ext.BidderName]AdaptedBidder, len(expectations))
  2544  	bidderInfos := make(config.BidderInfos, len(expectations))
  2545  	for _, bidderName := range openrtb_ext.CoreBidderNames() {
  2546  		if spec, ok := expectations[string(bidderName)]; ok {
  2547  			bidderAdapters[bidderName] = &validatingBidder{
  2548  				t:             t,
  2549  				fileName:      filename,
  2550  				bidderName:    string(bidderName),
  2551  				expectations:  map[string]*bidderRequest{string(bidderName): spec.ExpectedRequest},
  2552  				mockResponses: map[string]bidderResponse{string(bidderName): spec.MockResponse},
  2553  			}
  2554  			bidderInfos[string(bidderName)] = config.BidderInfo{ModifyingVastXmlAllowed: spec.ModifyingVastXmlAllowed}
  2555  		}
  2556  	}
  2557  
  2558  	for alias, coreBidder := range aliases {
  2559  		if spec, ok := expectations[alias]; ok {
  2560  			if bidder, ok := bidderAdapters[openrtb_ext.BidderName(coreBidder)]; ok {
  2561  				bidder.(*validatingBidder).expectations[alias] = spec.ExpectedRequest
  2562  				bidder.(*validatingBidder).mockResponses[alias] = spec.MockResponse
  2563  			} else {
  2564  				bidderAdapters[openrtb_ext.BidderName(coreBidder)] = &validatingBidder{
  2565  					t:             t,
  2566  					fileName:      filename,
  2567  					bidderName:    coreBidder,
  2568  					expectations:  map[string]*bidderRequest{alias: spec.ExpectedRequest},
  2569  					mockResponses: map[string]bidderResponse{alias: spec.MockResponse},
  2570  				}
  2571  			}
  2572  		}
  2573  	}
  2574  
  2575  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  2576  	if error != nil {
  2577  		t.Fatalf("Failed to create a category Fetcher: %v", error)
  2578  	}
  2579  
  2580  	gdprPermsBuilder := fakePermissionsBuilder{
  2581  		permissions: &permissionsMock{
  2582  			allowAllBidders: true,
  2583  		},
  2584  	}.Builder
  2585  
  2586  	bidderToSyncerKey := map[string]string{}
  2587  	for _, bidderName := range openrtb_ext.CoreBidderNames() {
  2588  		bidderToSyncerKey[string(bidderName)] = string(bidderName)
  2589  	}
  2590  
  2591  	gdprDefaultValue := gdpr.SignalYes
  2592  	if privacyConfig.GDPR.DefaultValue == "0" {
  2593  		gdprDefaultValue = gdpr.SignalNo
  2594  	}
  2595  
  2596  	var hostSChainNode *openrtb2.SupplyChainNode
  2597  	if hostSChainFlag {
  2598  		hostSChainNode = &openrtb2.SupplyChainNode{
  2599  			ASI: "pbshostcompany.com", SID: "00001", RID: "BidRequest", HP: openrtb2.Int8Ptr(1),
  2600  		}
  2601  	}
  2602  
  2603  	metricsEngine := metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil)
  2604  	requestSplitter := requestSplitter{
  2605  		bidderToSyncerKey: bidderToSyncerKey,
  2606  		me:                metricsEngine,
  2607  		privacyConfig:     privacyConfig,
  2608  		gdprPermsBuilder:  gdprPermsBuilder,
  2609  		hostSChainNode:    hostSChainNode,
  2610  		bidderInfo:        bidderInfos,
  2611  	}
  2612  
  2613  	return &exchange{
  2614  		adapterMap:               bidderAdapters,
  2615  		me:                       metricsEngine,
  2616  		cache:                    &wellBehavedCache{},
  2617  		cacheTime:                0,
  2618  		currencyConverter:        currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
  2619  		gdprDefaultValue:         gdprDefaultValue,
  2620  		gdprPermsBuilder:         gdprPermsBuilder,
  2621  		privacyConfig:            privacyConfig,
  2622  		categoriesFetcher:        categoriesFetcher,
  2623  		bidderInfo:               bidderInfos,
  2624  		bidderToSyncerKey:        bidderToSyncerKey,
  2625  		externalURL:              "http://localhost",
  2626  		bidIDGenerator:           bidIDGenerator,
  2627  		hostSChainNode:           hostSChainNode,
  2628  		server:                   config.Server{ExternalUrl: server.ExternalUrl, GvlID: server.GvlID, DataCenter: server.DataCenter},
  2629  		bidValidationEnforcement: hostBidValidation,
  2630  		requestSplitter:          requestSplitter,
  2631  		floor:                    config.PriceFloors{Enabled: floorsFlag},
  2632  	}
  2633  }
  2634  
  2635  type mockBidIDGenerator struct {
  2636  	GenerateBidID bool `json:"generateBidID"`
  2637  	ReturnError   bool `json:"returnError"`
  2638  }
  2639  
  2640  func (big *mockBidIDGenerator) Enabled() bool {
  2641  	return big.GenerateBidID
  2642  }
  2643  
  2644  func (big *mockBidIDGenerator) New() (string, error) {
  2645  
  2646  	if big.ReturnError {
  2647  		err := errors.New("Test error generating bid.ext.prebid.bidid")
  2648  		return "", err
  2649  	}
  2650  	return "mock_uuid", nil
  2651  
  2652  }
  2653  
  2654  type fakeRandomDeduplicateBidBooleanGenerator struct {
  2655  	returnValue bool
  2656  }
  2657  
  2658  func (m *fakeRandomDeduplicateBidBooleanGenerator) Generate() bool {
  2659  	return m.returnValue
  2660  }
  2661  
  2662  func newExtRequest() openrtb_ext.ExtRequest {
  2663  	priceGran := openrtb_ext.PriceGranularity{
  2664  		Precision: ptrutil.ToPtr(2),
  2665  		Ranges: []openrtb_ext.GranularityRange{
  2666  			{
  2667  				Min:       0.0,
  2668  				Max:       20.0,
  2669  				Increment: 2.0,
  2670  			},
  2671  		},
  2672  	}
  2673  
  2674  	translateCategories := true
  2675  	brandCat := openrtb_ext.ExtIncludeBrandCategory{PrimaryAdServer: 1, WithCategory: true, TranslateCategories: &translateCategories}
  2676  
  2677  	reqExt := openrtb_ext.ExtRequestTargeting{
  2678  		PriceGranularity:     &priceGran,
  2679  		IncludeWinners:       ptrutil.ToPtr(true),
  2680  		IncludeBrandCategory: &brandCat,
  2681  	}
  2682  
  2683  	return openrtb_ext.ExtRequest{
  2684  		Prebid: openrtb_ext.ExtRequestPrebid{
  2685  			Targeting: &reqExt,
  2686  		},
  2687  	}
  2688  }
  2689  
  2690  func newExtRequestNoBrandCat() openrtb_ext.ExtRequest {
  2691  	priceGran := openrtb_ext.PriceGranularity{
  2692  		Precision: ptrutil.ToPtr(2),
  2693  		Ranges: []openrtb_ext.GranularityRange{
  2694  			{
  2695  				Min:       0.0,
  2696  				Max:       20.0,
  2697  				Increment: 2.0,
  2698  			},
  2699  		},
  2700  	}
  2701  
  2702  	brandCat := openrtb_ext.ExtIncludeBrandCategory{WithCategory: false}
  2703  
  2704  	reqExt := openrtb_ext.ExtRequestTargeting{
  2705  		PriceGranularity:     &priceGran,
  2706  		IncludeWinners:       ptrutil.ToPtr(true),
  2707  		IncludeBrandCategory: &brandCat,
  2708  	}
  2709  
  2710  	return openrtb_ext.ExtRequest{
  2711  		Prebid: openrtb_ext.ExtRequestPrebid{
  2712  			Targeting: &reqExt,
  2713  		},
  2714  	}
  2715  }
  2716  
  2717  func TestCategoryMapping(t *testing.T) {
  2718  
  2719  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  2720  	if error != nil {
  2721  		t.Errorf("Failed to create a category Fetcher: %v", error)
  2722  	}
  2723  
  2724  	requestExt := newExtRequest()
  2725  
  2726  	targData := &targetData{
  2727  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  2728  		includeWinners:   true,
  2729  	}
  2730  
  2731  	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
  2732  
  2733  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  2734  
  2735  	cats1 := []string{"IAB1-3"}
  2736  	cats2 := []string{"IAB1-4"}
  2737  	cats3 := []string{"IAB1-1000"}
  2738  	cats4 := []string{"IAB1-2000"}
  2739  	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  2740  	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
  2741  	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
  2742  	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
  2743  
  2744  	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  2745  	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  2746  	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", ""}
  2747  	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 40.0000, "USD", ""}
  2748  
  2749  	innerBids := []*entities.PbsOrtbBid{
  2750  		&bid1_1,
  2751  		&bid1_2,
  2752  		&bid1_3,
  2753  		&bid1_4,
  2754  	}
  2755  
  2756  	seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
  2757  	bidderName1 := openrtb_ext.BidderName("appnexus")
  2758  
  2759  	adapterBids[bidderName1] = &seatBid
  2760  
  2761  	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  2762  
  2763  	assert.Equal(t, nil, err, "Category mapping error should be empty")
  2764  	assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message")
  2765  	assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[0], "Rejection message did not match expected")
  2766  	assert.Equal(t, "10.00_Electronics_30s", bidCategory["bid_id1"], "Category mapping doesn't match")
  2767  	assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match")
  2768  	assert.Equal(t, "20.00_AdapterOverride_30s", bidCategory["bid_id3"], "Category mapping override from adapter didn't take")
  2769  	assert.Equal(t, 3, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
  2770  	assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match")
  2771  }
  2772  
  2773  func TestCategoryMappingNoIncludeBrandCategory(t *testing.T) {
  2774  
  2775  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  2776  	if error != nil {
  2777  		t.Errorf("Failed to create a category Fetcher: %v", error)
  2778  	}
  2779  
  2780  	requestExt := newExtRequestNoBrandCat()
  2781  
  2782  	targData := &targetData{
  2783  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  2784  		includeWinners:   true,
  2785  	}
  2786  	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 40, 50}
  2787  
  2788  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  2789  
  2790  	cats1 := []string{"IAB1-3"}
  2791  	cats2 := []string{"IAB1-4"}
  2792  	cats3 := []string{"IAB1-1000"}
  2793  	cats4 := []string{"IAB1-2000"}
  2794  	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  2795  	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
  2796  	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
  2797  	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 40.0000, Cat: cats4, W: 1, H: 1}
  2798  
  2799  	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  2800  	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  2801  	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30, PrimaryCategory: "AdapterOverride"}, nil, nil, 0, false, "", 30.0000, "USD", ""}
  2802  	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, nil, 0, false, "", 40.0000, "USD", ""}
  2803  
  2804  	innerBids := []*entities.PbsOrtbBid{
  2805  		&bid1_1,
  2806  		&bid1_2,
  2807  		&bid1_3,
  2808  		&bid1_4,
  2809  	}
  2810  
  2811  	seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
  2812  	bidderName1 := openrtb_ext.BidderName("appnexus")
  2813  
  2814  	adapterBids[bidderName1] = &seatBid
  2815  
  2816  	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  2817  
  2818  	assert.Equal(t, nil, err, "Category mapping error should be empty")
  2819  	assert.Empty(t, rejections, "There should be no bid rejection messages")
  2820  	assert.Equal(t, "10.00_30s", bidCategory["bid_id1"], "Category mapping doesn't match")
  2821  	assert.Equal(t, "20.00_40s", bidCategory["bid_id2"], "Category mapping doesn't match")
  2822  	assert.Equal(t, "20.00_30s", bidCategory["bid_id3"], "Category mapping doesn't match")
  2823  	assert.Equal(t, "20.00_50s", bidCategory["bid_id4"], "Category mapping doesn't match")
  2824  	assert.Equal(t, 4, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
  2825  	assert.Equal(t, 4, len(bidCategory), "Bidders category mapping doesn't match")
  2826  }
  2827  
  2828  func TestCategoryMappingTranslateCategoriesNil(t *testing.T) {
  2829  
  2830  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  2831  	if error != nil {
  2832  		t.Errorf("Failed to create a category Fetcher: %v", error)
  2833  	}
  2834  
  2835  	requestExt := newExtRequestTranslateCategories(nil)
  2836  
  2837  	targData := &targetData{
  2838  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  2839  		includeWinners:   true,
  2840  	}
  2841  
  2842  	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
  2843  
  2844  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  2845  
  2846  	cats1 := []string{"IAB1-3"}
  2847  	cats2 := []string{"IAB1-4"}
  2848  	cats3 := []string{"IAB1-1000"}
  2849  	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  2850  	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
  2851  	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
  2852  
  2853  	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  2854  	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  2855  	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", ""}
  2856  
  2857  	innerBids := []*entities.PbsOrtbBid{
  2858  		&bid1_1,
  2859  		&bid1_2,
  2860  		&bid1_3,
  2861  	}
  2862  
  2863  	seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
  2864  	bidderName1 := openrtb_ext.BidderName("appnexus")
  2865  
  2866  	adapterBids[bidderName1] = &seatBid
  2867  
  2868  	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  2869  
  2870  	assert.Equal(t, nil, err, "Category mapping error should be empty")
  2871  	assert.Equal(t, 1, len(rejections), "There should be 1 bid rejection message")
  2872  	assert.Equal(t, "bid rejected [bid ID: bid_id3] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[0], "Rejection message did not match expected")
  2873  	assert.Equal(t, "10.00_Electronics_30s", bidCategory["bid_id1"], "Category mapping doesn't match")
  2874  	assert.Equal(t, "20.00_Sports_50s", bidCategory["bid_id2"], "Category mapping doesn't match")
  2875  	assert.Equal(t, 2, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
  2876  	assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match")
  2877  }
  2878  
  2879  func newExtRequestTranslateCategories(translateCategories *bool) openrtb_ext.ExtRequest {
  2880  	priceGran := openrtb_ext.PriceGranularity{
  2881  		Precision: ptrutil.ToPtr(2),
  2882  		Ranges: []openrtb_ext.GranularityRange{
  2883  			{
  2884  				Min:       0.0,
  2885  				Max:       20.0,
  2886  				Increment: 2.0,
  2887  			},
  2888  		},
  2889  	}
  2890  
  2891  	brandCat := openrtb_ext.ExtIncludeBrandCategory{WithCategory: true, PrimaryAdServer: 1}
  2892  	if translateCategories != nil {
  2893  		brandCat.TranslateCategories = translateCategories
  2894  	}
  2895  
  2896  	reqExt := openrtb_ext.ExtRequestTargeting{
  2897  		PriceGranularity:     &priceGran,
  2898  		IncludeWinners:       ptrutil.ToPtr(true),
  2899  		IncludeBrandCategory: &brandCat,
  2900  	}
  2901  
  2902  	return openrtb_ext.ExtRequest{
  2903  		Prebid: openrtb_ext.ExtRequestPrebid{
  2904  			Targeting: &reqExt,
  2905  		},
  2906  	}
  2907  }
  2908  
  2909  func TestCategoryMappingTranslateCategoriesFalse(t *testing.T) {
  2910  
  2911  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  2912  	if error != nil {
  2913  		t.Errorf("Failed to create a category Fetcher: %v", error)
  2914  	}
  2915  
  2916  	translateCategories := false
  2917  	requestExt := newExtRequestTranslateCategories(&translateCategories)
  2918  
  2919  	targData := &targetData{
  2920  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  2921  		includeWinners:   true,
  2922  	}
  2923  
  2924  	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
  2925  
  2926  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  2927  
  2928  	cats1 := []string{"IAB1-3"}
  2929  	cats2 := []string{"IAB1-4"}
  2930  	cats3 := []string{"IAB1-1000"}
  2931  	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  2932  	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
  2933  	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 30.0000, Cat: cats3, W: 1, H: 1}
  2934  
  2935  	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  2936  	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 40}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  2937  	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 30.0000, "USD", ""}
  2938  
  2939  	innerBids := []*entities.PbsOrtbBid{
  2940  		&bid1_1,
  2941  		&bid1_2,
  2942  		&bid1_3,
  2943  	}
  2944  
  2945  	seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
  2946  	bidderName1 := openrtb_ext.BidderName("appnexus")
  2947  
  2948  	adapterBids[bidderName1] = &seatBid
  2949  
  2950  	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  2951  
  2952  	assert.Equal(t, nil, err, "Category mapping error should be empty")
  2953  	assert.Empty(t, rejections, "There should be no bid rejection messages")
  2954  	assert.Equal(t, "10.00_IAB1-3_30s", bidCategory["bid_id1"], "Category should not be translated")
  2955  	assert.Equal(t, "20.00_IAB1-4_50s", bidCategory["bid_id2"], "Category should not be translated")
  2956  	assert.Equal(t, "20.00_IAB1-1000_30s", bidCategory["bid_id3"], "Bid should not be rejected")
  2957  	assert.Equal(t, 3, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
  2958  	assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match")
  2959  }
  2960  
  2961  func TestCategoryDedupe(t *testing.T) {
  2962  
  2963  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  2964  	if error != nil {
  2965  		t.Errorf("Failed to create a category Fetcher: %v", error)
  2966  	}
  2967  
  2968  	requestExt := newExtRequest()
  2969  
  2970  	targData := &targetData{
  2971  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  2972  		includeWinners:   true,
  2973  	}
  2974  
  2975  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  2976  
  2977  	cats1 := []string{"IAB1-3"}
  2978  	cats2 := []string{"IAB1-4"}
  2979  	// bid3 will be same price, category, and duration as bid1 so one of them should get removed
  2980  	cats4 := []string{"IAB1-2000"}
  2981  	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  2982  	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 15.0000, Cat: cats2, W: 1, H: 1}
  2983  	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1}
  2984  	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
  2985  	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 20.0000, Cat: cats1, W: 1, H: 1}
  2986  
  2987  	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  2988  	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 50}, nil, nil, 0, false, "", 15.0000, "USD", ""}
  2989  	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  2990  	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  2991  	bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  2992  
  2993  	selectedBids := make(map[string]int)
  2994  	expectedCategories := map[string]string{
  2995  		"bid_id1": "10.00_Electronics_30s",
  2996  		"bid_id2": "14.00_Sports_50s",
  2997  		"bid_id3": "20.00_Electronics_30s",
  2998  		"bid_id5": "20.00_Electronics_30s",
  2999  	}
  3000  
  3001  	numIterations := 10
  3002  
  3003  	// Run the function many times, this should be enough for the 50% chance of which bid to remove to remove bid1 sometimes
  3004  	// and bid3 others. It's conceivably possible (but highly unlikely) that the same bid get chosen every single time, but
  3005  	// if you notice false fails from this test increase numIterations to make it even less likely to happen.
  3006  	for i := 0; i < numIterations; i++ {
  3007  		innerBids := []*entities.PbsOrtbBid{
  3008  			&bid1_1,
  3009  			&bid1_2,
  3010  			&bid1_3,
  3011  			&bid1_4,
  3012  			&bid1_5,
  3013  		}
  3014  
  3015  		seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
  3016  		bidderName1 := openrtb_ext.BidderName("appnexus")
  3017  
  3018  		adapterBids[bidderName1] = &seatBid
  3019  
  3020  		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  3021  
  3022  		assert.Equal(t, nil, err, "Category mapping error should be empty")
  3023  		assert.Equal(t, 3, len(rejections), "There should be 2 bid rejection messages")
  3024  		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|3)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected")
  3025  		assert.Equal(t, "bid rejected [bid ID: bid_id4] reason: Category mapping file for primary ad server: 'freewheel', publisher: '' not found", rejections[1], "Rejection message did not match expected")
  3026  		assert.Equal(t, 2, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
  3027  		assert.Equal(t, 2, len(bidCategory), "Bidders category mapping doesn't match")
  3028  
  3029  		for bidId, bidCat := range bidCategory {
  3030  			assert.Equal(t, expectedCategories[bidId], bidCat, "Category mapping doesn't match")
  3031  			selectedBids[bidId]++
  3032  		}
  3033  	}
  3034  
  3035  	assert.Equal(t, numIterations, selectedBids["bid_id2"], "Bid 2 did not make it through every time")
  3036  	assert.Equal(t, 0, selectedBids["bid_id1"], "Bid 1 should be rejected on every iteration due to lower price")
  3037  	assert.NotEqual(t, 0, selectedBids["bid_id3"], "Bid 3 should be accepted at least once")
  3038  	assert.NotEqual(t, 0, selectedBids["bid_id5"], "Bid 5 should be accepted at least once")
  3039  }
  3040  
  3041  func TestNoCategoryDedupe(t *testing.T) {
  3042  
  3043  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  3044  	if error != nil {
  3045  		t.Errorf("Failed to create a category Fetcher: %v", error)
  3046  	}
  3047  
  3048  	requestExt := newExtRequestNoBrandCat()
  3049  
  3050  	targData := &targetData{
  3051  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  3052  		includeWinners:   true,
  3053  	}
  3054  
  3055  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  3056  
  3057  	cats1 := []string{"IAB1-3"}
  3058  	cats2 := []string{"IAB1-4"}
  3059  	cats4 := []string{"IAB1-2000"}
  3060  	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 14.0000, Cat: cats1, W: 1, H: 1}
  3061  	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 14.0000, Cat: cats2, W: 1, H: 1}
  3062  	bid3 := openrtb2.Bid{ID: "bid_id3", ImpID: "imp_id3", Price: 20.0000, Cat: cats1, W: 1, H: 1}
  3063  	bid4 := openrtb2.Bid{ID: "bid_id4", ImpID: "imp_id4", Price: 20.0000, Cat: cats4, W: 1, H: 1}
  3064  	bid5 := openrtb2.Bid{ID: "bid_id5", ImpID: "imp_id5", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  3065  
  3066  	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", ""}
  3067  	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 14.0000, "USD", ""}
  3068  	bid1_3 := entities.PbsOrtbBid{&bid3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  3069  	bid1_4 := entities.PbsOrtbBid{&bid4, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  3070  	bid1_5 := entities.PbsOrtbBid{&bid5, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3071  
  3072  	selectedBids := make(map[string]int)
  3073  	expectedCategories := map[string]string{
  3074  		"bid_id1": "14.00_30s",
  3075  		"bid_id2": "14.00_30s",
  3076  		"bid_id3": "20.00_30s",
  3077  		"bid_id4": "20.00_30s",
  3078  		"bid_id5": "10.00_30s",
  3079  	}
  3080  
  3081  	numIterations := 10
  3082  
  3083  	// Run the function many times, this should be enough for the 50% chance of which bid to remove to remove bid1 sometimes
  3084  	// and bid3 others. It's conceivably possible (but highly unlikely) that the same bid get chosen every single time, but
  3085  	// if you notice false fails from this test increase numIterations to make it even less likely to happen.
  3086  	for i := 0; i < numIterations; i++ {
  3087  		innerBids := []*entities.PbsOrtbBid{
  3088  			&bid1_1,
  3089  			&bid1_2,
  3090  			&bid1_3,
  3091  			&bid1_4,
  3092  			&bid1_5,
  3093  		}
  3094  
  3095  		seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
  3096  		bidderName1 := openrtb_ext.BidderName("appnexus")
  3097  
  3098  		adapterBids[bidderName1] = &seatBid
  3099  
  3100  		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  3101  
  3102  		assert.Equal(t, nil, err, "Category mapping error should be empty")
  3103  		assert.Equal(t, 2, len(rejections), "There should be 2 bid rejection messages")
  3104  		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(1|2)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected")
  3105  		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_id(3|4)\] reason: Bid was deduplicated`), rejections[1], "Rejection message did not match expected")
  3106  		assert.Equal(t, 3, len(adapterBids[bidderName1].Bids), "Bidders number doesn't match")
  3107  		assert.Equal(t, 3, len(bidCategory), "Bidders category mapping doesn't match")
  3108  
  3109  		for bidId, bidCat := range bidCategory {
  3110  			assert.Equal(t, expectedCategories[bidId], bidCat, "Category mapping doesn't match")
  3111  			selectedBids[bidId]++
  3112  		}
  3113  	}
  3114  	assert.Equal(t, numIterations, selectedBids["bid_id5"], "Bid 5 did not make it through every time")
  3115  	assert.NotEqual(t, 0, selectedBids["bid_id1"], "Bid 1 should be selected at least once")
  3116  	assert.NotEqual(t, 0, selectedBids["bid_id2"], "Bid 2 should be selected at least once")
  3117  	assert.NotEqual(t, 0, selectedBids["bid_id1"], "Bid 3 should be selected at least once")
  3118  	assert.NotEqual(t, 0, selectedBids["bid_id4"], "Bid 4 should be selected at least once")
  3119  
  3120  }
  3121  
  3122  func TestCategoryMappingBidderName(t *testing.T) {
  3123  
  3124  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  3125  	if error != nil {
  3126  		t.Errorf("Failed to create a category Fetcher: %v", error)
  3127  	}
  3128  
  3129  	requestExt := newExtRequest()
  3130  	requestExt.Prebid.Targeting.AppendBidderNames = true
  3131  
  3132  	targData := &targetData{
  3133  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  3134  		includeWinners:   true,
  3135  	}
  3136  
  3137  	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30}
  3138  
  3139  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  3140  
  3141  	cats1 := []string{"IAB1-1"}
  3142  	cats2 := []string{"IAB1-2"}
  3143  	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  3144  	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
  3145  
  3146  	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3147  	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3148  
  3149  	innerBids1 := []*entities.PbsOrtbBid{
  3150  		&bid1_1,
  3151  	}
  3152  	innerBids2 := []*entities.PbsOrtbBid{
  3153  		&bid1_2,
  3154  	}
  3155  
  3156  	seatBid1 := entities.PbsOrtbSeatBid{Bids: innerBids1, Currency: "USD"}
  3157  	bidderName1 := openrtb_ext.BidderName("bidder1")
  3158  
  3159  	seatBid2 := entities.PbsOrtbSeatBid{Bids: innerBids2, Currency: "USD"}
  3160  	bidderName2 := openrtb_ext.BidderName("bidder2")
  3161  
  3162  	adapterBids[bidderName1] = &seatBid1
  3163  	adapterBids[bidderName2] = &seatBid2
  3164  
  3165  	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  3166  
  3167  	assert.NoError(t, err, "Category mapping error should be empty")
  3168  	assert.Empty(t, rejections, "There should be 0 bid rejection messages")
  3169  	assert.Equal(t, "10.00_VideoGames_30s_bidder1", bidCategory["bid_id1"], "Category mapping doesn't match")
  3170  	assert.Equal(t, "10.00_HomeDecor_30s_bidder2", bidCategory["bid_id2"], "Category mapping doesn't match")
  3171  	assert.Len(t, adapterBids[bidderName1].Bids, 1, "Bidders number doesn't match")
  3172  	assert.Len(t, adapterBids[bidderName2].Bids, 1, "Bidders number doesn't match")
  3173  	assert.Len(t, bidCategory, 2, "Bidders category mapping doesn't match")
  3174  }
  3175  
  3176  func TestCategoryMappingBidderNameNoCategories(t *testing.T) {
  3177  
  3178  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  3179  	if error != nil {
  3180  		t.Errorf("Failed to create a category Fetcher: %v", error)
  3181  	}
  3182  
  3183  	requestExt := newExtRequestNoBrandCat()
  3184  	requestExt.Prebid.Targeting.AppendBidderNames = true
  3185  
  3186  	targData := &targetData{
  3187  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  3188  		includeWinners:   true,
  3189  	}
  3190  
  3191  	requestExt.Prebid.Targeting.DurationRangeSec = []int{30, 10, 25, 5, 20, 50}
  3192  
  3193  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  3194  
  3195  	cats1 := []string{"IAB1-1"}
  3196  	cats2 := []string{"IAB1-2"}
  3197  	bid1 := openrtb2.Bid{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  3198  	bid2 := openrtb2.Bid{ID: "bid_id2", ImpID: "imp_id2", Price: 12.0000, Cat: cats2, W: 1, H: 1}
  3199  
  3200  	bid1_1 := entities.PbsOrtbBid{&bid1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 17}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3201  	bid1_2 := entities.PbsOrtbBid{&bid2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 8}, nil, nil, 0, false, "", 12.0000, "USD", ""}
  3202  
  3203  	innerBids1 := []*entities.PbsOrtbBid{
  3204  		&bid1_1,
  3205  	}
  3206  	innerBids2 := []*entities.PbsOrtbBid{
  3207  		&bid1_2,
  3208  	}
  3209  
  3210  	seatBid1 := entities.PbsOrtbSeatBid{Bids: innerBids1, Currency: "USD"}
  3211  	bidderName1 := openrtb_ext.BidderName("bidder1")
  3212  
  3213  	seatBid2 := entities.PbsOrtbSeatBid{Bids: innerBids2, Currency: "USD"}
  3214  	bidderName2 := openrtb_ext.BidderName("bidder2")
  3215  
  3216  	adapterBids[bidderName1] = &seatBid1
  3217  	adapterBids[bidderName2] = &seatBid2
  3218  
  3219  	bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  3220  
  3221  	assert.NoError(t, err, "Category mapping error should be empty")
  3222  	assert.Empty(t, rejections, "There should be 0 bid rejection messages")
  3223  	assert.Equal(t, "10.00_20s_bidder1", bidCategory["bid_id1"], "Category mapping doesn't match")
  3224  	assert.Equal(t, "12.00_10s_bidder2", bidCategory["bid_id2"], "Category mapping doesn't match")
  3225  	assert.Len(t, adapterBids[bidderName1].Bids, 1, "Bidders number doesn't match")
  3226  	assert.Len(t, adapterBids[bidderName2].Bids, 1, "Bidders number doesn't match")
  3227  	assert.Len(t, bidCategory, 2, "Bidders category mapping doesn't match")
  3228  }
  3229  
  3230  func TestBidRejectionErrors(t *testing.T) {
  3231  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  3232  	if error != nil {
  3233  		t.Errorf("Failed to create a category Fetcher: %v", error)
  3234  	}
  3235  
  3236  	requestExt := newExtRequest()
  3237  	requestExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
  3238  
  3239  	targData := &targetData{
  3240  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  3241  		includeWinners:   true,
  3242  	}
  3243  
  3244  	invalidReqExt := newExtRequest()
  3245  	invalidReqExt.Prebid.Targeting.DurationRangeSec = []int{15, 30, 50}
  3246  	invalidReqExt.Prebid.Targeting.IncludeBrandCategory.PrimaryAdServer = 2
  3247  	invalidReqExt.Prebid.Targeting.IncludeBrandCategory.Publisher = "some_publisher"
  3248  
  3249  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  3250  	bidderName := openrtb_ext.BidderName("appnexus")
  3251  
  3252  	testCases := []struct {
  3253  		description        string
  3254  		reqExt             openrtb_ext.ExtRequest
  3255  		bids               []*openrtb2.Bid
  3256  		duration           int
  3257  		expectedRejections []string
  3258  		expectedCatDur     string
  3259  	}{
  3260  		{
  3261  			description: "Bid should be rejected due to not containing a category",
  3262  			reqExt:      requestExt,
  3263  			bids: []*openrtb2.Bid{
  3264  				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{}, W: 1, H: 1},
  3265  			},
  3266  			duration: 30,
  3267  			expectedRejections: []string{
  3268  				"bid rejected [bid ID: bid_id1] reason: Bid did not contain a category",
  3269  			},
  3270  		},
  3271  		{
  3272  			description: "Bid should be rejected due to missing category mapping file",
  3273  			reqExt:      invalidReqExt,
  3274  			bids: []*openrtb2.Bid{
  3275  				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1},
  3276  			},
  3277  			duration: 30,
  3278  			expectedRejections: []string{
  3279  				"bid rejected [bid ID: bid_id1] reason: Category mapping file for primary ad server: 'dfp', publisher: 'some_publisher' not found",
  3280  			},
  3281  		},
  3282  		{
  3283  			description: "Bid should be rejected due to duration exceeding maximum",
  3284  			reqExt:      requestExt,
  3285  			bids: []*openrtb2.Bid{
  3286  				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1},
  3287  			},
  3288  			duration: 70,
  3289  			expectedRejections: []string{
  3290  				"bid rejected [bid ID: bid_id1] reason: bid duration exceeds maximum allowed",
  3291  			},
  3292  		},
  3293  		{
  3294  			description: "Bid should be rejected due to duplicate bid",
  3295  			reqExt:      requestExt,
  3296  			bids: []*openrtb2.Bid{
  3297  				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1},
  3298  				{ID: "bid_id1", ImpID: "imp_id1", Price: 10.0000, Cat: []string{"IAB1-1"}, W: 1, H: 1},
  3299  			},
  3300  			duration: 30,
  3301  			expectedRejections: []string{
  3302  				"bid rejected [bid ID: bid_id1] reason: Bid was deduplicated",
  3303  			},
  3304  			expectedCatDur: "10.00_VideoGames_30s",
  3305  		},
  3306  	}
  3307  
  3308  	for _, test := range testCases {
  3309  		innerBids := []*entities.PbsOrtbBid{}
  3310  		for _, bid := range test.bids {
  3311  			currentBid := entities.PbsOrtbBid{
  3312  				bid, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: test.duration}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3313  			innerBids = append(innerBids, &currentBid)
  3314  		}
  3315  
  3316  		seatBid := entities.PbsOrtbSeatBid{Bids: innerBids, Currency: "USD"}
  3317  
  3318  		adapterBids[bidderName] = &seatBid
  3319  
  3320  		bidCategory, adapterBids, rejections, err := applyCategoryMapping(nil, *test.reqExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  3321  
  3322  		if len(test.expectedCatDur) > 0 {
  3323  			// Bid deduplication case
  3324  			assert.Equal(t, 1, len(adapterBids[bidderName].Bids), "Bidders number doesn't match")
  3325  			assert.Equal(t, 1, len(bidCategory), "Bidders category mapping doesn't match")
  3326  			assert.Equal(t, test.expectedCatDur, bidCategory["bid_id1"], "Bid category did not contain expected hb_pb_cat_dur")
  3327  		} else {
  3328  			assert.Empty(t, adapterBids[bidderName].Bids, "Bidders number doesn't match")
  3329  			assert.Empty(t, bidCategory, "Bidders category mapping doesn't match")
  3330  		}
  3331  
  3332  		assert.Empty(t, err, "Category mapping error should be empty")
  3333  		assert.Equal(t, test.expectedRejections, rejections, test.description)
  3334  	}
  3335  }
  3336  
  3337  func TestCategoryMappingTwoBiddersOneBidEachNoCategorySamePrice(t *testing.T) {
  3338  
  3339  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  3340  	if error != nil {
  3341  		t.Errorf("Failed to create a category Fetcher: %v", error)
  3342  	}
  3343  
  3344  	requestExt := newExtRequestTranslateCategories(nil)
  3345  
  3346  	targData := &targetData{
  3347  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  3348  		includeWinners:   true,
  3349  	}
  3350  
  3351  	requestExt.Prebid.Targeting.DurationRangeSec = []int{30}
  3352  	requestExt.Prebid.Targeting.IncludeBrandCategory.WithCategory = false
  3353  
  3354  	cats1 := []string{"IAB1-3"}
  3355  	cats2 := []string{"IAB1-4"}
  3356  
  3357  	bidApn1 := openrtb2.Bid{ID: "bid_idApn1", ImpID: "imp_idApn1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  3358  	bidApn2 := openrtb2.Bid{ID: "bid_idApn2", ImpID: "imp_idApn2", Price: 10.0000, Cat: cats2, W: 1, H: 1}
  3359  
  3360  	bid1_Apn1 := entities.PbsOrtbBid{&bidApn1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3361  	bid1_Apn2 := entities.PbsOrtbBid{&bidApn2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3362  
  3363  	innerBidsApn1 := []*entities.PbsOrtbBid{
  3364  		&bid1_Apn1,
  3365  	}
  3366  
  3367  	innerBidsApn2 := []*entities.PbsOrtbBid{
  3368  		&bid1_Apn2,
  3369  	}
  3370  
  3371  	for i := 1; i < 10; i++ {
  3372  		adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  3373  
  3374  		seatBidApn1 := entities.PbsOrtbSeatBid{Bids: innerBidsApn1, Currency: "USD"}
  3375  		bidderNameApn1 := openrtb_ext.BidderName("appnexus1")
  3376  
  3377  		seatBidApn2 := entities.PbsOrtbSeatBid{Bids: innerBidsApn2, Currency: "USD"}
  3378  		bidderNameApn2 := openrtb_ext.BidderName("appnexus2")
  3379  
  3380  		adapterBids[bidderNameApn1] = &seatBidApn1
  3381  		adapterBids[bidderNameApn2] = &seatBidApn2
  3382  
  3383  		bidCategory, _, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &randomDeduplicateBidBooleanGenerator{}, &nonBids{})
  3384  
  3385  		assert.NoError(t, err, "Category mapping error should be empty")
  3386  		assert.Len(t, rejections, 1, "There should be 1 bid rejection message")
  3387  		assert.Regexpf(t, regexp.MustCompile(`bid rejected \[bid ID: bid_idApn(1|2)\] reason: Bid was deduplicated`), rejections[0], "Rejection message did not match expected")
  3388  		assert.Len(t, bidCategory, 1, "Bidders category mapping should have only one element")
  3389  
  3390  		var resultBid string
  3391  		for bidId := range bidCategory {
  3392  			resultBid = bidId
  3393  		}
  3394  
  3395  		if resultBid == "bid_idApn1" {
  3396  			assert.Nil(t, seatBidApn2.Bids, "Appnexus_2 seat bid should not have any bids back")
  3397  			assert.Len(t, seatBidApn1.Bids, 1, "Appnexus_1 seat bid should have only one back")
  3398  
  3399  		} else {
  3400  			assert.Nil(t, seatBidApn1.Bids, "Appnexus_1 seat bid should not have any bids back")
  3401  			assert.Len(t, seatBidApn2.Bids, 1, "Appnexus_2 seat bid should have only one back")
  3402  		}
  3403  	}
  3404  }
  3405  
  3406  func TestCategoryMappingTwoBiddersManyBidsEachNoCategorySamePrice(t *testing.T) {
  3407  	// This test covers a very rare de-duplication case where bid needs to be removed from already processed bidder
  3408  	// This happens when current processing bidder has a bid that has same de-duplication key as a bid from already processed bidder
  3409  	// and already processed bid was selected to be removed
  3410  
  3411  	//In this test case bids bid_idApn1_1 and bid_idApn1_2 will be removed due to hardcoded "fakeRandomDeduplicateBidBooleanGenerator{true}"
  3412  
  3413  	// Also there are should be more than one bids in bidder to test how we remove single element from bids array.
  3414  	// In case there is just one bid to remove - we remove the entire bidder.
  3415  
  3416  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  3417  	if error != nil {
  3418  		t.Errorf("Failed to create a category Fetcher: %v", error)
  3419  	}
  3420  
  3421  	requestExt := newExtRequestTranslateCategories(nil)
  3422  
  3423  	targData := &targetData{
  3424  		priceGranularity: *requestExt.Prebid.Targeting.PriceGranularity,
  3425  		includeWinners:   true,
  3426  	}
  3427  
  3428  	requestExt.Prebid.Targeting.DurationRangeSec = []int{30}
  3429  	requestExt.Prebid.Targeting.IncludeBrandCategory.WithCategory = false
  3430  
  3431  	cats1 := []string{"IAB1-3"}
  3432  	cats2 := []string{"IAB1-4"}
  3433  
  3434  	bidApn1_1 := openrtb2.Bid{ID: "bid_idApn1_1", ImpID: "imp_idApn1_1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  3435  	bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1}
  3436  
  3437  	bidApn2_1 := openrtb2.Bid{ID: "bid_idApn2_1", ImpID: "imp_idApn2_1", Price: 10.0000, Cat: cats2, W: 1, H: 1}
  3438  	bidApn2_2 := openrtb2.Bid{ID: "bid_idApn2_2", ImpID: "imp_idApn2_2", Price: 20.0000, Cat: cats2, W: 1, H: 1}
  3439  
  3440  	bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3441  	bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  3442  
  3443  	bid1_Apn2_1 := entities.PbsOrtbBid{&bidApn2_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3444  	bid1_Apn2_2 := entities.PbsOrtbBid{&bidApn2_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  3445  
  3446  	innerBidsApn1 := []*entities.PbsOrtbBid{
  3447  		&bid1_Apn1_1,
  3448  		&bid1_Apn1_2,
  3449  	}
  3450  
  3451  	innerBidsApn2 := []*entities.PbsOrtbBid{
  3452  		&bid1_Apn2_1,
  3453  		&bid1_Apn2_2,
  3454  	}
  3455  
  3456  	adapterBids := make(map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
  3457  
  3458  	seatBidApn1 := entities.PbsOrtbSeatBid{Bids: innerBidsApn1, Currency: "USD"}
  3459  	bidderNameApn1 := openrtb_ext.BidderName("appnexus1")
  3460  
  3461  	seatBidApn2 := entities.PbsOrtbSeatBid{Bids: innerBidsApn2, Currency: "USD"}
  3462  	bidderNameApn2 := openrtb_ext.BidderName("appnexus2")
  3463  
  3464  	adapterBids[bidderNameApn1] = &seatBidApn1
  3465  	adapterBids[bidderNameApn2] = &seatBidApn2
  3466  
  3467  	_, adapterBids, rejections, err := applyCategoryMapping(nil, *requestExt.Prebid.Targeting, adapterBids, categoriesFetcher, targData, &fakeRandomDeduplicateBidBooleanGenerator{true}, &nonBids{})
  3468  
  3469  	assert.NoError(t, err, "Category mapping error should be empty")
  3470  
  3471  	//Total number of bids from all bidders in this case should be 2
  3472  	bidsFromFirstBidder := adapterBids[bidderNameApn1]
  3473  	bidsFromSecondBidder := adapterBids[bidderNameApn2]
  3474  
  3475  	totalNumberOfbids := 0
  3476  
  3477  	//due to random map order we need to identify what bidder was first
  3478  	firstBidderIndicator := true
  3479  
  3480  	if bidsFromFirstBidder.Bids != nil {
  3481  		totalNumberOfbids += len(bidsFromFirstBidder.Bids)
  3482  	}
  3483  
  3484  	if bidsFromSecondBidder.Bids != nil {
  3485  		firstBidderIndicator = false
  3486  		totalNumberOfbids += len(bidsFromSecondBidder.Bids)
  3487  	}
  3488  
  3489  	assert.Equal(t, 2, totalNumberOfbids, "2 bids total should be returned")
  3490  	assert.Len(t, rejections, 2, "2 bids should be de-duplicated")
  3491  
  3492  	if firstBidderIndicator {
  3493  		assert.Len(t, adapterBids[bidderNameApn1].Bids, 2)
  3494  		assert.Len(t, adapterBids[bidderNameApn2].Bids, 0)
  3495  
  3496  		assert.Equal(t, "bid_idApn1_1", adapterBids[bidderNameApn1].Bids[0].Bid.ID, "Incorrect expected bid 1 id")
  3497  		assert.Equal(t, "bid_idApn1_2", adapterBids[bidderNameApn1].Bids[1].Bid.ID, "Incorrect expected bid 2 id")
  3498  
  3499  		assert.Equal(t, "bid rejected [bid ID: bid_idApn2_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1")
  3500  		assert.Equal(t, "bid rejected [bid ID: bid_idApn2_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2")
  3501  
  3502  	} else {
  3503  		assert.Len(t, adapterBids[bidderNameApn1].Bids, 0)
  3504  		assert.Len(t, adapterBids[bidderNameApn2].Bids, 2)
  3505  
  3506  		assert.Equal(t, "bid_idApn2_1", adapterBids[bidderNameApn2].Bids[0].Bid.ID, "Incorrect expected bid 1 id")
  3507  		assert.Equal(t, "bid_idApn2_2", adapterBids[bidderNameApn2].Bids[1].Bid.ID, "Incorrect expected bid 2 id")
  3508  
  3509  		assert.Equal(t, "bid rejected [bid ID: bid_idApn1_1] reason: Bid was deduplicated", rejections[0], "Incorrect rejected bid 1")
  3510  		assert.Equal(t, "bid rejected [bid ID: bid_idApn1_2] reason: Bid was deduplicated", rejections[1], "Incorrect rejected bid 2")
  3511  
  3512  	}
  3513  }
  3514  
  3515  func TestRemoveBidById(t *testing.T) {
  3516  	cats1 := []string{"IAB1-3"}
  3517  
  3518  	bidApn1_1 := openrtb2.Bid{ID: "bid_idApn1_1", ImpID: "imp_idApn1_1", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  3519  	bidApn1_2 := openrtb2.Bid{ID: "bid_idApn1_2", ImpID: "imp_idApn1_2", Price: 20.0000, Cat: cats1, W: 1, H: 1}
  3520  	bidApn1_3 := openrtb2.Bid{ID: "bid_idApn1_3", ImpID: "imp_idApn1_3", Price: 10.0000, Cat: cats1, W: 1, H: 1}
  3521  
  3522  	bid1_Apn1_1 := entities.PbsOrtbBid{&bidApn1_1, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3523  	bid1_Apn1_2 := entities.PbsOrtbBid{&bidApn1_2, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 20.0000, "USD", ""}
  3524  	bid1_Apn1_3 := entities.PbsOrtbBid{&bidApn1_3, nil, "video", nil, &openrtb_ext.ExtBidPrebidVideo{Duration: 30}, nil, nil, 0, false, "", 10.0000, "USD", ""}
  3525  
  3526  	type aTest struct {
  3527  		desc      string
  3528  		inBidName string
  3529  		outBids   []*entities.PbsOrtbBid
  3530  	}
  3531  	testCases := []aTest{
  3532  		{
  3533  			desc:      "remove element from the middle",
  3534  			inBidName: "bid_idApn1_2",
  3535  			outBids:   []*entities.PbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_3},
  3536  		},
  3537  		{
  3538  			desc:      "remove element from the end",
  3539  			inBidName: "bid_idApn1_3",
  3540  			outBids:   []*entities.PbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2},
  3541  		},
  3542  		{
  3543  			desc:      "remove element from the beginning",
  3544  			inBidName: "bid_idApn1_1",
  3545  			outBids:   []*entities.PbsOrtbBid{&bid1_Apn1_2, &bid1_Apn1_3},
  3546  		},
  3547  		{
  3548  			desc:      "remove element that doesn't exist",
  3549  			inBidName: "bid_idApn",
  3550  			outBids:   []*entities.PbsOrtbBid{&bid1_Apn1_1, &bid1_Apn1_2, &bid1_Apn1_3},
  3551  		},
  3552  	}
  3553  	for _, test := range testCases {
  3554  
  3555  		innerBidsApn1 := []*entities.PbsOrtbBid{
  3556  			&bid1_Apn1_1,
  3557  			&bid1_Apn1_2,
  3558  			&bid1_Apn1_3,
  3559  		}
  3560  
  3561  		seatBidApn1 := &entities.PbsOrtbSeatBid{Bids: innerBidsApn1, Currency: "USD"}
  3562  
  3563  		removeBidById(seatBidApn1, test.inBidName)
  3564  		assert.Len(t, seatBidApn1.Bids, len(test.outBids), test.desc)
  3565  		assert.ElementsMatch(t, seatBidApn1.Bids, test.outBids, "Incorrect bids in response")
  3566  	}
  3567  
  3568  }
  3569  
  3570  func TestUpdateRejections(t *testing.T) {
  3571  	rejections := []string{}
  3572  
  3573  	rejections = updateRejections(rejections, "bid_id1", "some reason 1")
  3574  	rejections = updateRejections(rejections, "bid_id2", "some reason 2")
  3575  
  3576  	assert.Equal(t, 2, len(rejections), "Rejections should contain 2 rejection messages")
  3577  	assert.Containsf(t, rejections, "bid rejected [bid ID: bid_id1] reason: some reason 1", "Rejection message did not match expected")
  3578  	assert.Containsf(t, rejections, "bid rejected [bid ID: bid_id2] reason: some reason 2", "Rejection message did not match expected")
  3579  }
  3580  
  3581  func TestApplyDealSupport(t *testing.T) {
  3582  	testCases := []struct {
  3583  		description               string
  3584  		dealPriority              int
  3585  		impExt                    json.RawMessage
  3586  		targ                      map[string]string
  3587  		expectedHbPbCatDur        string
  3588  		expectedDealErr           string
  3589  		expectedDealTierSatisfied bool
  3590  	}{
  3591  		{
  3592  			description:  "hb_pb_cat_dur should be modified",
  3593  			dealPriority: 5,
  3594  			impExt:       json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3595  			targ: map[string]string{
  3596  				"hb_pb_cat_dur": "12.00_movies_30s",
  3597  			},
  3598  			expectedHbPbCatDur:        "tier5_movies_30s",
  3599  			expectedDealErr:           "",
  3600  			expectedDealTierSatisfied: true,
  3601  		},
  3602  		{
  3603  			description:  "hb_pb_cat_dur should not be modified due to priority not exceeding min",
  3604  			dealPriority: 9,
  3605  			impExt:       json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3606  			targ: map[string]string{
  3607  				"hb_pb_cat_dur": "12.00_medicine_30s",
  3608  			},
  3609  			expectedHbPbCatDur:        "12.00_medicine_30s",
  3610  			expectedDealErr:           "",
  3611  			expectedDealTierSatisfied: false,
  3612  		},
  3613  		{
  3614  			description:  "hb_pb_cat_dur should not be modified due to invalid config",
  3615  			dealPriority: 5,
  3616  			impExt:       json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}}}`),
  3617  			targ: map[string]string{
  3618  				"hb_pb_cat_dur": "12.00_games_30s",
  3619  			},
  3620  			expectedHbPbCatDur:        "12.00_games_30s",
  3621  			expectedDealErr:           "dealTier configuration invalid for bidder 'appnexus', imp ID 'imp_id1'",
  3622  			expectedDealTierSatisfied: false,
  3623  		},
  3624  		{
  3625  			description:  "hb_pb_cat_dur should not be modified due to deal priority of 0",
  3626  			dealPriority: 0,
  3627  			impExt:       json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3628  			targ: map[string]string{
  3629  				"hb_pb_cat_dur": "12.00_auto_30s",
  3630  			},
  3631  			expectedHbPbCatDur:        "12.00_auto_30s",
  3632  			expectedDealErr:           "",
  3633  			expectedDealTierSatisfied: false,
  3634  		},
  3635  	}
  3636  
  3637  	bidderName := openrtb_ext.BidderName("appnexus")
  3638  	for _, test := range testCases {
  3639  		bidRequest := &openrtb2.BidRequest{
  3640  			ID: "some-request-id",
  3641  			Imp: []openrtb2.Imp{
  3642  				{
  3643  					ID:  "imp_id1",
  3644  					Ext: test.impExt,
  3645  				},
  3646  			},
  3647  		}
  3648  
  3649  		bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", ""}
  3650  		bidCategory := map[string]string{
  3651  			bid.Bid.ID: test.targ["hb_pb_cat_dur"],
  3652  		}
  3653  
  3654  		auc := &auction{
  3655  			winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{
  3656  				"imp_id1": {
  3657  					bidderName: {&bid},
  3658  				},
  3659  			},
  3660  		}
  3661  
  3662  		dealErrs := applyDealSupport(bidRequest, auc, bidCategory, nil)
  3663  
  3664  		assert.Equal(t, test.expectedHbPbCatDur, bidCategory[auc.winningBidsByBidder["imp_id1"][bidderName][0].Bid.ID], test.description)
  3665  		assert.Equal(t, test.expectedDealTierSatisfied, auc.winningBidsByBidder["imp_id1"][bidderName][0].DealTierSatisfied, "expectedDealTierSatisfied=%v when %v", test.expectedDealTierSatisfied, test.description)
  3666  		if len(test.expectedDealErr) > 0 {
  3667  			assert.Containsf(t, dealErrs, errors.New(test.expectedDealErr), "Expected error message not found in deal errors")
  3668  		}
  3669  	}
  3670  }
  3671  
  3672  func TestApplyDealSupportMultiBid(t *testing.T) {
  3673  	type args struct {
  3674  		bidRequest  *openrtb2.BidRequest
  3675  		auc         *auction
  3676  		bidCategory map[string]string
  3677  		multiBid    map[string]openrtb_ext.ExtMultiBid
  3678  	}
  3679  	type want struct {
  3680  		errs                      []error
  3681  		expectedHbPbCatDur        map[string]map[string][]string
  3682  		expectedDealTierSatisfied map[string]map[string][]bool
  3683  	}
  3684  	tests := []struct {
  3685  		name string
  3686  		args args
  3687  		want want
  3688  	}{
  3689  		{
  3690  			name: "multibid disabled, hb_pb_cat_dur should be modified only for first bid",
  3691  			args: args{
  3692  				bidRequest: &openrtb2.BidRequest{
  3693  					ID: "some-request-id",
  3694  					Imp: []openrtb2.Imp{
  3695  						{
  3696  							ID:  "imp_id1",
  3697  							Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3698  						},
  3699  						{
  3700  							ID:  "imp_id1",
  3701  							Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3702  						},
  3703  					},
  3704  				},
  3705  				auc: &auction{
  3706  					winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{
  3707  						"imp_id1": {
  3708  							openrtb_ext.BidderName("appnexus"): {
  3709  								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
  3710  								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
  3711  							},
  3712  						},
  3713  					},
  3714  				},
  3715  				bidCategory: map[string]string{
  3716  					"123456": "12.00_movies_30s",
  3717  					"789101": "12.00_movies_30s",
  3718  				},
  3719  				multiBid: nil,
  3720  			},
  3721  			want: want{
  3722  				errs: []error{},
  3723  				expectedHbPbCatDur: map[string]map[string][]string{
  3724  					"imp_id1": {
  3725  						"appnexus": []string{"tier5_movies_30s", "12.00_movies_30s"},
  3726  					},
  3727  				},
  3728  				expectedDealTierSatisfied: map[string]map[string][]bool{
  3729  					"imp_id1": {
  3730  						"appnexus": []bool{true, false},
  3731  					},
  3732  				},
  3733  			},
  3734  		},
  3735  		{
  3736  			name: "multibid enabled, hb_pb_cat_dur should be modified for all winning bids",
  3737  			args: args{
  3738  				bidRequest: &openrtb2.BidRequest{
  3739  					ID: "some-request-id",
  3740  					Imp: []openrtb2.Imp{
  3741  						{
  3742  							ID:  "imp_id1",
  3743  							Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3744  						},
  3745  						{
  3746  							ID:  "imp_id1",
  3747  							Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3748  						},
  3749  					},
  3750  				},
  3751  				auc: &auction{
  3752  					winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{
  3753  						"imp_id1": {
  3754  							openrtb_ext.BidderName("appnexus"): {
  3755  								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
  3756  								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
  3757  							},
  3758  						},
  3759  					},
  3760  				},
  3761  				bidCategory: map[string]string{
  3762  					"123456": "12.00_movies_30s",
  3763  					"789101": "12.00_movies_30s",
  3764  				},
  3765  				multiBid: map[string]openrtb_ext.ExtMultiBid{
  3766  					"appnexus": {
  3767  						TargetBidderCodePrefix: "appN",
  3768  						MaxBids:                ptrutil.ToPtr(2),
  3769  					},
  3770  				},
  3771  			},
  3772  			want: want{
  3773  				errs: []error{},
  3774  				expectedHbPbCatDur: map[string]map[string][]string{
  3775  					"imp_id1": {
  3776  						"appnexus": []string{"tier5_movies_30s", "tier5_movies_30s"},
  3777  					},
  3778  				},
  3779  				expectedDealTierSatisfied: map[string]map[string][]bool{
  3780  					"imp_id1": {
  3781  						"appnexus": []bool{true, true},
  3782  					},
  3783  				},
  3784  			},
  3785  		},
  3786  		{
  3787  			name: "multibid enabled but TargetBidderCodePrefix not defined, hb_pb_cat_dur should be modified only for first bid",
  3788  			args: args{
  3789  				bidRequest: &openrtb2.BidRequest{
  3790  					ID: "some-request-id",
  3791  					Imp: []openrtb2.Imp{
  3792  						{
  3793  							ID:  "imp_id1",
  3794  							Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3795  						},
  3796  						{
  3797  							ID:  "imp_id1",
  3798  							Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
  3799  						},
  3800  					},
  3801  				},
  3802  				auc: &auction{
  3803  					winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{
  3804  						"imp_id1": {
  3805  							openrtb_ext.BidderName("appnexus"): {
  3806  								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
  3807  								&entities.PbsOrtbBid{&openrtb2.Bid{ID: "789101"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, 5, false, "", 0, "USD", ""},
  3808  							},
  3809  						},
  3810  					},
  3811  				},
  3812  				bidCategory: map[string]string{
  3813  					"123456": "12.00_movies_30s",
  3814  					"789101": "12.00_movies_30s",
  3815  				},
  3816  				multiBid: map[string]openrtb_ext.ExtMultiBid{
  3817  					"appnexus": {
  3818  						MaxBids: ptrutil.ToPtr(2),
  3819  					},
  3820  				},
  3821  			},
  3822  			want: want{
  3823  				errs: []error{},
  3824  				expectedHbPbCatDur: map[string]map[string][]string{
  3825  					"imp_id1": {
  3826  						"appnexus": []string{"tier5_movies_30s", "12.00_movies_30s"},
  3827  					},
  3828  				},
  3829  				expectedDealTierSatisfied: map[string]map[string][]bool{
  3830  					"imp_id1": {
  3831  						"appnexus": []bool{true, false},
  3832  					},
  3833  				},
  3834  			},
  3835  		},
  3836  	}
  3837  	for _, tt := range tests {
  3838  		t.Run(tt.name, func(t *testing.T) {
  3839  			errs := applyDealSupport(tt.args.bidRequest, tt.args.auc, tt.args.bidCategory, tt.args.multiBid)
  3840  			assert.Equal(t, tt.want.errs, errs)
  3841  
  3842  			for impID, topBidsPerImp := range tt.args.auc.winningBidsByBidder {
  3843  				for bidder, topBidsPerBidder := range topBidsPerImp {
  3844  					for i, topBid := range topBidsPerBidder {
  3845  						assert.Equal(t, tt.want.expectedHbPbCatDur[impID][bidder.String()][i], tt.args.bidCategory[topBid.Bid.ID], tt.name)
  3846  						assert.Equal(t, tt.want.expectedDealTierSatisfied[impID][bidder.String()][i], topBid.DealTierSatisfied, tt.name)
  3847  					}
  3848  				}
  3849  			}
  3850  		})
  3851  	}
  3852  }
  3853  
  3854  func TestGetDealTiers(t *testing.T) {
  3855  	testCases := []struct {
  3856  		description string
  3857  		request     openrtb2.BidRequest
  3858  		expected    map[string]openrtb_ext.DealTierBidderMap
  3859  	}{
  3860  		{
  3861  			description: "None",
  3862  			request: openrtb2.BidRequest{
  3863  				Imp: []openrtb2.Imp{},
  3864  			},
  3865  			expected: map[string]openrtb_ext.DealTierBidderMap{},
  3866  		},
  3867  		{
  3868  			description: "One",
  3869  			request: openrtb2.BidRequest{
  3870  				Imp: []openrtb2.Imp{
  3871  					{ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}}}}}`)},
  3872  				},
  3873  			},
  3874  			expected: map[string]openrtb_ext.DealTierBidderMap{
  3875  				"imp1": {openrtb_ext.BidderAppnexus: {Prefix: "tier", MinDealTier: 5}},
  3876  			},
  3877  		},
  3878  		{
  3879  			description: "Many",
  3880  			request: openrtb2.BidRequest{
  3881  				Imp: []openrtb2.Imp{
  3882  					{ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}}}`)},
  3883  					{ID: "imp2", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 8, "prefix": "tier2"}}}}}`)},
  3884  				},
  3885  			},
  3886  			expected: map[string]openrtb_ext.DealTierBidderMap{
  3887  				"imp1": {openrtb_ext.BidderAppnexus: {Prefix: "tier1", MinDealTier: 5}},
  3888  				"imp2": {openrtb_ext.BidderAppnexus: {Prefix: "tier2", MinDealTier: 8}},
  3889  			},
  3890  		},
  3891  		{
  3892  			description: "Many - Skips Malformed",
  3893  			request: openrtb2.BidRequest{
  3894  				Imp: []openrtb2.Imp{
  3895  					{ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}}}`)},
  3896  					{ID: "imp2", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": "wrong type"}}}}`)},
  3897  				},
  3898  			},
  3899  			expected: map[string]openrtb_ext.DealTierBidderMap{
  3900  				"imp1": {openrtb_ext.BidderAppnexus: {Prefix: "tier1", MinDealTier: 5}},
  3901  			},
  3902  		},
  3903  	}
  3904  
  3905  	for _, test := range testCases {
  3906  		result := getDealTiers(&test.request)
  3907  		assert.Equal(t, test.expected, result, test.description)
  3908  	}
  3909  }
  3910  
  3911  func TestValidateDealTier(t *testing.T) {
  3912  	testCases := []struct {
  3913  		description    string
  3914  		dealTier       openrtb_ext.DealTier
  3915  		expectedResult bool
  3916  	}{
  3917  		{
  3918  			description:    "Valid",
  3919  			dealTier:       openrtb_ext.DealTier{Prefix: "prefix", MinDealTier: 5},
  3920  			expectedResult: true,
  3921  		},
  3922  		{
  3923  			description:    "Invalid - Empty",
  3924  			dealTier:       openrtb_ext.DealTier{},
  3925  			expectedResult: false,
  3926  		},
  3927  		{
  3928  			description:    "Invalid - Empty Prefix",
  3929  			dealTier:       openrtb_ext.DealTier{MinDealTier: 5},
  3930  			expectedResult: false,
  3931  		},
  3932  		{
  3933  			description:    "Invalid - Empty Deal Tier",
  3934  			dealTier:       openrtb_ext.DealTier{Prefix: "prefix"},
  3935  			expectedResult: false,
  3936  		},
  3937  	}
  3938  
  3939  	for _, test := range testCases {
  3940  		assert.Equal(t, test.expectedResult, validateDealTier(test.dealTier), test.description)
  3941  	}
  3942  }
  3943  
  3944  func TestUpdateHbPbCatDur(t *testing.T) {
  3945  	testCases := []struct {
  3946  		description               string
  3947  		targ                      map[string]string
  3948  		dealTier                  openrtb_ext.DealTier
  3949  		dealPriority              int
  3950  		expectedHbPbCatDur        string
  3951  		expectedDealTierSatisfied bool
  3952  	}{
  3953  		{
  3954  			description: "hb_pb_cat_dur should be updated with prefix and tier",
  3955  			targ: map[string]string{
  3956  				"hb_pb":         "12.00",
  3957  				"hb_pb_cat_dur": "12.00_movies_30s",
  3958  			},
  3959  			dealTier: openrtb_ext.DealTier{
  3960  				Prefix:      "tier",
  3961  				MinDealTier: 5,
  3962  			},
  3963  			dealPriority:              5,
  3964  			expectedHbPbCatDur:        "tier5_movies_30s",
  3965  			expectedDealTierSatisfied: true,
  3966  		},
  3967  		{
  3968  			description: "hb_pb_cat_dur should not be updated due to bid priority",
  3969  			targ: map[string]string{
  3970  				"hb_pb":         "12.00",
  3971  				"hb_pb_cat_dur": "12.00_auto_30s",
  3972  			},
  3973  			dealTier: openrtb_ext.DealTier{
  3974  				Prefix:      "tier",
  3975  				MinDealTier: 10,
  3976  			},
  3977  			dealPriority:              6,
  3978  			expectedHbPbCatDur:        "12.00_auto_30s",
  3979  			expectedDealTierSatisfied: false,
  3980  		},
  3981  		{
  3982  			description: "hb_pb_cat_dur should be updated with prefix and tier",
  3983  			targ: map[string]string{
  3984  				"hb_pb":         "12.00",
  3985  				"hb_pb_cat_dur": "12.00_medicine_30s",
  3986  			},
  3987  			dealTier: openrtb_ext.DealTier{
  3988  				Prefix:      "tier",
  3989  				MinDealTier: 1,
  3990  			},
  3991  			dealPriority:              7,
  3992  			expectedHbPbCatDur:        "tier7_medicine_30s",
  3993  			expectedDealTierSatisfied: true,
  3994  		},
  3995  	}
  3996  
  3997  	for _, test := range testCases {
  3998  		bid := entities.PbsOrtbBid{&openrtb2.Bid{ID: "123456"}, nil, "video", map[string]string{}, &openrtb_ext.ExtBidPrebidVideo{}, nil, nil, test.dealPriority, false, "", 0, "USD", ""}
  3999  		bidCategory := map[string]string{
  4000  			bid.Bid.ID: test.targ["hb_pb_cat_dur"],
  4001  		}
  4002  
  4003  		updateHbPbCatDur(&bid, test.dealTier, bidCategory)
  4004  
  4005  		assert.Equal(t, test.expectedHbPbCatDur, bidCategory[bid.Bid.ID], test.description)
  4006  		assert.Equal(t, test.expectedDealTierSatisfied, bid.DealTierSatisfied, test.description)
  4007  	}
  4008  }
  4009  
  4010  func TestMakeBidExtJSON(t *testing.T) {
  4011  
  4012  	type aTest struct {
  4013  		description        string
  4014  		ext                json.RawMessage
  4015  		extBidPrebid       openrtb_ext.ExtBidPrebid
  4016  		impExtInfo         map[string]ImpExtInfo
  4017  		origbidcpm         float64
  4018  		origbidcur         string
  4019  		expectedBidExt     string
  4020  		expectedErrMessage string
  4021  	}
  4022  
  4023  	testCases := []aTest{
  4024  		{
  4025  			description:        "Valid extension, non empty extBidPrebid, valid imp ext info, meta from adapter",
  4026  			ext:                json.RawMessage(`{"video":{"h":100}}`),
  4027  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}, Passthrough: nil},
  4028  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}},
  4029  			origbidcpm:         10.0000,
  4030  			origbidcur:         "USD",
  4031  			expectedBidExt:     `{"prebid":{"meta": {"brandName": "foo"}, "passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4032  			expectedErrMessage: "",
  4033  		},
  4034  		{
  4035  			description:        "Valid extension, non empty extBidPrebid, valid imp ext info, meta from response, imp passthrough is nil",
  4036  			ext:                json.RawMessage(`{"video":{"h":100},"prebid":{"meta": {"brandName": "foo"}}}`),
  4037  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
  4038  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), nil}},
  4039  			origbidcpm:         10.0000,
  4040  			origbidcur:         "USD",
  4041  			expectedBidExt:     `{"prebid":{"meta": {"brandName": "foo"}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4042  			expectedErrMessage: "",
  4043  		},
  4044  		{
  4045  			description:        "Empty extension, non empty extBidPrebid and valid imp ext info",
  4046  			ext:                nil,
  4047  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
  4048  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}},
  4049  			origbidcpm:         0,
  4050  			expectedBidExt:     `{"origbidcpm": 0,"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]}}`,
  4051  			expectedErrMessage: "",
  4052  		},
  4053  		{
  4054  			description:        "Valid extension, non empty extBidPrebid and imp ext info not found",
  4055  			ext:                json.RawMessage(`{"video":{"h":100}}`),
  4056  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
  4057  			impExtInfo:         map[string]ImpExtInfo{"another_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}},
  4058  			origbidcpm:         10.0000,
  4059  			origbidcur:         "USD",
  4060  			expectedBidExt:     `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4061  			expectedErrMessage: "",
  4062  		},
  4063  		{
  4064  			description:        "Valid extension, empty extBidPrebid and valid imp ext info",
  4065  			ext:                json.RawMessage(`{"video":{"h":100}}`),
  4066  			extBidPrebid:       openrtb_ext.ExtBidPrebid{},
  4067  			origbidcpm:         10.0000,
  4068  			origbidcur:         "USD",
  4069  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}},
  4070  			expectedBidExt:     `{"prebid":{"passthrough":{"imp_passthrough_val":1}},"storedrequestattributes":{"h":480,"mimes":["video/mp4"]},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4071  			expectedErrMessage: "",
  4072  		},
  4073  		{
  4074  			description:        "Valid extension, non empty extBidPrebid and empty imp ext info",
  4075  			ext:                json.RawMessage(`{"video":{"h":100}}`),
  4076  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
  4077  			origbidcpm:         10.0000,
  4078  			origbidcur:         "USD",
  4079  			impExtInfo:         nil,
  4080  			expectedBidExt:     `{"prebid":{"type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4081  			expectedErrMessage: "",
  4082  		},
  4083  		{
  4084  			description:        "Valid extension, non empty extBidPrebid and valid imp ext info without video attr",
  4085  			ext:                json.RawMessage(`{"video":{"h":100}}`),
  4086  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
  4087  			origbidcpm:         10.0000,
  4088  			origbidcur:         "USD",
  4089  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}},
  4090  			expectedBidExt:     `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"},"video":{"h":100}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4091  			expectedErrMessage: "",
  4092  		},
  4093  		{
  4094  			description:        "Valid extension with prebid, non empty extBidPrebid and valid imp ext info without video attr",
  4095  			ext:                json.RawMessage(`{"prebid":{"targeting":100}}`),
  4096  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
  4097  			origbidcpm:         10.0000,
  4098  			origbidcur:         "USD",
  4099  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"banner":{"h":480}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}},
  4100  			expectedBidExt:     `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4101  			expectedErrMessage: "",
  4102  		},
  4103  		{
  4104  			description:        "Valid extension with prebid, non empty extBidPrebid and valid imp ext info with video attr",
  4105  			ext:                json.RawMessage(`{"prebid":{"targeting":100}}`),
  4106  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
  4107  			origbidcpm:         10.0000,
  4108  			origbidcur:         "USD",
  4109  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`{"imp_passthrough_val": 1}`)}},
  4110  			expectedBidExt:     `{"prebid":{"passthrough":{"imp_passthrough_val":1}, "type":"video"}, "storedrequestattributes":{"h":480,"mimes":["video/mp4"]}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4111  			expectedErrMessage: "",
  4112  		},
  4113  		{
  4114  			description:        "Meta - Defined By Bid - Nil Extension",
  4115  			ext:                nil,
  4116  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}},
  4117  			impExtInfo:         map[string]ImpExtInfo{},
  4118  			origbidcpm:         0,
  4119  			origbidcur:         "USD",
  4120  			expectedBidExt:     `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`,
  4121  			expectedErrMessage: "",
  4122  		},
  4123  		{
  4124  			description:        "Meta - Defined By Bid - Empty Extension",
  4125  			ext:                json.RawMessage(`{}`),
  4126  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}},
  4127  			impExtInfo:         nil,
  4128  			origbidcpm:         0,
  4129  			origbidcur:         "USD",
  4130  			expectedBidExt:     `{"origbidcpm": 0,"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`,
  4131  			expectedErrMessage: "",
  4132  		},
  4133  		{
  4134  			description:        "Meta - Defined By Bid - Existing Extension Overwritten",
  4135  			ext:                json.RawMessage(`{"prebid":{"meta":{"brandName":"notfoo", "brandId": 42}}}`),
  4136  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner"), Meta: &openrtb_ext.ExtBidPrebidMeta{BrandName: "foo"}},
  4137  			impExtInfo:         nil,
  4138  			origbidcpm:         10.0000,
  4139  			origbidcur:         "USD",
  4140  			expectedBidExt:     `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4141  			expectedErrMessage: "",
  4142  		},
  4143  		{
  4144  			description:        "Meta - Not Defined By Bid - Persists From Bid Ext",
  4145  			ext:                json.RawMessage(`{"prebid":{"meta":{"brandName":"foo"}}}`),
  4146  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")},
  4147  			impExtInfo:         nil,
  4148  			origbidcpm:         10.0000,
  4149  			origbidcur:         "USD",
  4150  			expectedBidExt:     `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcpm": 10, "origbidcur": "USD"}`,
  4151  			expectedErrMessage: "",
  4152  		},
  4153  		{
  4154  			description:        "Meta - Not Defined By Bid - Persists From Bid Ext - Invalid Fields Ignored",
  4155  			ext:                json.RawMessage(`{"prebid":{"meta":{"brandName":"foo","unknown":"value"}}}`),
  4156  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")},
  4157  			impExtInfo:         nil,
  4158  			origbidcpm:         -1,
  4159  			origbidcur:         "USD",
  4160  			expectedBidExt:     `{"prebid":{"meta":{"brandName":"foo"},"type":"banner"}, "origbidcur": "USD"}`,
  4161  			expectedErrMessage: "",
  4162  		},
  4163  		{
  4164  			description:        "Meta - Not Defined",
  4165  			ext:                nil,
  4166  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")},
  4167  			impExtInfo:         nil,
  4168  			origbidcpm:         0,
  4169  			origbidcur:         "USD",
  4170  			expectedBidExt:     `{"origbidcpm": 0,"prebid":{"type":"banner"}, "origbidcur": "USD"}`,
  4171  			expectedErrMessage: "",
  4172  		},
  4173  		//Error cases
  4174  		{
  4175  			description:        "Invalid extension, valid extBidPrebid and valid imp ext info",
  4176  			ext:                json.RawMessage(`{invalid json}`),
  4177  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("video")},
  4178  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{"h":480,"mimes":["video/mp4"]}}`), json.RawMessage(`"prebid": {"passthrough": {"imp_passthrough_val": some_val}}"`)}},
  4179  			expectedBidExt:     ``,
  4180  			expectedErrMessage: "invalid character",
  4181  		},
  4182  		{
  4183  			description:        "Valid extension, empty extBidPrebid and invalid imp ext info",
  4184  			ext:                json.RawMessage(`{"video":{"h":100}}`),
  4185  			extBidPrebid:       openrtb_ext.ExtBidPrebid{},
  4186  			impExtInfo:         map[string]ImpExtInfo{"test_imp_id": {true, []byte(`{"video":{!}}`), nil}},
  4187  			expectedBidExt:     ``,
  4188  			expectedErrMessage: "invalid character",
  4189  		},
  4190  		{
  4191  			description:        "Meta - Invalid",
  4192  			ext:                json.RawMessage(`{"prebid":{"meta":{"brandId":"foo"}}}`), // brandId should be an int, but is a string in this test case
  4193  			extBidPrebid:       openrtb_ext.ExtBidPrebid{Type: openrtb_ext.BidType("banner")},
  4194  			impExtInfo:         nil,
  4195  			expectedErrMessage: "error validaing response from server, json: cannot unmarshal string into Go struct field ExtBidPrebidMeta.prebid.meta.brandId of type int",
  4196  		},
  4197  		// add invalid
  4198  	}
  4199  
  4200  	for _, test := range testCases {
  4201  		result, err := makeBidExtJSON(test.ext, &test.extBidPrebid, test.impExtInfo, "test_imp_id", test.origbidcpm, test.origbidcur)
  4202  
  4203  		if test.expectedErrMessage == "" {
  4204  			assert.JSONEq(t, test.expectedBidExt, string(result), "Incorrect result")
  4205  			assert.NoError(t, err, "Error should not be returned")
  4206  		} else {
  4207  			assert.Contains(t, err.Error(), test.expectedErrMessage, "incorrect error message")
  4208  		}
  4209  	}
  4210  }
  4211  
  4212  func TestStoredAuctionResponses(t *testing.T) {
  4213  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  4214  	if error != nil {
  4215  		t.Errorf("Failed to create a category Fetcher: %v", error)
  4216  	}
  4217  
  4218  	e := new(exchange)
  4219  	e.cache = &wellBehavedCache{}
  4220  	e.me = &metricsConf.NilMetricsEngine{}
  4221  	e.categoriesFetcher = categoriesFetcher
  4222  	e.bidIDGenerator = &mockBidIDGenerator{false, false}
  4223  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  4224  	e.gdprPermsBuilder = fakePermissionsBuilder{
  4225  		permissions: &permissionsMock{
  4226  			allowAllBidders: true,
  4227  		},
  4228  	}.Builder
  4229  
  4230  	// Define mock incoming bid requeset
  4231  	mockBidRequest := &openrtb2.BidRequest{
  4232  		ID: "request-id",
  4233  		Imp: []openrtb2.Imp{{
  4234  			ID:    "impression-id",
  4235  			Video: &openrtb2.Video{W: 400, H: 300},
  4236  		}},
  4237  	}
  4238  
  4239  	expectedBidResponse := &openrtb2.BidResponse{
  4240  		ID: "request-id",
  4241  		SeatBid: []openrtb2.SeatBid{
  4242  			{
  4243  				Bid: []openrtb2.Bid{
  4244  					{ID: "bid_id", ImpID: "impression-id", Ext: json.RawMessage(`{"origbidcpm":0,"prebid":{"type":"video"}}`)},
  4245  				},
  4246  				Seat: "appnexus",
  4247  			},
  4248  		},
  4249  	}
  4250  
  4251  	testCases := []struct {
  4252  		desc              string
  4253  		storedAuctionResp map[string]json.RawMessage
  4254  		errorExpected     bool
  4255  	}{
  4256  		{
  4257  			desc: "Single imp with valid stored response",
  4258  			storedAuctionResp: map[string]json.RawMessage{
  4259  				"impression-id": json.RawMessage(`[{"bid": [{"id": "bid_id", "ext": {"prebid": {"type": "video"}}}],"seat": "appnexus"}]`),
  4260  			},
  4261  			errorExpected: false,
  4262  		},
  4263  		{
  4264  			desc: "Single imp with invalid stored response",
  4265  			storedAuctionResp: map[string]json.RawMessage{
  4266  				"impression-id": json.RawMessage(`[}]`),
  4267  			},
  4268  			errorExpected: true,
  4269  		},
  4270  	}
  4271  
  4272  	for _, test := range testCases {
  4273  
  4274  		auctionRequest := &AuctionRequest{
  4275  			BidRequestWrapper:      &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
  4276  			Account:                config.Account{},
  4277  			UserSyncs:              &emptyUsersync{},
  4278  			StoredAuctionResponses: test.storedAuctionResp,
  4279  			HookExecutor:           &hookexecution.EmptyHookExecutor{},
  4280  			TCF2Config:             gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  4281  		}
  4282  		// Run test
  4283  		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
  4284  		if test.errorExpected {
  4285  			assert.Error(t, err, "Error should be returned")
  4286  		} else {
  4287  			assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err)
  4288  			outBidResponse.Ext = nil
  4289  			assert.Equal(t, expectedBidResponse, outBidResponse.BidResponse, "Incorrect stored auction response")
  4290  		}
  4291  
  4292  	}
  4293  }
  4294  
  4295  func TestBuildStoredAuctionResponses(t *testing.T) {
  4296  
  4297  	type testIn struct {
  4298  		StoredAuctionResponses map[string]json.RawMessage
  4299  	}
  4300  	type testResults struct {
  4301  		adapterBids  map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid
  4302  		fledge       *openrtb_ext.Fledge
  4303  		liveAdapters []openrtb_ext.BidderName
  4304  	}
  4305  
  4306  	testCases := []struct {
  4307  		desc         string
  4308  		in           testIn
  4309  		expected     testResults
  4310  		errorMessage string
  4311  	}{
  4312  		{
  4313  			desc: "Single imp with single stored response bid",
  4314  			in: testIn{
  4315  				StoredAuctionResponses: map[string]json.RawMessage{
  4316  					"impression-id": json.RawMessage(`[{"bid": [{"id": "bid_id", "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}]`),
  4317  				},
  4318  			},
  4319  			expected: testResults{
  4320  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4321  					openrtb_ext.BidderName("appnexus"): {
  4322  						Bids: []*entities.PbsOrtbBid{
  4323  							{
  4324  								Bid:     &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id", Ext: []byte(`{"prebid": {"type": "native"}}`)},
  4325  								BidType: openrtb_ext.BidTypeNative,
  4326  							},
  4327  						},
  4328  					},
  4329  				},
  4330  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus")},
  4331  			},
  4332  		},
  4333  		{
  4334  			desc: "Single imp with single stored response bid with incorrect bid type",
  4335  			in: testIn{
  4336  				StoredAuctionResponses: map[string]json.RawMessage{
  4337  					"impression-id": json.RawMessage(`[{"bid": [{"id": "bid_id", "ext": {"prebid": {"type": "incorrect"}}}],"seat": "appnexus"}]`),
  4338  				},
  4339  			},
  4340  			expected: testResults{
  4341  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4342  					openrtb_ext.BidderName("appnexus"): {
  4343  						Bids: []*entities.PbsOrtbBid{
  4344  							{
  4345  								Bid:     &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id", Ext: []byte(`{"prebid": {"type": "native"}}`)},
  4346  								BidType: openrtb_ext.BidTypeNative,
  4347  							},
  4348  						},
  4349  					},
  4350  				},
  4351  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus")},
  4352  			},
  4353  			errorMessage: "Failed to parse bid mediatype for impression \"impression-id\", invalid BidType: incorrect",
  4354  		},
  4355  		{
  4356  			desc: "Single imp with multiple bids in stored response one bidder",
  4357  			in: testIn{
  4358  				StoredAuctionResponses: map[string]json.RawMessage{
  4359  					"impression-id": json.RawMessage(`[{"bid": [{"id": "bid_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "bid_id2", "ext": {"prebid": {"type": "video"}}}],"seat": "appnexus"}]`),
  4360  				},
  4361  			},
  4362  			expected: testResults{
  4363  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4364  					openrtb_ext.BidderName("appnexus"): {
  4365  						Bids: []*entities.PbsOrtbBid{
  4366  							{Bid: &openrtb2.Bid{ID: "bid_id1", ImpID: "impression-id", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4367  							{Bid: &openrtb2.Bid{ID: "bid_id2", ImpID: "impression-id", Ext: []byte(`{"prebid": {"type": "video"}}`)}, BidType: openrtb_ext.BidTypeVideo},
  4368  						},
  4369  					},
  4370  				},
  4371  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus")},
  4372  			},
  4373  		},
  4374  		{
  4375  			desc: "Single imp with multiple bids in stored response two bidders",
  4376  			in: testIn{
  4377  				StoredAuctionResponses: map[string]json.RawMessage{
  4378  					"impression-id": json.RawMessage(`[{"bid": [{"id": "apn_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "apn_id2", "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}, {"bid": [{"id": "rubicon_id1", "ext": {"prebid": {"type": "banner"}}}, {"id": "rubicon_id2", "ext": {"prebid": {"type": "banner"}}}],"seat": "rubicon"}]`),
  4379  				},
  4380  			},
  4381  			expected: testResults{
  4382  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4383  					openrtb_ext.BidderName("appnexus"): {
  4384  						Bids: []*entities.PbsOrtbBid{
  4385  							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4386  							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4387  						},
  4388  					},
  4389  					openrtb_ext.BidderName("rubicon"): {
  4390  						Bids: []*entities.PbsOrtbBid{
  4391  							{Bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id", Ext: []byte(`{"prebid": {"type": "banner"}}`)}, BidType: openrtb_ext.BidTypeBanner},
  4392  							{Bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id", Ext: []byte(`{"prebid": {"type": "banner"}}`)}, BidType: openrtb_ext.BidTypeBanner},
  4393  						},
  4394  					},
  4395  				},
  4396  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus"), openrtb_ext.BidderName("rubicon")},
  4397  			},
  4398  		},
  4399  		{
  4400  			desc: "Two imps with two bids in stored response two bidders, different bids number",
  4401  			in: testIn{
  4402  				StoredAuctionResponses: map[string]json.RawMessage{
  4403  					"impression-id1": json.RawMessage(`[{"bid": [{"id": "apn_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "apn_id2", "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}]`),
  4404  					"impression-id2": json.RawMessage(`[{"bid": [{"id": "apn_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "apn_id2", "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}, {"bid": [{"id": "rubicon_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "rubicon_id2", "ext": {"prebid": {"type": "native"}}}],"seat": "rubicon"}]`),
  4405  				},
  4406  			},
  4407  			expected: testResults{
  4408  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4409  					openrtb_ext.BidderName("appnexus"): {
  4410  						Bids: []*entities.PbsOrtbBid{
  4411  							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id1", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4412  							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id1", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4413  							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id2", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4414  							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id2", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4415  						},
  4416  					},
  4417  					openrtb_ext.BidderName("rubicon"): {
  4418  						Bids: []*entities.PbsOrtbBid{
  4419  							{Bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id2", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4420  							{Bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id2", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4421  						},
  4422  					},
  4423  				},
  4424  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus"), openrtb_ext.BidderName("rubicon")},
  4425  			},
  4426  		},
  4427  		{
  4428  			desc: "Two imps with two bids in stored response two bidders",
  4429  			in: testIn{
  4430  				StoredAuctionResponses: map[string]json.RawMessage{
  4431  					"impression-id1": json.RawMessage(`[{"bid": [{"id": "apn_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "apn_id2", "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}, {"bid": [{"id": "rubicon_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "rubicon_id2", "ext": {"prebid": {"type": "native"}}}],"seat": "rubicon"}]`),
  4432  					"impression-id2": json.RawMessage(`[{"bid": [{"id": "apn_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "apn_id2", "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}, {"bid": [{"id": "rubicon_id1", "ext": {"prebid": {"type": "native"}}}, {"id": "rubicon_id2", "ext": {"prebid": {"type": "native"}}}],"seat": "rubicon"}]`),
  4433  				},
  4434  			},
  4435  			expected: testResults{
  4436  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4437  					openrtb_ext.BidderName("appnexus"): {
  4438  						Bids: []*entities.PbsOrtbBid{
  4439  							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id1", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4440  							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id1", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4441  							{Bid: &openrtb2.Bid{ID: "apn_id1", ImpID: "impression-id2", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4442  							{Bid: &openrtb2.Bid{ID: "apn_id2", ImpID: "impression-id2", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4443  						},
  4444  					},
  4445  					openrtb_ext.BidderName("rubicon"): {
  4446  						Bids: []*entities.PbsOrtbBid{
  4447  							{Bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id1", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4448  							{Bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id1", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4449  							{Bid: &openrtb2.Bid{ID: "rubicon_id1", ImpID: "impression-id2", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4450  							{Bid: &openrtb2.Bid{ID: "rubicon_id2", ImpID: "impression-id2", Ext: []byte(`{"prebid": {"type": "native"}}`)}, BidType: openrtb_ext.BidTypeNative},
  4451  						},
  4452  					},
  4453  				},
  4454  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus"), openrtb_ext.BidderName("rubicon")},
  4455  			},
  4456  		},
  4457  		{
  4458  			desc: "Fledge in stored response bid",
  4459  			in: testIn{
  4460  				StoredAuctionResponses: map[string]json.RawMessage{
  4461  					"impression-id": json.RawMessage(`[{"bid": [],"seat": "openx", "ext": {"prebid": {"fledge": {"auctionconfigs": [{"impid": "1", "bidder": "openx", "adapter": "openx", "config": [1,2,3]}]}}}}]`),
  4462  				},
  4463  			},
  4464  			expected: testResults{
  4465  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4466  					openrtb_ext.BidderName("openx"): {
  4467  						Bids: []*entities.PbsOrtbBid{},
  4468  					},
  4469  				},
  4470  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("openx")},
  4471  				fledge: &openrtb_ext.Fledge{
  4472  					AuctionConfigs: []*openrtb_ext.FledgeAuctionConfig{
  4473  						{
  4474  							ImpId:   "impression-id",
  4475  							Bidder:  "openx",
  4476  							Adapter: "openx",
  4477  							Config:  json.RawMessage("[1,2,3]"),
  4478  						},
  4479  					},
  4480  				},
  4481  			},
  4482  		},
  4483  		{
  4484  			desc: "Single imp with single stored response bid with bid.mtype",
  4485  			in: testIn{
  4486  				StoredAuctionResponses: map[string]json.RawMessage{
  4487  					"impression-id": json.RawMessage(`[{"bid": [{"id": "bid_id", "mtype": 2, "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}]`),
  4488  				},
  4489  			},
  4490  			expected: testResults{
  4491  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4492  					openrtb_ext.BidderName("appnexus"): {
  4493  						Bids: []*entities.PbsOrtbBid{
  4494  							{
  4495  								Bid:     &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id", MType: 2, Ext: []byte(`{"prebid": {"type": "native"}}`)},
  4496  								BidType: openrtb_ext.BidTypeVideo,
  4497  							},
  4498  						},
  4499  					},
  4500  				},
  4501  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus")},
  4502  			},
  4503  		},
  4504  		{
  4505  			desc: "Multiple imps with multiple stored response bid with bid.mtype and different types",
  4506  			in: testIn{
  4507  				StoredAuctionResponses: map[string]json.RawMessage{
  4508  					"impression-id1": json.RawMessage(`[{"bid": [{"id": "bid_id", "mtype": 1, "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}]`),
  4509  					"impression-id2": json.RawMessage(`[{"bid": [{"id": "bid_id", "mtype": 2, "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}]`),
  4510  					"impression-id3": json.RawMessage(`[{"bid": [{"id": "bid_id", "mtype": 3, "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}]`),
  4511  					"impression-id4": json.RawMessage(`[{"bid": [{"id": "bid_id", "mtype": 4, "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}]`),
  4512  				},
  4513  			},
  4514  			expected: testResults{
  4515  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4516  					openrtb_ext.BidderName("appnexus"): {
  4517  						Bids: []*entities.PbsOrtbBid{
  4518  							{
  4519  								Bid:     &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id1", MType: 1, Ext: []byte(`{"prebid": {"type": "native"}}`)},
  4520  								BidType: openrtb_ext.BidTypeBanner,
  4521  							},
  4522  							{
  4523  								Bid:     &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id2", MType: 2, Ext: []byte(`{"prebid": {"type": "native"}}`)},
  4524  								BidType: openrtb_ext.BidTypeVideo,
  4525  							},
  4526  							{
  4527  								Bid:     &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id3", MType: 3, Ext: []byte(`{"prebid": {"type": "native"}}`)},
  4528  								BidType: openrtb_ext.BidTypeAudio,
  4529  							},
  4530  							{
  4531  								Bid:     &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id4", MType: 4, Ext: []byte(`{"prebid": {"type": "native"}}`)},
  4532  								BidType: openrtb_ext.BidTypeNative,
  4533  							},
  4534  						},
  4535  					},
  4536  				},
  4537  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus")},
  4538  			},
  4539  		},
  4540  		{
  4541  			desc: "Single imp with single stored response bid with incorrect bid.mtype",
  4542  			in: testIn{
  4543  				StoredAuctionResponses: map[string]json.RawMessage{
  4544  					"impression-id": json.RawMessage(`[{"bid": [{"id": "bid_id", "mtype": 10, "ext": {"prebid": {"type": "native"}}}],"seat": "appnexus"}]`),
  4545  				},
  4546  			},
  4547  			expected: testResults{
  4548  				adapterBids: map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid{
  4549  					openrtb_ext.BidderName("appnexus"): {
  4550  						Bids: []*entities.PbsOrtbBid{
  4551  							{
  4552  								Bid:     &openrtb2.Bid{ID: "bid_id", ImpID: "impression-id", MType: 2, Ext: []byte(`{"prebid": {"type": "native"}}`)},
  4553  								BidType: openrtb_ext.BidTypeVideo,
  4554  							},
  4555  						},
  4556  					},
  4557  				},
  4558  				liveAdapters: []openrtb_ext.BidderName{openrtb_ext.BidderName("appnexus")},
  4559  			},
  4560  			errorMessage: "Failed to parse bid mType for impression \"impression-id\"",
  4561  		},
  4562  	}
  4563  	for _, test := range testCases {
  4564  
  4565  		bids, fledge, adapters, err := buildStoredAuctionResponse(test.in.StoredAuctionResponses)
  4566  		if len(test.errorMessage) > 0 {
  4567  			assert.Equal(t, test.errorMessage, err.Error(), " incorrect expected error")
  4568  		} else {
  4569  			assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err)
  4570  
  4571  			assert.ElementsMatch(t, test.expected.liveAdapters, adapters, "Incorrect adapter list")
  4572  			assert.Equal(t, fledge, test.expected.fledge, "Incorrect FLEDGE response")
  4573  
  4574  			for _, bidderName := range test.expected.liveAdapters {
  4575  				assert.ElementsMatch(t, test.expected.adapterBids[bidderName].Bids, bids[bidderName].Bids, "Incorrect bids")
  4576  			}
  4577  		}
  4578  	}
  4579  }
  4580  
  4581  func TestAuctionDebugEnabled(t *testing.T) {
  4582  	categoriesFetcher, err := newCategoryFetcher("./test/category-mapping")
  4583  	assert.NoError(t, err, "error should be nil")
  4584  	e := new(exchange)
  4585  	e.cache = &wellBehavedCache{}
  4586  	e.me = &metricsConf.NilMetricsEngine{}
  4587  	e.categoriesFetcher = categoriesFetcher
  4588  	e.bidIDGenerator = &mockBidIDGenerator{false, false}
  4589  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  4590  	e.gdprPermsBuilder = fakePermissionsBuilder{
  4591  		permissions: &permissionsMock{
  4592  			allowAllBidders: true,
  4593  		},
  4594  	}.Builder
  4595  	e.requestSplitter = requestSplitter{
  4596  		me:               e.me,
  4597  		gdprPermsBuilder: e.gdprPermsBuilder,
  4598  	}
  4599  
  4600  	ctx := context.Background()
  4601  
  4602  	bidRequest := &openrtb2.BidRequest{
  4603  		ID:   "some-request-id",
  4604  		Test: 1,
  4605  	}
  4606  
  4607  	auctionRequest := &AuctionRequest{
  4608  		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest},
  4609  		Account:           config.Account{DebugAllow: false},
  4610  		UserSyncs:         &emptyUsersync{},
  4611  		StartTime:         time.Now(),
  4612  		RequestType:       metrics.ReqTypeORTB2Web,
  4613  		HookExecutor:      &hookexecution.EmptyHookExecutor{},
  4614  		TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  4615  	}
  4616  
  4617  	debugLog := &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true}
  4618  	resp, err := e.HoldAuction(ctx, auctionRequest, debugLog)
  4619  
  4620  	assert.NoError(t, err, "error should be nil")
  4621  
  4622  	expectedResolvedRequest := `{"id":"some-request-id","imp":null,"test":1}`
  4623  	actualResolvedRequest, _, _, err := jsonparser.Get(resp.Ext, "debug", "resolvedrequest")
  4624  	assert.NoError(t, err, "error should be nil")
  4625  	assert.NotNil(t, actualResolvedRequest, "actualResolvedRequest should not be nil")
  4626  	assert.JSONEq(t, expectedResolvedRequest, string(actualResolvedRequest), "Resolved request is incorrect")
  4627  
  4628  }
  4629  
  4630  func TestPassExperimentConfigsToHoldAuction(t *testing.T) {
  4631  	noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
  4632  	server := httptest.NewServer(http.HandlerFunc(noBidServer))
  4633  	defer server.Close()
  4634  
  4635  	cfg := &config.Configuration{}
  4636  
  4637  	biddersInfo, err := config.LoadBidderInfoFromDisk("../static/bidder-info")
  4638  	if err != nil {
  4639  		t.Fatal(err)
  4640  	}
  4641  	biddersInfo["appnexus"] = config.BidderInfo{
  4642  		Endpoint: "test.com",
  4643  		Capabilities: &config.CapabilitiesInfo{
  4644  			Site: &config.PlatformInfo{
  4645  				MediaTypes: []openrtb_ext.BidType{openrtb_ext.BidTypeBanner, openrtb_ext.BidTypeVideo},
  4646  			},
  4647  		},
  4648  		Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}}
  4649  
  4650  	signer := MockSigner{}
  4651  
  4652  	adapters, adaptersErr := BuildAdapters(server.Client(), cfg, biddersInfo, &metricsConf.NilMetricsEngine{})
  4653  	if adaptersErr != nil {
  4654  		t.Fatalf("Error intializing adapters: %v", adaptersErr)
  4655  	}
  4656  
  4657  	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  4658  
  4659  	gdprPermsBuilder := fakePermissionsBuilder{
  4660  		permissions: &permissionsMock{
  4661  			allowAllBidders: true,
  4662  		},
  4663  	}.Builder
  4664  
  4665  	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdprPermsBuilder, currencyConverter, nilCategoryFetcher{}, &signer, macros.NewStringIndexBasedReplacer()).(*exchange)
  4666  
  4667  	// Define mock incoming bid requeset
  4668  	mockBidRequest := &openrtb2.BidRequest{
  4669  		ID: "some-request-id",
  4670  		Imp: []openrtb2.Imp{{
  4671  			ID:     "some-impression-id",
  4672  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
  4673  			Ext:    json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":1}}}}`),
  4674  		}},
  4675  		Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
  4676  		Ext:  json.RawMessage(`{"prebid":{"experiment":{"adscert":{"enabled": true}}}}`),
  4677  	}
  4678  
  4679  	auctionRequest := &AuctionRequest{
  4680  		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
  4681  		Account:           config.Account{},
  4682  		UserSyncs:         &emptyUsersync{},
  4683  		HookExecutor:      &hookexecution.EmptyHookExecutor{},
  4684  		TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  4685  	}
  4686  
  4687  	debugLog := DebugLog{}
  4688  	_, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog)
  4689  
  4690  	assert.NoError(t, err, "unexpected error occured")
  4691  	assert.Equal(t, "test.com", signer.data, "incorrect signer data")
  4692  }
  4693  
  4694  func TestCallSignHeader(t *testing.T) {
  4695  	type aTest struct {
  4696  		description    string
  4697  		experiment     openrtb_ext.Experiment
  4698  		bidderInfo     config.BidderInfo
  4699  		expectedResult bool
  4700  	}
  4701  	var nilExperiment openrtb_ext.Experiment
  4702  
  4703  	testCases := []aTest{
  4704  		{
  4705  			description:    "both experiment.adsCert enabled for request and for bidder ",
  4706  			experiment:     openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: true}},
  4707  			bidderInfo:     config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}},
  4708  			expectedResult: true,
  4709  		},
  4710  		{
  4711  			description:    "experiment is not defined in request, bidder config adsCert enabled",
  4712  			experiment:     nilExperiment,
  4713  			bidderInfo:     config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}},
  4714  			expectedResult: false,
  4715  		},
  4716  		{
  4717  			description:    "experiment.adsCert is not defined in request, bidder config adsCert enabled",
  4718  			experiment:     openrtb_ext.Experiment{AdsCert: nil},
  4719  			bidderInfo:     config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}},
  4720  			expectedResult: false,
  4721  		},
  4722  		{
  4723  			description:    "experiment.adsCert is disabled in request, bidder config adsCert enabled",
  4724  			experiment:     openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: false}},
  4725  			bidderInfo:     config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: true}}},
  4726  			expectedResult: false,
  4727  		},
  4728  		{
  4729  			description:    "experiment.adsCert is enabled in request, bidder config adsCert disabled",
  4730  			experiment:     openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: true}},
  4731  			bidderInfo:     config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: false}}},
  4732  			expectedResult: false,
  4733  		},
  4734  		{
  4735  			description:    "experiment.adsCert is disabled in request, bidder config adsCert disabled",
  4736  			experiment:     openrtb_ext.Experiment{AdsCert: &openrtb_ext.AdsCert{Enabled: false}},
  4737  			bidderInfo:     config.BidderInfo{Experiment: config.BidderInfoExperiment{AdsCert: config.BidderAdsCert{Enabled: false}}},
  4738  			expectedResult: false,
  4739  		},
  4740  	}
  4741  	for _, test := range testCases {
  4742  		result := isAdsCertEnabled(&test.experiment, test.bidderInfo)
  4743  		assert.Equal(t, test.expectedResult, result, "incorrect result returned")
  4744  	}
  4745  
  4746  }
  4747  
  4748  func TestValidateBannerCreativeSize(t *testing.T) {
  4749  	exchange := exchange{bidValidationEnforcement: config.Validations{MaxCreativeWidth: 100, MaxCreativeHeight: 100},
  4750  		me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil),
  4751  	}
  4752  	testCases := []struct {
  4753  		description                 string
  4754  		givenBid                    *entities.PbsOrtbBid
  4755  		givenBidResponseExt         *openrtb_ext.ExtBidResponse
  4756  		givenBidderName             string
  4757  		givenPubID                  string
  4758  		expectedBannerCreativeValid bool
  4759  	}{
  4760  		{
  4761  			description:                 "The dimensions are invalid, both values bigger than the max",
  4762  			givenBid:                    &entities.PbsOrtbBid{Bid: &openrtb2.Bid{W: 200, H: 200}},
  4763  			givenBidResponseExt:         &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)},
  4764  			givenBidderName:             "bidder",
  4765  			givenPubID:                  "1",
  4766  			expectedBannerCreativeValid: false,
  4767  		},
  4768  		{
  4769  			description:                 "The width is invalid, height is valid, the dimensions as a whole are invalid",
  4770  			givenBid:                    &entities.PbsOrtbBid{Bid: &openrtb2.Bid{W: 200, H: 50}},
  4771  			givenBidResponseExt:         &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)},
  4772  			givenBidderName:             "bidder",
  4773  			givenPubID:                  "1",
  4774  			expectedBannerCreativeValid: false,
  4775  		},
  4776  		{
  4777  			description:                 "The width is valid, height is invalid, the dimensions as a whole are invalid",
  4778  			givenBid:                    &entities.PbsOrtbBid{Bid: &openrtb2.Bid{W: 50, H: 200}},
  4779  			givenBidResponseExt:         &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)},
  4780  			givenBidderName:             "bidder",
  4781  			givenPubID:                  "1",
  4782  			expectedBannerCreativeValid: false,
  4783  		},
  4784  		{
  4785  			description:                 "Both width and height are valid, the dimensions are valid",
  4786  			givenBid:                    &entities.PbsOrtbBid{Bid: &openrtb2.Bid{W: 50, H: 50}},
  4787  			givenBidResponseExt:         &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)},
  4788  			givenBidderName:             "bidder",
  4789  			givenPubID:                  "1",
  4790  			expectedBannerCreativeValid: true,
  4791  		},
  4792  	}
  4793  	for _, test := range testCases {
  4794  		acutalBannerCreativeValid := exchange.validateBannerCreativeSize(test.givenBid, test.givenBidResponseExt, openrtb_ext.BidderName(test.givenBidderName), test.givenPubID, "enforce")
  4795  		assert.Equal(t, test.expectedBannerCreativeValid, acutalBannerCreativeValid)
  4796  	}
  4797  }
  4798  
  4799  func TestValidateBidAdM(t *testing.T) {
  4800  	exchange := exchange{bidValidationEnforcement: config.Validations{MaxCreativeWidth: 100, MaxCreativeHeight: 100},
  4801  		me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil, nil),
  4802  	}
  4803  	testCases := []struct {
  4804  		description         string
  4805  		givenBid            *entities.PbsOrtbBid
  4806  		givenBidResponseExt *openrtb_ext.ExtBidResponse
  4807  		givenBidderName     string
  4808  		givenPubID          string
  4809  		expectedBidAdMValid bool
  4810  	}{
  4811  		{
  4812  			description:         "The adm of the bid contains insecure string and no secure string, adm is invalid",
  4813  			givenBid:            &entities.PbsOrtbBid{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid"}},
  4814  			givenBidResponseExt: &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)},
  4815  			givenBidderName:     "bidder",
  4816  			givenPubID:          "1",
  4817  			expectedBidAdMValid: false,
  4818  		},
  4819  		{
  4820  			description:         "The adm has both an insecure and secure string defined and therefore the adm is valid",
  4821  			givenBid:            &entities.PbsOrtbBid{Bid: &openrtb2.Bid{AdM: "http://www.foo.com https://www.bar.com"}},
  4822  			givenBidResponseExt: &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)},
  4823  			givenBidderName:     "bidder",
  4824  			givenPubID:          "1",
  4825  			expectedBidAdMValid: true,
  4826  		},
  4827  		{
  4828  			description:         "The adm has both an insecure and secure string defined and therefore the adm is valid",
  4829  			givenBid:            &entities.PbsOrtbBid{Bid: &openrtb2.Bid{AdM: "http%3A//www.foo.com https%3A//www.bar.com"}},
  4830  			givenBidResponseExt: &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)},
  4831  			givenBidderName:     "bidder",
  4832  			givenPubID:          "1",
  4833  			expectedBidAdMValid: true,
  4834  		},
  4835  		{
  4836  			description:         "The adm of the bid are valid with a secure string",
  4837  			givenBid:            &entities.PbsOrtbBid{Bid: &openrtb2.Bid{AdM: "https://domain.com/valid"}},
  4838  			givenBidResponseExt: &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)},
  4839  			givenBidderName:     "bidder",
  4840  			givenPubID:          "1",
  4841  			expectedBidAdMValid: true,
  4842  		},
  4843  	}
  4844  	for _, test := range testCases {
  4845  		actualBidAdMValid := exchange.validateBidAdM(test.givenBid, test.givenBidResponseExt, openrtb_ext.BidderName(test.givenBidderName), test.givenPubID, "enforce")
  4846  		assert.Equal(t, test.expectedBidAdMValid, actualBidAdMValid)
  4847  
  4848  	}
  4849  }
  4850  
  4851  func TestMakeBidWithValidation(t *testing.T) {
  4852  	sampleAd := "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST ...></VAST>"
  4853  	sampleOpenrtbBid := &openrtb2.Bid{ID: "some-bid-id", AdM: sampleAd}
  4854  
  4855  	// Define test cases
  4856  	testCases := []struct {
  4857  		description       string
  4858  		givenValidations  config.Validations
  4859  		givenBids         []*entities.PbsOrtbBid
  4860  		expectedNumOfBids int
  4861  	}{
  4862  		{
  4863  			description:       "Validation is enforced, and one bid out of the two is invalid based on dimensions",
  4864  			givenValidations:  config.Validations{BannerCreativeMaxSize: config.ValidationEnforce, MaxCreativeWidth: 100, MaxCreativeHeight: 100},
  4865  			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}},
  4866  			expectedNumOfBids: 1,
  4867  		},
  4868  		{
  4869  			description:       "Validation is warned, so no bids should be removed (Validating CreativeMaxSize) ",
  4870  			givenValidations:  config.Validations{BannerCreativeMaxSize: config.ValidationWarn, MaxCreativeWidth: 100, MaxCreativeHeight: 100},
  4871  			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}},
  4872  			expectedNumOfBids: 2,
  4873  		},
  4874  		{
  4875  			description:       "Validation is enforced, and one bid out of the two is invalid based on AdM",
  4876  			givenValidations:  config.Validations{SecureMarkup: config.ValidationEnforce},
  4877  			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid", ImpID: "1"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid", ImpID: "2"}, BidType: openrtb_ext.BidTypeBanner}},
  4878  			expectedNumOfBids: 1,
  4879  		},
  4880  		{
  4881  			description:       "Validation is warned so no bids should be removed (Validating SecureMarkup)",
  4882  			givenValidations:  config.Validations{SecureMarkup: config.ValidationWarn},
  4883  			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid", ImpID: "1"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid", ImpID: "2"}, BidType: openrtb_ext.BidTypeBanner}},
  4884  			expectedNumOfBids: 2,
  4885  		},
  4886  		{
  4887  			description:       "Adm validation is skipped, creative size validation is enforced, one Adm is invalid, but because we skip, no bids should be removed",
  4888  			givenValidations:  config.Validations{SecureMarkup: config.ValidationSkip, BannerCreativeMaxSize: config.ValidationEnforce},
  4889  			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{AdM: "http://domain.com/invalid"}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{AdM: "https://domain.com/valid"}, BidType: openrtb_ext.BidTypeBanner}},
  4890  			expectedNumOfBids: 2,
  4891  		},
  4892  		{
  4893  			description:       "Creative Size Validation is skipped, Adm Validation is enforced, one Creative Size is invalid, but because we skip, no bids should be removed",
  4894  			givenValidations:  config.Validations{BannerCreativeMaxSize: config.ValidationWarn, MaxCreativeWidth: 100, MaxCreativeHeight: 100},
  4895  			givenBids:         []*entities.PbsOrtbBid{{Bid: &openrtb2.Bid{W: 200, H: 200}, BidType: openrtb_ext.BidTypeBanner}, {Bid: &openrtb2.Bid{W: 50, H: 50}, BidType: openrtb_ext.BidTypeBanner}},
  4896  			expectedNumOfBids: 2,
  4897  		},
  4898  	}
  4899  
  4900  	// Test set up
  4901  	sampleAuction := &auction{cacheIds: map[*openrtb2.Bid]string{sampleOpenrtbBid: "CACHE_UUID_1234"}}
  4902  
  4903  	noBidHandler := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
  4904  	server := httptest.NewServer(http.HandlerFunc(noBidHandler))
  4905  	defer server.Close()
  4906  
  4907  	bidderImpl := &goodSingleBidder{
  4908  		httpRequest: &adapters.RequestData{
  4909  			Method:  "POST",
  4910  			Uri:     server.URL,
  4911  			Body:    []byte("{\"key\":\"val\"}"),
  4912  			Headers: http.Header{},
  4913  		},
  4914  		bidResponse: &adapters.BidderResponse{},
  4915  	}
  4916  	e := new(exchange)
  4917  	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
  4918  		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, ""),
  4919  	}
  4920  	e.cache = &wellBehavedCache{}
  4921  	e.me = &metricsConf.NilMetricsEngine{}
  4922  
  4923  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  4924  
  4925  	bidExtResponse := &openrtb_ext.ExtBidResponse{Errors: make(map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage)}
  4926  
  4927  	ImpExtInfoMap := make(map[string]ImpExtInfo)
  4928  	ImpExtInfoMap["1"] = ImpExtInfo{}
  4929  	ImpExtInfoMap["2"] = ImpExtInfo{}
  4930  
  4931  	//Run tests
  4932  	for _, test := range testCases {
  4933  		e.bidValidationEnforcement = test.givenValidations
  4934  		sampleBids := test.givenBids
  4935  		resultingBids, resultingErrs := e.makeBid(sampleBids, sampleAuction, true, ImpExtInfoMap, bidExtResponse, "", "")
  4936  
  4937  		assert.Equal(t, 0, len(resultingErrs), "%s. Test should not return errors \n", test.description)
  4938  		assert.Equal(t, test.expectedNumOfBids, len(resultingBids), "%s. Test returns more valid bids than expected\n", test.description)
  4939  	}
  4940  }
  4941  
  4942  func TestSetBidValidationStatus(t *testing.T) {
  4943  	testCases := []struct {
  4944  		description  string
  4945  		givenHost    config.Validations
  4946  		givenAccount config.Validations
  4947  		expected     config.Validations
  4948  	}{
  4949  		{
  4950  			description:  "Host configuration is different than account, account setting should be preferred (enforce)",
  4951  			givenHost:    config.Validations{BannerCreativeMaxSize: config.ValidationSkip, SecureMarkup: config.ValidationSkip},
  4952  			givenAccount: config.Validations{BannerCreativeMaxSize: config.ValidationEnforce, SecureMarkup: config.ValidationEnforce},
  4953  			expected:     config.Validations{BannerCreativeMaxSize: config.ValidationEnforce, SecureMarkup: config.ValidationSkip},
  4954  		},
  4955  		{
  4956  			description:  "Host configuration is different than account, account setting should be preferred (warn)",
  4957  			givenHost:    config.Validations{BannerCreativeMaxSize: config.ValidationEnforce, SecureMarkup: config.ValidationEnforce},
  4958  			givenAccount: config.Validations{BannerCreativeMaxSize: config.ValidationWarn, SecureMarkup: config.ValidationWarn},
  4959  			expected:     config.Validations{BannerCreativeMaxSize: config.ValidationWarn, SecureMarkup: config.ValidationEnforce},
  4960  		},
  4961  		{
  4962  			description:  "Host configuration is different than account, account setting should be preferred (skip)",
  4963  			givenHost:    config.Validations{BannerCreativeMaxSize: config.ValidationWarn, SecureMarkup: config.ValidationWarn},
  4964  			givenAccount: config.Validations{BannerCreativeMaxSize: config.ValidationSkip, SecureMarkup: config.ValidationSkip},
  4965  			expected:     config.Validations{BannerCreativeMaxSize: config.ValidationSkip, SecureMarkup: config.ValidationWarn},
  4966  		},
  4967  		{
  4968  			description:  "No account confiugration given, host confg should be preferred",
  4969  			givenHost:    config.Validations{BannerCreativeMaxSize: config.ValidationSkip, SecureMarkup: config.ValidationSkip},
  4970  			givenAccount: config.Validations{},
  4971  			expected:     config.Validations{BannerCreativeMaxSize: config.ValidationSkip, SecureMarkup: config.ValidationSkip},
  4972  		},
  4973  	}
  4974  	for _, test := range testCases {
  4975  		test.givenHost.SetBannerCreativeMaxSize(test.givenAccount)
  4976  		assert.Equal(t, test.expected, test.givenHost)
  4977  	}
  4978  }
  4979  
  4980  /*
  4981  TestOverrideConfigAlternateBidderCodesWithRequestValues makes sure that the correct alternabiddercodes list is forwarded to the adapters and only the approved bids are returned in auction response.
  4982  
  4983  1. request.ext.prebid.alternatebiddercodes has priority over the content of config.Account.Alternatebiddercodes.
  4984  
  4985  2. request is updated with config.Account.Alternatebiddercodes values if request.ext.prebid.alternatebiddercodes is empty or not specified.
  4986  
  4987  3. request.ext.prebid.alternatebiddercodes is given priority over config.Account.Alternatebiddercodes if both are specified.
  4988  */
  4989  func TestOverrideConfigAlternateBidderCodesWithRequestValues(t *testing.T) {
  4990  	type testIn struct {
  4991  		config     config.Configuration
  4992  		requestExt json.RawMessage
  4993  	}
  4994  	type testResults struct {
  4995  		expectedSeats []string
  4996  	}
  4997  
  4998  	testCases := []struct {
  4999  		desc     string
  5000  		in       testIn
  5001  		expected testResults
  5002  	}{
  5003  		{
  5004  			desc: "alternatebiddercode defined neither in config nor in the request",
  5005  			in: testIn{
  5006  				config: config.Configuration{},
  5007  			},
  5008  			expected: testResults{
  5009  				expectedSeats: []string{"pubmatic"},
  5010  			},
  5011  		},
  5012  		{
  5013  			desc: "alternatebiddercode defined in config and not in request",
  5014  			in: testIn{
  5015  				config: config.Configuration{
  5016  					AccountDefaults: config.Account{
  5017  						AlternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{
  5018  							Enabled: true,
  5019  							Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
  5020  								"pubmatic": {
  5021  									Enabled:            true,
  5022  									AllowedBidderCodes: []string{"groupm"},
  5023  								},
  5024  							},
  5025  						},
  5026  					},
  5027  				},
  5028  				requestExt: json.RawMessage(`{}`),
  5029  			},
  5030  			expected: testResults{
  5031  				expectedSeats: []string{"pubmatic", "groupm"},
  5032  			},
  5033  		},
  5034  		{
  5035  			desc: "alternatebiddercode defined in request and not in config",
  5036  			in: testIn{
  5037  				requestExt: json.RawMessage(`{"prebid": {"alternatebiddercodes": {"enabled": true, "bidders": {"pubmatic": {"enabled": true, "allowedbiddercodes": ["appnexus"]}}}}}`),
  5038  			},
  5039  			expected: testResults{
  5040  				expectedSeats: []string{"pubmatic", "appnexus"},
  5041  			},
  5042  		},
  5043  		{
  5044  			desc: "alternatebiddercode defined in both config and in request",
  5045  			in: testIn{
  5046  				config: config.Configuration{
  5047  					AccountDefaults: config.Account{
  5048  						AlternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{
  5049  							Enabled: true,
  5050  							Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{
  5051  								"pubmatic": {
  5052  									Enabled:            true,
  5053  									AllowedBidderCodes: []string{"groupm"},
  5054  								},
  5055  							},
  5056  						},
  5057  					},
  5058  				},
  5059  				requestExt: json.RawMessage(`{"prebid": {"alternatebiddercodes": {"enabled": true, "bidders": {"pubmatic": {"enabled": true, "allowedbiddercodes": ["ix"]}}}}}`),
  5060  			},
  5061  			expected: testResults{
  5062  				expectedSeats: []string{"pubmatic", "ix"},
  5063  			},
  5064  		},
  5065  	}
  5066  
  5067  	// Init an exchange to run an auction from
  5068  	noBidServer := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(204) }
  5069  	mockPubMaticBidService := httptest.NewServer(http.HandlerFunc(noBidServer))
  5070  	defer mockPubMaticBidService.Close()
  5071  
  5072  	categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
  5073  	if error != nil {
  5074  		t.Errorf("Failed to create a category Fetcher: %v", error)
  5075  	}
  5076  
  5077  	mockBidderRequestResponse := &goodSingleBidder{
  5078  		httpRequest: &adapters.RequestData{
  5079  			Method:  "POST",
  5080  			Uri:     mockPubMaticBidService.URL,
  5081  			Body:    []byte("{\"key\":\"val\"}"),
  5082  			Headers: http.Header{},
  5083  		},
  5084  		bidResponse: &adapters.BidderResponse{
  5085  			Bids: []*adapters.TypedBid{
  5086  				{Bid: &openrtb2.Bid{ID: "1"}, Seat: ""},
  5087  				{Bid: &openrtb2.Bid{ID: "2"}, Seat: "pubmatic"},
  5088  				{Bid: &openrtb2.Bid{ID: "3"}, Seat: "appnexus"},
  5089  				{Bid: &openrtb2.Bid{ID: "4"}, Seat: "groupm"},
  5090  				{Bid: &openrtb2.Bid{ID: "5"}, Seat: "ix"},
  5091  			},
  5092  			Currency: "USD",
  5093  		},
  5094  	}
  5095  
  5096  	e := new(exchange)
  5097  	e.cache = &wellBehavedCache{}
  5098  	e.me = &metricsConf.NilMetricsEngine{}
  5099  	e.gdprPermsBuilder = fakePermissionsBuilder{
  5100  		permissions: &permissionsMock{
  5101  			allowAllBidders: true,
  5102  		},
  5103  	}.Builder
  5104  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  5105  	e.categoriesFetcher = categoriesFetcher
  5106  	e.bidIDGenerator = &mockBidIDGenerator{false, false}
  5107  	e.requestSplitter = requestSplitter{
  5108  		me:               e.me,
  5109  		gdprPermsBuilder: e.gdprPermsBuilder,
  5110  	}
  5111  
  5112  	// Define mock incoming bid requeset
  5113  	mockBidRequest := &openrtb2.BidRequest{
  5114  		ID: "some-request-id",
  5115  		Imp: []openrtb2.Imp{{
  5116  			ID:     "some-impression-id",
  5117  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
  5118  			Ext:    json.RawMessage(`{"prebid":{"bidder":{"pubmatic": {"publisherId": 1}}}}`),
  5119  		}},
  5120  		Site: &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
  5121  	}
  5122  
  5123  	// Run tests
  5124  	for _, test := range testCases {
  5125  		e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
  5126  			openrtb_ext.BidderPubmatic: AdaptBidder(mockBidderRequestResponse, mockPubMaticBidService.Client(), &test.in.config, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderPubmatic, nil, ""),
  5127  		}
  5128  
  5129  		mockBidRequest.Ext = test.in.requestExt
  5130  
  5131  		auctionRequest := &AuctionRequest{
  5132  			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: mockBidRequest},
  5133  			Account:           test.in.config.AccountDefaults,
  5134  			UserSyncs:         &emptyUsersync{},
  5135  			HookExecutor:      &hookexecution.EmptyHookExecutor{},
  5136  			TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  5137  		}
  5138  
  5139  		// Run test
  5140  		outBidResponse, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
  5141  
  5142  		// Assertions
  5143  		assert.NoErrorf(t, err, "%s. HoldAuction error: %v \n", test.desc, err)
  5144  		assert.NotNil(t, outBidResponse)
  5145  		assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
  5146  
  5147  		// So 2 seatBids are expected as,
  5148  		// the default "" and "pubmatic" bids will be in one seat and the extra-bids "groupm"/"appnexus"/"ix" in another seat.
  5149  		assert.Len(t, outBidResponse.SeatBid, len(test.expected.expectedSeats), "%s. seatbid count miss-match\n", test.desc)
  5150  
  5151  		for i, seatBid := range outBidResponse.SeatBid {
  5152  			assert.Contains(t, test.expected.expectedSeats, seatBid.Seat, "%s. unexpected seatbid\n", test.desc)
  5153  
  5154  			if seatBid.Seat == string(openrtb_ext.BidderPubmatic) {
  5155  				assert.Len(t, outBidResponse.SeatBid[i].Bid, 2, "%s. unexpected bid count\n", test.desc)
  5156  			} else {
  5157  				assert.Len(t, outBidResponse.SeatBid[i].Bid, 1, "%s. unexpected bid count\n", test.desc)
  5158  			}
  5159  		}
  5160  	}
  5161  }
  5162  
  5163  type MockSigner struct {
  5164  	data string
  5165  }
  5166  
  5167  func (ms *MockSigner) Sign(destinationURL string, body []byte) (string, error) {
  5168  	ms.data = destinationURL
  5169  	return "mock data", nil
  5170  }
  5171  
  5172  type exchangeSpec struct {
  5173  	GDPREnabled                bool                   `json:"gdpr_enabled"`
  5174  	FloorsEnabled              bool                   `json:"floors_enabled"`
  5175  	IncomingRequest            exchangeRequest        `json:"incomingRequest"`
  5176  	OutgoingRequests           map[string]*bidderSpec `json:"outgoingRequests"`
  5177  	Response                   exchangeResponse       `json:"response,omitempty"`
  5178  	EnforceCCPA                bool                   `json:"enforceCcpa"`
  5179  	EnforceLMT                 bool                   `json:"enforceLmt"`
  5180  	AssumeGDPRApplies          bool                   `json:"assume_gdpr_applies"`
  5181  	DebugLog                   *DebugLog              `json:"debuglog,omitempty"`
  5182  	EventsEnabled              bool                   `json:"events_enabled,omitempty"`
  5183  	StartTime                  int64                  `json:"start_time_ms,omitempty"`
  5184  	BidIDGenerator             *mockBidIDGenerator    `json:"bidIDGenerator,omitempty"`
  5185  	RequestType                *metrics.RequestType   `json:"requestType,omitempty"`
  5186  	PassthroughFlag            bool                   `json:"passthrough_flag,omitempty"`
  5187  	HostSChainFlag             bool                   `json:"host_schain_flag,omitempty"`
  5188  	HostConfigBidValidation    config.Validations     `json:"host_bid_validations"`
  5189  	AccountConfigBidValidation config.Validations     `json:"account_bid_validations"`
  5190  	AccountFloorsEnabled       bool                   `json:"account_floors_enabled"`
  5191  	FledgeEnabled              bool                   `json:"fledge_enabled,omitempty"`
  5192  	MultiBid                   *multiBidSpec          `json:"multiBid,omitempty"`
  5193  	Server                     exchangeServer         `json:"server,omitempty"`
  5194  	AccountPrivacy             *config.AccountPrivacy `json:"accountPrivacy,omitempty"`
  5195  }
  5196  
  5197  type multiBidSpec struct {
  5198  	AccountMaxBid          int  `json:"default_bid_limit"`
  5199  	AssertMultiBidWarnings bool `json:"assert_multi_bid_warnings"`
  5200  }
  5201  
  5202  type exchangeRequest struct {
  5203  	OrtbRequest openrtb2.BidRequest `json:"ortbRequest"`
  5204  	Usersyncs   map[string]string   `json:"usersyncs"`
  5205  }
  5206  
  5207  type exchangeResponse struct {
  5208  	Bids  *openrtb2.BidResponse `json:"bids"`
  5209  	Error string                `json:"error,omitempty"`
  5210  	Ext   json.RawMessage       `json:"ext,omitempty"`
  5211  }
  5212  
  5213  type exchangeServer struct {
  5214  	ExternalUrl string `json:"externalURL"`
  5215  	GvlID       int    `json:"gvlID"`
  5216  	DataCenter  string `json:"dataCenter"`
  5217  }
  5218  
  5219  type bidderSpec struct {
  5220  	ExpectedRequest         *bidderRequest `json:"expectRequest"`
  5221  	MockResponse            bidderResponse `json:"mockResponse"`
  5222  	ModifyingVastXmlAllowed bool           `json:"modifyingVastXmlAllowed,omitempty"`
  5223  }
  5224  
  5225  type bidderRequest struct {
  5226  	OrtbRequest    openrtb2.BidRequest `json:"ortbRequest"`
  5227  	BidAdjustments map[string]float64  `json:"bidAdjustments"`
  5228  }
  5229  
  5230  type bidderResponse struct {
  5231  	SeatBids  []*bidderSeatBid           `json:"pbsSeatBids,omitempty"`
  5232  	Errors    []string                   `json:"errors,omitempty"`
  5233  	HttpCalls []*openrtb_ext.ExtHttpCall `json:"httpCalls,omitempty"`
  5234  }
  5235  
  5236  // bidderSeatBid is basically a subset of entities.PbsOrtbSeatBid from exchange/bidder.go.
  5237  // The only real reason I'm not reusing that type is because I don't want people to think that the
  5238  // JSON property tags on those types are contracts in prod.
  5239  type bidderSeatBid struct {
  5240  	Bids                 []bidderBid                        `json:"pbsBids,omitempty"`
  5241  	Seat                 string                             `json:"seat"`
  5242  	Currency             string                             `json:"currency"`
  5243  	FledgeAuctionConfigs []*openrtb_ext.FledgeAuctionConfig `json:"fledgeAuctionConfigs,omitempty"`
  5244  }
  5245  
  5246  // bidderBid is basically a subset of entities.PbsOrtbBid from exchange/bidder.go.
  5247  // See the comment on bidderSeatBid for more info.
  5248  type bidderBid struct {
  5249  	Bid  *openrtb2.Bid                 `json:"ortbBid,omitempty"`
  5250  	Type string                        `json:"bidType,omitempty"`
  5251  	Meta *openrtb_ext.ExtBidPrebidMeta `json:"bidMeta,omitempty"`
  5252  }
  5253  
  5254  type mockIdFetcher map[string]string
  5255  
  5256  func (f mockIdFetcher) GetUID(key string) (uid string, exists bool, notExpired bool) {
  5257  	uid, exists = f[string(key)]
  5258  	return
  5259  }
  5260  
  5261  func (f mockIdFetcher) HasAnyLiveSyncs() bool {
  5262  	return len(f) > 0
  5263  }
  5264  
  5265  type validatingBidder struct {
  5266  	t          *testing.T
  5267  	fileName   string
  5268  	bidderName string
  5269  
  5270  	// These are maps because they may contain aliases. They should _at least_ contain an entry for bidderName.
  5271  	expectations  map[string]*bidderRequest
  5272  	mockResponses map[string]bidderResponse
  5273  }
  5274  
  5275  func (b *validatingBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (seatBids []*entities.PbsOrtbSeatBid, extaInfo extraBidderRespInfo, errs []error) {
  5276  	if expectedRequest, ok := b.expectations[string(bidderRequest.BidderName)]; ok {
  5277  		if expectedRequest != nil {
  5278  			if !reflect.DeepEqual(expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments) {
  5279  				b.t.Errorf("%s: Bidder %s got wrong bid adjustment. Expected %v, got %v", b.fileName, bidderRequest.BidderName, expectedRequest.BidAdjustments, bidRequestOptions.bidAdjustments)
  5280  			}
  5281  			diffOrtbRequests(b.t, fmt.Sprintf("Request to %s in %s", string(bidderRequest.BidderName), b.fileName), &expectedRequest.OrtbRequest, bidderRequest.BidRequest)
  5282  		}
  5283  	} else {
  5284  		b.t.Errorf("%s: Bidder %s got unexpected request for alias %s. No input assertions.", b.fileName, b.bidderName, bidderRequest.BidderName)
  5285  	}
  5286  
  5287  	if mockResponse, ok := b.mockResponses[string(bidderRequest.BidderName)]; ok {
  5288  		if len(mockResponse.SeatBids) != 0 {
  5289  			for _, mockSeatBid := range mockResponse.SeatBids {
  5290  				var bids []*entities.PbsOrtbBid
  5291  
  5292  				if len(mockSeatBid.Bids) != 0 {
  5293  					bids = make([]*entities.PbsOrtbBid, len(mockSeatBid.Bids))
  5294  					for i := 0; i < len(bids); i++ {
  5295  						bids[i] = &entities.PbsOrtbBid{
  5296  							OriginalBidCPM: mockSeatBid.Bids[i].Bid.Price,
  5297  							Bid:            mockSeatBid.Bids[i].Bid,
  5298  							BidType:        openrtb_ext.BidType(mockSeatBid.Bids[i].Type),
  5299  							BidMeta:        mockSeatBid.Bids[i].Meta,
  5300  						}
  5301  					}
  5302  				}
  5303  
  5304  				seatBids = append(seatBids, &entities.PbsOrtbSeatBid{
  5305  					Bids:                 bids,
  5306  					HttpCalls:            mockResponse.HttpCalls,
  5307  					Seat:                 mockSeatBid.Seat,
  5308  					Currency:             mockSeatBid.Currency,
  5309  					FledgeAuctionConfigs: mockSeatBid.FledgeAuctionConfigs,
  5310  				})
  5311  			}
  5312  		} else {
  5313  			seatBids = []*entities.PbsOrtbSeatBid{{
  5314  				Bids:      nil,
  5315  				HttpCalls: mockResponse.HttpCalls,
  5316  				Seat:      string(bidderRequest.BidderName),
  5317  			}}
  5318  		}
  5319  
  5320  		for _, err := range mockResponse.Errors {
  5321  			errs = append(errs, errors.New(err))
  5322  		}
  5323  	} else {
  5324  		b.t.Errorf("%s: Bidder %s got unexpected request for alias %s. No mock responses.", b.fileName, b.bidderName, bidderRequest.BidderName)
  5325  	}
  5326  
  5327  	return
  5328  }
  5329  
  5330  type capturingRequestBidder struct {
  5331  	req *openrtb2.BidRequest
  5332  }
  5333  
  5334  func (b *capturingRequestBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestOptions bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (seatBid []*entities.PbsOrtbSeatBid, errs []error) {
  5335  	b.req = bidderRequest.BidRequest
  5336  	return []*entities.PbsOrtbSeatBid{{}}, nil
  5337  }
  5338  
  5339  func diffOrtbRequests(t *testing.T, description string, expected *openrtb2.BidRequest, actual *openrtb2.BidRequest) {
  5340  	t.Helper()
  5341  	actualJSON, err := json.Marshal(actual)
  5342  	if err != nil {
  5343  		t.Fatalf("%s failed to marshal actual BidRequest into JSON. %v", description, err)
  5344  	}
  5345  
  5346  	expectedJSON, err := json.Marshal(expected)
  5347  	if err != nil {
  5348  		t.Fatalf("%s failed to marshal expected BidRequest into JSON. %v", description, err)
  5349  	}
  5350  
  5351  	assert.JSONEq(t, string(expectedJSON), string(actualJSON), description)
  5352  }
  5353  
  5354  func diffOrtbResponses(t *testing.T, description string, expected *openrtb2.BidResponse, actual *openrtb2.BidResponse) {
  5355  	t.Helper()
  5356  	// The OpenRTB spec is wonky here. Since "bidresponse.seatbid" is an array, order technically matters to any JSON diff or
  5357  	// deep equals method. However, for all intents and purposes it really *doesn't* matter. ...so this nasty logic makes compares
  5358  	// the seatbids in an order-independent way.
  5359  	//
  5360  	// Note that the same thing is technically true of the "seatbid[i].bid" array... but since none of our exchange code relies on
  5361  	// this implementation detail, I'm cutting a corner and ignoring it here.
  5362  	actualSeats := mapifySeatBids(t, description, actual.SeatBid)
  5363  	expectedSeats := mapifySeatBids(t, description, expected.SeatBid)
  5364  	actualJSON, err := json.Marshal(actualSeats)
  5365  	if err != nil {
  5366  		t.Fatalf("%s failed to marshal actual BidResponse into JSON. %v", description, err)
  5367  	}
  5368  
  5369  	expectedJSON, err := json.Marshal(expectedSeats)
  5370  	if err != nil {
  5371  		t.Fatalf("%s failed to marshal expected BidResponse into JSON. %v", description, err)
  5372  	}
  5373  	assert.JSONEq(t, string(expectedJSON), string(actualJSON), description)
  5374  }
  5375  
  5376  func mapifySeatBids(t *testing.T, context string, seatBids []openrtb2.SeatBid) map[string]*openrtb2.SeatBid {
  5377  	seatMap := make(map[string]*openrtb2.SeatBid, len(seatBids))
  5378  	for i := 0; i < len(seatBids); i++ {
  5379  		seatName := seatBids[i].Seat
  5380  		if _, ok := seatMap[seatName]; ok {
  5381  			t.Fatalf("%s: Contains duplicate Seat: %s", context, seatName)
  5382  		} else {
  5383  			// The sequence of extra bids for same seat from different bidder is not guaranteed as we randomize the list of adapters
  5384  			// This is w.r.t changes at exchange.go#561 (club bids from different bidders for same extra-bid)
  5385  			sort.Slice(seatBids[i].Bid, func(x, y int) bool {
  5386  				return isNewWinningBid(&seatBids[i].Bid[x], &seatBids[i].Bid[y], true)
  5387  			})
  5388  			seatMap[seatName] = &seatBids[i]
  5389  		}
  5390  	}
  5391  
  5392  	return seatMap
  5393  }
  5394  
  5395  func mockHandler(statusCode int, getBody string, postBody string) http.Handler {
  5396  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5397  		w.WriteHeader(statusCode)
  5398  		if r.Method == "GET" {
  5399  			w.Write([]byte(getBody))
  5400  		} else {
  5401  			w.Write([]byte(postBody))
  5402  		}
  5403  	})
  5404  }
  5405  
  5406  func mockSlowHandler(delay time.Duration, statusCode int, body string) http.Handler {
  5407  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  5408  		time.Sleep(delay)
  5409  
  5410  		w.WriteHeader(statusCode)
  5411  		w.Write([]byte(body))
  5412  	})
  5413  }
  5414  
  5415  type wellBehavedCache struct{}
  5416  
  5417  func (c *wellBehavedCache) GetExtCacheData() (scheme string, host string, path string) {
  5418  	return "https", "www.pbcserver.com", "/pbcache/endpoint"
  5419  }
  5420  
  5421  func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) {
  5422  	ids := make([]string, len(values))
  5423  	for i := 0; i < len(values); i++ {
  5424  		ids[i] = strconv.Itoa(i)
  5425  	}
  5426  	return ids, nil
  5427  }
  5428  
  5429  type emptyUsersync struct{}
  5430  
  5431  func (e *emptyUsersync) GetUID(key string) (uid string, exists bool, notExpired bool) {
  5432  	return "", false, false
  5433  }
  5434  
  5435  func (e *emptyUsersync) HasAnyLiveSyncs() bool {
  5436  	return false
  5437  }
  5438  
  5439  type panicingAdapter struct{}
  5440  
  5441  func (panicingAdapter) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) (posb []*entities.PbsOrtbSeatBid, extraInfo extraBidderRespInfo, errs []error) {
  5442  	panic("Panic! Panic! The world is ending!")
  5443  }
  5444  
  5445  func blankAdapterConfig(bidderList []openrtb_ext.BidderName) map[string]config.Adapter {
  5446  	adapters := make(map[string]config.Adapter)
  5447  	for _, b := range bidderList {
  5448  		adapters[strings.ToLower(string(b))] = config.Adapter{}
  5449  	}
  5450  
  5451  	// Audience Network requires additional config to be built.
  5452  	adapters["audiencenetwork"] = config.Adapter{PlatformID: "anyID", AppSecret: "anySecret"}
  5453  
  5454  	return adapters
  5455  }
  5456  
  5457  type nilCategoryFetcher struct{}
  5458  
  5459  func (nilCategoryFetcher) FetchCategories(ctx context.Context, primaryAdServer, publisherId, iabCategory string) (string, error) {
  5460  	return "", nil
  5461  }
  5462  
  5463  // fakeCurrencyRatesHttpClient is a simple http client mock returning a constant response body
  5464  type fakeCurrencyRatesHttpClient struct {
  5465  	responseBody string
  5466  }
  5467  
  5468  func (m *fakeCurrencyRatesHttpClient) Do(req *http.Request) (*http.Response, error) {
  5469  	return &http.Response{
  5470  		Status:     "200 OK",
  5471  		StatusCode: http.StatusOK,
  5472  		Body:       io.NopCloser(strings.NewReader(m.responseBody)),
  5473  	}, nil
  5474  }
  5475  
  5476  type mockBidder struct {
  5477  	mock.Mock
  5478  	lastExtraRequestInfo *adapters.ExtraRequestInfo
  5479  }
  5480  
  5481  func (m *mockBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  5482  	m.lastExtraRequestInfo = reqInfo
  5483  
  5484  	args := m.Called(request, reqInfo)
  5485  	return args.Get(0).([]*adapters.RequestData), args.Get(1).([]error)
  5486  }
  5487  
  5488  func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
  5489  	args := m.Called(internalRequest, externalRequest, response)
  5490  	return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error)
  5491  }
  5492  
  5493  func getInfoFromImp(req *openrtb_ext.RequestWrapper) (json.RawMessage, string, error) {
  5494  	bidRequest := req.BidRequest
  5495  	imp := bidRequest.Imp[0]
  5496  	impID := imp.ID
  5497  
  5498  	var bidderExts map[string]json.RawMessage
  5499  	if err := json.Unmarshal(imp.Ext, &bidderExts); err != nil {
  5500  		return nil, "", err
  5501  	}
  5502  
  5503  	var extPrebid openrtb_ext.ExtImpPrebid
  5504  	if bidderExts[openrtb_ext.PrebidExtKey] != nil {
  5505  		if err := json.Unmarshal(bidderExts[openrtb_ext.PrebidExtKey], &extPrebid); err != nil {
  5506  			return nil, "", err
  5507  		}
  5508  	}
  5509  	return extPrebid.Passthrough, impID, nil
  5510  }
  5511  
  5512  func TestModulesCanBeExecutedForMultipleBiddersSimultaneously(t *testing.T) {
  5513  	noBidServer := func(w http.ResponseWriter, r *http.Request) {
  5514  		w.WriteHeader(204)
  5515  	}
  5516  	server := httptest.NewServer(http.HandlerFunc(noBidServer))
  5517  	defer server.Close()
  5518  
  5519  	bidderImpl := &goodSingleBidder{
  5520  		httpRequest: &adapters.RequestData{
  5521  			Method:  "POST",
  5522  			Uri:     server.URL,
  5523  			Body:    []byte(`{"key":"val"}`),
  5524  			Headers: http.Header{},
  5525  		},
  5526  		bidResponse: &adapters.BidderResponse{},
  5527  	}
  5528  
  5529  	e := new(exchange)
  5530  	e.me = &metricsConf.NilMetricsEngine{}
  5531  	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
  5532  	e.requestSplitter = requestSplitter{
  5533  		me:               e.me,
  5534  		gdprPermsBuilder: e.gdprPermsBuilder,
  5535  	}
  5536  
  5537  	bidRequest := &openrtb2.BidRequest{
  5538  		ID: "some-request-id",
  5539  		Imp: []openrtb2.Imp{{
  5540  			ID:     "some-impression-id",
  5541  			Banner: &openrtb2.Banner{Format: []openrtb2.Format{{W: 300, H: 250}, {W: 300, H: 600}}},
  5542  			Ext: json.RawMessage(
  5543  				`{"prebid":{"bidder":{"telaria": {"placementId": 1}, "appnexus": {"placementid": 2}, "33across": {"placementId": 3}, "aax": {"placementid": 4}}}}`,
  5544  			),
  5545  		}},
  5546  		Site:   &openrtb2.Site{Page: "prebid.org", Ext: json.RawMessage(`{"amp":0}`)},
  5547  		Device: &openrtb2.Device{UA: "curl/7.54.0", IP: "::1"},
  5548  		AT:     1,
  5549  		TMax:   500,
  5550  	}
  5551  
  5552  	exec := hookexecution.NewHookExecutor(TestApplyHookMutationsBuilder{}, "/openrtb2/auction", &metricsConfig.NilMetricsEngine{})
  5553  
  5554  	auctionRequest := &AuctionRequest{
  5555  		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidRequest},
  5556  		Account:           config.Account{DebugAllow: true},
  5557  		UserSyncs:         &emptyUsersync{},
  5558  		StartTime:         time.Now(),
  5559  		HookExecutor:      exec,
  5560  		TCF2Config:        gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  5561  	}
  5562  
  5563  	e.adapterMap = map[openrtb_ext.BidderName]AdaptedBidder{
  5564  		openrtb_ext.BidderAppnexus: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, ""),
  5565  		openrtb_ext.BidderTelaria:  AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, &config.DebugInfo{}, ""),
  5566  		openrtb_ext.Bidder33Across: AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.Bidder33Across, &config.DebugInfo{}, ""),
  5567  		openrtb_ext.BidderAax:      AdaptBidder(bidderImpl, server.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAax, &config.DebugInfo{}, ""),
  5568  	}
  5569  	// Run test
  5570  	_, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
  5571  	// Assert no HoldAuction err
  5572  	assert.NoErrorf(t, err, "ex.HoldAuction returned an err")
  5573  	assert.False(t, auctionRequest.BidderResponseStartTime.IsZero())
  5574  
  5575  	// check stage outcomes
  5576  	assert.Equal(t, len(exec.GetOutcomes()), len(e.adapterMap), "stage outcomes append operation failed")
  5577  	//check that all modules were applied and logged
  5578  	for _, sto := range exec.GetOutcomes() {
  5579  		assert.Equal(t, 2, len(sto.Groups), "not all groups were executed")
  5580  		for _, group := range sto.Groups {
  5581  			assert.Equal(t, 5, len(group.InvocationResults), "not all module hooks were applied")
  5582  			for _, r := range group.InvocationResults {
  5583  				assert.Equal(t, "success", string(r.Status), fmt.Sprintf("Module %s hook %s completed unsuccessfully", r.HookID.ModuleCode, r.HookID.HookImplCode))
  5584  			}
  5585  		}
  5586  	}
  5587  }
  5588  
  5589  type TestApplyHookMutationsBuilder struct {
  5590  	hooks.EmptyPlanBuilder
  5591  }
  5592  
  5593  func (e TestApplyHookMutationsBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] {
  5594  	return hooks.Plan[hookstage.BidderRequest]{
  5595  		hooks.Group[hookstage.BidderRequest]{
  5596  			Timeout: 100 * time.Millisecond,
  5597  			Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{
  5598  				{Module: "foobar1", Code: "foo1", Hook: mockUpdateBidRequestHook{}},
  5599  				{Module: "foobar2", Code: "foo2", Hook: mockUpdateBidRequestHook{}},
  5600  				{Module: "foobar3", Code: "foo3", Hook: mockUpdateBidRequestHook{}},
  5601  				{Module: "foobar4", Code: "foo4", Hook: mockUpdateBidRequestHook{}},
  5602  				{Module: "foobar5", Code: "foo5", Hook: mockUpdateBidRequestHook{}},
  5603  			},
  5604  		},
  5605  		hooks.Group[hookstage.BidderRequest]{
  5606  			Timeout: 100 * time.Millisecond,
  5607  			Hooks: []hooks.HookWrapper[hookstage.BidderRequest]{
  5608  				{Module: "foobar6", Code: "foo6", Hook: mockUpdateBidRequestHook{}},
  5609  				{Module: "foobar7", Code: "foo7", Hook: mockUpdateBidRequestHook{}},
  5610  				{Module: "foobar8", Code: "foo8", Hook: mockUpdateBidRequestHook{}},
  5611  				{Module: "foobar9", Code: "foo9", Hook: mockUpdateBidRequestHook{}},
  5612  				{Module: "foobar10", Code: "foo10", Hook: mockUpdateBidRequestHook{}},
  5613  			},
  5614  		},
  5615  	}
  5616  }
  5617  
  5618  type mockUpdateBidRequestHook struct{}
  5619  
  5620  func (e mockUpdateBidRequestHook) HandleBidderRequestHook(_ context.Context, mctx hookstage.ModuleInvocationContext, _ hookstage.BidderRequestPayload) (hookstage.HookResult[hookstage.BidderRequestPayload], error) {
  5621  	time.Sleep(50 * time.Millisecond)
  5622  	c := hookstage.ChangeSet[hookstage.BidderRequestPayload]{}
  5623  	c.AddMutation(
  5624  		func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) {
  5625  			payload.BidRequest.Site.Name = "test"
  5626  			return payload, nil
  5627  		}, hookstage.MutationUpdate, "bidRequest", "site.name",
  5628  	).AddMutation(
  5629  		func(payload hookstage.BidderRequestPayload) (hookstage.BidderRequestPayload, error) {
  5630  			payload.BidRequest.Site.Domain = "test.com"
  5631  			return payload, nil
  5632  		}, hookstage.MutationUpdate, "bidRequest", "site.domain",
  5633  	)
  5634  
  5635  	mctx.ModuleContext = map[string]interface{}{"some-ctx": "some-ctx"}
  5636  
  5637  	return hookstage.HookResult[hookstage.BidderRequestPayload]{ChangeSet: c, ModuleContext: mctx.ModuleContext}, nil
  5638  }
  5639  
  5640  func TestNilAuctionRequest(t *testing.T) {
  5641  	ex := &exchange{}
  5642  	response, err := ex.HoldAuction(context.Background(), nil, &DebugLog{})
  5643  	assert.Nil(t, response)
  5644  	assert.Nil(t, err)
  5645  }
  5646  
  5647  func TestSelectNewDuration(t *testing.T) {
  5648  	type testInput struct {
  5649  		dur       int
  5650  		durRanges []int
  5651  	}
  5652  	type testOutput struct {
  5653  		dur int
  5654  		err error
  5655  	}
  5656  	testCases := []struct {
  5657  		desc     string
  5658  		in       testInput
  5659  		expected testOutput
  5660  	}{
  5661  		{
  5662  			desc: "nil duration range array, don't expect error",
  5663  			in: testInput{
  5664  				dur:       1,
  5665  				durRanges: nil,
  5666  			},
  5667  			expected: testOutput{1, nil},
  5668  		},
  5669  		{
  5670  			desc: "empty duration range array, don't expect error",
  5671  			in: testInput{
  5672  				dur:       1,
  5673  				durRanges: []int{},
  5674  			},
  5675  			expected: testOutput{1, nil},
  5676  		},
  5677  		{
  5678  			desc: "all duration range array elements less than duration, expect error",
  5679  			in: testInput{
  5680  				dur:       5,
  5681  				durRanges: []int{-1, 0, 1, 2, 3},
  5682  			},
  5683  			expected: testOutput{5, errors.New("bid duration exceeds maximum allowed")},
  5684  		},
  5685  		{
  5686  			desc: "all duration range array elements greater than duration, expect smallest element in durRanges and nil error",
  5687  			in: testInput{
  5688  				dur:       5,
  5689  				durRanges: []int{9, math.MaxInt32, 8},
  5690  			},
  5691  			expected: testOutput{8, nil},
  5692  		},
  5693  		{
  5694  			desc: "some array elements greater than duration, expect the value greater than dur that is closest in value.",
  5695  			in: testInput{
  5696  				dur:       5,
  5697  				durRanges: []int{math.MaxInt32, -3, 7, 2},
  5698  			},
  5699  			expected: testOutput{7, nil},
  5700  		},
  5701  		{
  5702  			desc: "an entry in the duration range array is equal to duration, expect its value in return.",
  5703  			in: testInput{
  5704  				dur:       5,
  5705  				durRanges: []int{-3, math.MaxInt32, 5, 7},
  5706  			},
  5707  			expected: testOutput{5, nil},
  5708  		},
  5709  	}
  5710  	for _, tc := range testCases {
  5711  		newDur, err := findDurationRange(tc.in.dur, tc.in.durRanges)
  5712  
  5713  		assert.Equal(t, tc.expected.dur, newDur, tc.desc)
  5714  		assert.Equal(t, tc.expected.err, err, tc.desc)
  5715  	}
  5716  }
  5717  
  5718  func TestSetSeatNonBid(t *testing.T) {
  5719  	type args struct {
  5720  		bidResponseExt *openrtb_ext.ExtBidResponse
  5721  		seatNonBids    nonBids
  5722  	}
  5723  	tests := []struct {
  5724  		name string
  5725  		args args
  5726  		want *openrtb_ext.ExtBidResponse
  5727  	}{
  5728  		{
  5729  			name: "empty-seatNonBidsMap",
  5730  			args: args{seatNonBids: nonBids{}, bidResponseExt: nil},
  5731  			want: nil,
  5732  		},
  5733  		{
  5734  			name: "nil-bidResponseExt",
  5735  			args: args{seatNonBids: nonBids{seatNonBidsMap: map[string][]openrtb_ext.NonBid{"key": nil}}, bidResponseExt: nil},
  5736  			want: &openrtb_ext.ExtBidResponse{
  5737  				Prebid: &openrtb_ext.ExtResponsePrebid{
  5738  					SeatNonBid: []openrtb_ext.SeatNonBid{{
  5739  						Seat: "key",
  5740  					}},
  5741  				},
  5742  			},
  5743  		},
  5744  	}
  5745  	for _, tt := range tests {
  5746  		t.Run(tt.name, func(t *testing.T) {
  5747  			if got := setSeatNonBid(tt.args.bidResponseExt, tt.args.seatNonBids); !reflect.DeepEqual(got, tt.want) {
  5748  				t.Errorf("setSeatNonBid() = %v, want %v", got, tt.want)
  5749  			}
  5750  		})
  5751  	}
  5752  }