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

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