github.com/prebid/prebid-server/v2@v2.18.0/endpoints/openrtb2/auction_test.go (about)

     1  package openrtb2
     2  
     3  import (
     4  	"bytes"
     5  	"compress/gzip"
     6  	"context"
     7  	"encoding/json"
     8  	"errors"
     9  	"io"
    10  	"io/fs"
    11  	"net"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"os"
    15  	"path/filepath"
    16  	"sort"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/buger/jsonparser"
    22  	jsoniter "github.com/json-iterator/go"
    23  	"github.com/julienschmidt/httprouter"
    24  	"github.com/prebid/openrtb/v20/native1"
    25  	nativeRequests "github.com/prebid/openrtb/v20/native1/request"
    26  	"github.com/prebid/openrtb/v20/openrtb2"
    27  	"github.com/prebid/prebid-server/v2/analytics"
    28  	analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build"
    29  	"github.com/prebid/prebid-server/v2/config"
    30  	"github.com/prebid/prebid-server/v2/errortypes"
    31  	"github.com/prebid/prebid-server/v2/exchange"
    32  	"github.com/prebid/prebid-server/v2/hooks"
    33  	"github.com/prebid/prebid-server/v2/hooks/hookexecution"
    34  	"github.com/prebid/prebid-server/v2/hooks/hookstage"
    35  	"github.com/prebid/prebid-server/v2/metrics"
    36  	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
    37  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    38  	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
    39  	"github.com/prebid/prebid-server/v2/stored_responses"
    40  	"github.com/prebid/prebid-server/v2/util/iputil"
    41  	"github.com/prebid/prebid-server/v2/util/jsonutil"
    42  	"github.com/prebid/prebid-server/v2/util/ptrutil"
    43  	"github.com/stretchr/testify/assert"
    44  )
    45  
    46  const jsonFileExtension string = ".json"
    47  
    48  func TestMain(m *testing.M) {
    49  	jsoniter.RegisterExtension(&jsonutil.RawMessageExtension{})
    50  	os.Exit(m.Run())
    51  }
    52  
    53  func TestJsonSampleRequests(t *testing.T) {
    54  	testSuites := []struct {
    55  		description          string
    56  		sampleRequestsSubDir string
    57  	}{
    58  		{
    59  			"Assert 200s on all bidRequests from exemplary folder",
    60  			"valid-whole/exemplary",
    61  		},
    62  		{
    63  			"Asserts we return 200s on well-formed Native requests.",
    64  			"valid-native",
    65  		},
    66  		{
    67  			"Asserts we return 400s on requests that are not supposed to pass validation",
    68  			"invalid-whole",
    69  		},
    70  		{
    71  			"Asserts we return 400s on requests with Native requests that don't pass validation",
    72  			"invalid-native",
    73  		},
    74  		{
    75  			"Makes sure we handle aliased bidders properly",
    76  			"aliased",
    77  		},
    78  		{
    79  			"Makes sure we handle alternate bidder codes properly",
    80  			"alternate-bidder-code",
    81  		},
    82  		{
    83  			"Asserts we return 500s on requests referencing accounts with malformed configs.",
    84  			"account-malformed",
    85  		},
    86  		{
    87  			"Asserts we return 503s on requests with blacklisted accounts and apps.",
    88  			"blacklisted",
    89  		},
    90  		{
    91  			"Assert that requests that come with no user id nor app id return error if the `AccountRequired` field in the `config.Configuration` structure is set to true",
    92  			"account-required/no-account",
    93  		},
    94  		{
    95  			"Assert requests that come with a valid user id or app id when account is required",
    96  			"account-required/with-account",
    97  		},
    98  		{
    99  			"Tests diagnostic messages for invalid stored requests",
   100  			"invalid-stored",
   101  		},
   102  		{
   103  			"Make sure requests with disabled bidders will fail",
   104  			"disabled/bad",
   105  		},
   106  		{
   107  			"There are both disabled and non-disabled bidders, we expect a 200",
   108  			"disabled/good",
   109  		},
   110  		{
   111  			"Assert we correctly use the server conversion rates when needed",
   112  			"currency-conversion/server-rates/valid",
   113  		},
   114  		{
   115  			"Assert we correctly throw an error when no conversion rate was found in the server conversions map",
   116  			"currency-conversion/server-rates/errors",
   117  		},
   118  		{
   119  			"Assert we correctly use request-defined custom currency rates when present in root.ext",
   120  			"currency-conversion/custom-rates/valid",
   121  		},
   122  		{
   123  			"Assert we correctly validate request-defined custom currency rates when present in root.ext",
   124  			"currency-conversion/custom-rates/errors",
   125  		},
   126  		{
   127  			"Assert request with ad server targeting is processing correctly",
   128  			"adservertargeting",
   129  		},
   130  		{
   131  			"Assert request with bid adjustments defined is processing correctly",
   132  			"bidadjustments",
   133  		},
   134  	}
   135  
   136  	for _, tc := range testSuites {
   137  		err := filepath.WalkDir(filepath.Join("sample-requests", tc.sampleRequestsSubDir), func(path string, info fs.DirEntry, err error) error {
   138  			// According to documentation, needed to avoid panics
   139  			if err != nil {
   140  				return err
   141  			}
   142  
   143  			// Test suite will traverse the directory tree recursively and will only consider files with `json` extension
   144  			if !info.IsDir() && filepath.Ext(info.Name()) == jsonFileExtension {
   145  				t.Run(tc.description, func(t *testing.T) {
   146  					runJsonBasedTest(t, path, tc.description)
   147  				})
   148  			}
   149  
   150  			return nil
   151  		})
   152  		assert.NoError(t, err, "Test case %s. Error reading files from directory %s \n", tc.description, tc.sampleRequestsSubDir)
   153  	}
   154  }
   155  
   156  func runJsonBasedTest(t *testing.T, filename, desc string) {
   157  	t.Helper()
   158  
   159  	fileData, err := os.ReadFile(filename)
   160  	if !assert.NoError(t, err, "Test case %s. Error reading file %s \n", desc, filename) {
   161  		return
   162  	}
   163  
   164  	// Retrieve test case input and expected output from JSON file
   165  	test, err := parseTestData(fileData, filename)
   166  	if !assert.NoError(t, err) {
   167  		return
   168  	}
   169  
   170  	// Build endpoint for testing. If no error, run test case
   171  	cfg := &config.Configuration{MaxRequestSize: maxSize}
   172  	if test.Config != nil {
   173  		cfg.BlacklistedApps = test.Config.BlacklistedApps
   174  		cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap()
   175  		cfg.AccountRequired = test.Config.AccountRequired
   176  	}
   177  	cfg.MarshalAccountDefaults()
   178  	test.endpointType = OPENRTB_ENDPOINT
   179  
   180  	auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
   181  	if assert.NoError(t, err) {
   182  		assert.NotPanics(t, func() { runEndToEndTest(t, auctionEndpointHandler, test, fileData, filename) }, filename)
   183  	}
   184  
   185  	// Close servers regardless if the test case was run or not
   186  	for _, mockBidServer := range mockBidServers {
   187  		mockBidServer.Close()
   188  	}
   189  	mockCurrencyRatesServer.Close()
   190  }
   191  
   192  func runEndToEndTest(t *testing.T, auctionEndpointHandler httprouter.Handle, test testCase, fileData []byte, testFile string) {
   193  	t.Helper()
   194  
   195  	// Hit the auction endpoint with the test case configuration and mockBidRequest
   196  	request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(test.BidRequest))
   197  	recorder := httptest.NewRecorder()
   198  	auctionEndpointHandler(recorder, request, nil)
   199  
   200  	// Assertions
   201  	actualCode := recorder.Code
   202  	actualJsonBidResponse := recorder.Body.String()
   203  	assert.Equal(t, test.ExpectedReturnCode, actualCode, "Test failed. Filename: %s \n", testFile)
   204  
   205  	// Either assert bid response or expected error
   206  	if len(test.ExpectedErrorMessage) > 0 {
   207  		assert.Contains(t, actualJsonBidResponse, test.ExpectedErrorMessage, "Actual: %s \nExpected: %s. Filename: %s \n", actualJsonBidResponse, test.ExpectedErrorMessage, testFile)
   208  	}
   209  
   210  	if len(test.ExpectedBidResponse) > 0 {
   211  		var expectedBidResponse openrtb2.BidResponse
   212  		var actualBidResponse openrtb2.BidResponse
   213  		var err error
   214  
   215  		err = jsonutil.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse)
   216  		if assert.NoError(t, err, "Could not unmarshal expected bidResponse taken from test file.\n Test file: %s\n Error:%s\n", testFile, err) {
   217  			err = jsonutil.UnmarshalValid([]byte(actualJsonBidResponse), &actualBidResponse)
   218  			if assert.NoError(t, err, "Could not unmarshal actual bidResponse from auction.\n Test file: %s\n Error:%s\n actualJsonBidResponse: %s", testFile, err, actualJsonBidResponse) {
   219  				assertBidResponseEqual(t, testFile, expectedBidResponse, actualBidResponse)
   220  			}
   221  		}
   222  	}
   223  }
   224  
   225  func compareWarnings(t *testing.T, expectedBidResponseExt, actualBidResponseExt []byte, warnPath string) {
   226  	expectedWarnings, _, _, err := jsonparser.Get(expectedBidResponseExt, warnPath)
   227  	if err != nil && err != jsonparser.KeyPathNotFoundError {
   228  		assert.Fail(t, "error getting data from response extension")
   229  	}
   230  	if len(expectedWarnings) > 0 {
   231  		actualWarnings, _, _, err := jsonparser.Get(actualBidResponseExt, warnPath)
   232  		if err != nil && err != jsonparser.KeyPathNotFoundError {
   233  			assert.Fail(t, "error getting data from response extension")
   234  		}
   235  
   236  		var expectedWarn []openrtb_ext.ExtBidderMessage
   237  		err = jsonutil.UnmarshalValid(expectedWarnings, &expectedWarn)
   238  		if err != nil {
   239  			assert.Fail(t, "error unmarshalling expected warnings data from response extension")
   240  		}
   241  
   242  		var actualWarn []openrtb_ext.ExtBidderMessage
   243  		err = jsonutil.UnmarshalValid(actualWarnings, &actualWarn)
   244  		if err != nil {
   245  			assert.Fail(t, "error unmarshalling actual warnings data from response extension")
   246  		}
   247  
   248  		// warnings from different bidders may be returned in different order.
   249  		assert.Equal(t, len(expectedWarn), len(actualWarn), "incorrect warnings number")
   250  		for i, expWarn := range expectedWarn {
   251  			actualWarning := actualWarn[i]
   252  			assert.Contains(t, actualWarning.Message, expWarn.Message, "incorrect warning")
   253  		}
   254  	}
   255  }
   256  
   257  // Once unmarshalled, bidResponse objects can't simply be compared with an `assert.Equalf()` call
   258  // because tests fail if the elements inside the `bidResponse.SeatBid` and `bidResponse.SeatBid.Bid`
   259  // arrays, if any, are not listed in the exact same order in the actual version and in the expected version.
   260  func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse openrtb2.BidResponse, actualBidResponse openrtb2.BidResponse) {
   261  
   262  	//Assert non-array BidResponse fields
   263  	assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile)
   264  	assert.Equalf(t, expectedBidResponse.Cur, actualBidResponse.Cur, "BidResponse.Cur doesn't match expected. Test: %s\n", testFile)
   265  
   266  	if len(expectedBidResponse.Ext) > 0 {
   267  		compareWarnings(t, expectedBidResponse.Ext, actualBidResponse.Ext, "warnings.general")
   268  	}
   269  
   270  	//Assert []SeatBid and their Bid elements independently of their order
   271  	assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid is expected to contain %d elements but contains %d. Test: %s\n", len(expectedBidResponse.SeatBid), len(actualBidResponse.SeatBid), testFile)
   272  
   273  	//Given that bidResponses have the same length, compare them in an order-independent way using maps
   274  	var actualSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0)
   275  	for _, seatBid := range actualBidResponse.SeatBid {
   276  		actualSeatBidsMap[seatBid.Seat] = seatBid
   277  	}
   278  
   279  	var expectedSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0)
   280  	for _, seatBid := range expectedBidResponse.SeatBid {
   281  		expectedSeatBidsMap[seatBid.Seat] = seatBid
   282  	}
   283  
   284  	for bidderName, expectedSeatBid := range expectedSeatBidsMap {
   285  		if !assert.Contains(t, actualSeatBidsMap, bidderName, "BidResponse.SeatBid[%s] was not found as expected. Test: %s\n", bidderName, testFile) {
   286  			continue
   287  		}
   288  
   289  		//Assert non-array SeatBid fields
   290  		assert.Equalf(t, expectedSeatBid.Seat, actualSeatBidsMap[bidderName].Seat, "actualSeatBidsMap[%s].Seat doesn't match expected. Test: %s\n", bidderName, testFile)
   291  		assert.Equalf(t, expectedSeatBid.Group, actualSeatBidsMap[bidderName].Group, "actualSeatBidsMap[%s].Group doesn't match expected. Test: %s\n", bidderName, testFile)
   292  		assert.Equalf(t, expectedSeatBid.Ext, actualSeatBidsMap[bidderName].Ext, "actualSeatBidsMap[%s].Ext doesn't match expected. Test: %s\n", bidderName, testFile)
   293  
   294  		// Assert Bid arrays
   295  		assert.Len(t, actualSeatBidsMap[bidderName].Bid, len(expectedSeatBid.Bid), "BidResponse.SeatBid[].Bid array is expected to contain %d elements but has %d. Test: %s\n", len(expectedSeatBid.Bid), len(actualSeatBidsMap[bidderName].Bid), testFile)
   296  		// Given that actualSeatBidsMap[bidderName].Bid and expectedSeatBid.Bid have the same length, compare them in an order-independent way using maps
   297  		var expectedBidMap map[string]openrtb2.Bid = make(map[string]openrtb2.Bid, 0)
   298  		for _, bid := range expectedSeatBid.Bid {
   299  			expectedBidMap[bid.ID] = bid
   300  		}
   301  
   302  		var actualBidMap map[string]openrtb2.Bid = make(map[string]openrtb2.Bid, 0)
   303  		for _, bid := range actualSeatBidsMap[bidderName].Bid {
   304  			actualBidMap[bid.ID] = bid
   305  		}
   306  
   307  		for bidID, expectedBid := range expectedBidMap {
   308  			if !assert.Contains(t, actualBidMap, bidID, "BidResponse.SeatBid[%s].Bid[%s].ID doesn't match expected. Test: %s\n", bidderName, bidID, testFile) {
   309  				continue
   310  			}
   311  			assert.Equalf(t, expectedBid.ImpID, actualBidMap[bidID].ImpID, "BidResponse.SeatBid[%s].Bid[%s].ImpID doesn't match expected. Test: %s\n", bidderName, bidID, testFile)
   312  			assert.Equalf(t, expectedBid.Price, actualBidMap[bidID].Price, "BidResponse.SeatBid[%s].Bid[%s].Price doesn't match expected. Test: %s\n", bidderName, bidID, testFile)
   313  
   314  			if len(expectedBid.Ext) > 0 {
   315  				assert.JSONEq(t, string(expectedBid.Ext), string(actualBidMap[bidID].Ext), "Incorrect bid extension")
   316  			}
   317  		}
   318  	}
   319  }
   320  
   321  func TestBidRequestAssert(t *testing.T) {
   322  	appnexusB1 := openrtb2.Bid{ID: "appnexus-bid-1", Price: 5.00}
   323  	appnexusB2 := openrtb2.Bid{ID: "appnexus-bid-2", Price: 7.00}
   324  	rubiconB1 := openrtb2.Bid{ID: "rubicon-bid-1", Price: 1.50}
   325  	rubiconB2 := openrtb2.Bid{ID: "rubicon-bid-2", Price: 4.00}
   326  
   327  	sampleSeatBids := []openrtb2.SeatBid{
   328  		{
   329  			Seat: "appnexus-bids",
   330  			Bid:  []openrtb2.Bid{appnexusB1, appnexusB2},
   331  		},
   332  		{
   333  			Seat: "rubicon-bids",
   334  			Bid:  []openrtb2.Bid{rubiconB1, rubiconB2},
   335  		},
   336  	}
   337  
   338  	testSuites := []struct {
   339  		description         string
   340  		expectedBidResponse openrtb2.BidResponse
   341  		actualBidResponse   openrtb2.BidResponse
   342  	}{
   343  		{
   344  			"identical SeatBids, exact same SeatBid and Bid arrays order",
   345  			openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids},
   346  			openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids},
   347  		},
   348  		{
   349  			"identical SeatBids but Seatbid array elements come in different order",
   350  			openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids},
   351  			openrtb2.BidResponse{ID: "anId", BidID: "bidId",
   352  				SeatBid: []openrtb2.SeatBid{
   353  					{
   354  						Seat: "rubicon-bids",
   355  						Bid:  []openrtb2.Bid{rubiconB1, rubiconB2},
   356  					},
   357  					{
   358  						Seat: "appnexus-bids",
   359  						Bid:  []openrtb2.Bid{appnexusB1, appnexusB2},
   360  					},
   361  				},
   362  			},
   363  		},
   364  		{
   365  			"SeatBids seem to be identical except for the different order of Bid array elements in one of them",
   366  			openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids},
   367  			openrtb2.BidResponse{ID: "anId", BidID: "bidId",
   368  				SeatBid: []openrtb2.SeatBid{
   369  					{
   370  						Seat: "appnexus-bids",
   371  						Bid:  []openrtb2.Bid{appnexusB2, appnexusB1},
   372  					},
   373  					{
   374  						Seat: "rubicon-bids",
   375  						Bid:  []openrtb2.Bid{rubiconB1, rubiconB2},
   376  					},
   377  				},
   378  			},
   379  		},
   380  		{
   381  			"Both SeatBid elements and bid elements come in different order",
   382  			openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids},
   383  			openrtb2.BidResponse{ID: "anId", BidID: "bidId",
   384  				SeatBid: []openrtb2.SeatBid{
   385  					{
   386  						Seat: "rubicon-bids",
   387  						Bid:  []openrtb2.Bid{rubiconB2, rubiconB1},
   388  					},
   389  					{
   390  						Seat: "appnexus-bids",
   391  						Bid:  []openrtb2.Bid{appnexusB2, appnexusB1},
   392  					},
   393  				},
   394  			},
   395  		},
   396  	}
   397  
   398  	for _, test := range testSuites {
   399  		assertBidResponseEqual(t, test.description, test.expectedBidResponse, test.actualBidResponse)
   400  	}
   401  }
   402  
   403  // TestExplicitUserId makes sure that the cookie's ID doesn't override an explicit value sent in the request.
   404  func TestExplicitUserId(t *testing.T) {
   405  	cookieName := "userid"
   406  	mockId := "12345"
   407  	cfg := &config.Configuration{
   408  		MaxRequestSize: maxSize,
   409  		HostCookie: config.HostCookie{
   410  			CookieName: cookieName,
   411  		},
   412  	}
   413  	ex := &mockExchange{}
   414  
   415  	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(`{
   416  "id": "some-request-id",
   417  		"site": {
   418  			"page": "test.somepage.com"
   419  		},
   420  		"user": {
   421  			"id": "explicit"
   422  		},
   423  		"imp": [
   424  			{
   425  				"id": "my-imp-id",
   426  				"banner": {
   427  					"format": [
   428  						{
   429  							"w": 300,
   430  							"h": 600
   431  						}
   432  					]
   433  				},
   434  				"pmp": {
   435  					"deals": [
   436  						{
   437  							"id": "some-deal-id"
   438  						}
   439  					]
   440  				},
   441  				"ext": {
   442  					"appnexus": {
   443  						"placementId": 12883451
   444  					}
   445  				}
   446  			}
   447  		]
   448  	}`))
   449  	request.AddCookie(&http.Cookie{
   450  		Name:  cookieName,
   451  		Value: mockId,
   452  	})
   453  
   454  	endpoint, _ := NewEndpoint(
   455  		fakeUUIDGenerator{},
   456  		ex,
   457  		mockBidderParamValidator{},
   458  		empty_fetcher.EmptyFetcher{},
   459  		empty_fetcher.EmptyFetcher{},
   460  		cfg,
   461  		&metricsConfig.NilMetricsEngine{},
   462  		analyticsBuild.New(&config.Analytics{}),
   463  		map[string]string{},
   464  		[]byte{},
   465  		openrtb_ext.BuildBidderMap(),
   466  		empty_fetcher.EmptyFetcher{},
   467  		hooks.EmptyPlanBuilder{},
   468  		nil,
   469  	)
   470  
   471  	endpoint(httptest.NewRecorder(), request, nil)
   472  
   473  	if ex.lastRequest == nil {
   474  		t.Fatalf("The request never made it into the Exchange.")
   475  	}
   476  
   477  	if ex.lastRequest.User == nil {
   478  		t.Fatalf("The exchange should have received a request with a non-nil user.")
   479  	}
   480  
   481  	if ex.lastRequest.User.ID != "explicit" {
   482  		t.Errorf("Bad User ID. Expected explicit, got %s", ex.lastRequest.User.ID)
   483  	}
   484  }
   485  
   486  // TestBadAliasRequests() reuses two requests that would fail anyway.  Here, we
   487  // take advantage of our knowledge that processStoredRequests() in auction.go
   488  // processes aliases before it processes stored imps.  Changing that order
   489  // would probably cause this test to fail.
   490  func TestBadAliasRequests(t *testing.T) {
   491  	doBadAliasRequest(t, "sample-requests/invalid-stored/bad_stored_imp.json", "Invalid request: Invalid JSON Document\n")
   492  	doBadAliasRequest(t, "sample-requests/invalid-stored/bad_incoming_imp.json", "Invalid request: Invalid JSON Document\n")
   493  }
   494  
   495  // doBadAliasRequest() is a customized variation of doRequest(), above
   496  func doBadAliasRequest(t *testing.T, filename string, expectMsg string) {
   497  	t.Helper()
   498  	fileData := readFile(t, filename)
   499  	testBidRequest, _, _, err := jsonparser.Get(fileData, "mockBidRequest")
   500  	assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err)
   501  
   502  	// aliasJSON lacks a comma after the "appnexus" entry so is bad JSON
   503  	aliasJSON := []byte(`{"ext":{"prebid":{"aliases": {"test1": "appnexus" "test2": "rubicon", "test3": "openx"}}}}`)
   504  
   505  	bidderInfos := getBidderInfos(nil, openrtb_ext.CoreBidderNames())
   506  
   507  	bidderMap := exchange.GetActiveBidders(bidderInfos)
   508  	disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos)
   509  
   510  	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
   511  	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
   512  	endpoint, _ := NewEndpoint(
   513  		fakeUUIDGenerator{},
   514  		&nobidExchange{},
   515  		mockBidderParamValidator{},
   516  		&mockStoredReqFetcher{},
   517  		empty_fetcher.EmptyFetcher{},
   518  		&config.Configuration{MaxRequestSize: maxSize},
   519  		&metricsConfig.NilMetricsEngine{},
   520  		analyticsBuild.New(&config.Analytics{}),
   521  		disabledBidders,
   522  		aliasJSON,
   523  		bidderMap,
   524  		empty_fetcher.EmptyFetcher{},
   525  		hooks.EmptyPlanBuilder{},
   526  		nil,
   527  	)
   528  
   529  	request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(testBidRequest))
   530  	recorder := httptest.NewRecorder()
   531  	endpoint(recorder, request, nil)
   532  
   533  	assertResponseCode(t, filename, recorder.Code, http.StatusBadRequest, recorder.Body.String())
   534  	assert.Equal(t, string(expectMsg), recorder.Body.String(), "file %s had bad response body", filename)
   535  
   536  }
   537  
   538  func newParamsValidator(t *testing.T) openrtb_ext.BidderParamValidator {
   539  	paramValidator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
   540  	if err != nil {
   541  		t.Fatalf("Error creating the param validator: %v", err)
   542  	}
   543  	return paramValidator
   544  }
   545  
   546  func assertResponseCode(t *testing.T, filename string, actual int, expected int, msg string) {
   547  	t.Helper()
   548  	if actual != expected {
   549  		t.Errorf("Expected a %d response from %v. Got %d: %s", expected, filename, actual, msg)
   550  	}
   551  }
   552  
   553  func getRequestPayload(t *testing.T, example []byte) []byte {
   554  	t.Helper()
   555  	if value, _, _, err := jsonparser.Get(example, "requestPayload"); err != nil {
   556  		t.Fatalf("Error parsing root.requestPayload from request: %v.", err)
   557  	} else {
   558  		return value
   559  	}
   560  	return nil
   561  }
   562  
   563  // TestNilExchange makes sure we fail when given nil for the Exchange.
   564  func TestNilExchange(t *testing.T) {
   565  	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
   566  	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
   567  	_, err := NewEndpoint(
   568  		fakeUUIDGenerator{},
   569  		nil,
   570  		mockBidderParamValidator{},
   571  		empty_fetcher.EmptyFetcher{},
   572  		empty_fetcher.EmptyFetcher{},
   573  		&config.Configuration{MaxRequestSize: maxSize},
   574  		&metricsConfig.NilMetricsEngine{},
   575  		analyticsBuild.New(&config.Analytics{}), map[string]string{},
   576  		[]byte{},
   577  		openrtb_ext.BuildBidderMap(),
   578  		empty_fetcher.EmptyFetcher{},
   579  		hooks.EmptyPlanBuilder{},
   580  		nil,
   581  	)
   582  
   583  	if err == nil {
   584  		t.Errorf("NewEndpoint should return an error when given a nil Exchange.")
   585  	}
   586  }
   587  
   588  // TestNilValidator makes sure we fail when given nil for the BidderParamValidator.
   589  func TestNilValidator(t *testing.T) {
   590  	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
   591  	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
   592  	_, err := NewEndpoint(
   593  		fakeUUIDGenerator{},
   594  		&nobidExchange{},
   595  		nil,
   596  		empty_fetcher.EmptyFetcher{},
   597  		empty_fetcher.EmptyFetcher{},
   598  		&config.Configuration{MaxRequestSize: maxSize},
   599  		&metricsConfig.NilMetricsEngine{},
   600  		analyticsBuild.New(&config.Analytics{}),
   601  		map[string]string{},
   602  		[]byte{},
   603  		openrtb_ext.BuildBidderMap(),
   604  		empty_fetcher.EmptyFetcher{},
   605  		hooks.EmptyPlanBuilder{},
   606  		nil,
   607  	)
   608  
   609  	if err == nil {
   610  		t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.")
   611  	}
   612  }
   613  
   614  // TestExchangeError makes sure we return a 500 if the exchange auction fails.
   615  func TestExchangeError(t *testing.T) {
   616  	// NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it.
   617  	// As a side effect this gives us some coverage of the go_metrics piece of the metrics engine.
   618  	endpoint, _ := NewEndpoint(
   619  		fakeUUIDGenerator{},
   620  		&brokenExchange{},
   621  		mockBidderParamValidator{},
   622  		empty_fetcher.EmptyFetcher{},
   623  		empty_fetcher.EmptyFetcher{},
   624  		&config.Configuration{MaxRequestSize: maxSize},
   625  		&metricsConfig.NilMetricsEngine{},
   626  		analyticsBuild.New(&config.Analytics{}),
   627  		map[string]string{},
   628  		[]byte{},
   629  		openrtb_ext.BuildBidderMap(),
   630  		empty_fetcher.EmptyFetcher{},
   631  		hooks.EmptyPlanBuilder{},
   632  		nil,
   633  	)
   634  
   635  	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
   636  	recorder := httptest.NewRecorder()
   637  	endpoint(recorder, request, nil)
   638  
   639  	if recorder.Code != http.StatusInternalServerError {
   640  		t.Errorf("Expected status %d. Got %d. Input was: %s", http.StatusInternalServerError, recorder.Code, validRequest(t, "site.json"))
   641  	}
   642  }
   643  
   644  // TestUserAgentSetting makes sure we read the User-Agent header if it wasn't defined on the request.
   645  func TestUserAgentSetting(t *testing.T) {
   646  	httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
   647  	httpReq.Header.Set("User-Agent", "foo")
   648  	bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}
   649  
   650  	setUAImplicitly(httpReq, bidReq)
   651  
   652  	if bidReq.Device == nil {
   653  		t.Fatal("bidrequest.device should have been set implicitly.")
   654  	}
   655  	if bidReq.Device.UA != "foo" {
   656  		t.Errorf("bidrequest.device.ua should have been \"foo\". Got %s", bidReq.Device.UA)
   657  	}
   658  }
   659  
   660  // TestUserAgentOverride makes sure that the explicit UA from the request takes precedence.
   661  func TestUserAgentOverride(t *testing.T) {
   662  	httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
   663  	httpReq.Header.Set("User-Agent", "foo")
   664  	bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
   665  		Device: &openrtb2.Device{
   666  			UA: "bar",
   667  		},
   668  	}}
   669  
   670  	setUAImplicitly(httpReq, bidReq)
   671  
   672  	if bidReq.Device.UA != "bar" {
   673  		t.Errorf("bidrequest.device.ua should have been \"bar\". Got %s", bidReq.Device.UA)
   674  	}
   675  }
   676  
   677  func TestAuctionTypeDefault(t *testing.T) {
   678  	bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}
   679  	setAuctionTypeImplicitly(bidReq)
   680  
   681  	if bidReq.AT != 1 {
   682  		t.Errorf("Expected request.at to be 1. Got %d", bidReq.AT)
   683  	}
   684  }
   685  
   686  func TestImplicitIPsEndToEnd(t *testing.T) {
   687  	testCases := []struct {
   688  		description         string
   689  		reqJSONFile         string
   690  		xForwardedForHeader string
   691  		privateNetworksIPv4 []net.IPNet
   692  		privateNetworksIPv6 []net.IPNet
   693  		expectedDeviceIPv4  string
   694  		expectedDeviceIPv6  string
   695  	}{
   696  		{
   697  			description:         "IPv4",
   698  			reqJSONFile:         "site.json",
   699  			xForwardedForHeader: "1.1.1.1",
   700  			expectedDeviceIPv4:  "1.1.1.1",
   701  		},
   702  		{
   703  			description:         "IPv6",
   704  			reqJSONFile:         "site.json",
   705  			xForwardedForHeader: "1111::",
   706  			expectedDeviceIPv6:  "1111::",
   707  		},
   708  		{
   709  			description:         "IPv4 - Defined In Request",
   710  			reqJSONFile:         "site-has-ipv4.json",
   711  			xForwardedForHeader: "1.1.1.1",
   712  			expectedDeviceIPv4:  "8.8.8.8", // Hardcoded value in test file.
   713  		},
   714  		{
   715  			description:         "IPv6 - Defined In Request",
   716  			reqJSONFile:         "site-has-ipv6.json",
   717  			xForwardedForHeader: "1111::",
   718  			expectedDeviceIPv6:  "8888::", // Hardcoded value in test file.
   719  		},
   720  		{
   721  			description:         "IPv4 - Defined In Request - Private Network",
   722  			reqJSONFile:         "site-has-ipv4.json",
   723  			xForwardedForHeader: "1.1.1.1",
   724  			privateNetworksIPv4: []net.IPNet{{IP: net.IP{8, 8, 8, 0}, Mask: net.CIDRMask(24, 32)}}, // Hardcoded value in test file.
   725  			expectedDeviceIPv4:  "1.1.1.1",
   726  		},
   727  		{
   728  			description:         "IPv6 - Defined In Request - Private Network",
   729  			reqJSONFile:         "site-has-ipv6.json",
   730  			xForwardedForHeader: "1111::",
   731  			privateNetworksIPv6: []net.IPNet{{IP: net.ParseIP("8800::"), Mask: net.CIDRMask(8, 128)}}, // Hardcoded value in test file.
   732  			expectedDeviceIPv6:  "1111::",
   733  		},
   734  	}
   735  
   736  	for _, test := range testCases {
   737  		exchange := &nobidExchange{}
   738  		cfg := &config.Configuration{
   739  			MaxRequestSize: maxSize,
   740  			RequestValidation: config.RequestValidation{
   741  				IPv4PrivateNetworksParsed: test.privateNetworksIPv4,
   742  				IPv6PrivateNetworksParsed: test.privateNetworksIPv6,
   743  			},
   744  		}
   745  		endpoint, _ := NewEndpoint(
   746  			fakeUUIDGenerator{},
   747  			exchange,
   748  			mockBidderParamValidator{},
   749  			&mockStoredReqFetcher{},
   750  			empty_fetcher.EmptyFetcher{},
   751  			cfg,
   752  			&metricsConfig.NilMetricsEngine{},
   753  			analyticsBuild.New(&config.Analytics{}),
   754  			map[string]string{},
   755  			[]byte{},
   756  			openrtb_ext.BuildBidderMap(),
   757  			empty_fetcher.EmptyFetcher{},
   758  			hooks.EmptyPlanBuilder{},
   759  			nil,
   760  		)
   761  
   762  		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
   763  		httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader)
   764  
   765  		endpoint(httptest.NewRecorder(), httpReq, nil)
   766  
   767  		result := exchange.gotRequest
   768  		if !assert.NotEmpty(t, result, test.description+"Request received by the exchange.") {
   769  			t.FailNow()
   770  		}
   771  		assert.Equal(t, test.expectedDeviceIPv4, result.Device.IP, test.description+":ipv4")
   772  		assert.Equal(t, test.expectedDeviceIPv6, result.Device.IPv6, test.description+":ipv6")
   773  	}
   774  }
   775  
   776  func TestImplicitDNT(t *testing.T) {
   777  	var (
   778  		disabled int8 = 0
   779  		enabled  int8 = 1
   780  	)
   781  	testCases := []struct {
   782  		description     string
   783  		dntHeader       string
   784  		request         openrtb2.BidRequest
   785  		expectedRequest openrtb2.BidRequest
   786  	}{
   787  		{
   788  			description:     "Device Missing - Not Set In Header",
   789  			dntHeader:       "",
   790  			request:         openrtb2.BidRequest{},
   791  			expectedRequest: openrtb2.BidRequest{},
   792  		},
   793  		{
   794  			description: "Device Missing - Set To 0 In Header",
   795  			dntHeader:   "0",
   796  			request:     openrtb2.BidRequest{},
   797  			expectedRequest: openrtb2.BidRequest{
   798  				Device: &openrtb2.Device{
   799  					DNT: &disabled,
   800  				},
   801  			},
   802  		},
   803  		{
   804  			description: "Device Missing - Set To 1 In Header",
   805  			dntHeader:   "1",
   806  			request:     openrtb2.BidRequest{},
   807  			expectedRequest: openrtb2.BidRequest{
   808  				Device: &openrtb2.Device{
   809  					DNT: &enabled,
   810  				},
   811  			},
   812  		},
   813  		{
   814  			description: "Not Set In Request - Not Set In Header",
   815  			dntHeader:   "",
   816  			request: openrtb2.BidRequest{
   817  				Device: &openrtb2.Device{},
   818  			},
   819  			expectedRequest: openrtb2.BidRequest{
   820  				Device: &openrtb2.Device{},
   821  			},
   822  		},
   823  		{
   824  			description: "Not Set In Request - Set To 0 In Header",
   825  			dntHeader:   "0",
   826  			request: openrtb2.BidRequest{
   827  				Device: &openrtb2.Device{},
   828  			},
   829  			expectedRequest: openrtb2.BidRequest{
   830  				Device: &openrtb2.Device{
   831  					DNT: &disabled,
   832  				},
   833  			},
   834  		},
   835  		{
   836  			description: "Not Set In Request - Set To 1 In Header",
   837  			dntHeader:   "1",
   838  			request: openrtb2.BidRequest{
   839  				Device: &openrtb2.Device{},
   840  			},
   841  			expectedRequest: openrtb2.BidRequest{
   842  				Device: &openrtb2.Device{
   843  					DNT: &enabled,
   844  				},
   845  			},
   846  		},
   847  		{
   848  			description: "Set In Request - Not Set In Header",
   849  			dntHeader:   "",
   850  			request: openrtb2.BidRequest{
   851  				Device: &openrtb2.Device{
   852  					DNT: &enabled,
   853  				},
   854  			},
   855  			expectedRequest: openrtb2.BidRequest{
   856  				Device: &openrtb2.Device{
   857  					DNT: &enabled,
   858  				},
   859  			},
   860  		},
   861  		{
   862  			description: "Set In Request - Set To 0 In Header",
   863  			dntHeader:   "0",
   864  			request: openrtb2.BidRequest{
   865  				Device: &openrtb2.Device{
   866  					DNT: &enabled,
   867  				},
   868  			},
   869  			expectedRequest: openrtb2.BidRequest{
   870  				Device: &openrtb2.Device{
   871  					DNT: &enabled,
   872  				},
   873  			},
   874  		},
   875  		{
   876  			description: "Set In Request - Set To 1 In Header",
   877  			dntHeader:   "1",
   878  			request: openrtb2.BidRequest{
   879  				Device: &openrtb2.Device{
   880  					DNT: &enabled,
   881  				},
   882  			},
   883  			expectedRequest: openrtb2.BidRequest{
   884  				Device: &openrtb2.Device{
   885  					DNT: &enabled,
   886  				},
   887  			},
   888  		},
   889  	}
   890  
   891  	for _, test := range testCases {
   892  		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", nil)
   893  		httpReq.Header.Set("DNT", test.dntHeader)
   894  		reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &test.request}
   895  		setDoNotTrackImplicitly(httpReq, reqWrapper)
   896  		assert.Equal(t, test.expectedRequest, *reqWrapper.BidRequest, test.description)
   897  	}
   898  }
   899  
   900  func TestImplicitDNTEndToEnd(t *testing.T) {
   901  	var (
   902  		disabled int8 = 0
   903  		enabled  int8 = 1
   904  	)
   905  	testCases := []struct {
   906  		description string
   907  		reqJSONFile string
   908  		dntHeader   string
   909  		expectedDNT *int8
   910  	}{
   911  		{
   912  			description: "Not Set In Request - Not Set In Header",
   913  			reqJSONFile: "site.json",
   914  			dntHeader:   "",
   915  			expectedDNT: nil,
   916  		},
   917  		{
   918  			description: "Not Set In Request - Set To 0 In Header",
   919  			reqJSONFile: "site.json",
   920  			dntHeader:   "0",
   921  			expectedDNT: &disabled,
   922  		},
   923  		{
   924  			description: "Not Set In Request - Set To 1 In Header",
   925  			reqJSONFile: "site.json",
   926  			dntHeader:   "1",
   927  			expectedDNT: &enabled,
   928  		},
   929  		{
   930  			description: "Set In Request - Not Set In Header",
   931  			reqJSONFile: "site-has-dnt.json",
   932  			dntHeader:   "",
   933  			expectedDNT: &enabled, // Hardcoded value in test file.
   934  		},
   935  		{
   936  			description: "Set In Request - Not Overwritten By Header",
   937  			reqJSONFile: "site-has-dnt.json",
   938  			dntHeader:   "0",
   939  			expectedDNT: &enabled, // Hardcoded value in test file.
   940  		},
   941  	}
   942  
   943  	for _, test := range testCases {
   944  		exchange := &nobidExchange{}
   945  		endpoint, _ := NewEndpoint(
   946  			fakeUUIDGenerator{},
   947  			exchange,
   948  			mockBidderParamValidator{},
   949  			&mockStoredReqFetcher{},
   950  			empty_fetcher.EmptyFetcher{},
   951  			&config.Configuration{MaxRequestSize: maxSize},
   952  			&metricsConfig.NilMetricsEngine{},
   953  			analyticsBuild.New(&config.Analytics{}),
   954  			map[string]string{},
   955  			[]byte{},
   956  			openrtb_ext.BuildBidderMap(),
   957  			empty_fetcher.EmptyFetcher{},
   958  			hooks.EmptyPlanBuilder{},
   959  			nil,
   960  		)
   961  
   962  		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile)))
   963  		httpReq.Header.Set("DNT", test.dntHeader)
   964  
   965  		endpoint(httptest.NewRecorder(), httpReq, nil)
   966  
   967  		result := exchange.gotRequest
   968  		if !assert.NotEmpty(t, result, test.description+"Request received by the exchange.") {
   969  			t.FailNow()
   970  		}
   971  		assert.Equal(t, test.expectedDNT, result.Device.DNT, test.description+":dnt")
   972  	}
   973  }
   974  
   975  func TestReferer(t *testing.T) {
   976  	testCases := []struct {
   977  		description             string
   978  		givenReferer            string
   979  		expectedDomain          string
   980  		expectedPage            string
   981  		expectedPublisherDomain string
   982  		bidReq                  *openrtb_ext.RequestWrapper
   983  	}{
   984  		{
   985  			description:             "site.page/domain are unchanged when site.page/domain and http referer are not set",
   986  			expectedDomain:          "",
   987  			expectedPage:            "",
   988  			expectedPublisherDomain: "",
   989  			bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
   990  				Site: &openrtb2.Site{
   991  					Publisher: &openrtb2.Publisher{},
   992  				},
   993  			}},
   994  		},
   995  		{
   996  			description:             "site.page/domain are derived from referer when neither is set and http referer is set",
   997  			givenReferer:            "https://test.somepage.com",
   998  			expectedDomain:          "test.somepage.com",
   999  			expectedPublisherDomain: "somepage.com",
  1000  			expectedPage:            "https://test.somepage.com",
  1001  			bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1002  				Site: &openrtb2.Site{
  1003  					Publisher: &openrtb2.Publisher{},
  1004  				},
  1005  			}},
  1006  		},
  1007  		{
  1008  			description:             "site.domain is derived from site.page when site.page is set and http referer is not set",
  1009  			expectedDomain:          "test.somepage.com",
  1010  			expectedPublisherDomain: "somepage.com",
  1011  			expectedPage:            "https://test.somepage.com",
  1012  			bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1013  				Site: &openrtb2.Site{
  1014  					Page:      "https://test.somepage.com",
  1015  					Publisher: &openrtb2.Publisher{},
  1016  				},
  1017  			}},
  1018  		},
  1019  		{
  1020  			description:             "site.domain is derived from http referer when site.page and http referer are set",
  1021  			givenReferer:            "http://test.com",
  1022  			expectedDomain:          "test.com",
  1023  			expectedPublisherDomain: "test.com",
  1024  			expectedPage:            "https://test.somepage.com",
  1025  			bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1026  				Site: &openrtb2.Site{
  1027  					Page:      "https://test.somepage.com",
  1028  					Publisher: &openrtb2.Publisher{},
  1029  				},
  1030  			}},
  1031  		},
  1032  		{
  1033  			description:             "site.page/domain are unchanged when site.page/domain and http referer are set",
  1034  			givenReferer:            "http://test.com",
  1035  			expectedDomain:          "some.domain.com",
  1036  			expectedPublisherDomain: "test.com",
  1037  			expectedPage:            "https://test.somepage.com",
  1038  			bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1039  				Site: &openrtb2.Site{
  1040  					Domain:    "some.domain.com",
  1041  					Page:      "https://test.somepage.com",
  1042  					Publisher: &openrtb2.Publisher{},
  1043  				},
  1044  			}},
  1045  		},
  1046  		{
  1047  			description:             "Publisher domain shouldn't be overrwriten if already set",
  1048  			expectedDomain:          "test.somepage.com",
  1049  			expectedPublisherDomain: "differentpage.com",
  1050  			expectedPage:            "https://test.somepage.com",
  1051  			bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1052  				Site: &openrtb2.Site{
  1053  					Domain: "",
  1054  					Page:   "https://test.somepage.com",
  1055  					Publisher: &openrtb2.Publisher{
  1056  						Domain: "differentpage.com",
  1057  					},
  1058  				},
  1059  			}},
  1060  		},
  1061  		{
  1062  			description:             "request.site is nil",
  1063  			givenReferer:            "http://test.com",
  1064  			expectedDomain:          "test.com",
  1065  			expectedPublisherDomain: "test.com",
  1066  			expectedPage:            "http://test.com",
  1067  			bidReq:                  &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
  1068  		},
  1069  	}
  1070  
  1071  	for _, test := range testCases {
  1072  		httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
  1073  		httpReq.Header.Set("Referer", test.givenReferer)
  1074  
  1075  		setSiteImplicitly(httpReq, test.bidReq)
  1076  
  1077  		assert.NotNil(t, test.bidReq.Site, test.description)
  1078  		assert.Equal(t, test.expectedDomain, test.bidReq.Site.Domain, test.description)
  1079  		assert.Equal(t, test.expectedPage, test.bidReq.Site.Page, test.description)
  1080  		assert.Equal(t, test.expectedPublisherDomain, test.bidReq.Site.Publisher.Domain, test.description)
  1081  	}
  1082  }
  1083  
  1084  func TestParseImpInfoSingleImpression(t *testing.T) {
  1085  
  1086  	expectedRes := []ImpExtPrebidData{
  1087  		{
  1088  			Imp:          json.RawMessage(`{"video":{"h":300,"w":200},"ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}}}}`),
  1089  			ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}},
  1090  		},
  1091  		{
  1092  			Imp:          json.RawMessage(`{"id": "adUnit2","ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}},"appnexus": {"placementId": "def","trafficSourceCode": "mysite.com","reserve": null},"rubicon": null}}`),
  1093  			ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}},
  1094  		},
  1095  		{
  1096  			Imp:          json.RawMessage(`{"ext": {"prebid": {"storedrequest": {"id": "2"},"options": {"echovideoattrs": false}}}}`),
  1097  			ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "2"}, Options: &openrtb_ext.Options{EchoVideoAttrs: false}},
  1098  		},
  1099  		{
  1100  			//in this case impression doesn't have storedrequest so we don't expect any data about this imp will be returned
  1101  			Imp:          json.RawMessage(`{"id": "some-static-imp","video":{"mimes":["video/mp4"]},"ext": {"appnexus": {"placementId": "abc","position": "below"}}}`),
  1102  			ImpExtPrebid: openrtb_ext.ExtImpPrebid{},
  1103  		},
  1104  		{
  1105  			Imp:          json.RawMessage(`{"id":"my-imp-id", "video":{"h":300, "w":200}, "ext":{"prebid":{"storedrequest": {"id": "6"}}}}`),
  1106  			ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "6"}},
  1107  		},
  1108  	}
  1109  
  1110  	for i, requestData := range testStoredRequests {
  1111  		impInfo, errs := parseImpInfo([]byte(requestData))
  1112  		assert.Len(t, errs, 0, "No errors should be returned")
  1113  		assert.JSONEq(t, string(expectedRes[i].Imp), string(impInfo[0].Imp), "Incorrect impression data")
  1114  		assert.Equal(t, expectedRes[i].ImpExtPrebid, impInfo[0].ImpExtPrebid, "Incorrect impression ext prebid data")
  1115  
  1116  	}
  1117  }
  1118  
  1119  func TestParseImpInfoMultipleImpressions(t *testing.T) {
  1120  
  1121  	inputData := []byte(`{
  1122  		"id": "ThisID",
  1123  		"imp": [
  1124  			{
  1125  				"id": "imp1",
  1126  				"ext": {
  1127  					"prebid": {
  1128  						"storedrequest": {
  1129  							"id": "1"
  1130  						},
  1131  						"options": {
  1132  							"echovideoattrs": true
  1133  						}
  1134  					}
  1135  				}
  1136  			},
  1137  			{
  1138  				"id": "imp2",
  1139  				"ext": {
  1140  					"prebid": {
  1141  						"storedrequest": {
  1142  							"id": "2"
  1143  						},
  1144  						"options": {
  1145  							"echovideoattrs": false
  1146  						}
  1147  					}
  1148  				}
  1149  			},
  1150  			{
  1151  				"id": "imp3"
  1152  			}
  1153  		]
  1154  	}`)
  1155  
  1156  	expectedRes := []ImpExtPrebidData{
  1157  		{
  1158  			Imp:          json.RawMessage(`{"id": "imp1","ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}}}}`),
  1159  			ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}},
  1160  		},
  1161  		{
  1162  			Imp:          json.RawMessage(`{"id": "imp2","ext": {"prebid": {"storedrequest": {"id": "2"},"options": {"echovideoattrs": false}}}}`),
  1163  			ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "2"}, Options: &openrtb_ext.Options{EchoVideoAttrs: false}},
  1164  		},
  1165  		{
  1166  			Imp:          json.RawMessage(`{"id": "imp3"}`),
  1167  			ImpExtPrebid: openrtb_ext.ExtImpPrebid{},
  1168  		},
  1169  	}
  1170  
  1171  	impInfo, errs := parseImpInfo([]byte(inputData))
  1172  	assert.Len(t, errs, 0, "No errors should be returned")
  1173  	for i, res := range expectedRes {
  1174  		assert.JSONEq(t, string(res.Imp), string(impInfo[i].Imp), "Incorrect impression data")
  1175  		assert.Equal(t, res.ImpExtPrebid, impInfo[i].ImpExtPrebid, "Incorrect impression ext prebid data")
  1176  	}
  1177  }
  1178  
  1179  // Test the stored request functionality
  1180  func TestStoredRequests(t *testing.T) {
  1181  	deps := &endpointDeps{
  1182  		fakeUUIDGenerator{},
  1183  		&nobidExchange{},
  1184  		mockBidderParamValidator{},
  1185  		&mockStoredReqFetcher{},
  1186  		empty_fetcher.EmptyFetcher{},
  1187  		empty_fetcher.EmptyFetcher{},
  1188  		&config.Configuration{MaxRequestSize: maxSize},
  1189  		&metricsConfig.NilMetricsEngine{},
  1190  		analyticsBuild.New(&config.Analytics{}),
  1191  		map[string]string{},
  1192  		false,
  1193  		[]byte{},
  1194  		openrtb_ext.BuildBidderMap(),
  1195  		nil,
  1196  		nil,
  1197  		hardcodedResponseIPValidator{response: true},
  1198  		empty_fetcher.EmptyFetcher{},
  1199  		hooks.EmptyPlanBuilder{},
  1200  		nil,
  1201  		openrtb_ext.NormalizeBidderName,
  1202  	}
  1203  
  1204  	testStoreVideoAttr := []bool{true, true, false, false, false}
  1205  
  1206  	for i, requestData := range testStoredRequests {
  1207  		impInfo, errs := parseImpInfo([]byte(requestData))
  1208  		assert.Len(t, errs, 0, "No errors should be returned")
  1209  		storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(context.Background(), json.RawMessage(requestData), impInfo)
  1210  		assert.Len(t, errs, 0, "No errors should be returned")
  1211  		newRequest, impExtInfoMap, errList := deps.processStoredRequests(json.RawMessage(requestData), impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest)
  1212  		if len(errList) != 0 {
  1213  			for _, err := range errList {
  1214  				if err != nil {
  1215  					t.Errorf("processStoredRequests Error: %s", err.Error())
  1216  				} else {
  1217  					t.Error("processStoredRequests Error: received nil error")
  1218  				}
  1219  			}
  1220  		}
  1221  		expectJson := json.RawMessage(testFinalRequests[i])
  1222  		assert.JSONEq(t, string(expectJson), string(newRequest), "Incorrect result request %d", i)
  1223  		expectedImp := testStoredImpIds[i]
  1224  		expectedStoredImp := json.RawMessage(testStoredImps[i])
  1225  		if len(impExtInfoMap[expectedImp].StoredImp) > 0 {
  1226  			assert.JSONEq(t, string(expectedStoredImp), string(impExtInfoMap[expectedImp].StoredImp), "Incorrect expected stored imp %d", i)
  1227  
  1228  		}
  1229  		assert.Equalf(t, testStoreVideoAttr[i], impExtInfoMap[expectedImp].EchoVideoAttrs, "EchoVideoAttrs value is incorrect")
  1230  	}
  1231  }
  1232  
  1233  func TestMergeBidderParams(t *testing.T) {
  1234  	testCases := []struct {
  1235  		description         string
  1236  		givenRequest        openrtb2.BidRequest
  1237  		expectedRequestImps []openrtb2.Imp
  1238  	}{
  1239  		{
  1240  			description: "No Request Params",
  1241  			givenRequest: openrtb2.BidRequest{
  1242  				Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}},
  1243  			},
  1244  			expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}},
  1245  		},
  1246  		{
  1247  			description: "No Request Params - Empty Object",
  1248  			givenRequest: openrtb2.BidRequest{
  1249  				Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}},
  1250  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{}}}`),
  1251  			},
  1252  			expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}},
  1253  		},
  1254  		{
  1255  			description: "Malformed Request Params",
  1256  			givenRequest: openrtb2.BidRequest{
  1257  				Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}},
  1258  				Ext: json.RawMessage(`{"prebid":{"bidderparams":malformed}}`),
  1259  			},
  1260  			expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}},
  1261  		},
  1262  		{
  1263  			description: "No Imps",
  1264  			givenRequest: openrtb2.BidRequest{
  1265  				Imp: []openrtb2.Imp{},
  1266  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`),
  1267  			},
  1268  			expectedRequestImps: []openrtb2.Imp{},
  1269  		},
  1270  		{
  1271  			description: "One Imp - imp.ext Modified",
  1272  			givenRequest: openrtb2.BidRequest{
  1273  				Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}},
  1274  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`),
  1275  			},
  1276  			expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2}}`)}},
  1277  		},
  1278  		{
  1279  			description: "One Imp - imp.ext.prebid.bidder Modified",
  1280  			givenRequest: openrtb2.BidRequest{
  1281  				Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidder1":{"a":1}}}}`)}},
  1282  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`),
  1283  			},
  1284  			expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidder1":{"a":1,"b":2}}}}`)}},
  1285  		},
  1286  		{
  1287  			description: "One Imp - imp.ext + imp.ext.prebid.bidder Modified - Different Bidders",
  1288  			givenRequest: openrtb2.BidRequest{
  1289  				Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)}},
  1290  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2},"bidder2":{"b":"two"}}}}`),
  1291  			},
  1292  			expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2},"prebid":{"bidder":{"bidder2":{"a":"one","b":"two"}}}}`)}},
  1293  		},
  1294  		{
  1295  			description: "One Imp - imp.ext + imp.ext.prebid.bidder Modified - Same Bidder",
  1296  			givenRequest: openrtb2.BidRequest{
  1297  				Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder1":{"a":"one"}}}}`)}},
  1298  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`),
  1299  			},
  1300  			expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2},"prebid":{"bidder":{"bidder1":{"a":"one","b":2}}}}`)}},
  1301  		},
  1302  		{
  1303  			description: "One Imp - No imp.ext",
  1304  			givenRequest: openrtb2.BidRequest{
  1305  				Imp: []openrtb2.Imp{{ID: "1"}},
  1306  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`),
  1307  			},
  1308  			expectedRequestImps: []openrtb2.Imp{{ID: "1"}},
  1309  		},
  1310  		{
  1311  			description: "Multiple Imps - Modified Mixed",
  1312  			givenRequest: openrtb2.BidRequest{
  1313  				Imp: []openrtb2.Imp{
  1314  					{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder1":{"a":"one"}}}}`)},
  1315  					{ID: "2", Ext: json.RawMessage(`{"bidder2":{"a":1,"b":"existing"},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)}},
  1316  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2},"bidder2":{"b":"two"}}}}`),
  1317  			},
  1318  			expectedRequestImps: []openrtb2.Imp{
  1319  				{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2},"prebid":{"bidder":{"bidder1":{"a":"one","b":2}}}}`)},
  1320  				{ID: "2", Ext: json.RawMessage(`{"bidder2":{"a":1,"b":"existing"},"prebid":{"bidder":{"bidder2":{"a":"one","b":"two"}}}}`)}},
  1321  		},
  1322  		{
  1323  			description: "Multiple Imps - None Modified Mixed",
  1324  			givenRequest: openrtb2.BidRequest{
  1325  				Imp: []openrtb2.Imp{
  1326  					{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)},
  1327  					{ID: "2", Ext: json.RawMessage(`{"bidder1":{"a":2},"prebid":{"bidder":{"bidder2":{"a":"two"}}}}`)}},
  1328  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder3":{"c":3}}}}`),
  1329  			},
  1330  			expectedRequestImps: []openrtb2.Imp{
  1331  				{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)},
  1332  				{ID: "2", Ext: json.RawMessage(`{"bidder1":{"a":2},"prebid":{"bidder":{"bidder2":{"a":"two"}}}}`)}},
  1333  		},
  1334  		{
  1335  			description: "Multiple Imps - One Malformed",
  1336  			givenRequest: openrtb2.BidRequest{
  1337  				Imp: []openrtb2.Imp{
  1338  					{ID: "1", Ext: json.RawMessage(`malformed`)},
  1339  					{ID: "2", Ext: json.RawMessage(`{"bidder2":{"a":1,"b":"existing"},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)}},
  1340  				Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2},"bidder2":{"b":"two"}}}}`),
  1341  			},
  1342  			expectedRequestImps: []openrtb2.Imp{
  1343  				{ID: "1", Ext: json.RawMessage(`malformed`)},
  1344  				{ID: "2", Ext: json.RawMessage(`{"bidder2":{"a":1,"b":"existing"},"prebid":{"bidder":{"bidder2":{"a":"one","b":"two"}}}}`)}},
  1345  		},
  1346  	}
  1347  
  1348  	for _, test := range testCases {
  1349  		w := &openrtb_ext.RequestWrapper{BidRequest: &test.givenRequest}
  1350  		actualErr := mergeBidderParams(w)
  1351  
  1352  		// errors are only possible from the marshal operation, which is not testable
  1353  		assert.NoError(t, actualErr, test.description+":err")
  1354  
  1355  		// rebuild request before asserting value
  1356  		assert.NoError(t, w.RebuildRequest(), test.description+":rebuild_request")
  1357  
  1358  		assert.Equal(t, test.givenRequest.Imp, test.expectedRequestImps, test.description+":imps")
  1359  	}
  1360  }
  1361  
  1362  func TestMergeBidderParamsImpExt(t *testing.T) {
  1363  	testCases := []struct {
  1364  		description       string
  1365  		givenImpExt       map[string]json.RawMessage
  1366  		givenReqExtParams map[string]map[string]json.RawMessage
  1367  		expectedModified  bool
  1368  		expectedImpExt    map[string]json.RawMessage
  1369  	}{
  1370  		{
  1371  			description:       "One Bidder - Modified (no collision)",
  1372  			givenImpExt:       map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`)},
  1373  			givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1374  			expectedModified:  true,
  1375  			expectedImpExt:    map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)},
  1376  		},
  1377  		{
  1378  			description:       "One Bidder - Modified (imp.ext bidder empty)",
  1379  			givenImpExt:       map[string]json.RawMessage{"bidder1": json.RawMessage(`{}`)},
  1380  			givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1381  			expectedModified:  true,
  1382  			expectedImpExt:    map[string]json.RawMessage{"bidder1": json.RawMessage(`{"b":2}`)},
  1383  		},
  1384  		{
  1385  			description:       "One Bidder - Not Modified (imp.ext bidder not defined)",
  1386  			givenImpExt:       map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)},
  1387  			givenReqExtParams: map[string]map[string]json.RawMessage{"bidder-not-defined": {"b": json.RawMessage(`4`)}},
  1388  			expectedModified:  false,
  1389  			expectedImpExt:    map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)},
  1390  		},
  1391  		{
  1392  			description:       "One Bidder - Not Modified (imp.ext bidder nil)",
  1393  			givenImpExt:       map[string]json.RawMessage{"bidder1": nil},
  1394  			givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}},
  1395  			expectedModified:  false,
  1396  			expectedImpExt:    map[string]json.RawMessage{"bidder1": nil},
  1397  		},
  1398  		{
  1399  			description:       "One Bidder - Not Modified (imp.ext wins)",
  1400  			givenImpExt:       map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)},
  1401  			givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}},
  1402  			expectedModified:  false,
  1403  			expectedImpExt:    map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)},
  1404  		},
  1405  		{
  1406  			description:       "One Bidder - Not Modified (reserved bidder ignored)",
  1407  			givenImpExt:       map[string]json.RawMessage{"gpid": json.RawMessage(`{"a":1}`)},
  1408  			givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}},
  1409  			expectedModified:  false,
  1410  			expectedImpExt:    map[string]json.RawMessage{"gpid": json.RawMessage(`{"a":1}`)},
  1411  		},
  1412  		{
  1413  			description:       "One Bidder - Not Modified (reserved bidder ignored - not embedded object)",
  1414  			givenImpExt:       map[string]json.RawMessage{"gpid": json.RawMessage(`1`)},
  1415  			givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}},
  1416  			expectedModified:  false,
  1417  			expectedImpExt:    map[string]json.RawMessage{"gpid": json.RawMessage(`1`)},
  1418  		},
  1419  		{
  1420  			description:       "One Bidder - Not Modified (malformed ignored)",
  1421  			givenImpExt:       map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)},
  1422  			givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1423  			expectedModified:  false,
  1424  			expectedImpExt:    map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)},
  1425  		},
  1426  		{
  1427  			description:       "Multiple Bidders - Mixed",
  1428  			givenImpExt:       map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)},
  1429  			givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}, "bidder2": {"b": json.RawMessage(`"three"`)}},
  1430  			expectedModified:  true,
  1431  			expectedImpExt:    map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)},
  1432  		},
  1433  		{
  1434  			description:       "Multiple Bidders - None Modified",
  1435  			givenImpExt:       map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)},
  1436  			givenReqExtParams: map[string]map[string]json.RawMessage{},
  1437  			expectedModified:  false,
  1438  			expectedImpExt:    map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)},
  1439  		},
  1440  	}
  1441  
  1442  	for _, test := range testCases {
  1443  		impExt := openrtb_ext.CreateImpExtForTesting(test.givenImpExt, nil)
  1444  
  1445  		err := mergeBidderParamsImpExt(&impExt, test.givenReqExtParams)
  1446  
  1447  		// errors are only possible from the marshal operation, which is not testable
  1448  		assert.NoError(t, err, test.description+":err")
  1449  
  1450  		assert.Equal(t, test.expectedModified, impExt.Dirty(), test.description+":modified")
  1451  		assert.Equal(t, test.expectedImpExt, impExt.GetExt(), test.description+":imp.ext")
  1452  	}
  1453  }
  1454  
  1455  func TestMergeBidderParamsImpExtPrebid(t *testing.T) {
  1456  	testCases := []struct {
  1457  		description          string
  1458  		givenImpExtPrebid    *openrtb_ext.ExtImpPrebid
  1459  		givenReqExtParams    map[string]map[string]json.RawMessage
  1460  		expectedModified     bool
  1461  		expectedImpExtPrebid *openrtb_ext.ExtImpPrebid
  1462  	}{
  1463  		{
  1464  			description:          "No Prebid Section",
  1465  			givenImpExtPrebid:    nil,
  1466  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1467  			expectedModified:     false,
  1468  			expectedImpExtPrebid: nil,
  1469  		},
  1470  		{
  1471  			description:          "No Prebid Bidder Section",
  1472  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: nil},
  1473  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1474  			expectedModified:     false,
  1475  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: nil},
  1476  		},
  1477  		{
  1478  			description:          "Empty Prebid Bidder Section",
  1479  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{}},
  1480  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1481  			expectedModified:     false,
  1482  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{}},
  1483  		},
  1484  		{
  1485  			description:          "One Bidder - Modified (no collision)",
  1486  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`)}},
  1487  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1488  			expectedModified:     true,
  1489  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}},
  1490  		},
  1491  		{
  1492  			description:          "One Bidder - Modified (imp.ext bidder empty)",
  1493  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{}`)}},
  1494  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1495  			expectedModified:     true,
  1496  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"b":2}`)}},
  1497  		},
  1498  		{
  1499  			description:          "One Bidder - Not Modified (imp.ext wins)",
  1500  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}},
  1501  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}},
  1502  			expectedModified:     false,
  1503  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}},
  1504  		},
  1505  		{
  1506  			description:          "One Bidder - Not Modified (imp.ext bidder not defined)",
  1507  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}},
  1508  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder-not-defined": {"b": json.RawMessage(`4`)}},
  1509  			expectedModified:     false,
  1510  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}},
  1511  		},
  1512  		{
  1513  			description:          "One Bidder - Not Modified (imp.ext bidder nil)",
  1514  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": nil}},
  1515  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}},
  1516  			expectedModified:     false,
  1517  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": nil}},
  1518  		},
  1519  		{
  1520  			description:          "One Bidder - Not Modified (malformed ignored)",
  1521  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}},
  1522  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}},
  1523  			expectedModified:     false,
  1524  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}},
  1525  		},
  1526  		{
  1527  			description:          "Multiple Bidders - Mixed",
  1528  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}},
  1529  			givenReqExtParams:    map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}, "bidder2": {"b": json.RawMessage(`"three"`)}},
  1530  			expectedModified:     true,
  1531  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}},
  1532  		},
  1533  		{
  1534  			description:          "Multiple Bidders - None Modified",
  1535  			givenImpExtPrebid:    &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}},
  1536  			givenReqExtParams:    map[string]map[string]json.RawMessage{},
  1537  			expectedModified:     false,
  1538  			expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}},
  1539  		},
  1540  	}
  1541  
  1542  	for _, test := range testCases {
  1543  		impExt := openrtb_ext.CreateImpExtForTesting(map[string]json.RawMessage{}, test.givenImpExtPrebid)
  1544  
  1545  		err := mergeBidderParamsImpExtPrebid(&impExt, test.givenReqExtParams)
  1546  
  1547  		// errors are only possible from the marshal operation, which is not testable
  1548  		assert.NoError(t, err, test.description+":err")
  1549  
  1550  		assert.Equal(t, test.expectedModified, impExt.Dirty(), test.description+":modified")
  1551  		assert.Equal(t, test.expectedImpExtPrebid, impExt.GetPrebid(), test.description+":imp.ext.prebid")
  1552  	}
  1553  }
  1554  
  1555  func TestValidateExactlyOneInventoryType(t *testing.T) {
  1556  
  1557  	testCases := []struct {
  1558  		description         string
  1559  		givenRequestWrapper *openrtb_ext.RequestWrapper
  1560  		expectedError       error
  1561  	}{
  1562  		{
  1563  			description:         "None provided - invalid",
  1564  			givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
  1565  			expectedError:       errors.New("One of request.site or request.app or request.dooh must be defined"),
  1566  		},
  1567  		{
  1568  			description: "Only site provided",
  1569  			givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1570  				Site: &openrtb2.Site{},
  1571  			}},
  1572  			expectedError: nil,
  1573  		},
  1574  		{
  1575  			description: "Only app provided",
  1576  			givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1577  				App: &openrtb2.App{},
  1578  			}},
  1579  			expectedError: nil,
  1580  		},
  1581  		{
  1582  			description: "Only dooh provided",
  1583  			givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1584  				DOOH: &openrtb2.DOOH{},
  1585  			}},
  1586  			expectedError: nil,
  1587  		},
  1588  		{
  1589  			description: "Two provided (site+app) - invalid",
  1590  			givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1591  				Site: &openrtb2.Site{},
  1592  				App:  &openrtb2.App{},
  1593  			}},
  1594  			expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"),
  1595  		},
  1596  		{
  1597  			description: "Two provided (site+dooh) - invalid",
  1598  			givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1599  				Site: &openrtb2.Site{},
  1600  				DOOH: &openrtb2.DOOH{},
  1601  			}},
  1602  			expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"),
  1603  		},
  1604  		{
  1605  			description: "Two provided (app+dooh) - invalid",
  1606  			givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1607  				App:  &openrtb2.App{},
  1608  				DOOH: &openrtb2.DOOH{},
  1609  			}},
  1610  			expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"),
  1611  		},
  1612  		{
  1613  			description: "Three provided - invalid",
  1614  			givenRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  1615  				Site: &openrtb2.Site{},
  1616  				App:  &openrtb2.App{},
  1617  				DOOH: &openrtb2.DOOH{},
  1618  			}},
  1619  			expectedError: errors.New("No more than one of request.site or request.app or request.dooh can be defined"),
  1620  		},
  1621  	}
  1622  
  1623  	for _, test := range testCases {
  1624  		error := validateExactlyOneInventoryType(test.givenRequestWrapper)
  1625  		assert.Equalf(t, test.expectedError, error, "Error doesn't match: %s\n", test.description)
  1626  	}
  1627  
  1628  }
  1629  
  1630  func TestValidateRequest(t *testing.T) {
  1631  	deps := &endpointDeps{
  1632  		fakeUUIDGenerator{},
  1633  		&nobidExchange{},
  1634  		mockBidderParamValidator{},
  1635  		&mockStoredReqFetcher{},
  1636  		empty_fetcher.EmptyFetcher{},
  1637  		empty_fetcher.EmptyFetcher{},
  1638  		&config.Configuration{MaxRequestSize: maxSize},
  1639  		&metricsConfig.NilMetricsEngine{},
  1640  		analyticsBuild.New(&config.Analytics{}),
  1641  		map[string]string{},
  1642  		false,
  1643  		[]byte{},
  1644  		openrtb_ext.BuildBidderMap(),
  1645  		nil,
  1646  		nil,
  1647  		hardcodedResponseIPValidator{response: true},
  1648  		empty_fetcher.EmptyFetcher{},
  1649  		hooks.EmptyPlanBuilder{},
  1650  		nil,
  1651  		openrtb_ext.NormalizeBidderName,
  1652  	}
  1653  
  1654  	testCases := []struct {
  1655  		description           string
  1656  		givenIsAmp            bool
  1657  		givenRequestWrapper   *openrtb_ext.RequestWrapper
  1658  		givenHttpRequest      *http.Request
  1659  		givenAccount          *config.Account
  1660  		expectedErrorList     []error
  1661  		expectedChannelObject *openrtb_ext.ExtRequestPrebidChannel
  1662  	}{
  1663  		{
  1664  			description: "No errors in bid request with request.ext.prebid.channel info, expect validate request to throw no errors",
  1665  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  1666  				BidRequest: &openrtb2.BidRequest{
  1667  					ID:  "Some-ID",
  1668  					App: &openrtb2.App{},
  1669  					Imp: []openrtb2.Imp{
  1670  						{
  1671  							ID: "Some-Imp-ID",
  1672  							Banner: &openrtb2.Banner{
  1673  								Format: []openrtb2.Format{
  1674  									{
  1675  										W: 600,
  1676  										H: 500,
  1677  									},
  1678  									{
  1679  										W: 300,
  1680  										H: 600,
  1681  									},
  1682  								},
  1683  							},
  1684  							Ext: []byte(`{"appnexus":{"placementId": 12345678}}`),
  1685  						},
  1686  					},
  1687  					Ext: []byte(`{"prebid":{"channel": {"name": "nameOfChannel", "version": "1.0"}}}`),
  1688  				},
  1689  			},
  1690  			givenIsAmp:            false,
  1691  			expectedErrorList:     []error{},
  1692  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: "nameOfChannel", Version: "1.0"},
  1693  		},
  1694  		{
  1695  			description: "Error in bid request with request.ext.prebid.channel.name being blank, expect validate request to return error",
  1696  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  1697  				BidRequest: &openrtb2.BidRequest{
  1698  					ID:  "Some-ID",
  1699  					App: &openrtb2.App{},
  1700  					Imp: []openrtb2.Imp{
  1701  						{
  1702  							ID: "Some-Imp-ID",
  1703  							Banner: &openrtb2.Banner{
  1704  								Format: []openrtb2.Format{
  1705  									{
  1706  										W: 600,
  1707  										H: 500,
  1708  									},
  1709  									{
  1710  										W: 300,
  1711  										H: 600,
  1712  									},
  1713  								},
  1714  							},
  1715  							Ext: []byte(`{"appnexus":{"placementId": 12345678}}`),
  1716  						},
  1717  					},
  1718  					Ext: []byte(`{"prebid":{"channel": {"name": "", "version": ""}}}`),
  1719  				},
  1720  			},
  1721  			givenIsAmp:        false,
  1722  			expectedErrorList: []error{errors.New("ext.prebid.channel.name can't be empty")},
  1723  		},
  1724  		{
  1725  			description: "AliasGVLID validation error",
  1726  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  1727  				BidRequest: &openrtb2.BidRequest{
  1728  					ID:  "Some-ID",
  1729  					App: &openrtb2.App{},
  1730  					Imp: []openrtb2.Imp{
  1731  						{
  1732  							ID: "Some-Imp-ID",
  1733  							Banner: &openrtb2.Banner{
  1734  								Format: []openrtb2.Format{
  1735  									{
  1736  										W: 600,
  1737  										H: 500,
  1738  									},
  1739  									{
  1740  										W: 300,
  1741  										H: 600,
  1742  									},
  1743  								},
  1744  							},
  1745  							Ext: []byte(`{"appnexus":{"placementId": 12345678}}`),
  1746  						},
  1747  					},
  1748  					Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"pubmatic1":1}}}`),
  1749  				},
  1750  			},
  1751  			givenIsAmp:            false,
  1752  			expectedErrorList:     []error{errors.New("request.ext.prebid.aliasgvlids. vendorId 1 refers to unknown bidder alias: pubmatic1")},
  1753  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""},
  1754  		},
  1755  		{
  1756  			description: "AliasGVLID validation error as vendorID < 1",
  1757  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  1758  				BidRequest: &openrtb2.BidRequest{
  1759  					ID:  "Some-ID",
  1760  					App: &openrtb2.App{},
  1761  					Imp: []openrtb2.Imp{
  1762  						{
  1763  							ID: "Some-Imp-ID",
  1764  							Banner: &openrtb2.Banner{
  1765  								Format: []openrtb2.Format{
  1766  									{
  1767  										W: 600,
  1768  										H: 500,
  1769  									},
  1770  									{
  1771  										W: 300,
  1772  										H: 600,
  1773  									},
  1774  								},
  1775  							},
  1776  							Ext: []byte(`{"appnexus":{"placementId": 12345678}}`),
  1777  						},
  1778  					},
  1779  					Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"yahoossp":0}}}`),
  1780  				},
  1781  			},
  1782  			givenIsAmp:            false,
  1783  			expectedErrorList:     []error{errors.New("request.ext.prebid.aliasgvlids. Invalid vendorId 0 for alias: yahoossp. Choose a different vendorId, or remove this entry.")},
  1784  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""},
  1785  		},
  1786  		{
  1787  			description: "No errors in bid request with request.ext.prebid but no channel info, expect validate request to throw no errors and fill channel with app",
  1788  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  1789  				BidRequest: &openrtb2.BidRequest{
  1790  					ID:  "Some-ID",
  1791  					App: &openrtb2.App{},
  1792  					Imp: []openrtb2.Imp{
  1793  						{
  1794  							ID: "Some-Imp-ID",
  1795  							Banner: &openrtb2.Banner{
  1796  								Format: []openrtb2.Format{
  1797  									{
  1798  										W: 600,
  1799  										H: 500,
  1800  									},
  1801  									{
  1802  										W: 300,
  1803  										H: 600,
  1804  									},
  1805  								},
  1806  							},
  1807  							Ext: []byte(`{"appnexus":{"placementId": 12345678}}`),
  1808  						},
  1809  					},
  1810  					Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"yahoossp":1}}}`),
  1811  				},
  1812  			},
  1813  			givenIsAmp:            false,
  1814  			expectedErrorList:     []error{},
  1815  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""},
  1816  		},
  1817  		{
  1818  			description: "Minimum required site attributes missing",
  1819  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  1820  				BidRequest: &openrtb2.BidRequest{
  1821  					ID:   "Some-ID",
  1822  					Site: &openrtb2.Site{},
  1823  					Imp: []openrtb2.Imp{
  1824  						{
  1825  							ID: "Some-Imp-ID",
  1826  							Banner: &openrtb2.Banner{
  1827  								Format: []openrtb2.Format{
  1828  									{
  1829  										W: 600,
  1830  										H: 500,
  1831  									},
  1832  									{
  1833  										W: 300,
  1834  										H: 600,
  1835  									},
  1836  								},
  1837  							},
  1838  							Ext: []byte(`{"appnexus":{"placementId": 12345678}}`),
  1839  						},
  1840  					},
  1841  				},
  1842  			},
  1843  			expectedErrorList: []error{
  1844  				errors.New("request.site should include at least one of request.site.id or request.site.page."),
  1845  			},
  1846  		},
  1847  		{
  1848  			description: "Minimum required DOOH attributes missing",
  1849  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  1850  				BidRequest: &openrtb2.BidRequest{
  1851  					ID:   "Some-ID",
  1852  					DOOH: &openrtb2.DOOH{},
  1853  					Imp: []openrtb2.Imp{
  1854  						{
  1855  							ID: "Some-Imp-ID",
  1856  							Banner: &openrtb2.Banner{
  1857  								Format: []openrtb2.Format{
  1858  									{
  1859  										W: 600,
  1860  										H: 500,
  1861  									},
  1862  									{
  1863  										W: 300,
  1864  										H: 600,
  1865  									},
  1866  								},
  1867  							},
  1868  							Ext: []byte(`{"appnexus":{"placementId": 12345678}}`),
  1869  						},
  1870  					},
  1871  				},
  1872  			},
  1873  			expectedErrorList: []error{
  1874  				errors.New("request.dooh should include at least one of request.dooh.id or request.dooh.venuetype."),
  1875  			},
  1876  		},
  1877  	}
  1878  
  1879  	for _, test := range testCases {
  1880  		errorList := deps.validateRequest(test.givenAccount, test.givenHttpRequest, test.givenRequestWrapper, test.givenIsAmp, false, nil, false)
  1881  		assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
  1882  
  1883  		if len(errorList) == 0 {
  1884  			requestExt, err := test.givenRequestWrapper.GetRequestExt()
  1885  			assert.Empty(t, err, test.description)
  1886  			requestPrebid := requestExt.GetPrebid()
  1887  
  1888  			assert.Equalf(t, test.expectedChannelObject, requestPrebid.Channel, "Channel information isn't correct: %s\n", test.description)
  1889  		}
  1890  	}
  1891  }
  1892  
  1893  func TestValidateRequestExt(t *testing.T) {
  1894  	testCases := []struct {
  1895  		description     string
  1896  		givenRequestExt json.RawMessage
  1897  		expectedErrors  []string
  1898  	}{
  1899  		{
  1900  			description:     "nil",
  1901  			givenRequestExt: nil,
  1902  		},
  1903  		{
  1904  			description:     "prebid - nil",
  1905  			givenRequestExt: json.RawMessage(`{}`),
  1906  		},
  1907  		{
  1908  			description:     "prebid - empty",
  1909  			givenRequestExt: json.RawMessage(`{"prebid":{}}`),
  1910  		},
  1911  		{
  1912  			description:     "prebid cache - empty",
  1913  			givenRequestExt: json.RawMessage(`{"prebid":{"cache":{}}}`),
  1914  			expectedErrors:  []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`},
  1915  		},
  1916  		{
  1917  			description:     "prebid cache - bids - null",
  1918  			givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":null}}}`),
  1919  			expectedErrors:  []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`},
  1920  		},
  1921  		{
  1922  			description:     "prebid cache - bids - wrong type",
  1923  			givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":true}}}`),
  1924  			expectedErrors:  []string{"cannot unmarshal openrtb_ext.ExtRequestPrebidCache.Bids: expect { or n, but found t"},
  1925  		},
  1926  		{
  1927  			description:     "prebid cache - bids - provided",
  1928  			givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":{}}}}`),
  1929  		},
  1930  		{
  1931  			description:     "prebid cache - vastxml - null",
  1932  			givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"vastxml": null}}}`),
  1933  			expectedErrors:  []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`},
  1934  		},
  1935  		{
  1936  			description:     "prebid cache - vastxml - wrong type",
  1937  			givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"vastxml":true}}}`),
  1938  			expectedErrors:  []string{"cannot unmarshal openrtb_ext.ExtRequestPrebidCache.VastXML: expect { or n, but found t"},
  1939  		},
  1940  		{
  1941  			description:     "prebid cache - vastxml - provided",
  1942  			givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"vastxml":{}}}}`),
  1943  		},
  1944  		{
  1945  			description:     "prebid cache - bids + vastxml - provided",
  1946  			givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":{},"vastxml":{}}}}`),
  1947  		},
  1948  		{
  1949  			description:     "prebid price granularity invalid",
  1950  			givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{"pricegranularity":{"precision":-1,"ranges":[{"min":0,"max":20,"increment":0.1}]}}}}`),
  1951  			expectedErrors:  []string{"Price granularity error: precision must be non-negative"},
  1952  		},
  1953  		{
  1954  			description:     "prebid native media type price granualrity valid",
  1955  			givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{"mediatypepricegranularity":{"native":{"precision":3,"ranges":[{"max":20,"increment":4.5}]}}}}}`),
  1956  		},
  1957  		{
  1958  			description:     "valid multibid",
  1959  			givenRequestExt: json.RawMessage(`{"prebid": {"multibid": [{"Bidder": "pubmatic", "MaxBids": 2}]}}`),
  1960  		},
  1961  		{
  1962  			description:     "multibid with invalid entries",
  1963  			givenRequestExt: json.RawMessage(`{"prebid": {"multibid": [{"Bidder": "pubmatic"}, {"Bidder": "pubmatic", "MaxBids": 2}, {"Bidders": ["pubmatic"], "MaxBids": 3}]}}`),
  1964  			expectedErrors: []string{
  1965  				`maxBids not defined for {Bidder:pubmatic, Bidders:[], MaxBids:<nil>, TargetBidderCodePrefix:}`,
  1966  				`multiBid already defined for pubmatic, ignoring this instance {Bidder:, Bidders:[pubmatic], MaxBids:3, TargetBidderCodePrefix:}`,
  1967  			},
  1968  		},
  1969  	}
  1970  
  1971  	for _, test := range testCases {
  1972  		w := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.givenRequestExt}}
  1973  		errs := validateRequestExt(w)
  1974  
  1975  		if len(test.expectedErrors) > 0 {
  1976  			for i, expectedError := range test.expectedErrors {
  1977  				assert.EqualError(t, errs[i], expectedError, test.description)
  1978  			}
  1979  		} else {
  1980  			assert.Nil(t, errs, test.description)
  1981  		}
  1982  	}
  1983  }
  1984  
  1985  func TestValidateTargeting(t *testing.T) {
  1986  	testCases := []struct {
  1987  		name           string
  1988  		givenTargeting *openrtb_ext.ExtRequestTargeting
  1989  		expectedError  error
  1990  	}{
  1991  		{
  1992  			name:           "nil",
  1993  			givenTargeting: nil,
  1994  			expectedError:  nil,
  1995  		},
  1996  		{
  1997  			name: "price granularity ranges out of order",
  1998  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  1999  				PriceGranularity: &openrtb_ext.PriceGranularity{
  2000  					Precision: ptrutil.ToPtr(2),
  2001  					Ranges: []openrtb_ext.GranularityRange{
  2002  						{Min: 1.0, Max: 2.0, Increment: 0.2},
  2003  						{Min: 0.0, Max: 1.0, Increment: 0.5},
  2004  					},
  2005  				},
  2006  			},
  2007  			expectedError: errors.New(`Price granularity error: range list must be ordered with increasing "max"`),
  2008  		},
  2009  		{
  2010  			name: "media type price granularity video correct",
  2011  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2012  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2013  					Video: &openrtb_ext.PriceGranularity{
  2014  						Precision: ptrutil.ToPtr(2),
  2015  						Ranges: []openrtb_ext.GranularityRange{
  2016  							{Min: 0.0, Max: 10.0, Increment: 1},
  2017  						},
  2018  					},
  2019  				},
  2020  			},
  2021  			expectedError: nil,
  2022  		},
  2023  		{
  2024  			name: "media type price granularity banner correct",
  2025  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2026  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2027  					Banner: &openrtb_ext.PriceGranularity{
  2028  						Precision: ptrutil.ToPtr(2),
  2029  						Ranges: []openrtb_ext.GranularityRange{
  2030  							{Min: 0.0, Max: 10.0, Increment: 1},
  2031  						},
  2032  					},
  2033  				},
  2034  			},
  2035  			expectedError: nil,
  2036  		},
  2037  		{
  2038  			name: "media type price granularity native correct",
  2039  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2040  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2041  					Native: &openrtb_ext.PriceGranularity{
  2042  						Precision: ptrutil.ToPtr(2),
  2043  						Ranges: []openrtb_ext.GranularityRange{
  2044  							{Min: 0.0, Max: 20.0, Increment: 1},
  2045  						},
  2046  					},
  2047  				},
  2048  			},
  2049  			expectedError: nil,
  2050  		},
  2051  		{
  2052  			name: "media type price granularity video and banner correct",
  2053  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2054  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2055  					Banner: &openrtb_ext.PriceGranularity{
  2056  						Precision: ptrutil.ToPtr(2),
  2057  						Ranges: []openrtb_ext.GranularityRange{
  2058  							{Min: 0.0, Max: 10.0, Increment: 1},
  2059  						},
  2060  					},
  2061  					Video: &openrtb_ext.PriceGranularity{
  2062  						Precision: ptrutil.ToPtr(2),
  2063  						Ranges: []openrtb_ext.GranularityRange{
  2064  							{Min: 0.0, Max: 10.0, Increment: 1},
  2065  						},
  2066  					},
  2067  				},
  2068  			},
  2069  			expectedError: nil,
  2070  		},
  2071  		{
  2072  			name: "media type price granularity video incorrect",
  2073  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2074  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2075  					Video: &openrtb_ext.PriceGranularity{
  2076  						Precision: ptrutil.ToPtr(2),
  2077  						Ranges: []openrtb_ext.GranularityRange{
  2078  							{Min: 0.0, Max: 10.0, Increment: -1},
  2079  						},
  2080  					},
  2081  				},
  2082  			},
  2083  			expectedError: errors.New("Price granularity error: increment must be a nonzero positive number"),
  2084  		},
  2085  		{
  2086  			name: "media type price granularity banner incorrect",
  2087  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2088  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2089  					Banner: &openrtb_ext.PriceGranularity{
  2090  						Precision: ptrutil.ToPtr(2),
  2091  						Ranges: []openrtb_ext.GranularityRange{
  2092  							{Min: 0.0, Max: 0.0, Increment: 1},
  2093  						},
  2094  					},
  2095  				},
  2096  			},
  2097  			expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""),
  2098  		},
  2099  		{
  2100  			name: "media type price granularity native incorrect",
  2101  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2102  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2103  					Native: &openrtb_ext.PriceGranularity{
  2104  						Precision: ptrutil.ToPtr(2),
  2105  						Ranges: []openrtb_ext.GranularityRange{
  2106  							{Min: 0.0, Max: 0.0, Increment: 1},
  2107  						},
  2108  					},
  2109  				},
  2110  			},
  2111  			expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""),
  2112  		},
  2113  		{
  2114  			name: "media type price granularity video correct and banner incorrect",
  2115  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2116  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2117  					Banner: &openrtb_ext.PriceGranularity{
  2118  						Precision: ptrutil.ToPtr(2),
  2119  						Ranges: []openrtb_ext.GranularityRange{
  2120  							{Min: 0.0, Max: 10.0, Increment: -1},
  2121  						},
  2122  					},
  2123  					Video: &openrtb_ext.PriceGranularity{
  2124  						Precision: ptrutil.ToPtr(2),
  2125  						Ranges: []openrtb_ext.GranularityRange{
  2126  							{Min: 0.0, Max: 0.0, Increment: 1},
  2127  						},
  2128  					},
  2129  				},
  2130  			},
  2131  			expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""),
  2132  		},
  2133  		{
  2134  			name: "media type price granularity native incorrect and banner correct",
  2135  			givenTargeting: &openrtb_ext.ExtRequestTargeting{
  2136  				MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{
  2137  					Native: &openrtb_ext.PriceGranularity{
  2138  						Precision: ptrutil.ToPtr(2),
  2139  						Ranges: []openrtb_ext.GranularityRange{
  2140  							{Min: 0.0, Max: 10.0, Increment: -1},
  2141  						},
  2142  					},
  2143  					Video: &openrtb_ext.PriceGranularity{
  2144  						Precision: ptrutil.ToPtr(2),
  2145  						Ranges: []openrtb_ext.GranularityRange{
  2146  							{Min: 0.0, Max: 0.0, Increment: 1},
  2147  						},
  2148  					},
  2149  				},
  2150  			},
  2151  			expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""),
  2152  		},
  2153  	}
  2154  
  2155  	for _, tc := range testCases {
  2156  		t.Run(tc.name, func(t *testing.T) {
  2157  			assert.Equal(t, tc.expectedError, validateTargeting(tc.givenTargeting), "Targeting")
  2158  		})
  2159  	}
  2160  }
  2161  
  2162  func TestValidatePriceGranularity(t *testing.T) {
  2163  	testCases := []struct {
  2164  		description           string
  2165  		givenPriceGranularity *openrtb_ext.PriceGranularity
  2166  		expectedError         error
  2167  	}{
  2168  		{
  2169  			description: "Precision is nil",
  2170  			givenPriceGranularity: &openrtb_ext.PriceGranularity{
  2171  				Precision: nil,
  2172  			},
  2173  			expectedError: errors.New("Price granularity error: precision is required"),
  2174  		},
  2175  		{
  2176  			description: "Precision is negative",
  2177  			givenPriceGranularity: &openrtb_ext.PriceGranularity{
  2178  				Precision: ptrutil.ToPtr(-1),
  2179  			},
  2180  			expectedError: errors.New("Price granularity error: precision must be non-negative"),
  2181  		},
  2182  		{
  2183  			description: "Precision is too big",
  2184  			givenPriceGranularity: &openrtb_ext.PriceGranularity{
  2185  				Precision: ptrutil.ToPtr(20),
  2186  			},
  2187  			expectedError: errors.New("Price granularity error: precision of more than 15 significant figures is not supported"),
  2188  		},
  2189  		{
  2190  			description: "price granularity ranges out of order",
  2191  			givenPriceGranularity: &openrtb_ext.PriceGranularity{
  2192  				Precision: ptrutil.ToPtr(2),
  2193  				Ranges: []openrtb_ext.GranularityRange{
  2194  					{Min: 1.0, Max: 2.0, Increment: 0.2},
  2195  					{Min: 0.0, Max: 1.0, Increment: 0.5},
  2196  				},
  2197  			},
  2198  			expectedError: errors.New(`Price granularity error: range list must be ordered with increasing "max"`),
  2199  		},
  2200  		{
  2201  			description: "price granularity negative increment",
  2202  			givenPriceGranularity: &openrtb_ext.PriceGranularity{
  2203  				Precision: ptrutil.ToPtr(2),
  2204  				Ranges: []openrtb_ext.GranularityRange{
  2205  					{Min: 0.0, Max: 1.0, Increment: -0.1},
  2206  				},
  2207  			},
  2208  			expectedError: errors.New("Price granularity error: increment must be a nonzero positive number"),
  2209  		},
  2210  		{
  2211  			description: "price granularity correct",
  2212  			givenPriceGranularity: &openrtb_ext.PriceGranularity{
  2213  				Precision: ptrutil.ToPtr(2),
  2214  				Ranges: []openrtb_ext.GranularityRange{
  2215  					{Min: 0.0, Max: 10.0, Increment: 1},
  2216  				},
  2217  			},
  2218  			expectedError: nil,
  2219  		},
  2220  		{
  2221  			description: "price granularity with correct precision and ranges not specified",
  2222  			givenPriceGranularity: &openrtb_ext.PriceGranularity{
  2223  				Precision: ptrutil.ToPtr(2),
  2224  			},
  2225  			expectedError: nil,
  2226  		},
  2227  	}
  2228  
  2229  	for _, tc := range testCases {
  2230  		t.Run(tc.description, func(t *testing.T) {
  2231  			assert.Equal(t, tc.expectedError, validatePriceGranularity(tc.givenPriceGranularity))
  2232  		})
  2233  	}
  2234  }
  2235  
  2236  func TestValidateOrFillChannel(t *testing.T) {
  2237  	testCases := []struct {
  2238  		description           string
  2239  		givenIsAmp            bool
  2240  		givenRequestWrapper   *openrtb_ext.RequestWrapper
  2241  		expectedError         error
  2242  		expectedChannelObject *openrtb_ext.ExtRequestPrebidChannel
  2243  	}{
  2244  		{
  2245  			description: "No request.ext info in app request, so we expect channel name to be set to app",
  2246  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2247  				BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}},
  2248  			},
  2249  			givenIsAmp:            false,
  2250  			expectedError:         nil,
  2251  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""},
  2252  		},
  2253  		{
  2254  			description: "No request.ext info in amp request, so we expect channel name to be set to amp",
  2255  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2256  				BidRequest: &openrtb2.BidRequest{},
  2257  			},
  2258  			givenIsAmp:            true,
  2259  			expectedError:         nil,
  2260  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""},
  2261  		},
  2262  		{
  2263  			description: "Channel object in request with populated name/version, we expect same name/version in object that's created",
  2264  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2265  				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"channel": {"name": "video", "version": "1.0"}}}`)},
  2266  			},
  2267  			givenIsAmp:            false,
  2268  			expectedError:         nil,
  2269  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: "video", Version: "1.0"},
  2270  		},
  2271  		{
  2272  			description: "No channel object in site request, expect nil",
  2273  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2274  				BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{}, Ext: []byte(`{"prebid":{}}`)},
  2275  			},
  2276  			givenIsAmp:            false,
  2277  			expectedError:         nil,
  2278  			expectedChannelObject: nil,
  2279  		},
  2280  		{
  2281  			description: "No channel name given in channel object, we expect error to be thrown",
  2282  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2283  				BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{"prebid":{"channel": {"name": "", "version": ""}}}`)},
  2284  			},
  2285  			givenIsAmp:            false,
  2286  			expectedError:         errors.New("ext.prebid.channel.name can't be empty"),
  2287  			expectedChannelObject: nil,
  2288  		},
  2289  		{
  2290  			description: "App request, has request.ext, no request.ext.prebid, expect channel name to be filled with app",
  2291  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2292  				BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{}`)},
  2293  			},
  2294  			givenIsAmp:            false,
  2295  			expectedError:         nil,
  2296  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""},
  2297  		},
  2298  		{
  2299  			description: "App request, has request.ext.prebid, but no channel object, expect channel name to be filled with app",
  2300  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2301  				BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{"prebid":{}}`)},
  2302  			},
  2303  			givenIsAmp:            false,
  2304  			expectedError:         nil,
  2305  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""},
  2306  		},
  2307  		{
  2308  			description: "Amp request, has request.ext, no request.ext.prebid, expect channel name to be filled with amp",
  2309  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2310  				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{}`)},
  2311  			},
  2312  			givenIsAmp:            true,
  2313  			expectedError:         nil,
  2314  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""},
  2315  		},
  2316  		{
  2317  			description: "Amp request, has request.ext.prebid, but no channel object, expect channel name to be filled with amp",
  2318  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2319  				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{}}`)},
  2320  			},
  2321  			givenIsAmp:            true,
  2322  			expectedError:         nil,
  2323  			expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""},
  2324  		},
  2325  	}
  2326  
  2327  	for _, test := range testCases {
  2328  		err := validateOrFillChannel(test.givenRequestWrapper, test.givenIsAmp)
  2329  		assert.Equalf(t, test.expectedError, err, "Error doesn't match: %s\n", test.description)
  2330  
  2331  		if err == nil {
  2332  			requestExt, err := test.givenRequestWrapper.GetRequestExt()
  2333  			assert.Empty(t, err, test.description)
  2334  			requestPrebid := requestExt.GetPrebid()
  2335  
  2336  			assert.Equalf(t, test.expectedChannelObject, requestPrebid.Channel, "Channel information isn't correct: %s\n", test.description)
  2337  		}
  2338  	}
  2339  }
  2340  
  2341  func TestSetIntegrationType(t *testing.T) {
  2342  	deps := &endpointDeps{
  2343  		fakeUUIDGenerator{},
  2344  		&nobidExchange{},
  2345  		mockBidderParamValidator{},
  2346  		&mockStoredReqFetcher{},
  2347  		empty_fetcher.EmptyFetcher{},
  2348  		empty_fetcher.EmptyFetcher{},
  2349  		&config.Configuration{},
  2350  		&metricsConfig.NilMetricsEngine{},
  2351  		analyticsBuild.New(&config.Analytics{}),
  2352  		map[string]string{},
  2353  		false,
  2354  		[]byte{},
  2355  		openrtb_ext.BuildBidderMap(),
  2356  		nil,
  2357  		nil,
  2358  		hardcodedResponseIPValidator{response: true},
  2359  		empty_fetcher.EmptyFetcher{},
  2360  		hooks.EmptyPlanBuilder{},
  2361  		nil,
  2362  		openrtb_ext.NormalizeBidderName,
  2363  	}
  2364  
  2365  	testCases := []struct {
  2366  		description             string
  2367  		givenRequestWrapper     *openrtb_ext.RequestWrapper
  2368  		givenAccount            *config.Account
  2369  		expectedIntegrationType string
  2370  	}{
  2371  		{
  2372  			description: "Request has integration type defined, expect that same integration type",
  2373  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2374  				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"integration": "TestIntegrationType"}}`)},
  2375  			},
  2376  			givenAccount:            &config.Account{DefaultIntegration: "TestDefaultIntegration"},
  2377  			expectedIntegrationType: "TestIntegrationType",
  2378  		},
  2379  		{
  2380  			description: "Request doesn't have request.ext.prebid path, expect default integration value",
  2381  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2382  				BidRequest: &openrtb2.BidRequest{Ext: []byte(``)},
  2383  			},
  2384  			givenAccount:            &config.Account{DefaultIntegration: "TestDefaultIntegration"},
  2385  			expectedIntegrationType: "TestDefaultIntegration",
  2386  		},
  2387  		{
  2388  			description: "Request has blank integration in request, expect default integration value ",
  2389  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  2390  				BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"integration": ""}}`)},
  2391  			},
  2392  			givenAccount:            &config.Account{DefaultIntegration: "TestDefaultIntegration"},
  2393  			expectedIntegrationType: "TestDefaultIntegration",
  2394  		},
  2395  	}
  2396  
  2397  	for _, test := range testCases {
  2398  		err := deps.setIntegrationType(test.givenRequestWrapper, test.givenAccount)
  2399  		assert.Empty(t, err, test.description)
  2400  		integrationTypeFromReq, err2 := getIntegrationFromRequest(test.givenRequestWrapper)
  2401  		assert.Empty(t, err2, test.description)
  2402  		assert.Equalf(t, test.expectedIntegrationType, integrationTypeFromReq, "Integration type information isn't correct: %s\n", test.description)
  2403  	}
  2404  }
  2405  
  2406  func TestStoredRequestGenerateUuid(t *testing.T) {
  2407  	uuid := "foo"
  2408  
  2409  	deps := &endpointDeps{
  2410  		fakeUUIDGenerator{id: "foo", err: nil},
  2411  		&nobidExchange{},
  2412  		mockBidderParamValidator{},
  2413  		&mockStoredReqFetcher{},
  2414  		empty_fetcher.EmptyFetcher{},
  2415  		empty_fetcher.EmptyFetcher{},
  2416  		&config.Configuration{MaxRequestSize: maxSize},
  2417  		&metricsConfig.NilMetricsEngine{},
  2418  		analyticsBuild.New(&config.Analytics{}),
  2419  		map[string]string{},
  2420  		false,
  2421  		[]byte{},
  2422  		openrtb_ext.BuildBidderMap(),
  2423  		nil,
  2424  		nil,
  2425  		hardcodedResponseIPValidator{response: true},
  2426  		empty_fetcher.EmptyFetcher{},
  2427  		hooks.EmptyPlanBuilder{},
  2428  		nil,
  2429  		openrtb_ext.NormalizeBidderName,
  2430  	}
  2431  
  2432  	req := &openrtb2.BidRequest{}
  2433  
  2434  	testCases := []struct {
  2435  		description            string
  2436  		givenRawData           string
  2437  		givenGenerateRequestID bool
  2438  		expectedID             string
  2439  		expectedCur            string
  2440  	}{
  2441  		{
  2442  			description:            "GenerateRequestID is true, rawData is an app request and has stored bid request we should generate uuid",
  2443  			givenRawData:           testBidRequests[2],
  2444  			givenGenerateRequestID: true,
  2445  			expectedID:             uuid,
  2446  		},
  2447  		{
  2448  			description:            "GenerateRequestID is true, rawData is a site request, has stored bid, and stored bidrequestID is not the macro {{UUID}}, we should not generate uuid",
  2449  			givenRawData:           testBidRequests[3],
  2450  			givenGenerateRequestID: true,
  2451  			expectedID:             "ThisID",
  2452  		},
  2453  		{
  2454  			description:            "GenerateRequestID is false, rawData is an app request and has stored bid, and stored bidrequestID is the macro {{UUID}}, so we should generate uuid",
  2455  			givenRawData:           testBidRequests[4],
  2456  			givenGenerateRequestID: false,
  2457  			expectedID:             uuid,
  2458  		},
  2459  		{
  2460  			description:            "GenerateRequestID is true, rawData is an app request, but no stored bid, we should not generate uuid",
  2461  			givenRawData:           testBidRequests[0],
  2462  			givenGenerateRequestID: true,
  2463  			expectedID:             "ThisID",
  2464  		},
  2465  		{
  2466  			description:            "GenerateRequestID is false and macro ID is not present, so we should not generate uuid",
  2467  			givenRawData:           testBidRequests[0],
  2468  			givenGenerateRequestID: false,
  2469  			expectedID:             "ThisID",
  2470  		},
  2471  		{
  2472  			description:            "GenerateRequestID is false, and rawData is a site request, and macro {{UUID}} is present, we should generate uuid",
  2473  			givenRawData:           testBidRequests[1],
  2474  			givenGenerateRequestID: false,
  2475  			expectedID:             uuid,
  2476  		},
  2477  		{
  2478  			description:            "Macro ID {{UUID}} case sensitivity check meaning a macro that is lowercase {{uuid}} shouldn't generate a uuid",
  2479  			givenRawData:           testBidRequests[2],
  2480  			givenGenerateRequestID: false,
  2481  			expectedID:             "ThisID",
  2482  		},
  2483  		{
  2484  			description:            "Test to check that stored requests are being merged properly when UUID isn't being generated",
  2485  			givenRawData:           testBidRequests[5],
  2486  			givenGenerateRequestID: false,
  2487  			expectedID:             "ThisID",
  2488  			expectedCur:            "USD",
  2489  		},
  2490  	}
  2491  
  2492  	for _, test := range testCases {
  2493  		deps.cfg.GenerateRequestID = test.givenGenerateRequestID
  2494  		impInfo, errs := parseImpInfo([]byte(test.givenRawData))
  2495  		assert.Empty(t, errs, test.description)
  2496  		storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(context.Background(), json.RawMessage(test.givenRawData), impInfo)
  2497  		assert.Empty(t, errs, test.description)
  2498  		newRequest, _, errList := deps.processStoredRequests(json.RawMessage(test.givenRawData), impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest)
  2499  		assert.Empty(t, errList, test.description)
  2500  
  2501  		if err := jsonutil.UnmarshalValid(newRequest, req); err != nil {
  2502  			t.Errorf("processStoredRequests Error: %s", err.Error())
  2503  		}
  2504  		if test.expectedCur != "" {
  2505  			assert.Equalf(t, test.expectedCur, req.Cur[0], "The stored request wasn't merged properly: %s\n", test.description)
  2506  		}
  2507  		assert.Equalf(t, test.expectedID, req.ID, "The Bid Request ID is incorrect: %s\n", test.description)
  2508  	}
  2509  }
  2510  
  2511  // TestOversizedRequest makes sure we behave properly when the request size exceeds the configured max.
  2512  func TestOversizedRequest(t *testing.T) {
  2513  	reqBody := validRequest(t, "site.json")
  2514  	deps := &endpointDeps{
  2515  		fakeUUIDGenerator{},
  2516  		&nobidExchange{},
  2517  		mockBidderParamValidator{},
  2518  		&mockStoredReqFetcher{},
  2519  		empty_fetcher.EmptyFetcher{},
  2520  		empty_fetcher.EmptyFetcher{},
  2521  		&config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)},
  2522  		&metricsConfig.NilMetricsEngine{},
  2523  		analyticsBuild.New(&config.Analytics{}),
  2524  		map[string]string{},
  2525  		false,
  2526  		[]byte{},
  2527  		openrtb_ext.BuildBidderMap(),
  2528  		nil,
  2529  		nil,
  2530  		hardcodedResponseIPValidator{response: true},
  2531  		empty_fetcher.EmptyFetcher{},
  2532  		hooks.EmptyPlanBuilder{},
  2533  		nil,
  2534  		openrtb_ext.NormalizeBidderName,
  2535  	}
  2536  
  2537  	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
  2538  	recorder := httptest.NewRecorder()
  2539  
  2540  	deps.Auction(recorder, req, nil)
  2541  
  2542  	if recorder.Code != http.StatusBadRequest {
  2543  		t.Errorf("Endpoint should return a 400 if the request exceeds the size max.")
  2544  	}
  2545  
  2546  	if bytesRead, err := req.Body.Read(make([]byte, 1)); bytesRead != 0 || err != io.EOF {
  2547  		t.Errorf("The request body should still be fully read.")
  2548  	}
  2549  }
  2550  
  2551  // TestRequestSizeEdgeCase makes sure we behave properly when the request size *equals* the configured max.
  2552  func TestRequestSizeEdgeCase(t *testing.T) {
  2553  	reqBody := validRequest(t, "site.json")
  2554  	deps := &endpointDeps{
  2555  		fakeUUIDGenerator{},
  2556  		&nobidExchange{},
  2557  		mockBidderParamValidator{},
  2558  		&mockStoredReqFetcher{},
  2559  		empty_fetcher.EmptyFetcher{},
  2560  		empty_fetcher.EmptyFetcher{},
  2561  		&config.Configuration{MaxRequestSize: int64(len(reqBody))},
  2562  		&metricsConfig.NilMetricsEngine{},
  2563  		analyticsBuild.New(&config.Analytics{}),
  2564  		map[string]string{},
  2565  		false,
  2566  		[]byte{},
  2567  		openrtb_ext.BuildBidderMap(),
  2568  		nil,
  2569  		nil,
  2570  		hardcodedResponseIPValidator{response: true},
  2571  		empty_fetcher.EmptyFetcher{},
  2572  		hooks.EmptyPlanBuilder{},
  2573  		nil,
  2574  		openrtb_ext.NormalizeBidderName,
  2575  	}
  2576  
  2577  	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
  2578  	recorder := httptest.NewRecorder()
  2579  
  2580  	deps.Auction(recorder, req, nil)
  2581  
  2582  	if recorder.Code != http.StatusOK {
  2583  		t.Errorf("Endpoint should return a 200 if the request equals the size max.")
  2584  	}
  2585  
  2586  	if bytesRead, err := req.Body.Read(make([]byte, 1)); bytesRead != 0 || err != io.EOF {
  2587  		t.Errorf("The request body should have been read to completion.")
  2588  	}
  2589  }
  2590  
  2591  // TestNoEncoding prevents #231.
  2592  func TestNoEncoding(t *testing.T) {
  2593  	endpoint, _ := NewEndpoint(
  2594  		fakeUUIDGenerator{},
  2595  		&mockExchange{},
  2596  		mockBidderParamValidator{},
  2597  		&mockStoredReqFetcher{},
  2598  		empty_fetcher.EmptyFetcher{},
  2599  		&config.Configuration{MaxRequestSize: maxSize},
  2600  		&metricsConfig.NilMetricsEngine{},
  2601  		analyticsBuild.New(&config.Analytics{}),
  2602  		map[string]string{},
  2603  		[]byte{},
  2604  		openrtb_ext.BuildBidderMap(),
  2605  		empty_fetcher.EmptyFetcher{},
  2606  		hooks.EmptyPlanBuilder{},
  2607  		nil,
  2608  	)
  2609  	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
  2610  	recorder := httptest.NewRecorder()
  2611  	endpoint(recorder, request, nil)
  2612  
  2613  	if !strings.Contains(recorder.Body.String(), "<script></script>") {
  2614  		t.Errorf("The Response from the exchange should not be html-encoded")
  2615  	}
  2616  }
  2617  
  2618  // TestTimeoutParser makes sure we parse tmax properly.
  2619  func TestTimeoutParser(t *testing.T) {
  2620  	reqJson := json.RawMessage(`{"tmax":22}`)
  2621  	timeout := parseTimeout(reqJson, 11*time.Millisecond)
  2622  	if timeout != 22*time.Millisecond {
  2623  		t.Errorf("Failed to parse tmax properly. Expected %d, got %d", 22*time.Millisecond, timeout)
  2624  	}
  2625  }
  2626  
  2627  func TestImplicitAMPNoExt(t *testing.T) {
  2628  	httpReq, err := http.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
  2629  	if !assert.NoError(t, err) {
  2630  		return
  2631  	}
  2632  
  2633  	reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  2634  		Site: &openrtb2.Site{},
  2635  	}}
  2636  
  2637  	setSiteImplicitly(httpReq, reqWrapper)
  2638  
  2639  	assert.NoError(t, reqWrapper.RebuildRequest())
  2640  	assert.JSONEq(t, `{"amp":0}`, string(reqWrapper.Site.Ext))
  2641  }
  2642  
  2643  func TestImplicitAMPOtherExt(t *testing.T) {
  2644  	httpReq, err := http.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
  2645  	if !assert.NoError(t, err) {
  2646  		return
  2647  	}
  2648  
  2649  	reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  2650  		Site: &openrtb2.Site{
  2651  			Ext: json.RawMessage(`{"other":true}`),
  2652  		},
  2653  	}}
  2654  
  2655  	setSiteImplicitly(httpReq, reqWrapper)
  2656  
  2657  	assert.NoError(t, reqWrapper.RebuildRequest())
  2658  	assert.JSONEq(t, `{"amp":0,"other":true}`, string(reqWrapper.Site.Ext))
  2659  }
  2660  
  2661  func TestExplicitAMP(t *testing.T) {
  2662  	httpReq, err := http.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site-amp.json")))
  2663  	if !assert.NoError(t, err) {
  2664  		return
  2665  	}
  2666  
  2667  	bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  2668  		Site: &openrtb2.Site{
  2669  			Ext: json.RawMessage(`{"amp":1}`),
  2670  		},
  2671  	}}
  2672  	setSiteImplicitly(httpReq, bidReq)
  2673  	assert.JSONEq(t, `{"amp":1}`, string(bidReq.Site.Ext))
  2674  }
  2675  
  2676  // TestContentType prevents #328
  2677  func TestContentType(t *testing.T) {
  2678  	endpoint, _ := NewEndpoint(
  2679  		fakeUUIDGenerator{},
  2680  		&mockExchange{},
  2681  		mockBidderParamValidator{},
  2682  		&mockStoredReqFetcher{},
  2683  		empty_fetcher.EmptyFetcher{},
  2684  		&config.Configuration{MaxRequestSize: maxSize},
  2685  		&metricsConfig.NilMetricsEngine{},
  2686  		analyticsBuild.New(&config.Analytics{}),
  2687  		map[string]string{},
  2688  		[]byte{},
  2689  		openrtb_ext.BuildBidderMap(),
  2690  		empty_fetcher.EmptyFetcher{},
  2691  		hooks.EmptyPlanBuilder{},
  2692  		nil,
  2693  	)
  2694  	request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
  2695  	recorder := httptest.NewRecorder()
  2696  	endpoint(recorder, request, nil)
  2697  
  2698  	if recorder.Header().Get("Content-Type") != "application/json" {
  2699  		t.Errorf("Content-Type should be application/json. Got %s", recorder.Header().Get("Content-Type"))
  2700  	}
  2701  }
  2702  
  2703  func TestValidateImpExt(t *testing.T) {
  2704  	type testCase struct {
  2705  		description    string
  2706  		impExt         json.RawMessage
  2707  		expectedImpExt string
  2708  		expectedErrs   []error
  2709  	}
  2710  	testGroups := []struct {
  2711  		description string
  2712  		testCases   []testCase
  2713  	}{
  2714  		{
  2715  			"Empty",
  2716  			[]testCase{
  2717  				{
  2718  					description:    "Empty",
  2719  					impExt:         nil,
  2720  					expectedImpExt: "",
  2721  					expectedErrs:   []error{errors.New("request.imp[0].ext is required")},
  2722  				},
  2723  			},
  2724  		},
  2725  		{
  2726  			"Unknown bidder tests",
  2727  			[]testCase{
  2728  				{
  2729  					description:    "Unknown Bidder only",
  2730  					impExt:         json.RawMessage(`{"unknownbidder":{"placement_id":555}}`),
  2731  					expectedImpExt: `{"unknownbidder":{"placement_id":555}}`,
  2732  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2733  				},
  2734  				{
  2735  					description:    "Unknown Prebid Ext Bidder only",
  2736  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`),
  2737  					expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`,
  2738  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2739  				},
  2740  				{
  2741  					description:    "Unknown Prebid Ext Bidder + First Party Data Context",
  2742  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`),
  2743  					expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
  2744  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2745  				},
  2746  				{
  2747  					description:    "Unknown Bidder + First Party Data Context",
  2748  					impExt:         json.RawMessage(`{"unknownbidder":{"placement_id":555} ,"context":{"data":{"keywords":"prebid server example"}}}`),
  2749  					expectedImpExt: `{"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`,
  2750  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2751  				},
  2752  				{
  2753  					description:    "Unknown Bidder + Disabled Bidder",
  2754  					impExt:         json.RawMessage(`{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`),
  2755  					expectedImpExt: `{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`,
  2756  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2757  				},
  2758  				{
  2759  					description:    "Unknown Bidder + Disabled Prebid Ext Bidder",
  2760  					impExt:         json.RawMessage(`{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`),
  2761  					expectedImpExt: `{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`,
  2762  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2763  				},
  2764  			},
  2765  		},
  2766  		{
  2767  			"Disabled bidder tests",
  2768  			[]testCase{
  2769  				{
  2770  					description:    "Disabled Bidder",
  2771  					impExt:         json.RawMessage(`{"disabledbidder":{"foo":"bar"}}`),
  2772  					expectedImpExt: `{"disabledbidder":{"foo":"bar"}}`,
  2773  					expectedErrs: []error{
  2774  						&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."},
  2775  						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
  2776  					},
  2777  					// if only bidder(s) found in request.imp[x].ext.{biddername} or request.imp[x].ext.prebid.bidder.{biddername} are disabled, return error
  2778  				},
  2779  				{
  2780  					description:    "Disabled Prebid Ext Bidder",
  2781  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`),
  2782  					expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`,
  2783  					expectedErrs: []error{
  2784  						&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."},
  2785  						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
  2786  					},
  2787  				},
  2788  				{
  2789  					description:    "Disabled Bidder + First Party Data Context",
  2790  					impExt:         json.RawMessage(`{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`),
  2791  					expectedImpExt: `{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`,
  2792  					expectedErrs: []error{
  2793  						&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."},
  2794  						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
  2795  					},
  2796  				},
  2797  				{
  2798  					description:    "Disabled Prebid Ext Bidder + First Party Data Context",
  2799  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`),
  2800  					expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`,
  2801  					expectedErrs: []error{
  2802  						&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."},
  2803  						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
  2804  					},
  2805  				},
  2806  			},
  2807  		},
  2808  		{
  2809  			"First Party only",
  2810  			[]testCase{
  2811  				{
  2812  					description:    "First Party Data Context",
  2813  					impExt:         json.RawMessage(`{"context":{"data":{"keywords":"prebid server example"}}}`),
  2814  					expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}}}`,
  2815  					expectedErrs: []error{
  2816  						errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"),
  2817  					},
  2818  				},
  2819  			},
  2820  		},
  2821  		{
  2822  			"Valid bidder tests",
  2823  			[]testCase{
  2824  				{
  2825  					description:    "Valid bidder root ext",
  2826  					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555}}`),
  2827  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`,
  2828  					expectedErrs:   []error{},
  2829  				},
  2830  				{
  2831  					description:    "Valid bidder in prebid field",
  2832  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`),
  2833  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`,
  2834  					expectedErrs:   []error{},
  2835  				},
  2836  				{
  2837  					description:    "Valid Bidder + First Party Data Context",
  2838  					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`),
  2839  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
  2840  					expectedErrs:   []error{},
  2841  				},
  2842  				{
  2843  					description:    "Valid Prebid Ext Bidder + First Party Data Context",
  2844  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}} ,"context":{"data":{"keywords":"prebid server example"}}}`),
  2845  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
  2846  					expectedErrs:   []error{},
  2847  				},
  2848  				{
  2849  					description:    "Valid Bidder + Unknown Bidder",
  2850  					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`),
  2851  					expectedImpExt: `{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`,
  2852  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2853  				},
  2854  				{
  2855  					description:    "Valid Bidder + Disabled Bidder",
  2856  					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`),
  2857  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`,
  2858  					expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}},
  2859  				},
  2860  				{
  2861  					description:    "Valid Bidder + Disabled Bidder + First Party Data Context",
  2862  					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`),
  2863  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
  2864  					expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}},
  2865  				},
  2866  				{
  2867  					description:    "Valid Bidder + Disabled Bidder + Unknown Bidder + First Party Data Context",
  2868  					impExt:         json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`),
  2869  					expectedImpExt: `{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`,
  2870  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2871  				},
  2872  				{
  2873  					description:    "Valid Prebid Ext Bidder + Disabled Bidder Ext",
  2874  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}}}`),
  2875  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}}}`,
  2876  					expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}},
  2877  				},
  2878  				{
  2879  					description:    "Valid Prebid Ext Bidder + Disabled Ext Bidder + First Party Data Context",
  2880  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`),
  2881  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
  2882  					expectedErrs:   []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}},
  2883  				},
  2884  				{
  2885  					description:    "Valid Prebid Ext Bidder + Disabled Ext Bidder + Unknown Ext + First Party Data Context",
  2886  					impExt:         json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`),
  2887  					expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`,
  2888  					expectedErrs:   []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")},
  2889  				},
  2890  			},
  2891  		},
  2892  	}
  2893  
  2894  	deps := &endpointDeps{
  2895  		fakeUUIDGenerator{},
  2896  		&nobidExchange{},
  2897  		mockBidderParamValidator{},
  2898  		&mockStoredReqFetcher{},
  2899  		empty_fetcher.EmptyFetcher{},
  2900  		empty_fetcher.EmptyFetcher{},
  2901  		&config.Configuration{MaxRequestSize: int64(8096)},
  2902  		&metricsConfig.NilMetricsEngine{},
  2903  		analyticsBuild.New(&config.Analytics{}),
  2904  		map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."},
  2905  		false,
  2906  		[]byte{},
  2907  		openrtb_ext.BuildBidderMap(),
  2908  		nil,
  2909  		nil,
  2910  		hardcodedResponseIPValidator{response: true},
  2911  		empty_fetcher.EmptyFetcher{},
  2912  		hooks.EmptyPlanBuilder{},
  2913  		nil,
  2914  		openrtb_ext.NormalizeBidderName,
  2915  	}
  2916  
  2917  	for _, group := range testGroups {
  2918  		for _, test := range group.testCases {
  2919  			t.Run(test.description, func(t *testing.T) {
  2920  				imp := &openrtb2.Imp{Ext: test.impExt}
  2921  				impWrapper := &openrtb_ext.ImpWrapper{Imp: imp}
  2922  
  2923  				errs := deps.validateImpExt(impWrapper, nil, 0, false, nil)
  2924  
  2925  				assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp")
  2926  
  2927  				if len(test.expectedImpExt) > 0 {
  2928  					assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description)
  2929  				} else {
  2930  					assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description)
  2931  				}
  2932  				assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description)
  2933  			})
  2934  		}
  2935  	}
  2936  }
  2937  
  2938  func validRequest(t *testing.T, filename string) string {
  2939  	requestData, err := os.ReadFile("sample-requests/valid-whole/supplementary/" + filename)
  2940  	if err != nil {
  2941  		t.Fatalf("Failed to fetch a valid request: %v", err)
  2942  	}
  2943  	testBidRequest, _, _, err := jsonparser.Get(requestData, "mockBidRequest")
  2944  	assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err)
  2945  
  2946  	return string(testBidRequest)
  2947  }
  2948  
  2949  func TestCurrencyTrunc(t *testing.T) {
  2950  	deps := &endpointDeps{
  2951  		fakeUUIDGenerator{},
  2952  		&nobidExchange{},
  2953  		mockBidderParamValidator{},
  2954  		&mockStoredReqFetcher{},
  2955  		empty_fetcher.EmptyFetcher{},
  2956  		empty_fetcher.EmptyFetcher{},
  2957  		&config.Configuration{},
  2958  		&metricsConfig.NilMetricsEngine{},
  2959  		analyticsBuild.New(&config.Analytics{}),
  2960  		map[string]string{},
  2961  		false,
  2962  		[]byte{},
  2963  		openrtb_ext.BuildBidderMap(),
  2964  		nil,
  2965  		nil,
  2966  		hardcodedResponseIPValidator{response: true},
  2967  		empty_fetcher.EmptyFetcher{},
  2968  		hooks.EmptyPlanBuilder{},
  2969  		nil,
  2970  		openrtb_ext.NormalizeBidderName,
  2971  	}
  2972  
  2973  	ui := int64(1)
  2974  	req := openrtb2.BidRequest{
  2975  		ID: "anyRequestID",
  2976  		Imp: []openrtb2.Imp{
  2977  			{
  2978  				ID: "anyImpID",
  2979  				Banner: &openrtb2.Banner{
  2980  					W: &ui,
  2981  					H: &ui,
  2982  				},
  2983  				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
  2984  			},
  2985  		},
  2986  		Site: &openrtb2.Site{
  2987  			ID: "anySiteID",
  2988  		},
  2989  		Cur: []string{"USD", "EUR"},
  2990  	}
  2991  
  2992  	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
  2993  
  2994  	expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"}
  2995  	assert.ElementsMatch(t, errL, []error{&expectedError})
  2996  }
  2997  
  2998  func TestCCPAInvalid(t *testing.T) {
  2999  	deps := &endpointDeps{
  3000  		fakeUUIDGenerator{},
  3001  		&nobidExchange{},
  3002  		mockBidderParamValidator{},
  3003  		&mockStoredReqFetcher{},
  3004  		empty_fetcher.EmptyFetcher{},
  3005  		empty_fetcher.EmptyFetcher{},
  3006  		&config.Configuration{},
  3007  		&metricsConfig.NilMetricsEngine{},
  3008  		analyticsBuild.New(&config.Analytics{}),
  3009  		map[string]string{},
  3010  		false,
  3011  		[]byte{},
  3012  		openrtb_ext.BuildBidderMap(),
  3013  		nil,
  3014  		nil,
  3015  		hardcodedResponseIPValidator{response: true},
  3016  		empty_fetcher.EmptyFetcher{},
  3017  		hooks.EmptyPlanBuilder{},
  3018  		nil,
  3019  		openrtb_ext.NormalizeBidderName,
  3020  	}
  3021  
  3022  	ui := int64(1)
  3023  	req := openrtb2.BidRequest{
  3024  		ID: "anyRequestID",
  3025  		Imp: []openrtb2.Imp{
  3026  			{
  3027  				ID: "anyImpID",
  3028  				Banner: &openrtb2.Banner{
  3029  					W: &ui,
  3030  					H: &ui,
  3031  				},
  3032  				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
  3033  			},
  3034  		},
  3035  		Site: &openrtb2.Site{
  3036  			ID: "anySiteID",
  3037  		},
  3038  		Regs: &openrtb2.Regs{
  3039  			Ext: json.RawMessage(`{"us_privacy": "invalid by length"}`),
  3040  		},
  3041  	}
  3042  
  3043  	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
  3044  
  3045  	expectedWarning := errortypes.Warning{
  3046  		Message:     "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)",
  3047  		WarningCode: errortypes.InvalidPrivacyConsentWarningCode}
  3048  	assert.ElementsMatch(t, errL, []error{&expectedWarning})
  3049  }
  3050  
  3051  func TestNoSaleInvalid(t *testing.T) {
  3052  	deps := &endpointDeps{
  3053  		fakeUUIDGenerator{},
  3054  		&nobidExchange{},
  3055  		mockBidderParamValidator{},
  3056  		&mockStoredReqFetcher{},
  3057  		empty_fetcher.EmptyFetcher{},
  3058  		empty_fetcher.EmptyFetcher{},
  3059  		&config.Configuration{},
  3060  		&metricsConfig.NilMetricsEngine{},
  3061  		analyticsBuild.New(&config.Analytics{}),
  3062  		map[string]string{},
  3063  		false,
  3064  		[]byte{},
  3065  		openrtb_ext.BuildBidderMap(),
  3066  		nil,
  3067  		nil,
  3068  		hardcodedResponseIPValidator{response: true},
  3069  		empty_fetcher.EmptyFetcher{},
  3070  		hooks.EmptyPlanBuilder{},
  3071  		nil,
  3072  		openrtb_ext.NormalizeBidderName,
  3073  	}
  3074  
  3075  	ui := int64(1)
  3076  	req := openrtb2.BidRequest{
  3077  		ID: "anyRequestID",
  3078  		Imp: []openrtb2.Imp{
  3079  			{
  3080  				ID: "anyImpID",
  3081  				Banner: &openrtb2.Banner{
  3082  					W: &ui,
  3083  					H: &ui,
  3084  				},
  3085  				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
  3086  			},
  3087  		},
  3088  		Site: &openrtb2.Site{
  3089  			ID: "anySiteID",
  3090  		},
  3091  		Regs: &openrtb2.Regs{
  3092  			Ext: json.RawMessage(`{"us_privacy": "1NYN"}`),
  3093  		},
  3094  		Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`),
  3095  	}
  3096  
  3097  	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
  3098  
  3099  	expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided")
  3100  	assert.ElementsMatch(t, errL, []error{expectedError})
  3101  }
  3102  
  3103  func TestValidateSourceTID(t *testing.T) {
  3104  	cfg := &config.Configuration{
  3105  		AutoGenSourceTID: true,
  3106  	}
  3107  
  3108  	deps := &endpointDeps{
  3109  		fakeUUIDGenerator{},
  3110  		&nobidExchange{},
  3111  		mockBidderParamValidator{},
  3112  		&mockStoredReqFetcher{},
  3113  		empty_fetcher.EmptyFetcher{},
  3114  		empty_fetcher.EmptyFetcher{},
  3115  		cfg,
  3116  		&metricsConfig.NilMetricsEngine{},
  3117  		analyticsBuild.New(&config.Analytics{}),
  3118  		map[string]string{},
  3119  		false,
  3120  		[]byte{},
  3121  		openrtb_ext.BuildBidderMap(),
  3122  		nil,
  3123  		nil,
  3124  		hardcodedResponseIPValidator{response: true},
  3125  		empty_fetcher.EmptyFetcher{},
  3126  		hooks.EmptyPlanBuilder{},
  3127  		nil,
  3128  		openrtb_ext.NormalizeBidderName,
  3129  	}
  3130  
  3131  	ui := int64(1)
  3132  	req := openrtb2.BidRequest{
  3133  		ID: "anyRequestID",
  3134  		Imp: []openrtb2.Imp{
  3135  			{
  3136  				ID: "anyImpID",
  3137  				Banner: &openrtb2.Banner{
  3138  					W: &ui,
  3139  					H: &ui,
  3140  				},
  3141  				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
  3142  			},
  3143  		},
  3144  		Site: &openrtb2.Site{
  3145  			ID: "anySiteID",
  3146  		},
  3147  	}
  3148  
  3149  	deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
  3150  	assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID")
  3151  }
  3152  
  3153  func TestSChainInvalid(t *testing.T) {
  3154  	deps := &endpointDeps{
  3155  		fakeUUIDGenerator{},
  3156  		&nobidExchange{},
  3157  		mockBidderParamValidator{},
  3158  		&mockStoredReqFetcher{},
  3159  		empty_fetcher.EmptyFetcher{},
  3160  		empty_fetcher.EmptyFetcher{},
  3161  		&config.Configuration{},
  3162  		&metricsConfig.NilMetricsEngine{},
  3163  		analyticsBuild.New(&config.Analytics{}),
  3164  		map[string]string{},
  3165  		false,
  3166  		[]byte{},
  3167  		openrtb_ext.BuildBidderMap(),
  3168  		nil,
  3169  		nil,
  3170  		hardcodedResponseIPValidator{response: true},
  3171  		empty_fetcher.EmptyFetcher{},
  3172  		hooks.EmptyPlanBuilder{},
  3173  		nil,
  3174  		openrtb_ext.NormalizeBidderName,
  3175  	}
  3176  
  3177  	ui := int64(1)
  3178  	req := openrtb2.BidRequest{
  3179  		ID: "anyRequestID",
  3180  		Imp: []openrtb2.Imp{
  3181  			{
  3182  				ID: "anyImpID",
  3183  				Banner: &openrtb2.Banner{
  3184  					W: &ui,
  3185  					H: &ui,
  3186  				},
  3187  				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
  3188  			},
  3189  		},
  3190  		Site: &openrtb2.Site{
  3191  			ID: "anySiteID",
  3192  		},
  3193  		Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`),
  3194  	}
  3195  
  3196  	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
  3197  
  3198  	expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.")
  3199  	assert.ElementsMatch(t, errL, []error{expectedError})
  3200  }
  3201  
  3202  func TestMapSChains(t *testing.T) {
  3203  	const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}`
  3204  	const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}`
  3205  
  3206  	seller1SChainUnpacked := openrtb2.SupplyChain{
  3207  		Complete: 1,
  3208  		Nodes: []openrtb2.SupplyChainNode{{
  3209  			ASI: "directseller1.com",
  3210  			SID: "00001",
  3211  			RID: "BidRequest1",
  3212  			HP:  openrtb2.Int8Ptr(1),
  3213  		}},
  3214  		Ver: "1.0",
  3215  	}
  3216  
  3217  	tests := []struct {
  3218  		description         string
  3219  		bidRequest          openrtb2.BidRequest
  3220  		wantReqExtSChain    *openrtb2.SupplyChain
  3221  		wantSourceExtSChain *openrtb2.SupplyChain
  3222  		wantError           bool
  3223  	}{
  3224  		{
  3225  			description: "invalid req.ext",
  3226  			bidRequest: openrtb2.BidRequest{
  3227  				Ext: json.RawMessage(`{"prebid":{"schains":invalid}}`),
  3228  				Source: &openrtb2.Source{
  3229  					Ext: json.RawMessage(`{}`),
  3230  				},
  3231  			},
  3232  			wantError: true,
  3233  		},
  3234  		{
  3235  			description: "invalid source.ext",
  3236  			bidRequest: openrtb2.BidRequest{
  3237  				Ext: json.RawMessage(`{}`),
  3238  				Source: &openrtb2.Source{
  3239  					Ext: json.RawMessage(`{"schain":invalid}}`),
  3240  				},
  3241  			},
  3242  			wantError: true,
  3243  		},
  3244  		{
  3245  			description: "req.ext.prebid.schains, req.source.ext.schain and req.ext.schain are nil",
  3246  			bidRequest: openrtb2.BidRequest{
  3247  				Ext: json.RawMessage(`{}`),
  3248  				Source: &openrtb2.Source{
  3249  					Ext: json.RawMessage(`{}`),
  3250  				},
  3251  			},
  3252  			wantReqExtSChain:    nil,
  3253  			wantSourceExtSChain: nil,
  3254  		},
  3255  		{
  3256  			description: "req.ext.prebid.schains is not nil",
  3257  			bidRequest: openrtb2.BidRequest{
  3258  				Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`),
  3259  				Source: &openrtb2.Source{
  3260  					Ext: json.RawMessage(`{}`),
  3261  				},
  3262  			},
  3263  			wantReqExtSChain:    nil,
  3264  			wantSourceExtSChain: nil,
  3265  		},
  3266  		{
  3267  			description: "req.source.ext is not nil",
  3268  			bidRequest: openrtb2.BidRequest{
  3269  				Ext: json.RawMessage(`{}`),
  3270  				Source: &openrtb2.Source{
  3271  					Ext: json.RawMessage(`{` + seller1SChain + `}`),
  3272  				},
  3273  			},
  3274  			wantReqExtSChain:    nil,
  3275  			wantSourceExtSChain: &seller1SChainUnpacked,
  3276  		},
  3277  		{
  3278  			description: "req.ext.schain is not nil",
  3279  			bidRequest: openrtb2.BidRequest{
  3280  				Ext: json.RawMessage(`{` + seller1SChain + `}`),
  3281  				Source: &openrtb2.Source{
  3282  					Ext: json.RawMessage(`{}`),
  3283  				},
  3284  			},
  3285  			wantReqExtSChain:    nil,
  3286  			wantSourceExtSChain: &seller1SChainUnpacked,
  3287  		},
  3288  		{
  3289  			description: "req.source.ext.schain and req.ext.schain are not nil",
  3290  			bidRequest: openrtb2.BidRequest{
  3291  				Ext: json.RawMessage(`{` + seller2SChain + `}`),
  3292  				Source: &openrtb2.Source{
  3293  					Ext: json.RawMessage(`{` + seller1SChain + `}`),
  3294  				},
  3295  			},
  3296  			wantReqExtSChain:    nil,
  3297  			wantSourceExtSChain: &seller1SChainUnpacked,
  3298  		},
  3299  	}
  3300  
  3301  	for _, test := range tests {
  3302  		reqWrapper := openrtb_ext.RequestWrapper{
  3303  			BidRequest: &test.bidRequest,
  3304  		}
  3305  
  3306  		err := mapSChains(&reqWrapper)
  3307  
  3308  		if test.wantError {
  3309  			assert.NotNil(t, err, test.description)
  3310  		} else {
  3311  			assert.Nil(t, err, test.description)
  3312  
  3313  			reqExt, err := reqWrapper.GetRequestExt()
  3314  			if err != nil {
  3315  				assert.Fail(t, "Error getting request ext from wrapper", test.description)
  3316  			}
  3317  			reqExtSChain := reqExt.GetSChain()
  3318  			assert.Equal(t, test.wantReqExtSChain, reqExtSChain, test.description)
  3319  
  3320  			sourceExt, err := reqWrapper.GetSourceExt()
  3321  			if err != nil {
  3322  				assert.Fail(t, "Error getting source ext from wrapper", test.description)
  3323  			}
  3324  			sourceExtSChain := sourceExt.GetSChain()
  3325  			assert.Equal(t, test.wantSourceExtSChain, sourceExtSChain, test.description)
  3326  		}
  3327  	}
  3328  }
  3329  
  3330  func TestSearchAccountID(t *testing.T) {
  3331  	// Correctness for lookup within Publisher object left to TestGetAccountID
  3332  	// This however tests the expected lookup paths in outer site, app and dooh
  3333  	testCases := []struct {
  3334  		description       string
  3335  		request           []byte
  3336  		expectedAccID     string
  3337  		expectedError     error
  3338  		expectedIsAppReq  bool
  3339  		expectedIsSiteReq bool
  3340  		expectedIsDOOHReq bool
  3341  	}{
  3342  		{
  3343  			description:       "No publisher available",
  3344  			request:           []byte(`{}`),
  3345  			expectedAccID:     "",
  3346  			expectedError:     nil,
  3347  			expectedIsAppReq:  false,
  3348  			expectedIsDOOHReq: false,
  3349  		},
  3350  		{
  3351  			description:       "Publisher.ID doesn't exist",
  3352  			request:           []byte(`{"site":{"publisher":{}}}`),
  3353  			expectedAccID:     "",
  3354  			expectedError:     nil,
  3355  			expectedIsAppReq:  false,
  3356  			expectedIsDOOHReq: false,
  3357  		},
  3358  		{
  3359  			description:       "Publisher.ID not a string",
  3360  			request:           []byte(`{"site":{"publisher":{"id":42}}}`),
  3361  			expectedAccID:     "",
  3362  			expectedError:     errors.New("site.publisher.id must be a string"),
  3363  			expectedIsAppReq:  false,
  3364  			expectedIsDOOHReq: false,
  3365  		},
  3366  		{
  3367  			description:       "Publisher available in request.site",
  3368  			request:           []byte(`{"site":{"publisher":{"id":"42"}}}`),
  3369  			expectedAccID:     "42",
  3370  			expectedError:     nil,
  3371  			expectedIsAppReq:  false,
  3372  			expectedIsDOOHReq: false,
  3373  		},
  3374  		{
  3375  			description:       "Publisher available in request.app",
  3376  			request:           []byte(`{"app":{"publisher":{"id":"42"}}}`),
  3377  			expectedAccID:     "42",
  3378  			expectedError:     nil,
  3379  			expectedIsAppReq:  true,
  3380  			expectedIsDOOHReq: false,
  3381  		},
  3382  		{
  3383  			description:       "Publisher available in request.dooh",
  3384  			request:           []byte(`{"dooh":{"publisher":{"id":"42"}}}`),
  3385  			expectedAccID:     "42",
  3386  			expectedError:     nil,
  3387  			expectedIsAppReq:  false,
  3388  			expectedIsDOOHReq: true,
  3389  		},
  3390  	}
  3391  
  3392  	for _, test := range testCases {
  3393  		accountId, isAppReq, isDOOHReq, err := searchAccountId(test.request)
  3394  		assert.Equal(t, test.expectedAccID, accountId, "searchAccountID should return expected account ID for test case: %s", test.description)
  3395  		assert.Equal(t, test.expectedIsAppReq, isAppReq, "searchAccountID should return expected isAppReq for test case: %s", test.description)
  3396  		assert.Equal(t, test.expectedIsDOOHReq, isDOOHReq, "searchAccountID should return expected isDOOHReq for test case: %s", test.description)
  3397  		assert.Equal(t, test.expectedError, err, "searchAccountID should return expected error for test case: %s", test.description)
  3398  	}
  3399  
  3400  }
  3401  
  3402  func TestGetAccountID(t *testing.T) {
  3403  	testPubID := "test-pub"
  3404  	testParentAccount := "test-account"
  3405  	testPubExt := openrtb_ext.ExtPublisher{
  3406  		Prebid: &openrtb_ext.ExtPublisherPrebid{
  3407  			ParentAccount: &testParentAccount,
  3408  		},
  3409  	}
  3410  	testPubExtJSON, err := jsonutil.Marshal(testPubExt)
  3411  	assert.NoError(t, err)
  3412  
  3413  	testCases := []struct {
  3414  		description   string
  3415  		pub           *openrtb2.Publisher
  3416  		expectedAccID string
  3417  	}{
  3418  		{
  3419  			description: "Publisher.ID and Publisher.Ext.Prebid.ParentAccount both present",
  3420  			pub: &openrtb2.Publisher{
  3421  				ID:  testPubID,
  3422  				Ext: testPubExtJSON,
  3423  			},
  3424  			expectedAccID: testParentAccount,
  3425  		},
  3426  		{
  3427  			description: "Only Publisher.Ext.Prebid.ParentAccount present",
  3428  			pub: &openrtb2.Publisher{
  3429  				ID:  "",
  3430  				Ext: testPubExtJSON,
  3431  			},
  3432  			expectedAccID: testParentAccount,
  3433  		},
  3434  		{
  3435  			description: "Only Publisher.ID present",
  3436  			pub: &openrtb2.Publisher{
  3437  				ID: testPubID,
  3438  			},
  3439  			expectedAccID: testPubID,
  3440  		},
  3441  		{
  3442  			description:   "Neither Publisher.ID or Publisher.Ext.Prebid.ParentAccount present",
  3443  			pub:           &openrtb2.Publisher{},
  3444  			expectedAccID: metrics.PublisherUnknown,
  3445  		},
  3446  		{
  3447  			description:   "Publisher is nil",
  3448  			pub:           nil,
  3449  			expectedAccID: metrics.PublisherUnknown,
  3450  		},
  3451  	}
  3452  
  3453  	for _, test := range testCases {
  3454  		acc := getAccountID(test.pub)
  3455  		assert.Equal(t, test.expectedAccID, acc, "getAccountID should return expected account for test case: %s", test.description)
  3456  	}
  3457  }
  3458  
  3459  func TestSanitizeRequest(t *testing.T) {
  3460  	testCases := []struct {
  3461  		description  string
  3462  		req          *openrtb2.BidRequest
  3463  		ipValidator  iputil.IPValidator
  3464  		expectedIPv4 string
  3465  		expectedIPv6 string
  3466  	}{
  3467  		{
  3468  			description: "Empty",
  3469  			req: &openrtb2.BidRequest{
  3470  				Device: &openrtb2.Device{
  3471  					IP:   "",
  3472  					IPv6: "",
  3473  				},
  3474  			},
  3475  			expectedIPv4: "",
  3476  			expectedIPv6: "",
  3477  		},
  3478  		{
  3479  			description: "Valid",
  3480  			req: &openrtb2.BidRequest{
  3481  				Device: &openrtb2.Device{
  3482  					IP:   "1.1.1.1",
  3483  					IPv6: "1111::",
  3484  				},
  3485  			},
  3486  			ipValidator:  hardcodedResponseIPValidator{response: true},
  3487  			expectedIPv4: "1.1.1.1",
  3488  			expectedIPv6: "1111::",
  3489  		},
  3490  		{
  3491  			description: "Invalid",
  3492  			req: &openrtb2.BidRequest{
  3493  				Device: &openrtb2.Device{
  3494  					IP:   "1.1.1.1",
  3495  					IPv6: "1111::",
  3496  				},
  3497  			},
  3498  			ipValidator:  hardcodedResponseIPValidator{response: false},
  3499  			expectedIPv4: "",
  3500  			expectedIPv6: "",
  3501  		},
  3502  		{
  3503  			description: "Invalid - Wrong IP Types",
  3504  			req: &openrtb2.BidRequest{
  3505  				Device: &openrtb2.Device{
  3506  					IP:   "1111::",
  3507  					IPv6: "1.1.1.1",
  3508  				},
  3509  			},
  3510  			ipValidator:  hardcodedResponseIPValidator{response: true},
  3511  			expectedIPv4: "",
  3512  			expectedIPv6: "",
  3513  		},
  3514  		{
  3515  			description: "Malformed",
  3516  			req: &openrtb2.BidRequest{
  3517  				Device: &openrtb2.Device{
  3518  					IP:   "malformed",
  3519  					IPv6: "malformed",
  3520  				},
  3521  			},
  3522  			expectedIPv4: "",
  3523  			expectedIPv6: "",
  3524  		},
  3525  	}
  3526  
  3527  	for _, test := range testCases {
  3528  		bidReq := &openrtb_ext.RequestWrapper{BidRequest: test.req}
  3529  
  3530  		sanitizeRequest(bidReq, test.ipValidator)
  3531  		assert.Equal(t, test.expectedIPv4, test.req.Device.IP, test.description+":ipv4")
  3532  		assert.Equal(t, test.expectedIPv6, test.req.Device.IPv6, test.description+":ipv6")
  3533  	}
  3534  }
  3535  
  3536  func TestValidateAndFillSourceTID(t *testing.T) {
  3537  	testTID := "some-tid"
  3538  	testCases := []struct {
  3539  		description         string
  3540  		req                 *openrtb_ext.RequestWrapper
  3541  		generateRequestID   bool
  3542  		hasStoredBidRequest bool
  3543  		isAmp               bool
  3544  		expectRandImpTID    bool
  3545  		expectRandSourceTID bool
  3546  		expectSourceTid     *string
  3547  		expectImpTid        *string
  3548  	}{
  3549  		{
  3550  			description: "req source.tid not set, expect random value",
  3551  			req: &openrtb_ext.RequestWrapper{
  3552  				BidRequest: &openrtb2.BidRequest{
  3553  					ID:     "1",
  3554  					Imp:    []openrtb2.Imp{{ID: "1"}},
  3555  					Source: &openrtb2.Source{},
  3556  				},
  3557  			},
  3558  			generateRequestID:   false,
  3559  			hasStoredBidRequest: false,
  3560  			isAmp:               false,
  3561  			expectRandSourceTID: true,
  3562  			expectRandImpTID:    false,
  3563  		},
  3564  		{
  3565  			description: "req source.tid set to {{UUID}}, expect to be replaced by random value",
  3566  			req: &openrtb_ext.RequestWrapper{
  3567  				BidRequest: &openrtb2.BidRequest{
  3568  					ID:     "1",
  3569  					Imp:    []openrtb2.Imp{{ID: "1"}},
  3570  					Source: &openrtb2.Source{TID: "{{UUID}}"},
  3571  				},
  3572  			},
  3573  			generateRequestID:   false,
  3574  			hasStoredBidRequest: false,
  3575  			isAmp:               false,
  3576  			expectRandSourceTID: true,
  3577  			expectRandImpTID:    false,
  3578  		},
  3579  		{
  3580  			description: "req source.tid is set, isAmp = true, generateRequestID = true, expect to be replaced by random value",
  3581  			req: &openrtb_ext.RequestWrapper{
  3582  				BidRequest: &openrtb2.BidRequest{
  3583  					ID:     "1",
  3584  					Imp:    []openrtb2.Imp{{ID: "1"}},
  3585  					Source: &openrtb2.Source{TID: "test-tid"},
  3586  				},
  3587  			},
  3588  			generateRequestID:   true,
  3589  			hasStoredBidRequest: false,
  3590  			isAmp:               true,
  3591  			expectRandSourceTID: true,
  3592  			expectRandImpTID:    false,
  3593  		},
  3594  		{
  3595  			description: "req source.tid is set,  hasStoredBidRequest = true, generateRequestID = true, expect to be replaced by random value",
  3596  			req: &openrtb_ext.RequestWrapper{
  3597  				BidRequest: &openrtb2.BidRequest{
  3598  					ID:     "1",
  3599  					Imp:    []openrtb2.Imp{{ID: "1"}},
  3600  					Source: &openrtb2.Source{TID: "test-tid"},
  3601  				},
  3602  			},
  3603  			generateRequestID:   true,
  3604  			hasStoredBidRequest: true,
  3605  			isAmp:               false,
  3606  			expectRandSourceTID: true,
  3607  			expectRandImpTID:    false,
  3608  		},
  3609  		{
  3610  			description: "req source.tid is set,  hasStoredBidRequest = true, generateRequestID = false, expect NOT to be replaced by random value",
  3611  			req: &openrtb_ext.RequestWrapper{
  3612  				BidRequest: &openrtb2.BidRequest{
  3613  					ID:     "1",
  3614  					Imp:    []openrtb2.Imp{{ID: "1"}},
  3615  					Source: &openrtb2.Source{TID: testTID},
  3616  				},
  3617  			},
  3618  			generateRequestID:   false,
  3619  			hasStoredBidRequest: true,
  3620  			isAmp:               false,
  3621  			expectRandSourceTID: false,
  3622  			expectRandImpTID:    false,
  3623  			expectSourceTid:     &testTID,
  3624  		},
  3625  		{
  3626  			description: "req imp.ext.tid not set, expect random value",
  3627  			req: &openrtb_ext.RequestWrapper{
  3628  				BidRequest: &openrtb2.BidRequest{
  3629  					ID:     "1",
  3630  					Imp:    []openrtb2.Imp{{ID: "1"}},
  3631  					Source: &openrtb2.Source{},
  3632  				},
  3633  			},
  3634  			generateRequestID:   false,
  3635  			hasStoredBidRequest: false,
  3636  			isAmp:               false,
  3637  			expectRandSourceTID: false,
  3638  			expectRandImpTID:    true,
  3639  		},
  3640  		{
  3641  			description: "req imp.ext.tid set to {{UUID}}, expect random value",
  3642  			req: &openrtb_ext.RequestWrapper{
  3643  				BidRequest: &openrtb2.BidRequest{
  3644  					ID:     "1",
  3645  					Imp:    []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"tid": "{{UUID}}"}`)}},
  3646  					Source: &openrtb2.Source{},
  3647  				},
  3648  			},
  3649  			generateRequestID:   false,
  3650  			hasStoredBidRequest: false,
  3651  			isAmp:               false,
  3652  			expectRandSourceTID: false,
  3653  			expectRandImpTID:    true,
  3654  		},
  3655  		{
  3656  			description: "req imp.tid is set,  hasStoredBidRequest = true, generateRequestID = true, expect to be replaced by random value",
  3657  			req: &openrtb_ext.RequestWrapper{
  3658  				BidRequest: &openrtb2.BidRequest{
  3659  					ID:     "1",
  3660  					Imp:    []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"tid": "some-tid"}`)}},
  3661  					Source: &openrtb2.Source{TID: "test-tid"},
  3662  				},
  3663  			},
  3664  			generateRequestID:   true,
  3665  			hasStoredBidRequest: true,
  3666  			isAmp:               false,
  3667  			expectRandSourceTID: false,
  3668  			expectRandImpTID:    true,
  3669  		},
  3670  		{
  3671  			description: "req imp.tid is set,  isAmp = true, generateRequestID = true, expect to be replaced by random value",
  3672  			req: &openrtb_ext.RequestWrapper{
  3673  				BidRequest: &openrtb2.BidRequest{
  3674  					ID:     "1",
  3675  					Imp:    []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"tid": "some-tid"}`)}},
  3676  					Source: &openrtb2.Source{TID: "test-tid"},
  3677  				},
  3678  			},
  3679  			generateRequestID:   true,
  3680  			hasStoredBidRequest: false,
  3681  			isAmp:               true,
  3682  			expectRandSourceTID: false,
  3683  			expectRandImpTID:    true,
  3684  		},
  3685  		{
  3686  			description: "req imp.tid is set,  hasStoredBidRequest = true, generateRequestID = false, expect NOT to be replaced by random value",
  3687  			req: &openrtb_ext.RequestWrapper{
  3688  				BidRequest: &openrtb2.BidRequest{
  3689  					ID:     "1",
  3690  					Imp:    []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"tid": "some-tid"}`)}},
  3691  					Source: &openrtb2.Source{TID: testTID},
  3692  				},
  3693  			},
  3694  			generateRequestID:   false,
  3695  			hasStoredBidRequest: true,
  3696  			isAmp:               false,
  3697  			expectRandSourceTID: false,
  3698  			expectRandImpTID:    false,
  3699  			expectImpTid:        &testTID,
  3700  		},
  3701  	}
  3702  
  3703  	for _, test := range testCases {
  3704  		_ = validateAndFillSourceTID(test.req, test.generateRequestID, test.hasStoredBidRequest, test.isAmp)
  3705  		impWrapper := &openrtb_ext.ImpWrapper{}
  3706  		impWrapper.Imp = &test.req.Imp[0]
  3707  		ie, _ := impWrapper.GetImpExt()
  3708  		impTID := ie.GetTid()
  3709  		if test.expectRandSourceTID {
  3710  			assert.NotEmpty(t, test.req.Source.TID, test.description)
  3711  		} else if test.expectRandImpTID {
  3712  			assert.NotEqual(t, testTID, impTID, test.description)
  3713  			assert.NotEmpty(t, impTID, test.description)
  3714  		} else if test.expectSourceTid != nil {
  3715  			assert.Equal(t, test.req.Source.TID, *test.expectSourceTid, test.description)
  3716  		} else if test.expectImpTid != nil {
  3717  			assert.Equal(t, impTID, *test.expectImpTid, test.description)
  3718  		}
  3719  	}
  3720  }
  3721  
  3722  func TestEidPermissionsInvalid(t *testing.T) {
  3723  	deps := &endpointDeps{
  3724  		fakeUUIDGenerator{},
  3725  		&nobidExchange{},
  3726  		mockBidderParamValidator{},
  3727  		&mockStoredReqFetcher{},
  3728  		empty_fetcher.EmptyFetcher{},
  3729  		empty_fetcher.EmptyFetcher{},
  3730  		&config.Configuration{},
  3731  		&metricsConfig.NilMetricsEngine{},
  3732  		analyticsBuild.New(&config.Analytics{}),
  3733  		map[string]string{},
  3734  		false,
  3735  		[]byte{},
  3736  		openrtb_ext.BuildBidderMap(),
  3737  		nil,
  3738  		nil,
  3739  		hardcodedResponseIPValidator{response: true},
  3740  		empty_fetcher.EmptyFetcher{},
  3741  		hooks.EmptyPlanBuilder{},
  3742  		nil,
  3743  		openrtb_ext.NormalizeBidderName,
  3744  	}
  3745  
  3746  	ui := int64(1)
  3747  	req := openrtb2.BidRequest{
  3748  		ID: "anyRequestID",
  3749  		Imp: []openrtb2.Imp{
  3750  			{
  3751  				ID: "anyImpID",
  3752  				Banner: &openrtb2.Banner{
  3753  					W: &ui,
  3754  					H: &ui,
  3755  				},
  3756  				Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`),
  3757  			},
  3758  		},
  3759  		Site: &openrtb2.Site{
  3760  			ID: "anySiteID",
  3761  		},
  3762  		Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`),
  3763  	}
  3764  
  3765  	errL := deps.validateRequest(nil, nil, &openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false)
  3766  
  3767  	expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`)
  3768  	assert.ElementsMatch(t, errL, []error{expectedError})
  3769  }
  3770  
  3771  func TestValidateEidPermissions(t *testing.T) {
  3772  	knownBidders := map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}
  3773  	knownAliases := map[string]string{"b": "b"}
  3774  
  3775  	testCases := []struct {
  3776  		name          string
  3777  		request       *openrtb_ext.ExtRequest
  3778  		expectedError error
  3779  	}{
  3780  		{
  3781  			name:          "valid-empty-ext",
  3782  			request:       &openrtb_ext.ExtRequest{},
  3783  			expectedError: nil,
  3784  		},
  3785  		{
  3786  			name:          "valid-nil-ext.prebid.data",
  3787  			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{}},
  3788  			expectedError: nil,
  3789  		},
  3790  		{
  3791  			name:          "valid-empty-ext.prebid.data",
  3792  			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{}}},
  3793  			expectedError: nil,
  3794  		},
  3795  		{
  3796  			name:          "valid-nil-ext.prebid.data.eidpermissions",
  3797  			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: nil}}},
  3798  			expectedError: nil,
  3799  		},
  3800  		{
  3801  			name:          "valid-none",
  3802  			request:       &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}}}},
  3803  			expectedError: nil,
  3804  		},
  3805  		{
  3806  			name: "valid-one",
  3807  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3808  				{Source: "sourceA", Bidders: []string{"a"}},
  3809  			}}}},
  3810  			expectedError: nil,
  3811  		},
  3812  		{
  3813  			name: "valid-one-case-insensitive",
  3814  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3815  				{Source: "sourceA", Bidders: []string{"A"}},
  3816  			}}}},
  3817  			expectedError: nil,
  3818  		},
  3819  		{
  3820  			name: "valid-many",
  3821  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3822  				{Source: "sourceA", Bidders: []string{"a"}},
  3823  				{Source: "sourceB", Bidders: []string{"a"}},
  3824  			}}}},
  3825  			expectedError: nil,
  3826  		},
  3827  		{
  3828  			name: "invalid-missing-source",
  3829  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3830  				{Source: "sourceA", Bidders: []string{"a"}},
  3831  				{Bidders: []string{"a"}},
  3832  			}}}},
  3833  			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing required field: "source"`),
  3834  		},
  3835  		{
  3836  			name: "invalid-duplicate-source",
  3837  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3838  				{Source: "sourceA", Bidders: []string{"a"}},
  3839  				{Source: "sourceA", Bidders: []string{"a"}},
  3840  			}}}},
  3841  			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] duplicate entry with field: "source"`),
  3842  		},
  3843  		{
  3844  			name: "invalid-missing-bidders-nil",
  3845  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3846  				{Source: "sourceA", Bidders: []string{"a"}},
  3847  				{Source: "sourceB"},
  3848  			}}}},
  3849  			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`),
  3850  		},
  3851  		{
  3852  			name: "invalid-missing-bidders-empty",
  3853  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3854  				{Source: "sourceA", Bidders: []string{"a"}},
  3855  				{Source: "sourceB", Bidders: []string{}},
  3856  			}}}},
  3857  			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`),
  3858  		},
  3859  		{
  3860  			name: "invalid-invalid-bidders",
  3861  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3862  				{Source: "sourceA", Bidders: []string{"a"}},
  3863  				{Source: "sourceB", Bidders: []string{"z"}},
  3864  			}}}},
  3865  			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] contains unrecognized bidder "z"`),
  3866  		},
  3867  		{
  3868  			name: "invalid-alias-case-sensitive",
  3869  			request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{
  3870  				{Source: "sourceA", Bidders: []string{"B"}},
  3871  			}}}},
  3872  			expectedError: errors.New(`request.ext.prebid.data.eidpermissions[0] contains unrecognized bidder "B"`),
  3873  		},
  3874  	}
  3875  
  3876  	endpoint := &endpointDeps{bidderMap: knownBidders, normalizeBidderName: fakeNormalizeBidderName}
  3877  	for _, test := range testCases {
  3878  		t.Run(test.name, func(t *testing.T) {
  3879  			result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases)
  3880  			assert.Equal(t, test.expectedError, result)
  3881  		})
  3882  	}
  3883  }
  3884  
  3885  func TestValidateBidders(t *testing.T) {
  3886  	testCases := []struct {
  3887  		description   string
  3888  		bidders       []string
  3889  		knownBidders  map[string]openrtb_ext.BidderName
  3890  		knownAliases  map[string]string
  3891  		expectedError error
  3892  	}{
  3893  		{
  3894  			description:   "Valid - No Bidders",
  3895  			bidders:       []string{},
  3896  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3897  			knownAliases:  map[string]string{"c": "c"},
  3898  			expectedError: nil,
  3899  		},
  3900  		{
  3901  			description:   "Valid - All Bidders",
  3902  			bidders:       []string{"*"},
  3903  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3904  			knownAliases:  map[string]string{"c": "c"},
  3905  			expectedError: nil,
  3906  		},
  3907  		{
  3908  			description:   "Valid - One Core Bidder",
  3909  			bidders:       []string{"a"},
  3910  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3911  			knownAliases:  map[string]string{"c": "c"},
  3912  			expectedError: nil,
  3913  		},
  3914  		{
  3915  			description:   "Valid - One Core Bidder - Case Insensitive",
  3916  			bidders:       []string{"A"},
  3917  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3918  			knownAliases:  map[string]string{"c": "c"},
  3919  			expectedError: nil,
  3920  		},
  3921  		{
  3922  			description:   "Valid - Many Core Bidders",
  3923  			bidders:       []string{"a", "b"},
  3924  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a"), "b": openrtb_ext.BidderName("b")},
  3925  			knownAliases:  map[string]string{"c": "c"},
  3926  			expectedError: nil,
  3927  		},
  3928  		{
  3929  			description:   "Valid - One Alias Bidder",
  3930  			bidders:       []string{"c"},
  3931  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3932  			knownAliases:  map[string]string{"c": "c"},
  3933  			expectedError: nil,
  3934  		},
  3935  		{
  3936  			description:   "Valid - One Alias Bidder - Case Sensitive",
  3937  			bidders:       []string{"C"},
  3938  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3939  			knownAliases:  map[string]string{"c": "c"},
  3940  			expectedError: errors.New(`unrecognized bidder "C"`),
  3941  		},
  3942  		{
  3943  			description:   "Valid - Many Alias Bidders",
  3944  			bidders:       []string{"c", "d"},
  3945  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3946  			knownAliases:  map[string]string{"c": "c", "d": "d"},
  3947  			expectedError: nil,
  3948  		},
  3949  		{
  3950  			description:   "Valid - Mixed Core + Alias Bidders",
  3951  			bidders:       []string{"a", "c"},
  3952  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3953  			knownAliases:  map[string]string{"c": "c"},
  3954  			expectedError: nil,
  3955  		},
  3956  		{
  3957  			description:   "Invalid - Unknown Bidder",
  3958  			bidders:       []string{"z"},
  3959  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3960  			knownAliases:  map[string]string{"c": "c"},
  3961  			expectedError: errors.New(`unrecognized bidder "z"`),
  3962  		},
  3963  		{
  3964  			description:   "Invalid - Unknown Bidder With Known Bidders",
  3965  			bidders:       []string{"a", "c", "z"},
  3966  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3967  			knownAliases:  map[string]string{"c": "c"},
  3968  			expectedError: errors.New(`unrecognized bidder "z"`),
  3969  		},
  3970  		{
  3971  			description:   "Invalid - All Bidders With Known Bidder",
  3972  			bidders:       []string{"*", "a"},
  3973  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3974  			knownAliases:  map[string]string{"c": "c"},
  3975  			expectedError: errors.New(`bidder wildcard "*" mixed with specific bidders`),
  3976  		},
  3977  		{
  3978  			description:   "Invalid - Returns First Error - All Bidders",
  3979  			bidders:       []string{"*", "z"},
  3980  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3981  			knownAliases:  map[string]string{"c": "c"},
  3982  			expectedError: errors.New(`bidder wildcard "*" mixed with specific bidders`),
  3983  		},
  3984  		{
  3985  			description:   "Invalid - Returns First Error - Unknown Bidder",
  3986  			bidders:       []string{"z", "*"},
  3987  			knownBidders:  map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")},
  3988  			knownAliases:  map[string]string{"c": "c"},
  3989  			expectedError: errors.New(`unrecognized bidder "z"`),
  3990  		},
  3991  	}
  3992  
  3993  	endpoint := &endpointDeps{normalizeBidderName: fakeNormalizeBidderName}
  3994  	for _, test := range testCases {
  3995  		result := endpoint.validateBidders(test.bidders, test.knownBidders, test.knownAliases)
  3996  		assert.Equal(t, test.expectedError, result, test.description)
  3997  	}
  3998  }
  3999  
  4000  func TestIOS14EndToEnd(t *testing.T) {
  4001  	exchange := &nobidExchange{}
  4002  
  4003  	endpoint, _ := NewEndpoint(
  4004  		fakeUUIDGenerator{},
  4005  		exchange,
  4006  		mockBidderParamValidator{},
  4007  		&mockStoredReqFetcher{},
  4008  		empty_fetcher.EmptyFetcher{},
  4009  		&config.Configuration{MaxRequestSize: maxSize},
  4010  		&metricsConfig.NilMetricsEngine{},
  4011  		analyticsBuild.New(&config.Analytics{}),
  4012  		map[string]string{},
  4013  		[]byte{},
  4014  		openrtb_ext.BuildBidderMap(),
  4015  		empty_fetcher.EmptyFetcher{},
  4016  		hooks.EmptyPlanBuilder{},
  4017  		nil,
  4018  	)
  4019  
  4020  	httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "app-ios140-no-ifa.json")))
  4021  
  4022  	endpoint(httptest.NewRecorder(), httpReq, nil)
  4023  
  4024  	result := exchange.gotRequest
  4025  	if !assert.NotEmpty(t, result, "request received by the exchange.") {
  4026  		t.FailNow()
  4027  	}
  4028  
  4029  	var lmtOne int8 = 1
  4030  	assert.Equal(t, &lmtOne, result.Device.Lmt)
  4031  }
  4032  
  4033  func TestAuctionWarnings(t *testing.T) {
  4034  	testCases := []struct {
  4035  		name            string
  4036  		file            string
  4037  		expectedWarning string
  4038  	}{
  4039  		{
  4040  			name:            "us-privacy-invalid",
  4041  			file:            "us-privacy-invalid.json",
  4042  			expectedWarning: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)",
  4043  		},
  4044  		{
  4045  			name:            "us-privacy-signals-conflict",
  4046  			file:            "us-privacy-conflict.json",
  4047  			expectedWarning: "regs.us_privacy consent does not match uspv1 in GPP, using regs.gpp",
  4048  		},
  4049  		{
  4050  			name:            "empty-gppsid-array-conflicts-with-regs-gdpr", // gdpr set to 1, an empty non-nil gpp_sid array doesn't match
  4051  			file:            "empty-gppsid-conflict.json",
  4052  			expectedWarning: "regs.gdpr signal conflicts with GPP (regs.gpp_sid) and will be ignored",
  4053  		},
  4054  		{
  4055  			name:            "gdpr-signals-conflict", // gdpr signals do not match
  4056  			file:            "gdpr-conflict.json",
  4057  			expectedWarning: "regs.gdpr signal conflicts with GPP (regs.gpp_sid) and will be ignored",
  4058  		},
  4059  		{
  4060  			name:            "gdpr-signals-conflict2", // gdpr consent strings do not match
  4061  			file:            "gdpr-conflict2.json",
  4062  			expectedWarning: "user.consent GDPR string conflicts with GPP (regs.gpp) GDPR string, using regs.gpp",
  4063  		},
  4064  	}
  4065  	deps := &endpointDeps{
  4066  		fakeUUIDGenerator{},
  4067  		&warningsCheckExchange{},
  4068  		mockBidderParamValidator{},
  4069  		&mockStoredReqFetcher{},
  4070  		empty_fetcher.EmptyFetcher{},
  4071  		empty_fetcher.EmptyFetcher{},
  4072  		&config.Configuration{MaxRequestSize: maxSize},
  4073  		&metricsConfig.NilMetricsEngine{},
  4074  		analyticsBuild.New(&config.Analytics{}),
  4075  		map[string]string{},
  4076  		false,
  4077  		[]byte{},
  4078  		openrtb_ext.BuildBidderMap(),
  4079  		nil,
  4080  		nil,
  4081  		hardcodedResponseIPValidator{response: true},
  4082  		empty_fetcher.EmptyFetcher{},
  4083  		hooks.EmptyPlanBuilder{},
  4084  		nil,
  4085  		openrtb_ext.NormalizeBidderName,
  4086  	}
  4087  	for _, test := range testCases {
  4088  		t.Run(test.name, func(t *testing.T) {
  4089  			reqBody := validRequest(t, test.file)
  4090  			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
  4091  			recorder := httptest.NewRecorder()
  4092  
  4093  			deps.Auction(recorder, req, nil)
  4094  
  4095  			if recorder.Code != http.StatusOK {
  4096  				t.Errorf("Endpoint should return a 200")
  4097  			}
  4098  			warnings := deps.ex.(*warningsCheckExchange).auctionRequest.Warnings
  4099  			if !assert.Len(t, warnings, 1, "One warning should be returned from exchange") {
  4100  				t.FailNow()
  4101  			}
  4102  			actualWarning := warnings[0].(*errortypes.Warning)
  4103  			assert.Equal(t, test.expectedWarning, actualWarning.Message, "Warning message is incorrect")
  4104  
  4105  			assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect")
  4106  		})
  4107  	}
  4108  }
  4109  
  4110  func TestParseRequestParseImpInfoError(t *testing.T) {
  4111  	reqBody := validRequest(t, "imp-info-invalid.json")
  4112  	deps := &endpointDeps{
  4113  		fakeUUIDGenerator{},
  4114  		&warningsCheckExchange{},
  4115  		mockBidderParamValidator{},
  4116  		&mockStoredReqFetcher{},
  4117  		empty_fetcher.EmptyFetcher{},
  4118  		empty_fetcher.EmptyFetcher{},
  4119  		&config.Configuration{MaxRequestSize: int64(len(reqBody))},
  4120  		&metricsConfig.NilMetricsEngine{},
  4121  		analyticsBuild.New(&config.Analytics{}),
  4122  		map[string]string{},
  4123  		false,
  4124  		[]byte{},
  4125  		openrtb_ext.BuildBidderMap(),
  4126  		nil,
  4127  		nil,
  4128  		hardcodedResponseIPValidator{response: true},
  4129  		empty_fetcher.EmptyFetcher{},
  4130  		hooks.EmptyPlanBuilder{},
  4131  		nil,
  4132  		openrtb_ext.NormalizeBidderName,
  4133  	}
  4134  
  4135  	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
  4136  
  4137  	req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody))
  4138  
  4139  	resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
  4140  
  4141  	assert.Nil(t, resReq, "Result request should be nil due to incorrect imp")
  4142  	assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp")
  4143  	assert.Len(t, errL, 1, "One error should be returned")
  4144  	assert.Contains(t, errL[0].Error(), "cannot unmarshal openrtb_ext.Options.EchoVideoAttrs", "Incorrect error message")
  4145  }
  4146  
  4147  func TestParseGzipedRequest(t *testing.T) {
  4148  	testCases :=
  4149  		[]struct {
  4150  			desc           string
  4151  			reqContentEnc  string
  4152  			maxReqSize     int64
  4153  			compressionCfg config.Compression
  4154  			expectedErr    string
  4155  		}{
  4156  			{
  4157  				desc:           "Gzip compression enabled, request size exceeds max request size",
  4158  				reqContentEnc:  "gzip",
  4159  				maxReqSize:     10,
  4160  				compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: true}},
  4161  				expectedErr:    "request size exceeded max size of 10 bytes.",
  4162  			},
  4163  			{
  4164  				desc:           "Gzip compression enabled, request size is within max request size",
  4165  				reqContentEnc:  "gzip",
  4166  				maxReqSize:     2000,
  4167  				compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: true}},
  4168  				expectedErr:    "",
  4169  			},
  4170  			{
  4171  				desc:           "Gzip compression enabled, request size is within max request size, content-encoding value not in lower case",
  4172  				reqContentEnc:  "GZIP",
  4173  				maxReqSize:     2000,
  4174  				compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: true}},
  4175  				expectedErr:    "",
  4176  			},
  4177  			{
  4178  				desc:           "Request is Gzip compressed, but Gzip compression is disabled",
  4179  				reqContentEnc:  "gzip",
  4180  				compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: false}},
  4181  				expectedErr:    "Content-Encoding of type gzip is not supported",
  4182  			},
  4183  			{
  4184  				desc:           "Request is not Gzip compressed, but Gzip compression is enabled",
  4185  				reqContentEnc:  "",
  4186  				maxReqSize:     2000,
  4187  				compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: true}},
  4188  				expectedErr:    "",
  4189  			},
  4190  		}
  4191  
  4192  	reqBody := []byte(validRequest(t, "site.json"))
  4193  	deps := &endpointDeps{
  4194  		fakeUUIDGenerator{},
  4195  		&warningsCheckExchange{},
  4196  		mockBidderParamValidator{},
  4197  		&mockStoredReqFetcher{},
  4198  		empty_fetcher.EmptyFetcher{},
  4199  		empty_fetcher.EmptyFetcher{},
  4200  		&config.Configuration{MaxRequestSize: int64(50), Compression: config.Compression{Request: config.CompressionInfo{GZIP: false}}},
  4201  		&metricsConfig.NilMetricsEngine{},
  4202  		analyticsBuild.New(&config.Analytics{}),
  4203  		map[string]string{},
  4204  		false,
  4205  		[]byte{},
  4206  		openrtb_ext.BuildBidderMap(),
  4207  		nil,
  4208  		nil,
  4209  		hardcodedResponseIPValidator{response: true},
  4210  		empty_fetcher.EmptyFetcher{},
  4211  		hooks.EmptyPlanBuilder{},
  4212  		nil,
  4213  		openrtb_ext.NormalizeBidderName,
  4214  	}
  4215  
  4216  	hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
  4217  	for _, test := range testCases {
  4218  		var req *http.Request
  4219  		deps.cfg.MaxRequestSize = test.maxReqSize
  4220  		deps.cfg.Compression = test.compressionCfg
  4221  		if test.reqContentEnc == "gzip" {
  4222  			var compressed bytes.Buffer
  4223  			gw := gzip.NewWriter(&compressed)
  4224  			_, err := gw.Write(reqBody)
  4225  			assert.NoError(t, err, "Error writing gzip compressed request body", test.desc)
  4226  			assert.NoError(t, gw.Close(), "Error closing gzip writer", test.desc)
  4227  
  4228  			req = httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(compressed.Bytes()))
  4229  			req.Header.Set("Content-Encoding", "gzip")
  4230  		} else {
  4231  			req = httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(reqBody))
  4232  		}
  4233  		resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
  4234  
  4235  		if test.expectedErr == "" {
  4236  			assert.Nil(t, errL, "Error list should be nil", test.desc)
  4237  			assert.NotNil(t, resReq, "Result request should not be nil", test.desc)
  4238  			assert.NotNil(t, impExtInfoMap, "Impression info map should not be nil", test.desc)
  4239  		} else {
  4240  			assert.Nil(t, resReq, "Result request should be nil due to incorrect imp", test.desc)
  4241  			assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp", test.desc)
  4242  			assert.Len(t, errL, 1, "One error should be returned", test.desc)
  4243  			assert.Contains(t, errL[0].Error(), test.expectedErr, "Incorrect error message", test.desc)
  4244  		}
  4245  	}
  4246  }
  4247  
  4248  func TestValidateNativeContextTypes(t *testing.T) {
  4249  	impIndex := 4
  4250  
  4251  	testCases := []struct {
  4252  		description      string
  4253  		givenContextType native1.ContextType
  4254  		givenSubType     native1.ContextSubType
  4255  		expectedError    string
  4256  	}{
  4257  		{
  4258  			description:      "No Types Specified",
  4259  			givenContextType: 0,
  4260  			givenSubType:     0,
  4261  			expectedError:    "",
  4262  		},
  4263  		{
  4264  			description:      "All Types Exchange Specific",
  4265  			givenContextType: 500,
  4266  			givenSubType:     500,
  4267  			expectedError:    "",
  4268  		},
  4269  		{
  4270  			description:      "Context Type Known Value - Sub Type Unspecified",
  4271  			givenContextType: 1,
  4272  			givenSubType:     0,
  4273  			expectedError:    "",
  4274  		},
  4275  		{
  4276  			description:      "Context Type Negative",
  4277  			givenContextType: -1,
  4278  			givenSubType:     0,
  4279  			expectedError:    "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4280  		},
  4281  		{
  4282  			description:      "Context Type Just Above Range",
  4283  			givenContextType: 4, // Range is currently 1-3
  4284  			givenSubType:     0,
  4285  			expectedError:    "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4286  		},
  4287  		{
  4288  			description:      "Sub Type Negative",
  4289  			givenContextType: 1,
  4290  			givenSubType:     -1,
  4291  			expectedError:    "request.imp[4].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4292  		},
  4293  		{
  4294  			description:      "Content - Sub Type Just Below Range",
  4295  			givenContextType: 1, // Content constant
  4296  			givenSubType:     9, // Content range is currently 10-15
  4297  			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4298  		},
  4299  		{
  4300  			description:      "Content - Sub Type In Range",
  4301  			givenContextType: 1,  // Content constant
  4302  			givenSubType:     10, // Content range is currently 10-15
  4303  			expectedError:    "",
  4304  		},
  4305  		{
  4306  			description:      "Content - Sub Type In Range - Context Type Exchange Specific Boundary",
  4307  			givenContextType: 500,
  4308  			givenSubType:     10, // Content range is currently 10-15
  4309  			expectedError:    "",
  4310  		},
  4311  		{
  4312  			description:      "Content - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
  4313  			givenContextType: 501,
  4314  			givenSubType:     10, // Content range is currently 10-15
  4315  			expectedError:    "",
  4316  		},
  4317  		{
  4318  			description:      "Content - Sub Type Just Above Range",
  4319  			givenContextType: 1,  // Content constant
  4320  			givenSubType:     16, // Content range is currently 10-15
  4321  			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4322  		},
  4323  		{
  4324  			description:      "Content - Sub Type Exchange Specific Boundary",
  4325  			givenContextType: 1, // Content constant
  4326  			givenSubType:     500,
  4327  			expectedError:    "",
  4328  		},
  4329  		{
  4330  			description:      "Content - Sub Type Exchange Specific Boundary + 1",
  4331  			givenContextType: 1, // Content constant
  4332  			givenSubType:     501,
  4333  			expectedError:    "",
  4334  		},
  4335  		{
  4336  			description:      "Content - Invalid Context Type",
  4337  			givenContextType: 2,  // Not content constant
  4338  			givenSubType:     10, // Content range is currently 10-15
  4339  			expectedError:    "request.imp[4].native.request.context is 2, but contextsubtype is 10. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4340  		},
  4341  		{
  4342  			description:      "Social - Sub Type Just Below Range",
  4343  			givenContextType: 2,  // Social constant
  4344  			givenSubType:     19, // Social range is currently 20-22
  4345  			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4346  		},
  4347  		{
  4348  			description:      "Social - Sub Type In Range",
  4349  			givenContextType: 2,  // Social constant
  4350  			givenSubType:     20, // Social range is currently 20-22
  4351  			expectedError:    "",
  4352  		},
  4353  		{
  4354  			description:      "Social - Sub Type In Range - Context Type Exchange Specific Boundary",
  4355  			givenContextType: 500,
  4356  			givenSubType:     20, // Social range is currently 20-22
  4357  			expectedError:    "",
  4358  		},
  4359  		{
  4360  			description:      "Social - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
  4361  			givenContextType: 501,
  4362  			givenSubType:     20, // Social range is currently 20-22
  4363  			expectedError:    "",
  4364  		},
  4365  		{
  4366  			description:      "Social - Sub Type Just Above Range",
  4367  			givenContextType: 2,  // Social constant
  4368  			givenSubType:     23, // Social range is currently 20-22
  4369  			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4370  		},
  4371  		{
  4372  			description:      "Social - Sub Type Exchange Specific Boundary",
  4373  			givenContextType: 2, // Social constant
  4374  			givenSubType:     500,
  4375  			expectedError:    "",
  4376  		},
  4377  		{
  4378  			description:      "Social - Sub Type Exchange Specific Boundary + 1",
  4379  			givenContextType: 2, // Social constant
  4380  			givenSubType:     501,
  4381  			expectedError:    "",
  4382  		},
  4383  		{
  4384  			description:      "Social - Invalid Context Type",
  4385  			givenContextType: 3,  // Not social constant
  4386  			givenSubType:     20, // Social range is currently 20-22
  4387  			expectedError:    "request.imp[4].native.request.context is 3, but contextsubtype is 20. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4388  		},
  4389  		{
  4390  			description:      "Product - Sub Type Just Below Range",
  4391  			givenContextType: 3,  // Product constant
  4392  			givenSubType:     29, // Product range is currently 30-32
  4393  			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4394  		},
  4395  		{
  4396  			description:      "Product - Sub Type In Range",
  4397  			givenContextType: 3,  // Product constant
  4398  			givenSubType:     30, // Product range is currently 30-32
  4399  			expectedError:    "",
  4400  		},
  4401  		{
  4402  			description:      "Product - Sub Type In Range - Context Type Exchange Specific Boundary",
  4403  			givenContextType: 500,
  4404  			givenSubType:     30, // Product range is currently 30-32
  4405  			expectedError:    "",
  4406  		},
  4407  		{
  4408  			description:      "Product - Sub Type In Range - Context Type Exchange Specific Boundary + 1",
  4409  			givenContextType: 501,
  4410  			givenSubType:     30, // Product range is currently 30-32
  4411  			expectedError:    "",
  4412  		},
  4413  		{
  4414  			description:      "Product - Sub Type Just Above Range",
  4415  			givenContextType: 3,  // Product constant
  4416  			givenSubType:     33, // Product range is currently 30-32
  4417  			expectedError:    "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4418  		},
  4419  		{
  4420  			description:      "Product - Sub Type Exchange Specific Boundary",
  4421  			givenContextType: 3, // Product constant
  4422  			givenSubType:     500,
  4423  			expectedError:    "",
  4424  		},
  4425  		{
  4426  			description:      "Product - Sub Type Exchange Specific Boundary + 1",
  4427  			givenContextType: 3, // Product constant
  4428  			givenSubType:     501,
  4429  			expectedError:    "",
  4430  		},
  4431  		{
  4432  			description:      "Product - Invalid Context Type",
  4433  			givenContextType: 1,  // Not product constant
  4434  			givenSubType:     30, // Product range is currently 30-32
  4435  			expectedError:    "request.imp[4].native.request.context is 1, but contextsubtype is 30. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39",
  4436  		},
  4437  	}
  4438  
  4439  	for _, test := range testCases {
  4440  		err := validateNativeContextTypes(test.givenContextType, test.givenSubType, impIndex)
  4441  		if test.expectedError == "" {
  4442  			assert.NoError(t, err, test.description)
  4443  		} else {
  4444  			assert.EqualError(t, err, test.expectedError, test.description)
  4445  		}
  4446  	}
  4447  }
  4448  
  4449  func TestValidateNativePlacementType(t *testing.T) {
  4450  	impIndex := 4
  4451  
  4452  	testCases := []struct {
  4453  		description        string
  4454  		givenPlacementType native1.PlacementType
  4455  		expectedError      string
  4456  	}{
  4457  		{
  4458  			description:        "Not Specified",
  4459  			givenPlacementType: 0,
  4460  			expectedError:      "",
  4461  		},
  4462  		{
  4463  			description:        "Known Value",
  4464  			givenPlacementType: 1, // Range is currently 1-4
  4465  			expectedError:      "",
  4466  		},
  4467  		{
  4468  			description:        "Exchange Specific - Boundary",
  4469  			givenPlacementType: 500,
  4470  			expectedError:      "",
  4471  		},
  4472  		{
  4473  			description:        "Exchange Specific - Boundary + 1",
  4474  			givenPlacementType: 501,
  4475  			expectedError:      "",
  4476  		},
  4477  		{
  4478  			description:        "Negative",
  4479  			givenPlacementType: -1,
  4480  			expectedError:      "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
  4481  		},
  4482  		{
  4483  			description:        "Just Above Range",
  4484  			givenPlacementType: 5, // Range is currently 1-4
  4485  			expectedError:      "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
  4486  		},
  4487  	}
  4488  
  4489  	for _, test := range testCases {
  4490  		err := validateNativePlacementType(test.givenPlacementType, impIndex)
  4491  		if test.expectedError == "" {
  4492  			assert.NoError(t, err, test.description)
  4493  		} else {
  4494  			assert.EqualError(t, err, test.expectedError, test.description)
  4495  		}
  4496  	}
  4497  }
  4498  
  4499  func TestValidateNativeEventTracker(t *testing.T) {
  4500  	impIndex := 4
  4501  	eventIndex := 8
  4502  
  4503  	testCases := []struct {
  4504  		description   string
  4505  		givenEvent    nativeRequests.EventTracker
  4506  		expectedError string
  4507  	}{
  4508  		{
  4509  			description: "Valid",
  4510  			givenEvent: nativeRequests.EventTracker{
  4511  				Event:   1,
  4512  				Methods: []native1.EventTrackingMethod{1},
  4513  			},
  4514  			expectedError: "",
  4515  		},
  4516  		{
  4517  			description: "Event - Exchange Specific - Boundary",
  4518  			givenEvent: nativeRequests.EventTracker{
  4519  				Event:   500,
  4520  				Methods: []native1.EventTrackingMethod{1},
  4521  			},
  4522  			expectedError: "",
  4523  		},
  4524  		{
  4525  			description: "Event - Exchange Specific - Boundary + 1",
  4526  			givenEvent: nativeRequests.EventTracker{
  4527  				Event:   501,
  4528  				Methods: []native1.EventTrackingMethod{1},
  4529  			},
  4530  			expectedError: "",
  4531  		},
  4532  		{
  4533  			description: "Event - Negative",
  4534  			givenEvent: nativeRequests.EventTracker{
  4535  				Event:   -1,
  4536  				Methods: []native1.EventTrackingMethod{1},
  4537  			},
  4538  			expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
  4539  		},
  4540  		{
  4541  			description: "Event - Just Above Range",
  4542  			givenEvent: nativeRequests.EventTracker{
  4543  				Event:   5, // Range is currently 1-4
  4544  				Methods: []native1.EventTrackingMethod{1},
  4545  			},
  4546  			expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
  4547  		},
  4548  		{
  4549  			description: "Methods - Many Valid",
  4550  			givenEvent: nativeRequests.EventTracker{
  4551  				Event:   1,
  4552  				Methods: []native1.EventTrackingMethod{1, 2},
  4553  			},
  4554  			expectedError: "",
  4555  		},
  4556  		{
  4557  			description: "Methods - Empty",
  4558  			givenEvent: nativeRequests.EventTracker{
  4559  				Event:   1,
  4560  				Methods: []native1.EventTrackingMethod{},
  4561  			},
  4562  			expectedError: "request.imp[4].native.request.eventtrackers[8].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
  4563  		},
  4564  		{
  4565  			description: "Methods - Exchange Specific - Boundary",
  4566  			givenEvent: nativeRequests.EventTracker{
  4567  				Event:   1,
  4568  				Methods: []native1.EventTrackingMethod{500},
  4569  			},
  4570  			expectedError: "",
  4571  		},
  4572  		{
  4573  			description: "Methods - Exchange Specific - Boundary + 1",
  4574  			givenEvent: nativeRequests.EventTracker{
  4575  				Event:   1,
  4576  				Methods: []native1.EventTrackingMethod{501},
  4577  			},
  4578  			expectedError: "",
  4579  		},
  4580  		{
  4581  			description: "Methods - Negative",
  4582  			givenEvent: nativeRequests.EventTracker{
  4583  				Event:   1,
  4584  				Methods: []native1.EventTrackingMethod{-1},
  4585  			},
  4586  			expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
  4587  		},
  4588  		{
  4589  			description: "Methods - Just Above Range",
  4590  			givenEvent: nativeRequests.EventTracker{
  4591  				Event:   1,
  4592  				Methods: []native1.EventTrackingMethod{3}, // Known values are currently 1-2
  4593  			},
  4594  			expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
  4595  		},
  4596  		{
  4597  			description: "Methods - Mixed Valid + Invalid",
  4598  			givenEvent: nativeRequests.EventTracker{
  4599  				Event:   1,
  4600  				Methods: []native1.EventTrackingMethod{1, -1},
  4601  			},
  4602  			expectedError: "request.imp[4].native.request.eventtrackers[8].methods[1] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43",
  4603  		},
  4604  	}
  4605  
  4606  	for _, test := range testCases {
  4607  		err := validateNativeEventTracker(test.givenEvent, impIndex, eventIndex)
  4608  		if test.expectedError == "" {
  4609  			assert.NoError(t, err, test.description)
  4610  		} else {
  4611  			assert.EqualError(t, err, test.expectedError, test.description)
  4612  		}
  4613  	}
  4614  }
  4615  
  4616  func TestValidateNativeAssetData(t *testing.T) {
  4617  	impIndex := 4
  4618  	assetIndex := 8
  4619  
  4620  	testCases := []struct {
  4621  		description   string
  4622  		givenData     nativeRequests.Data
  4623  		expectedError string
  4624  	}{
  4625  		{
  4626  			description:   "Valid",
  4627  			givenData:     nativeRequests.Data{Type: 1},
  4628  			expectedError: "",
  4629  		},
  4630  		{
  4631  			description:   "Exchange Specific - Boundary",
  4632  			givenData:     nativeRequests.Data{Type: 500},
  4633  			expectedError: "",
  4634  		},
  4635  		{
  4636  			description:   "Exchange Specific - Boundary + 1",
  4637  			givenData:     nativeRequests.Data{Type: 501},
  4638  			expectedError: "",
  4639  		},
  4640  		{
  4641  			description:   "Not Specified",
  4642  			givenData:     nativeRequests.Data{},
  4643  			expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
  4644  		},
  4645  		{
  4646  			description:   "Negative",
  4647  			givenData:     nativeRequests.Data{Type: -1},
  4648  			expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
  4649  		},
  4650  		{
  4651  			description:   "Just Above Range",
  4652  			givenData:     nativeRequests.Data{Type: 13}, // Range is currently 1-12
  4653  			expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40",
  4654  		},
  4655  	}
  4656  
  4657  	for _, test := range testCases {
  4658  		err := validateNativeAssetData(&test.givenData, impIndex, assetIndex)
  4659  		if test.expectedError == "" {
  4660  			assert.NoError(t, err, test.description)
  4661  		} else {
  4662  			assert.EqualError(t, err, test.expectedError, test.description)
  4663  		}
  4664  	}
  4665  }
  4666  
  4667  func TestAuctionResponseHeaders(t *testing.T) {
  4668  	testCases := []struct {
  4669  		description     string
  4670  		httpRequest     *http.Request
  4671  		expectedStatus  int
  4672  		expectedHeaders func(http.Header)
  4673  	}{
  4674  		{
  4675  			description:    "Success Response",
  4676  			httpRequest:    httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))),
  4677  			expectedStatus: 200,
  4678  			expectedHeaders: func(h http.Header) {
  4679  				h.Set("X-Prebid", "pbs-go/unknown")
  4680  				h.Set("Content-Type", "application/json")
  4681  			},
  4682  		},
  4683  		{
  4684  			description:    "Failure Response",
  4685  			httpRequest:    httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader("{}")),
  4686  			expectedStatus: 400,
  4687  			expectedHeaders: func(h http.Header) {
  4688  				h.Set("X-Prebid", "pbs-go/unknown")
  4689  			},
  4690  		},
  4691  		{
  4692  			description: "Success Response with Chrome BrowsingTopicsHeader",
  4693  			httpRequest: func() *http.Request {
  4694  				httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json")))
  4695  				httpReq.Header.Add(secBrowsingTopics, "sample-value")
  4696  				return httpReq
  4697  			}(),
  4698  			expectedStatus: 200,
  4699  			expectedHeaders: func(h http.Header) {
  4700  				h.Set("X-Prebid", "pbs-go/unknown")
  4701  				h.Set("Content-Type", "application/json")
  4702  				h.Set("Observe-Browsing-Topics", "?1")
  4703  			},
  4704  		},
  4705  		{
  4706  			description: "Failure Response with Chrome BrowsingTopicsHeader",
  4707  			httpRequest: func() *http.Request {
  4708  				httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader("{}"))
  4709  				httpReq.Header.Add(secBrowsingTopics, "sample-value")
  4710  				return httpReq
  4711  			}(),
  4712  			expectedStatus: 400,
  4713  			expectedHeaders: func(h http.Header) {
  4714  				h.Set("X-Prebid", "pbs-go/unknown")
  4715  				h.Set("Observe-Browsing-Topics", "?1")
  4716  			},
  4717  		},
  4718  	}
  4719  
  4720  	exchange := &nobidExchange{}
  4721  	endpoint, _ := NewEndpoint(
  4722  		fakeUUIDGenerator{},
  4723  		exchange,
  4724  		mockBidderParamValidator{},
  4725  		empty_fetcher.EmptyFetcher{},
  4726  		empty_fetcher.EmptyFetcher{},
  4727  		&config.Configuration{MaxRequestSize: maxSize},
  4728  		&metricsConfig.NilMetricsEngine{},
  4729  		analyticsBuild.New(&config.Analytics{}),
  4730  		map[string]string{},
  4731  		[]byte{},
  4732  		openrtb_ext.BuildBidderMap(),
  4733  		empty_fetcher.EmptyFetcher{},
  4734  		hooks.EmptyPlanBuilder{},
  4735  		nil,
  4736  	)
  4737  
  4738  	for _, test := range testCases {
  4739  		recorder := httptest.NewRecorder()
  4740  
  4741  		endpoint(recorder, test.httpRequest, nil)
  4742  
  4743  		expectedHeaders := http.Header{}
  4744  		test.expectedHeaders(expectedHeaders)
  4745  
  4746  		assert.Equal(t, test.expectedStatus, recorder.Result().StatusCode, test.description+":statuscode")
  4747  		assert.Equal(t, expectedHeaders, recorder.Result().Header, test.description+":statuscode")
  4748  	}
  4749  }
  4750  
  4751  // StoredRequest testing
  4752  
  4753  // Test stored request data
  4754  
  4755  func TestValidateBanner(t *testing.T) {
  4756  	impIndex := 0
  4757  
  4758  	testCases := []struct {
  4759  		description    string
  4760  		banner         *openrtb2.Banner
  4761  		impIndex       int
  4762  		isInterstitial bool
  4763  		expectedError  error
  4764  	}{
  4765  		{
  4766  			description:    "isInterstitial Equals False (not set to 1)",
  4767  			banner:         &openrtb2.Banner{W: nil, H: nil, Format: nil},
  4768  			impIndex:       impIndex,
  4769  			isInterstitial: false,
  4770  			expectedError:  errors.New("request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements."),
  4771  		},
  4772  		{
  4773  			description:    "isInterstitial Equals True (is set to 1)",
  4774  			banner:         &openrtb2.Banner{W: nil, H: nil, Format: nil},
  4775  			impIndex:       impIndex,
  4776  			isInterstitial: true,
  4777  			expectedError:  nil,
  4778  		},
  4779  	}
  4780  
  4781  	for _, test := range testCases {
  4782  		result := validateBanner(test.banner, test.impIndex, test.isInterstitial)
  4783  		assert.Equal(t, test.expectedError, result, test.description)
  4784  	}
  4785  }
  4786  
  4787  func TestParseRequestMergeBidderParams(t *testing.T) {
  4788  	tests := []struct {
  4789  		name               string
  4790  		givenRequestBody   string
  4791  		expectedImpExt     json.RawMessage
  4792  		expectedReqExt     json.RawMessage
  4793  		expectedErrorCount int
  4794  	}{
  4795  		{
  4796  			name:               "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder",
  4797  			givenRequestBody:   validRequest(t, "req-ext-bidder-params.json"),
  4798  			expectedImpExt:     getObject(t, "req-ext-bidder-params.json", "expectedImpExt"),
  4799  			expectedReqExt:     getObject(t, "req-ext-bidder-params.json", "expectedReqExt"),
  4800  			expectedErrorCount: 0,
  4801  		},
  4802  		{
  4803  			name:               "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params",
  4804  			givenRequestBody:   validRequest(t, "req-ext-bidder-params-merge.json"),
  4805  			expectedImpExt:     getObject(t, "req-ext-bidder-params-merge.json", "expectedImpExt"),
  4806  			expectedReqExt:     getObject(t, "req-ext-bidder-params-merge.json", "expectedReqExt"),
  4807  			expectedErrorCount: 0,
  4808  		},
  4809  		{
  4810  			name:               "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility",
  4811  			givenRequestBody:   validRequest(t, "req-ext-bidder-params-backward-compatible-merge.json"),
  4812  			expectedImpExt:     getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedImpExt"),
  4813  			expectedReqExt:     getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedReqExt"),
  4814  			expectedErrorCount: 0,
  4815  		},
  4816  	}
  4817  	for _, test := range tests {
  4818  		t.Run(test.name, func(t *testing.T) {
  4819  
  4820  			deps := &endpointDeps{
  4821  				fakeUUIDGenerator{},
  4822  				&warningsCheckExchange{},
  4823  				mockBidderParamValidator{},
  4824  				&mockStoredReqFetcher{},
  4825  				empty_fetcher.EmptyFetcher{},
  4826  				empty_fetcher.EmptyFetcher{},
  4827  				&config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))},
  4828  				&metricsConfig.NilMetricsEngine{},
  4829  				analyticsBuild.New(&config.Analytics{}),
  4830  				map[string]string{},
  4831  				false,
  4832  				[]byte{},
  4833  				openrtb_ext.BuildBidderMap(),
  4834  				nil,
  4835  				nil,
  4836  				hardcodedResponseIPValidator{response: true},
  4837  				empty_fetcher.EmptyFetcher{},
  4838  				hooks.EmptyPlanBuilder{},
  4839  				nil,
  4840  				openrtb_ext.NormalizeBidderName,
  4841  			}
  4842  
  4843  			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
  4844  
  4845  			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
  4846  
  4847  			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
  4848  
  4849  			assert.NoError(t, resReq.RebuildRequest())
  4850  
  4851  			var expIExt, iExt map[string]interface{}
  4852  			err := jsonutil.UnmarshalValid(test.expectedImpExt, &expIExt)
  4853  			assert.Nil(t, err, "unmarshal() should return nil error")
  4854  
  4855  			assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil")
  4856  			err = jsonutil.UnmarshalValid(resReq.BidRequest.Imp[0].Ext, &iExt)
  4857  			assert.Nil(t, err, "unmarshal() should return nil error")
  4858  
  4859  			assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match")
  4860  
  4861  			var eReqE, reqE map[string]interface{}
  4862  			err = jsonutil.UnmarshalValid(test.expectedReqExt, &eReqE)
  4863  			assert.Nil(t, err, "unmarshal() should return nil error")
  4864  
  4865  			err = jsonutil.UnmarshalValid(resReq.BidRequest.Ext, &reqE)
  4866  			assert.Nil(t, err, "unmarshal() should return nil error")
  4867  
  4868  			assert.Equal(t, eReqE, reqE, "req.Ext should match")
  4869  
  4870  			assert.Len(t, errL, test.expectedErrorCount, "error length should match")
  4871  		})
  4872  	}
  4873  }
  4874  
  4875  func TestParseRequestStoredResponses(t *testing.T) {
  4876  	mockStoredResponses := map[string]json.RawMessage{
  4877  		"6d718149": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`),
  4878  		"6d715835": json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "appnexus"}]`),
  4879  	}
  4880  
  4881  	tests := []struct {
  4882  		name                    string
  4883  		givenRequestBody        string
  4884  		expectedStoredResponses stored_responses.ImpsWithBidResponses
  4885  		expectedErrorCount      int
  4886  		expectedError           string
  4887  	}{
  4888  		{
  4889  			name:             "req imp has valid stored response",
  4890  			givenRequestBody: validRequest(t, "req-imp-stored-response.json"),
  4891  			expectedStoredResponses: map[string]json.RawMessage{
  4892  				"imp-id1": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`),
  4893  			},
  4894  			expectedErrorCount: 0,
  4895  		},
  4896  		{
  4897  			name:             "req has two imps valid stored responses",
  4898  			givenRequestBody: validRequest(t, "req-two-imps-stored-response.json"),
  4899  			expectedStoredResponses: map[string]json.RawMessage{
  4900  				"imp-id1": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`),
  4901  				"imp-id2": json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "appnexus"}]`),
  4902  			},
  4903  			expectedErrorCount: 0,
  4904  		},
  4905  		{
  4906  			name:                    "req has two imps with missing stored responses",
  4907  			givenRequestBody:        validRequest(t, "req-two-imps-missing-stored-response.json"),
  4908  			expectedStoredResponses: nil,
  4909  			expectedErrorCount:      2,
  4910  		},
  4911  		{
  4912  			name:             "req has two imps: one with stored response and another imp without stored resp",
  4913  			givenRequestBody: validRequest(t, "req-two-imps-one-stored-response.json"),
  4914  			expectedStoredResponses: map[string]json.RawMessage{
  4915  				"imp-id1": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`),
  4916  			},
  4917  			expectedErrorCount: 1,
  4918  			expectedError:      `request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[1].ext.prebid`,
  4919  		},
  4920  	}
  4921  	for _, test := range tests {
  4922  		t.Run(test.name, func(t *testing.T) {
  4923  
  4924  			deps := &endpointDeps{
  4925  				fakeUUIDGenerator{},
  4926  				&warningsCheckExchange{},
  4927  				mockBidderParamValidator{},
  4928  				&mockStoredReqFetcher{},
  4929  				empty_fetcher.EmptyFetcher{},
  4930  				empty_fetcher.EmptyFetcher{},
  4931  				&config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))},
  4932  				&metricsConfig.NilMetricsEngine{},
  4933  				analyticsBuild.New(&config.Analytics{}),
  4934  				map[string]string{},
  4935  				false,
  4936  				[]byte{},
  4937  				openrtb_ext.BuildBidderMap(),
  4938  				nil,
  4939  				nil,
  4940  				hardcodedResponseIPValidator{response: true},
  4941  				&mockStoredResponseFetcher{mockStoredResponses},
  4942  				hooks.EmptyPlanBuilder{},
  4943  				nil,
  4944  				openrtb_ext.NormalizeBidderName,
  4945  			}
  4946  
  4947  			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
  4948  
  4949  			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
  4950  
  4951  			_, _, storedResponses, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
  4952  
  4953  			if test.expectedErrorCount == 0 {
  4954  				assert.Equal(t, test.expectedStoredResponses, storedResponses, "stored responses should match")
  4955  			} else {
  4956  				assert.Contains(t, errL[0].Error(), test.expectedError, "error should match")
  4957  			}
  4958  
  4959  		})
  4960  	}
  4961  }
  4962  
  4963  func TestParseRequestStoredBidResponses(t *testing.T) {
  4964  	bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "telaria"}], "bidid": "123", "cur": "USD"}`)
  4965  	bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "amx"}], "bidid": "124", "cur": "USD"}`)
  4966  	bidRespId3 := json.RawMessage(`{"id": "resp_id3", "seatbid": [{"bid": [{"id": "bid_id3"}], "seat": "APPNEXUS"}], "bidid": "125", "cur": "USD"}`)
  4967  	mockStoredBidResponses := map[string]json.RawMessage{
  4968  		"bidResponseId1": bidRespId1,
  4969  		"bidResponseId2": bidRespId2,
  4970  		"bidResponseId3": bidRespId3,
  4971  	}
  4972  
  4973  	tests := []struct {
  4974  		name                       string
  4975  		givenRequestBody           string
  4976  		expectedStoredBidResponses stored_responses.ImpBidderStoredResp
  4977  		expectedErrorCount         int
  4978  		expectedError              string
  4979  	}{
  4980  		{
  4981  			name:             "req imp has valid stored bid response",
  4982  			givenRequestBody: validRequest(t, "imp-with-stored-bid-resp.json"),
  4983  			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
  4984  				"imp-id1": {"telaria": bidRespId1},
  4985  			},
  4986  			expectedErrorCount: 0,
  4987  		},
  4988  		{
  4989  			name:             "req imp has valid stored bid response with case not-matching bidder name",
  4990  			givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-not-matching-bidder-name.json"),
  4991  			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
  4992  				"imp-id3": {"appnexus": bidRespId3},
  4993  			},
  4994  			expectedErrorCount: 0,
  4995  		},
  4996  		{
  4997  			name:             "req imp has valid stored bid response with case matching bidder name",
  4998  			givenRequestBody: validRequest(t, "imp-with-stored-bid-resp-case-matching-bidder-name.json"),
  4999  			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
  5000  				"imp-id3": {"appnexus": bidRespId3},
  5001  			},
  5002  			expectedErrorCount: 0,
  5003  		},
  5004  		{
  5005  			name:             "req has two imps with valid stored bid responses",
  5006  			givenRequestBody: validRequest(t, "req-two-imps-stored-bid-responses.json"),
  5007  			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
  5008  				"imp-id1": {"telaria": bidRespId1},
  5009  				"imp-id2": {"amx": bidRespId2},
  5010  			},
  5011  			expectedErrorCount: 0,
  5012  		},
  5013  		{
  5014  			name:             "req has two imps one with valid stored bid responses and another one without stored bid responses",
  5015  			givenRequestBody: validRequest(t, "req-two-imps-with-and-without-stored-bid-responses.json"),
  5016  			expectedStoredBidResponses: map[string]map[string]json.RawMessage{
  5017  				"imp-id2": {"amx": bidRespId2},
  5018  			},
  5019  			expectedErrorCount: 0,
  5020  		},
  5021  		{
  5022  			name:                       "req has two imps with missing stored bid responses",
  5023  			givenRequestBody:           validRequest(t, "req-two-imps-missing-stored-bid-response.json"),
  5024  			expectedStoredBidResponses: nil,
  5025  			expectedErrorCount:         1,
  5026  		},
  5027  		{
  5028  			name:                       "req imp has valid stored bid response with non existing bidder name",
  5029  			givenRequestBody:           validRequest(t, "imp-with-stored-bid-resp-non-existing-bidder-name.json"),
  5030  			expectedStoredBidResponses: nil,
  5031  			expectedErrorCount:         1,
  5032  		},
  5033  	}
  5034  	for _, test := range tests {
  5035  		t.Run(test.name, func(t *testing.T) {
  5036  
  5037  			deps := &endpointDeps{
  5038  				fakeUUIDGenerator{},
  5039  				&warningsCheckExchange{},
  5040  				mockBidderParamValidator{},
  5041  				&mockStoredReqFetcher{},
  5042  				empty_fetcher.EmptyFetcher{},
  5043  				empty_fetcher.EmptyFetcher{},
  5044  				&config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))},
  5045  				&metricsConfig.NilMetricsEngine{},
  5046  				analyticsBuild.New(&config.Analytics{}),
  5047  				map[string]string{},
  5048  				false,
  5049  				[]byte{},
  5050  				map[string]openrtb_ext.BidderName{"telaria": "telaria", "amx": "amx", "appnexus": "appnexus"},
  5051  				nil,
  5052  				nil,
  5053  				hardcodedResponseIPValidator{response: true},
  5054  				&mockStoredResponseFetcher{mockStoredBidResponses},
  5055  				hooks.EmptyPlanBuilder{},
  5056  				nil,
  5057  				openrtb_ext.NormalizeBidderName,
  5058  			}
  5059  
  5060  			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
  5061  
  5062  			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
  5063  			_, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
  5064  			if test.expectedErrorCount == 0 {
  5065  				assert.Empty(t, errL)
  5066  				assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match")
  5067  			} else {
  5068  				assert.Contains(t, errL[0].Error(), test.expectedError, "error should match")
  5069  			}
  5070  		})
  5071  	}
  5072  }
  5073  
  5074  func TestValidateStoredResp(t *testing.T) {
  5075  	deps := &endpointDeps{
  5076  		fakeUUIDGenerator{},
  5077  		&nobidExchange{},
  5078  		mockBidderParamValidator{},
  5079  		&mockStoredReqFetcher{},
  5080  		empty_fetcher.EmptyFetcher{},
  5081  		empty_fetcher.EmptyFetcher{},
  5082  		&config.Configuration{MaxRequestSize: maxSize},
  5083  		&metricsConfig.NilMetricsEngine{},
  5084  		analyticsBuild.New(&config.Analytics{}),
  5085  		map[string]string{},
  5086  		false,
  5087  		[]byte{},
  5088  		openrtb_ext.BuildBidderMap(),
  5089  		nil,
  5090  		nil,
  5091  		hardcodedResponseIPValidator{response: true},
  5092  		&mockStoredResponseFetcher{},
  5093  		hooks.EmptyPlanBuilder{},
  5094  		nil,
  5095  		openrtb_ext.NormalizeBidderName,
  5096  	}
  5097  
  5098  	testCases := []struct {
  5099  		description               string
  5100  		givenRequestWrapper       *openrtb_ext.RequestWrapper
  5101  		givenHttpRequest          *http.Request
  5102  		givenAccount              *config.Account
  5103  		expectedErrorList         []error
  5104  		hasStoredAuctionResponses bool
  5105  		storedBidResponses        stored_responses.ImpBidderStoredResp
  5106  	}{
  5107  		{
  5108  			description: "One imp with stored response, expect validate request to throw no errors",
  5109  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5110  				BidRequest: &openrtb2.BidRequest{
  5111  					ID:  "Some-ID",
  5112  					App: &openrtb2.App{},
  5113  					Imp: []openrtb2.Imp{
  5114  						{
  5115  							ID: "Some-Imp-ID",
  5116  							Banner: &openrtb2.Banner{
  5117  								Format: []openrtb2.Format{
  5118  									{
  5119  										W: 600,
  5120  										H: 500,
  5121  									},
  5122  									{
  5123  										W: 300,
  5124  										H: 600,
  5125  									},
  5126  								},
  5127  							},
  5128  							Ext: []byte(`{"appnexus":{"placementId": 12345678}, "prebid": {"storedAuctionResponse": {"id": "6d718149-6dfe-25ae-a7d6-305399f77f04"}}}`),
  5129  						},
  5130  					},
  5131  				},
  5132  			},
  5133  			expectedErrorList:         []error{},
  5134  			hasStoredAuctionResponses: true,
  5135  			storedBidResponses:        nil,
  5136  		},
  5137  		{
  5138  			description: "Two imps with stored responses, expect validate request to throw no errors",
  5139  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5140  				BidRequest: &openrtb2.BidRequest{
  5141  					ID:  "Some-ID",
  5142  					App: &openrtb2.App{},
  5143  					Imp: []openrtb2.Imp{
  5144  						{
  5145  							ID: "Some-Imp-ID",
  5146  							Banner: &openrtb2.Banner{
  5147  								Format: []openrtb2.Format{
  5148  									{
  5149  										W: 600,
  5150  										H: 500,
  5151  									},
  5152  									{
  5153  										W: 300,
  5154  										H: 600,
  5155  									},
  5156  								},
  5157  							},
  5158  							Ext: []byte(`{"appnexus":{"placementId": 12345678}, "prebid": {"storedAuctionResponse": {"id": "6d718149-6dfe-25ae-a7d6-305399f77f04"}}}`),
  5159  						},
  5160  						{
  5161  							ID: "Some-Imp-ID2",
  5162  							Banner: &openrtb2.Banner{
  5163  								Format: []openrtb2.Format{
  5164  									{
  5165  										W: 600,
  5166  										H: 500,
  5167  									},
  5168  									{
  5169  										W: 300,
  5170  										H: 600,
  5171  									},
  5172  								},
  5173  							},
  5174  							Ext: []byte(`{"appnexus":{"placementId": 12345678}, "prebid": {"storedAuctionResponse": {"id": "6d718149-6dfe-25ae-a7d6-305399f77f04"}}}`),
  5175  						},
  5176  					},
  5177  				},
  5178  			},
  5179  			expectedErrorList:         []error{},
  5180  			hasStoredAuctionResponses: true,
  5181  			storedBidResponses:        nil,
  5182  		},
  5183  		{
  5184  			description: "Two imps, one with stored response, expect validate request to throw validation error",
  5185  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5186  				BidRequest: &openrtb2.BidRequest{
  5187  					ID:  "Some-ID",
  5188  					App: &openrtb2.App{},
  5189  					Imp: []openrtb2.Imp{
  5190  						{
  5191  							ID: "Some-Imp-ID",
  5192  							Banner: &openrtb2.Banner{
  5193  								Format: []openrtb2.Format{
  5194  									{
  5195  										W: 600,
  5196  										H: 500,
  5197  									},
  5198  									{
  5199  										W: 300,
  5200  										H: 600,
  5201  									},
  5202  								},
  5203  							},
  5204  							Ext: []byte(`{"appnexus":{"placementId": 12345678}, "prebid": {"storedAuctionResponse": {"id": "6d718149-6dfe-25ae-a7d6-305399f77f04"}}}`),
  5205  						},
  5206  						{
  5207  							ID: "Some-Imp-ID2",
  5208  							Banner: &openrtb2.Banner{
  5209  								Format: []openrtb2.Format{
  5210  									{
  5211  										W: 600,
  5212  										H: 500,
  5213  									},
  5214  									{
  5215  										W: 300,
  5216  										H: 600,
  5217  									},
  5218  								},
  5219  							},
  5220  							Ext: []byte(`{"appnexus":{"placementId": 12345678}}`),
  5221  						},
  5222  					},
  5223  				},
  5224  			},
  5225  			expectedErrorList:         []error{errors.New("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[1].ext.prebid \n")},
  5226  			hasStoredAuctionResponses: true,
  5227  			storedBidResponses:        nil,
  5228  		},
  5229  		{
  5230  			description: "One imp with stored bid response and corresponding bidder in imp.ext, expect validate request to throw no errors",
  5231  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5232  				BidRequest: &openrtb2.BidRequest{
  5233  					ID:  "Some-ID",
  5234  					App: &openrtb2.App{},
  5235  					Imp: []openrtb2.Imp{
  5236  						{
  5237  							ID: "Some-Imp-ID",
  5238  							Banner: &openrtb2.Banner{
  5239  								Format: []openrtb2.Format{
  5240  									{
  5241  										W: 600,
  5242  										H: 500,
  5243  									},
  5244  									{
  5245  										W: 300,
  5246  										H: 600,
  5247  									},
  5248  								},
  5249  							},
  5250  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"storedbidresponse": []}}`),
  5251  						},
  5252  					},
  5253  				},
  5254  			},
  5255  			expectedErrorList:         []error{},
  5256  			hasStoredAuctionResponses: false,
  5257  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}},
  5258  		},
  5259  		{
  5260  			description: "One imp with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors",
  5261  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5262  				BidRequest: &openrtb2.BidRequest{
  5263  					ID:  "Some-ID",
  5264  					App: &openrtb2.App{},
  5265  					Imp: []openrtb2.Imp{
  5266  						{
  5267  							ID: "Some-Imp-ID",
  5268  							Banner: &openrtb2.Banner{
  5269  								Format: []openrtb2.Format{
  5270  									{
  5271  										W: 600,
  5272  										H: 500,
  5273  									},
  5274  									{
  5275  										W: 300,
  5276  										H: 600,
  5277  									},
  5278  								},
  5279  							},
  5280  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
  5281  						},
  5282  					},
  5283  				},
  5284  			},
  5285  			expectedErrorList:         []error{},
  5286  			hasStoredAuctionResponses: false,
  5287  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
  5288  		},
  5289  		{
  5290  			description: "Two imps, one with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors",
  5291  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5292  				BidRequest: &openrtb2.BidRequest{
  5293  					ID:  "Some-ID",
  5294  					App: &openrtb2.App{},
  5295  					Imp: []openrtb2.Imp{
  5296  						{
  5297  							ID: "Some-Imp-ID",
  5298  							Banner: &openrtb2.Banner{
  5299  								Format: []openrtb2.Format{
  5300  									{
  5301  										W: 600,
  5302  										H: 500,
  5303  									},
  5304  									{
  5305  										W: 300,
  5306  										H: 600,
  5307  									},
  5308  								},
  5309  							},
  5310  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
  5311  						},
  5312  						{
  5313  							ID: "Some-Imp-ID2",
  5314  							Banner: &openrtb2.Banner{
  5315  								Format: []openrtb2.Format{
  5316  									{
  5317  										W: 600,
  5318  										H: 500,
  5319  									},
  5320  									{
  5321  										W: 300,
  5322  										H: 600,
  5323  									},
  5324  								},
  5325  							},
  5326  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
  5327  						},
  5328  					},
  5329  				},
  5330  			},
  5331  			expectedErrorList:         []error{},
  5332  			hasStoredAuctionResponses: false,
  5333  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
  5334  		},
  5335  		{
  5336  			description: "Two imps, both with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors",
  5337  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5338  				BidRequest: &openrtb2.BidRequest{
  5339  					ID:  "Some-ID",
  5340  					App: &openrtb2.App{},
  5341  					Imp: []openrtb2.Imp{
  5342  						{
  5343  							ID: "Some-Imp-ID",
  5344  							Banner: &openrtb2.Banner{
  5345  								Format: []openrtb2.Format{
  5346  									{
  5347  										W: 600,
  5348  										H: 500,
  5349  									},
  5350  									{
  5351  										W: 300,
  5352  										H: 600,
  5353  									},
  5354  								},
  5355  							},
  5356  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
  5357  						},
  5358  						{
  5359  							ID: "Some-Imp-ID2",
  5360  							Banner: &openrtb2.Banner{
  5361  								Format: []openrtb2.Format{
  5362  									{
  5363  										W: 600,
  5364  										H: 500,
  5365  									},
  5366  									{
  5367  										W: 300,
  5368  										H: 600,
  5369  									},
  5370  								},
  5371  							},
  5372  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
  5373  						},
  5374  					},
  5375  				},
  5376  			},
  5377  			expectedErrorList:         []error{},
  5378  			hasStoredAuctionResponses: false,
  5379  			storedBidResponses: stored_responses.ImpBidderStoredResp{
  5380  				"Some-Imp-ID":  {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)},
  5381  				"Some-Imp-ID1": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)},
  5382  			},
  5383  		},
  5384  		{
  5385  			description: "One imp with 2 stored bid responses and 1 bidder in imp.ext, expect validate request to throw an errors",
  5386  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5387  				BidRequest: &openrtb2.BidRequest{
  5388  					ID:  "Some-ID",
  5389  					App: &openrtb2.App{},
  5390  					Imp: []openrtb2.Imp{
  5391  						{
  5392  							ID: "Some-Imp-ID",
  5393  							Banner: &openrtb2.Banner{
  5394  								Format: []openrtb2.Format{
  5395  									{
  5396  										W: 600,
  5397  										H: 500,
  5398  									},
  5399  									{
  5400  										W: 300,
  5401  										H: 600,
  5402  									},
  5403  								},
  5404  							},
  5405  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"storedbidresponse": []}}`),
  5406  						},
  5407  					},
  5408  				},
  5409  			},
  5410  			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
  5411  			hasStoredAuctionResponses: false,
  5412  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
  5413  		},
  5414  		{
  5415  			description: "One imp with 1 stored bid responses and 2 bidders in imp.ext, expect validate request to throw an errors",
  5416  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5417  				BidRequest: &openrtb2.BidRequest{
  5418  					ID:  "Some-ID",
  5419  					App: &openrtb2.App{},
  5420  					Imp: []openrtb2.Imp{
  5421  						{
  5422  							ID: "Some-Imp-ID",
  5423  							Banner: &openrtb2.Banner{
  5424  								Format: []openrtb2.Format{
  5425  									{
  5426  										W: 600,
  5427  										H: 500,
  5428  									},
  5429  									{
  5430  										W: 300,
  5431  										H: 600,
  5432  									},
  5433  								},
  5434  							},
  5435  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
  5436  						},
  5437  					},
  5438  				},
  5439  			},
  5440  			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
  5441  			hasStoredAuctionResponses: false,
  5442  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}},
  5443  		},
  5444  		{
  5445  			description: "One imp with 2 stored bid responses and 2 different bidders in imp.ext, expect validate request to throw an errors",
  5446  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5447  				BidRequest: &openrtb2.BidRequest{
  5448  					ID:  "Some-ID",
  5449  					App: &openrtb2.App{},
  5450  					Imp: []openrtb2.Imp{
  5451  						{
  5452  							ID: "Some-Imp-ID",
  5453  							Banner: &openrtb2.Banner{
  5454  								Format: []openrtb2.Format{
  5455  									{
  5456  										W: 600,
  5457  										H: 500,
  5458  									},
  5459  									{
  5460  										W: 300,
  5461  										H: 600,
  5462  									},
  5463  								},
  5464  							},
  5465  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`),
  5466  						},
  5467  					},
  5468  				},
  5469  			},
  5470  			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
  5471  			hasStoredAuctionResponses: false,
  5472  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "rubicon": json.RawMessage(`{"test":true}`)}},
  5473  		},
  5474  		{
  5475  			description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder, expect validate request to throw no errors",
  5476  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5477  				BidRequest: &openrtb2.BidRequest{
  5478  					ID:  "Some-ID",
  5479  					App: &openrtb2.App{},
  5480  					Imp: []openrtb2.Imp{
  5481  						{
  5482  							ID: "Some-Imp-ID",
  5483  							Banner: &openrtb2.Banner{
  5484  								Format: []openrtb2.Format{
  5485  									{
  5486  										W: 600,
  5487  										H: 500,
  5488  									},
  5489  									{
  5490  										W: 300,
  5491  										H: 600,
  5492  									},
  5493  								},
  5494  							},
  5495  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
  5496  						},
  5497  					},
  5498  				},
  5499  			},
  5500  			expectedErrorList:         []error{},
  5501  			hasStoredAuctionResponses: false,
  5502  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
  5503  		},
  5504  		{
  5505  			description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error",
  5506  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5507  				BidRequest: &openrtb2.BidRequest{
  5508  					ID:  "Some-ID",
  5509  					App: &openrtb2.App{},
  5510  					Imp: []openrtb2.Imp{
  5511  						{
  5512  							ID: "Some-Imp-ID",
  5513  							Banner: &openrtb2.Banner{
  5514  								Format: []openrtb2.Format{
  5515  									{
  5516  										W: 600,
  5517  										H: 500,
  5518  									},
  5519  									{
  5520  										W: 300,
  5521  										H: 600,
  5522  									},
  5523  								},
  5524  							},
  5525  							Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"bidder":{"rubicon": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
  5526  						},
  5527  					},
  5528  				},
  5529  			},
  5530  			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
  5531  			hasStoredAuctionResponses: false,
  5532  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}},
  5533  		},
  5534  		{
  5535  			description: "One imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is defined in stored bid responses, expect validate request to throw no errors",
  5536  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5537  				BidRequest: &openrtb2.BidRequest{
  5538  					ID:  "Some-ID",
  5539  					App: &openrtb2.App{},
  5540  					Imp: []openrtb2.Imp{
  5541  						{
  5542  							ID: "Some-Imp-ID",
  5543  							Banner: &openrtb2.Banner{
  5544  								Format: []openrtb2.Format{
  5545  									{
  5546  										W: 600,
  5547  										H: 500,
  5548  									},
  5549  									{
  5550  										W: 300,
  5551  										H: 600,
  5552  									},
  5553  								},
  5554  							},
  5555  							Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
  5556  						},
  5557  					},
  5558  				},
  5559  			},
  5560  			expectedErrorList:         []error{},
  5561  			hasStoredAuctionResponses: false,
  5562  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"telaria": json.RawMessage(`{"test":true}`)}},
  5563  		},
  5564  		{
  5565  			description: "One imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error",
  5566  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5567  				BidRequest: &openrtb2.BidRequest{
  5568  					ID:  "Some-ID",
  5569  					App: &openrtb2.App{},
  5570  					Imp: []openrtb2.Imp{
  5571  						{
  5572  							ID: "Some-Imp-ID",
  5573  							Banner: &openrtb2.Banner{
  5574  								Format: []openrtb2.Format{
  5575  									{
  5576  										W: 600,
  5577  										H: 500,
  5578  									},
  5579  									{
  5580  										W: 300,
  5581  										H: 600,
  5582  									},
  5583  								},
  5584  							},
  5585  							Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
  5586  						},
  5587  					},
  5588  				},
  5589  			},
  5590  			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
  5591  			hasStoredAuctionResponses: false,
  5592  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}},
  5593  		},
  5594  		{
  5595  			description: "2 imps, one imp without stored responses, another imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error",
  5596  			givenRequestWrapper: &openrtb_ext.RequestWrapper{
  5597  				BidRequest: &openrtb2.BidRequest{
  5598  					ID:  "Some-ID",
  5599  					App: &openrtb2.App{},
  5600  					Imp: []openrtb2.Imp{
  5601  						{
  5602  							ID: "Some-Imp-ID",
  5603  							Banner: &openrtb2.Banner{
  5604  								Format: []openrtb2.Format{
  5605  									{
  5606  										W: 600,
  5607  										H: 500,
  5608  									},
  5609  									{
  5610  										W: 300,
  5611  										H: 600,
  5612  									},
  5613  								},
  5614  							},
  5615  							Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}}}`),
  5616  						},
  5617  						{
  5618  							ID: "Some-Imp-ID2",
  5619  							Banner: &openrtb2.Banner{
  5620  								Format: []openrtb2.Format{
  5621  									{
  5622  										W: 600,
  5623  										H: 500,
  5624  									},
  5625  									{
  5626  										W: 300,
  5627  										H: 600,
  5628  									},
  5629  								},
  5630  							},
  5631  							Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`),
  5632  						},
  5633  					},
  5634  				},
  5635  			},
  5636  			expectedErrorList:         []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID2. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")},
  5637  			hasStoredAuctionResponses: false,
  5638  			storedBidResponses:        stored_responses.ImpBidderStoredResp{"Some-Imp-ID2": {"appnexus": json.RawMessage(`{"test":true}`)}},
  5639  		},
  5640  	}
  5641  
  5642  	for _, test := range testCases {
  5643  		t.Run(test.description, func(t *testing.T) {
  5644  			errorList := deps.validateRequest(test.givenAccount, test.givenHttpRequest, test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false)
  5645  			assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description)
  5646  		})
  5647  	}
  5648  }
  5649  
  5650  func TestValidResponseAfterExecutingStages(t *testing.T) {
  5651  	const nbr int = 123
  5652  
  5653  	hooksPlanBuilder := mockPlanBuilder{
  5654  		entrypointPlan: hooks.Plan[hookstage.Entrypoint]{
  5655  			{
  5656  				Timeout: 5 * time.Millisecond,
  5657  				Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{
  5658  					entryPointHookUpdateWithErrors,
  5659  					entryPointHookUpdateWithErrorsAndWarnings,
  5660  				},
  5661  			},
  5662  			{
  5663  				Timeout: 5 * time.Millisecond,
  5664  				Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{
  5665  					entryPointHookUpdate,
  5666  				},
  5667  			},
  5668  		},
  5669  		rawAuctionPlan: hooks.Plan[hookstage.RawAuctionRequest]{
  5670  			{
  5671  				Timeout: 5 * time.Millisecond,
  5672  				Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{
  5673  					rawAuctionHookNone,
  5674  				},
  5675  			},
  5676  		},
  5677  	}
  5678  
  5679  	testCases := []struct {
  5680  		description string
  5681  		file        string
  5682  		planBuilder hooks.ExecutionPlanBuilder
  5683  	}{
  5684  		{
  5685  			description: "Assert correct BidResponse when request rejected at entrypoint stage",
  5686  			file:        "sample-requests/hooks/auction_entrypoint_reject.json",
  5687  			planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr, nil})},
  5688  		},
  5689  		{
  5690  			description: "Assert correct BidResponse when request rejected at raw-auction stage",
  5691  			file:        "sample-requests/hooks/auction_raw_auction_request_reject.json",
  5692  			planBuilder: mockPlanBuilder{rawAuctionPlan: makePlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr, nil})},
  5693  		},
  5694  		{
  5695  			description: "Assert correct BidResponse when request rejected at processed-auction stage",
  5696  			file:        "sample-requests/hooks/auction_processed_auction_request_reject.json",
  5697  			planBuilder: mockPlanBuilder{processedAuctionPlan: makePlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr, nil})},
  5698  		},
  5699  		{
  5700  			// bidder-request stage doesn't reject whole request, so we do not expect NBR code in response
  5701  			description: "Assert correct BidResponse when request rejected at bidder-request stage",
  5702  			file:        "sample-requests/hooks/auction_bidder_reject.json",
  5703  			planBuilder: mockPlanBuilder{bidderRequestPlan: makePlan[hookstage.BidderRequest](mockRejectionHook{nbr, nil})},
  5704  		},
  5705  		{
  5706  			description: "Assert correct BidResponse when request rejected at raw-bidder-response stage",
  5707  			file:        "sample-requests/hooks/auction_bidder_response_reject.json",
  5708  			planBuilder: mockPlanBuilder{rawBidderResponsePlan: makePlan[hookstage.RawBidderResponse](mockRejectionHook{nbr, nil})},
  5709  		},
  5710  		{
  5711  			description: "Assert correct BidResponse when request rejected with error from hook",
  5712  			file:        "sample-requests/hooks/auction_reject_with_error.json",
  5713  			planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr, errors.New("dummy")})},
  5714  		},
  5715  		{
  5716  			description: "Assert correct BidResponse with debug information from modules added to ext.prebid.modules",
  5717  			file:        "sample-requests/hooks/auction.json",
  5718  			planBuilder: hooksPlanBuilder,
  5719  		},
  5720  	}
  5721  
  5722  	for _, tc := range testCases {
  5723  		t.Run(tc.description, func(t *testing.T) {
  5724  			fileData, err := os.ReadFile(tc.file)
  5725  			assert.NoError(t, err, "Failed to read test file.")
  5726  
  5727  			test, err := parseTestData(fileData, tc.file)
  5728  			assert.NoError(t, err, "Failed to parse test file.")
  5729  			test.planBuilder = tc.planBuilder
  5730  			test.endpointType = OPENRTB_ENDPOINT
  5731  
  5732  			cfg := &config.Configuration{MaxRequestSize: maxSize, AccountDefaults: config.Account{DebugAllow: true}}
  5733  			auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
  5734  			assert.NoError(t, err, "Failed to build test endpoint.")
  5735  
  5736  			recorder := httptest.NewRecorder()
  5737  			req := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(test.BidRequest))
  5738  			auctionEndpointHandler(recorder, req, nil)
  5739  			assert.Equal(t, recorder.Code, http.StatusOK, "Endpoint should return 200 OK.")
  5740  
  5741  			var actualResp openrtb2.BidResponse
  5742  			var expectedResp openrtb2.BidResponse
  5743  			var actualExt openrtb_ext.ExtBidResponse
  5744  			var expectedExt openrtb_ext.ExtBidResponse
  5745  
  5746  			assert.NoError(t, jsonutil.UnmarshalValid(test.ExpectedBidResponse, &expectedResp), "Unable to unmarshal expected BidResponse.")
  5747  			assert.NoError(t, jsonutil.UnmarshalValid(recorder.Body.Bytes(), &actualResp), "Unable to unmarshal actual BidResponse.")
  5748  			if expectedResp.Ext != nil {
  5749  				assert.NoError(t, jsonutil.UnmarshalValid(expectedResp.Ext, &expectedExt), "Unable to unmarshal expected ExtBidResponse.")
  5750  				assert.NoError(t, jsonutil.UnmarshalValid(actualResp.Ext, &actualExt), "Unable to unmarshal actual ExtBidResponse.")
  5751  			}
  5752  
  5753  			assertBidResponseEqual(t, tc.file, expectedResp, actualResp)
  5754  			assert.Equal(t, expectedResp.NBR, actualResp.NBR, "Invalid NBR.")
  5755  			assert.Equal(t, expectedExt.Warnings, actualExt.Warnings, "Wrong bidResponse.ext.warnings.")
  5756  
  5757  			if expectedExt.Prebid != nil {
  5758  				hookexecution.AssertEqualModulesData(t, expectedExt.Prebid.Modules, actualExt.Prebid.Modules)
  5759  			} else {
  5760  				assert.Nil(t, actualExt.Prebid, "Invalid BidResponse.ext.prebid")
  5761  			}
  5762  
  5763  			// Close servers regardless if the test case was run or not
  5764  			for _, mockBidServer := range mockBidServers {
  5765  				mockBidServer.Close()
  5766  			}
  5767  			mockCurrencyRatesServer.Close()
  5768  		})
  5769  	}
  5770  }
  5771  
  5772  func TestSendAuctionResponse_LogsErrors(t *testing.T) {
  5773  	hookExecutor := &mockStageExecutor{
  5774  		outcomes: []hookexecution.StageOutcome{
  5775  			{
  5776  				Entity: "bid-request",
  5777  				Stage:  hooks.StageBidderRequest.String(),
  5778  				Groups: []hookexecution.GroupOutcome{
  5779  					{
  5780  						InvocationResults: []hookexecution.HookOutcome{
  5781  							{
  5782  								HookID: hookexecution.HookID{
  5783  									ModuleCode:   "foobar",
  5784  									HookImplCode: "foo",
  5785  								},
  5786  								Status:   hookexecution.StatusSuccess,
  5787  								Action:   hookexecution.ActionNone,
  5788  								Warnings: []string{"warning message"},
  5789  							},
  5790  						},
  5791  					},
  5792  				},
  5793  			},
  5794  		},
  5795  	}
  5796  
  5797  	testCases := []struct {
  5798  		description    string
  5799  		expectedErrors []error
  5800  		expectedStatus int
  5801  		request        *openrtb2.BidRequest
  5802  		response       *openrtb2.BidResponse
  5803  		hookExecutor   hookexecution.HookStageExecutor
  5804  	}{
  5805  		{
  5806  			description: "Error logged if hook enrichment fails",
  5807  			expectedErrors: []error{
  5808  				errors.New("Failed to enrich Bid Response with hook debug information: Invalid JSON Document"),
  5809  				errors.New("/openrtb2/auction Failed to send response: json: error calling MarshalJSON for type json.RawMessage: invalid character '.' looking for beginning of value"),
  5810  			},
  5811  			expectedStatus: 0,
  5812  			request:        &openrtb2.BidRequest{ID: "some-id", Test: 1},
  5813  			response:       &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("...")},
  5814  			hookExecutor:   hookExecutor,
  5815  		},
  5816  		{
  5817  			description: "Error logged if hook enrichment returns warnings",
  5818  			expectedErrors: []error{
  5819  				errors.New("Value is not a string: 1"),
  5820  				errors.New("Value is not a boolean: active"),
  5821  			},
  5822  			expectedStatus: 0,
  5823  			request:        &openrtb2.BidRequest{ID: "some-id", Test: 1, Ext: json.RawMessage(`{"prebid": {"debug": "active", "trace": 1}}`)},
  5824  			response:       &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("{}")},
  5825  			hookExecutor:   hookExecutor,
  5826  		},
  5827  	}
  5828  
  5829  	for _, test := range testCases {
  5830  		t.Run(test.description, func(t *testing.T) {
  5831  			writer := httptest.NewRecorder()
  5832  			labels := metrics.Labels{}
  5833  			ao := analytics.AuctionObject{}
  5834  			account := &config.Account{DebugAllow: true}
  5835  
  5836  			_, ao = sendAuctionResponse(writer, test.hookExecutor, test.response, test.request, account, labels, ao)
  5837  
  5838  			assert.Equal(t, ao.Errors, test.expectedErrors, "Invalid errors.")
  5839  			assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.")
  5840  		})
  5841  	}
  5842  }
  5843  
  5844  func TestParseRequestMultiBid(t *testing.T) {
  5845  	tests := []struct {
  5846  		name             string
  5847  		givenRequestBody string
  5848  		expectedReqExt   json.RawMessage
  5849  		expectedErrors   []error
  5850  	}{
  5851  		{
  5852  			name:             "validate and build multi-bid extension",
  5853  			givenRequestBody: validRequest(t, "multi-bid-error.json"),
  5854  			expectedReqExt:   getObject(t, "multi-bid-error.json", "expectedReqExt"),
  5855  			expectedErrors: []error{
  5856  				&errortypes.Warning{
  5857  					WarningCode: errortypes.MultiBidWarningCode,
  5858  					Message:     "maxBids not defined for {Bidder:appnexus, Bidders:[], MaxBids:<nil>, TargetBidderCodePrefix:}",
  5859  				},
  5860  				&errortypes.Warning{
  5861  					WarningCode: errortypes.MultiBidWarningCode,
  5862  					Message:     "invalid maxBids value, using minimum 1 limit for {Bidder:rubicon, Bidders:[], MaxBids:-1, TargetBidderCodePrefix:rubN}",
  5863  				},
  5864  				&errortypes.Warning{
  5865  					WarningCode: errortypes.MultiBidWarningCode,
  5866  					Message:     "invalid maxBids value, using maximum 9 limit for {Bidder:pubmatic, Bidders:[], MaxBids:10, TargetBidderCodePrefix:pm}",
  5867  				},
  5868  				&errortypes.Warning{
  5869  					WarningCode: errortypes.MultiBidWarningCode,
  5870  					Message:     "multiBid already defined for pubmatic, ignoring this instance {Bidder:pubmatic, Bidders:[], MaxBids:4, TargetBidderCodePrefix:pubM}",
  5871  				},
  5872  				&errortypes.Warning{
  5873  					WarningCode: errortypes.MultiBidWarningCode,
  5874  					Message:     "ignoring bidders from {Bidder:groupm, Bidders:[someBidder], MaxBids:5, TargetBidderCodePrefix:gm}",
  5875  				},
  5876  				&errortypes.Warning{
  5877  					WarningCode: errortypes.MultiBidWarningCode,
  5878  					Message:     "multiBid already defined for groupm, ignoring this instance {Bidder:, Bidders:[groupm], MaxBids:6, TargetBidderCodePrefix:}",
  5879  				},
  5880  				&errortypes.Warning{
  5881  					WarningCode: errortypes.MultiBidWarningCode,
  5882  					Message:     "ignoring targetbiddercodeprefix for {Bidder:, Bidders:[33across], MaxBids:7, TargetBidderCodePrefix:abc}",
  5883  				},
  5884  				&errortypes.Warning{
  5885  					WarningCode: errortypes.MultiBidWarningCode,
  5886  					Message:     "bidder(s) not specified for {Bidder:, Bidders:[], MaxBids:8, TargetBidderCodePrefix:xyz}",
  5887  				},
  5888  			},
  5889  		},
  5890  	}
  5891  	for _, test := range tests {
  5892  		t.Run(test.name, func(t *testing.T) {
  5893  			deps := &endpointDeps{
  5894  				fakeUUIDGenerator{},
  5895  				&warningsCheckExchange{},
  5896  				mockBidderParamValidator{},
  5897  				&mockStoredReqFetcher{},
  5898  				empty_fetcher.EmptyFetcher{},
  5899  				empty_fetcher.EmptyFetcher{},
  5900  				&config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))},
  5901  				&metricsConfig.NilMetricsEngine{},
  5902  				analyticsBuild.New(&config.Analytics{}),
  5903  				map[string]string{},
  5904  				false,
  5905  				[]byte{},
  5906  				openrtb_ext.BuildBidderMap(),
  5907  				nil,
  5908  				nil,
  5909  				hardcodedResponseIPValidator{response: true},
  5910  				empty_fetcher.EmptyFetcher{},
  5911  				hooks.EmptyPlanBuilder{},
  5912  				nil,
  5913  				openrtb_ext.NormalizeBidderName,
  5914  			}
  5915  
  5916  			hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine)
  5917  
  5918  			req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody))
  5919  
  5920  			resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor)
  5921  
  5922  			assert.NoError(t, resReq.RebuildRequest())
  5923  
  5924  			assert.JSONEq(t, string(test.expectedReqExt), string(resReq.Ext))
  5925  
  5926  			assert.Equal(t, errL, test.expectedErrors, "error length should match")
  5927  		})
  5928  	}
  5929  }
  5930  
  5931  type mockStoredResponseFetcher struct {
  5932  	data map[string]json.RawMessage
  5933  }
  5934  
  5935  func (cf *mockStoredResponseFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
  5936  	return nil, nil, nil
  5937  }
  5938  
  5939  func (cf *mockStoredResponseFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
  5940  	return cf.data, nil
  5941  }
  5942  
  5943  func getObject(t *testing.T, filename, key string) json.RawMessage {
  5944  	requestData, err := os.ReadFile("sample-requests/valid-whole/supplementary/" + filename)
  5945  	if err != nil {
  5946  		t.Fatalf("Failed to fetch a valid request: %v", err)
  5947  	}
  5948  	testBidRequest, _, _, err := jsonparser.Get(requestData, key)
  5949  	assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err)
  5950  
  5951  	var obj json.RawMessage
  5952  	err = jsonutil.UnmarshalValid(testBidRequest, &obj)
  5953  	if err != nil {
  5954  		t.Fatalf("Failed to fetch object with key '%s' ... got error: %v", key, err)
  5955  	}
  5956  	return obj
  5957  }
  5958  
  5959  func getIntegrationFromRequest(req *openrtb_ext.RequestWrapper) (string, error) {
  5960  	reqExt, err := req.GetRequestExt()
  5961  	if err != nil {
  5962  		return "", err
  5963  	}
  5964  	reqPrebid := reqExt.GetPrebid()
  5965  	return reqPrebid.Integration, nil
  5966  }
  5967  
  5968  type mockStageExecutor struct {
  5969  	hookexecution.EmptyHookExecutor
  5970  
  5971  	outcomes []hookexecution.StageOutcome
  5972  }
  5973  
  5974  func (e mockStageExecutor) GetOutcomes() []hookexecution.StageOutcome {
  5975  	return e.outcomes
  5976  }
  5977  
  5978  func TestSetSeatNonBidRaw(t *testing.T) {
  5979  	type args struct {
  5980  		request         *openrtb_ext.RequestWrapper
  5981  		auctionResponse *exchange.AuctionResponse
  5982  	}
  5983  	tests := []struct {
  5984  		name    string
  5985  		args    args
  5986  		wantErr bool
  5987  	}{
  5988  		{
  5989  			name:    "nil-auctionResponse",
  5990  			args:    args{auctionResponse: nil},
  5991  			wantErr: false,
  5992  		},
  5993  		{
  5994  			name:    "nil-bidResponse",
  5995  			args:    args{auctionResponse: &exchange.AuctionResponse{BidResponse: nil}},
  5996  			wantErr: false,
  5997  		},
  5998  		{
  5999  			name:    "invalid-response.Ext",
  6000  			args:    args{auctionResponse: &exchange.AuctionResponse{BidResponse: &openrtb2.BidResponse{Ext: []byte(`invalid_json`)}}},
  6001  			wantErr: true,
  6002  		},
  6003  		{
  6004  			name: "update-seatnonbid-in-ext",
  6005  			args: args{
  6006  				request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid": { "returnallbidstatus" : true }}`)}},
  6007  				auctionResponse: &exchange.AuctionResponse{
  6008  					ExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: &openrtb_ext.ExtResponsePrebid{SeatNonBid: []openrtb_ext.SeatNonBid{}}},
  6009  					BidResponse:    &openrtb2.BidResponse{Ext: []byte(`{}`)},
  6010  				},
  6011  			},
  6012  			wantErr: false,
  6013  		},
  6014  	}
  6015  	for _, tt := range tests {
  6016  		t.Run(tt.name, func(t *testing.T) {
  6017  			if err := setSeatNonBidRaw(tt.args.request, tt.args.auctionResponse); (err != nil) != tt.wantErr {
  6018  				t.Errorf("setSeatNonBidRaw() error = %v, wantErr %v", err, tt.wantErr)
  6019  			}
  6020  		})
  6021  	}
  6022  }
  6023  
  6024  func TestValidateAliases(t *testing.T) {
  6025  	deps := &endpointDeps{
  6026  		disabledBidders: map[string]string{"rubicon": "rubicon"},
  6027  		bidderMap:       map[string]openrtb_ext.BidderName{"appnexus": openrtb_ext.BidderName("appnexus")},
  6028  	}
  6029  
  6030  	testCases := []struct {
  6031  		description     string
  6032  		aliases         map[string]string
  6033  		expectedAliases map[string]string
  6034  		expectedError   error
  6035  	}{
  6036  		{
  6037  			description:     "valid case",
  6038  			aliases:         map[string]string{"test": "appnexus"},
  6039  			expectedAliases: map[string]string{"test": "appnexus"},
  6040  			expectedError:   nil,
  6041  		},
  6042  		{
  6043  			description:     "valid case - case insensitive",
  6044  			aliases:         map[string]string{"test": "Appnexus"},
  6045  			expectedAliases: map[string]string{"test": "appnexus"},
  6046  			expectedError:   nil,
  6047  		},
  6048  		{
  6049  			description:     "disabled bidder",
  6050  			aliases:         map[string]string{"test": "rubicon"},
  6051  			expectedAliases: nil,
  6052  			expectedError:   errors.New("request.ext.prebid.aliases.test refers to disabled bidder: rubicon"),
  6053  		},
  6054  		{
  6055  			description:     "coreBidderName not found",
  6056  			aliases:         map[string]string{"test": "anyBidder"},
  6057  			expectedAliases: nil,
  6058  			expectedError:   errors.New("request.ext.prebid.aliases.test refers to unknown bidder: anyBidder"),
  6059  		},
  6060  		{
  6061  			description:     "alias name is coreBidder name",
  6062  			aliases:         map[string]string{"appnexus": "appnexus"},
  6063  			expectedAliases: nil,
  6064  			expectedError:   errors.New("request.ext.prebid.aliases.appnexus defines a no-op alias. Choose a different alias, or remove this entry."),
  6065  		},
  6066  	}
  6067  
  6068  	for _, testCase := range testCases {
  6069  		t.Run(testCase.description, func(t *testing.T) {
  6070  			err := deps.validateAliases(testCase.aliases)
  6071  			if err != nil {
  6072  				assert.Equal(t, testCase.expectedError, err)
  6073  			} else {
  6074  				assert.ObjectsAreEqualValues(testCase.expectedAliases, map[string]string{"test": "appnexus"})
  6075  			}
  6076  		})
  6077  	}
  6078  }
  6079  
  6080  func fakeNormalizeBidderName(name string) (openrtb_ext.BidderName, bool) {
  6081  	return openrtb_ext.BidderName(strings.ToLower(name)), true
  6082  }
  6083  
  6084  func TestValidateOrFillCookieDeprecation(t *testing.T) {
  6085  	type args struct {
  6086  		httpReq *http.Request
  6087  		req     *openrtb_ext.RequestWrapper
  6088  		account config.Account
  6089  	}
  6090  	tests := []struct {
  6091  		name          string
  6092  		args          args
  6093  		wantDeviceExt json.RawMessage
  6094  		wantErr       error
  6095  	}{
  6096  		{
  6097  			name:          "account-nil",
  6098  			wantDeviceExt: nil,
  6099  			wantErr:       nil,
  6100  		},
  6101  		{
  6102  			name: "cookie-deprecation-not-enabled",
  6103  			args: args{
  6104  				httpReq: &http.Request{
  6105  					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
  6106  				},
  6107  				req: &openrtb_ext.RequestWrapper{
  6108  					BidRequest: &openrtb2.BidRequest{},
  6109  				},
  6110  			},
  6111  			wantDeviceExt: nil,
  6112  			wantErr:       nil,
  6113  		},
  6114  		{
  6115  			name: "cookie-deprecation-disabled-explicitly",
  6116  			args: args{
  6117  				httpReq: &http.Request{
  6118  					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
  6119  				},
  6120  				req: &openrtb_ext.RequestWrapper{
  6121  					BidRequest: &openrtb2.BidRequest{},
  6122  				},
  6123  				account: config.Account{
  6124  					Privacy: config.AccountPrivacy{
  6125  						PrivacySandbox: config.PrivacySandbox{
  6126  							CookieDeprecation: config.CookieDeprecation{
  6127  								Enabled: false,
  6128  							},
  6129  						},
  6130  					},
  6131  				},
  6132  			},
  6133  			wantDeviceExt: nil,
  6134  			wantErr:       nil,
  6135  		},
  6136  		{
  6137  			name: "cookie-deprecation-enabled-header-not-present-in-request",
  6138  			args: args{
  6139  				httpReq: &http.Request{},
  6140  				req: &openrtb_ext.RequestWrapper{
  6141  					BidRequest: &openrtb2.BidRequest{},
  6142  				},
  6143  				account: config.Account{
  6144  					Privacy: config.AccountPrivacy{
  6145  						PrivacySandbox: config.PrivacySandbox{
  6146  							CookieDeprecation: config.CookieDeprecation{
  6147  								Enabled: true,
  6148  							},
  6149  						},
  6150  					},
  6151  				},
  6152  			},
  6153  			wantDeviceExt: nil,
  6154  			wantErr:       nil,
  6155  		},
  6156  		{
  6157  			name: "header-present-request-device-nil",
  6158  			args: args{
  6159  				httpReq: &http.Request{
  6160  					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
  6161  				},
  6162  				req: &openrtb_ext.RequestWrapper{
  6163  					BidRequest: &openrtb2.BidRequest{},
  6164  				},
  6165  				account: config.Account{
  6166  					Privacy: config.AccountPrivacy{
  6167  						PrivacySandbox: config.PrivacySandbox{
  6168  							CookieDeprecation: config.CookieDeprecation{
  6169  								Enabled: true,
  6170  							},
  6171  						},
  6172  					},
  6173  				},
  6174  			},
  6175  			wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1"}`),
  6176  			wantErr:       nil,
  6177  		},
  6178  		{
  6179  			name: "header-present-request-device-ext-nil",
  6180  			args: args{
  6181  				httpReq: &http.Request{
  6182  					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
  6183  				},
  6184  				req: &openrtb_ext.RequestWrapper{
  6185  					BidRequest: &openrtb2.BidRequest{
  6186  						Device: &openrtb2.Device{
  6187  							Ext: nil,
  6188  						},
  6189  					},
  6190  				},
  6191  				account: config.Account{
  6192  					Privacy: config.AccountPrivacy{
  6193  						PrivacySandbox: config.PrivacySandbox{
  6194  							CookieDeprecation: config.CookieDeprecation{
  6195  								Enabled: true,
  6196  							},
  6197  						},
  6198  					},
  6199  				},
  6200  			},
  6201  			wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1"}`),
  6202  			wantErr:       nil,
  6203  		},
  6204  		{
  6205  			name: "header-present-request-device-ext-not-nil",
  6206  			args: args{
  6207  				httpReq: &http.Request{
  6208  					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
  6209  				},
  6210  				req: &openrtb_ext.RequestWrapper{
  6211  					BidRequest: &openrtb2.BidRequest{
  6212  						Device: &openrtb2.Device{
  6213  							Ext: json.RawMessage(`{"foo":"bar"}`),
  6214  						},
  6215  					},
  6216  				},
  6217  				account: config.Account{
  6218  					Privacy: config.AccountPrivacy{
  6219  						PrivacySandbox: config.PrivacySandbox{
  6220  							CookieDeprecation: config.CookieDeprecation{
  6221  								Enabled: true,
  6222  							},
  6223  						},
  6224  					},
  6225  				},
  6226  			},
  6227  			wantDeviceExt: json.RawMessage(`{"cdep":"example_label_1","foo":"bar"}`),
  6228  			wantErr:       nil,
  6229  		},
  6230  		{
  6231  			name: "header-present-with-length-more-than-100",
  6232  			args: args{
  6233  				httpReq: &http.Request{
  6234  					Header: http.Header{secCookieDeprecation: []string{"zjfXqGxXFI8yura8AhQl1DK2EMMmryrC8haEpAlwjoerrFfEo2MQTXUq6cSmLohI8gjsnkGU4oAzvXd4TTAESzEKsoYjRJ2zKxmEa"}},
  6235  				},
  6236  				req: &openrtb_ext.RequestWrapper{
  6237  					BidRequest: &openrtb2.BidRequest{
  6238  						Device: &openrtb2.Device{
  6239  							Ext: json.RawMessage(`{"foo":"bar"}`),
  6240  						},
  6241  					},
  6242  				},
  6243  				account: config.Account{
  6244  					Privacy: config.AccountPrivacy{
  6245  						PrivacySandbox: config.PrivacySandbox{
  6246  							CookieDeprecation: config.CookieDeprecation{
  6247  								Enabled: true,
  6248  							},
  6249  						},
  6250  					},
  6251  				},
  6252  			},
  6253  			wantDeviceExt: json.RawMessage(`{"foo":"bar"}`),
  6254  			wantErr: &errortypes.Warning{
  6255  				Message:     "request.device.ext.cdep must not exceed 100 characters",
  6256  				WarningCode: errortypes.SecCookieDeprecationLenWarningCode,
  6257  			},
  6258  		},
  6259  		{
  6260  			name: "header-present-request-device-ext-cdep-present",
  6261  			args: args{
  6262  				httpReq: &http.Request{
  6263  					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
  6264  				},
  6265  				req: &openrtb_ext.RequestWrapper{
  6266  					BidRequest: &openrtb2.BidRequest{
  6267  						Device: &openrtb2.Device{
  6268  							Ext: json.RawMessage(`{"foo":"bar","cdep":"example_label_2"}`),
  6269  						},
  6270  					},
  6271  				},
  6272  				account: config.Account{
  6273  					Privacy: config.AccountPrivacy{
  6274  						PrivacySandbox: config.PrivacySandbox{
  6275  							CookieDeprecation: config.CookieDeprecation{
  6276  								Enabled: true,
  6277  							},
  6278  						},
  6279  					},
  6280  				},
  6281  			},
  6282  			wantDeviceExt: json.RawMessage(`{"foo":"bar","cdep":"example_label_2"}`),
  6283  			wantErr:       nil,
  6284  		},
  6285  		{
  6286  			name: "header-present-request-device-ext-invalid",
  6287  			args: args{
  6288  				httpReq: &http.Request{
  6289  					Header: http.Header{secCookieDeprecation: []string{"example_label_1"}},
  6290  				},
  6291  				req: &openrtb_ext.RequestWrapper{
  6292  					BidRequest: &openrtb2.BidRequest{
  6293  						Device: &openrtb2.Device{
  6294  							Ext: json.RawMessage(`{`),
  6295  						},
  6296  					},
  6297  				},
  6298  				account: config.Account{
  6299  					Privacy: config.AccountPrivacy{
  6300  						PrivacySandbox: config.PrivacySandbox{
  6301  							CookieDeprecation: config.CookieDeprecation{
  6302  								Enabled: true,
  6303  							},
  6304  						},
  6305  					},
  6306  				},
  6307  			},
  6308  			wantDeviceExt: json.RawMessage(`{`),
  6309  			wantErr: &errortypes.FailedToUnmarshal{
  6310  				Message: "expects \" or n, but found \x00",
  6311  			},
  6312  		},
  6313  	}
  6314  	for _, tt := range tests {
  6315  		t.Run(tt.name, func(t *testing.T) {
  6316  			err := validateOrFillCookieDeprecation(tt.args.httpReq, tt.args.req, &tt.args.account)
  6317  			assert.Equal(t, tt.wantErr, err)
  6318  			if tt.args.req != nil {
  6319  				err := tt.args.req.RebuildRequest()
  6320  				assert.NoError(t, err)
  6321  			}
  6322  			if tt.wantDeviceExt == nil {
  6323  				if tt.args.req != nil && tt.args.req.Device != nil {
  6324  					assert.Nil(t, tt.args.req.Device.Ext)
  6325  				}
  6326  			} else {
  6327  				assert.Equal(t, string(tt.wantDeviceExt), string(tt.args.req.Device.Ext))
  6328  			}
  6329  		})
  6330  	}
  6331  }
  6332  
  6333  func TestValidateRequestCookieDeprecation(t *testing.T) {
  6334  	testCases :=
  6335  		[]struct {
  6336  			name         string
  6337  			givenAccount *config.Account
  6338  			httpReq      *http.Request
  6339  			reqWrapper   *openrtb_ext.RequestWrapper
  6340  			wantErrs     []error
  6341  			wantCDep     string
  6342  		}{
  6343  			{
  6344  				name: "header-with-length-less-than-100",
  6345  				httpReq: func() *http.Request {
  6346  					req := httptest.NewRequest("POST", "/openrtb2/auction", nil)
  6347  					req.Header.Set(secCookieDeprecation, "sample-value")
  6348  					return req
  6349  				}(),
  6350  				givenAccount: &config.Account{
  6351  					ID: "1",
  6352  					Privacy: config.AccountPrivacy{
  6353  						PrivacySandbox: config.PrivacySandbox{
  6354  							CookieDeprecation: config.CookieDeprecation{
  6355  								Enabled: true,
  6356  								TTLSec:  86400,
  6357  							},
  6358  						},
  6359  					},
  6360  				},
  6361  				reqWrapper: &openrtb_ext.RequestWrapper{
  6362  					BidRequest: &openrtb2.BidRequest{
  6363  						ID:  "Some-ID",
  6364  						App: &openrtb2.App{},
  6365  						Imp: []openrtb2.Imp{
  6366  							{
  6367  								ID: "Some-Imp-ID",
  6368  								Banner: &openrtb2.Banner{
  6369  									Format: []openrtb2.Format{
  6370  										{
  6371  											W: 600,
  6372  											H: 500,
  6373  										},
  6374  										{
  6375  											W: 300,
  6376  											H: 600,
  6377  										},
  6378  									},
  6379  								},
  6380  								Ext: []byte(`{"pubmatic":{"publisherId": 12345678}}`),
  6381  							},
  6382  						},
  6383  					},
  6384  				},
  6385  				wantErrs: []error{},
  6386  				wantCDep: "sample-value",
  6387  			},
  6388  			{
  6389  				name: "header-with-length-more-than-100",
  6390  				httpReq: func() *http.Request {
  6391  					req := httptest.NewRequest("POST", "/openrtb2/auction", nil)
  6392  					req.Header.Set(secCookieDeprecation, "zjfXqGxXFI8yura8AhQl1DK2EMMmryrC8haEpAlwjoerrFfEo2MQTXUq6cSmLohI8gjsnkGU4oAzvXd4TTAESzEKsoYjRJ2zKxmEa")
  6393  					return req
  6394  				}(),
  6395  				givenAccount: &config.Account{
  6396  					ID: "1",
  6397  					Privacy: config.AccountPrivacy{
  6398  						PrivacySandbox: config.PrivacySandbox{
  6399  							CookieDeprecation: config.CookieDeprecation{
  6400  								Enabled: true,
  6401  								TTLSec:  86400,
  6402  							},
  6403  						},
  6404  					},
  6405  				},
  6406  				reqWrapper: &openrtb_ext.RequestWrapper{
  6407  					BidRequest: &openrtb2.BidRequest{
  6408  						ID:  "Some-ID",
  6409  						App: &openrtb2.App{},
  6410  						Imp: []openrtb2.Imp{
  6411  							{
  6412  								ID: "Some-Imp-ID",
  6413  								Banner: &openrtb2.Banner{
  6414  									Format: []openrtb2.Format{
  6415  										{
  6416  											W: 600,
  6417  											H: 500,
  6418  										},
  6419  										{
  6420  											W: 300,
  6421  											H: 600,
  6422  										},
  6423  									},
  6424  								},
  6425  								Ext: []byte(`{"pubmatic":{"publisherId": 12345678}}`),
  6426  							},
  6427  						},
  6428  					},
  6429  				},
  6430  				wantErrs: []error{
  6431  					&errortypes.Warning{
  6432  						Message:     "request.device.ext.cdep must not exceed 100 characters",
  6433  						WarningCode: errortypes.SecCookieDeprecationLenWarningCode,
  6434  					},
  6435  				},
  6436  				wantCDep: "",
  6437  			},
  6438  		}
  6439  
  6440  	deps := &endpointDeps{
  6441  		fakeUUIDGenerator{},
  6442  		&warningsCheckExchange{},
  6443  		mockBidderParamValidator{},
  6444  		&mockStoredReqFetcher{},
  6445  		empty_fetcher.EmptyFetcher{},
  6446  		&mockAccountFetcher{},
  6447  		&config.Configuration{},
  6448  		&metricsConfig.NilMetricsEngine{},
  6449  		analyticsBuild.New(&config.Analytics{}),
  6450  		map[string]string{},
  6451  		false,
  6452  		[]byte{},
  6453  		openrtb_ext.BuildBidderMap(),
  6454  		nil,
  6455  		nil,
  6456  		hardcodedResponseIPValidator{response: true},
  6457  		empty_fetcher.EmptyFetcher{},
  6458  		hooks.EmptyPlanBuilder{},
  6459  		nil,
  6460  		openrtb_ext.NormalizeBidderName,
  6461  	}
  6462  
  6463  	for _, test := range testCases {
  6464  		errs := deps.validateRequest(test.givenAccount, test.httpReq, test.reqWrapper, false, false, stored_responses.ImpBidderStoredResp{}, false)
  6465  		assert.Equal(t, test.wantErrs, errs)
  6466  		test.reqWrapper.RebuildRequest()
  6467  		deviceExt, err := test.reqWrapper.GetDeviceExt()
  6468  		assert.NoError(t, err)
  6469  		assert.Equal(t, test.wantCDep, deviceExt.GetCDep())
  6470  	}
  6471  }
  6472  
  6473  func TestSetSecBrowsingTopicsImplicitly(t *testing.T) {
  6474  	type args struct {
  6475  		httpReq *http.Request
  6476  		r       *openrtb_ext.RequestWrapper
  6477  		account *config.Account
  6478  	}
  6479  	tests := []struct {
  6480  		name     string
  6481  		args     args
  6482  		wantUser *openrtb2.User
  6483  	}{
  6484  		{
  6485  			name: "empty HTTP request, no change in user data",
  6486  			args: args{
  6487  				httpReq: &http.Request{},
  6488  				r:       &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
  6489  				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}},
  6490  			},
  6491  			wantUser: nil,
  6492  		},
  6493  		{
  6494  			name: "valid topic in request but topicsdomain not configured by host, no change in user data",
  6495  			args: args{
  6496  				httpReq: &http.Request{
  6497  					Header: http.Header{
  6498  						secBrowsingTopics: []string{"(1);v=chrome.1:1:2, ();p=P00000000000"},
  6499  					},
  6500  				},
  6501  				r:       &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
  6502  				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: ""}}},
  6503  			},
  6504  			wantUser: nil,
  6505  		},
  6506  		{
  6507  			name: "valid topic in request and topicsdomain configured by host, topics copied to user data",
  6508  			args: args{
  6509  				httpReq: &http.Request{
  6510  					Header: http.Header{
  6511  						secBrowsingTopics: []string{"(1);v=chrome.1:1:2, ();p=P00000000000"},
  6512  					},
  6513  				},
  6514  				r:       &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
  6515  				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}},
  6516  			},
  6517  			wantUser: &openrtb2.User{
  6518  				Data: []openrtb2.Data{
  6519  					{
  6520  						Name: "ads.pubmatic.com",
  6521  						Segment: []openrtb2.Segment{
  6522  							{
  6523  								ID: "1",
  6524  							},
  6525  						},
  6526  						Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
  6527  					},
  6528  				},
  6529  			},
  6530  		},
  6531  		{
  6532  			name: "valid empty topic in request, no change in user data",
  6533  			args: args{
  6534  				httpReq: &http.Request{
  6535  					Header: http.Header{
  6536  						secBrowsingTopics: []string{"();p=P0000000000000000000000000000000"},
  6537  					},
  6538  				},
  6539  				r:       &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}},
  6540  				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}},
  6541  			},
  6542  			wantUser: nil,
  6543  		},
  6544  		{
  6545  			name: "request with a few valid topics (including duplicate topics, segIDs, matching segtax, segclass, etc) and a few invalid topics(different invalid format), only valid and unique topics copied/merged to/with user data",
  6546  			args: args{
  6547  				httpReq: &http.Request{
  6548  					Header: http.Header{
  6549  						secBrowsingTopics: []string{"(1);v=chrome.1:1:2, (1 2);v=chrome.1:1:2,(4);v=chrome.1:1:2,();p=P0000000000,(4);v=chrome.1, 5);v=chrome.1, (6;v=chrome.1, ();v=chrome.1, (	);v=chrome.1, (1);v=chrome.1:1:2, (1 2 4		6 7			4567	  ) ; v=chrome.1: 2 : 3,();p=P0000000000"},
  6550  					},
  6551  				},
  6552  				r: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{
  6553  					User: &openrtb2.User{
  6554  						Data: []openrtb2.Data{
  6555  							{
  6556  								Name: "chrome.com",
  6557  								Segment: []openrtb2.Segment{
  6558  									{ID: "1"},
  6559  								},
  6560  								Ext: json.RawMessage(`{"segtax":603,"segclass":"4"}`),
  6561  							},
  6562  							{
  6563  								Name: "ads.pubmatic.com",
  6564  								Segment: []openrtb2.Segment{
  6565  									{ID: "1"},
  6566  									{ID: "3"},
  6567  								},
  6568  								Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
  6569  							},
  6570  						},
  6571  					},
  6572  				}},
  6573  				account: &config.Account{Privacy: config.AccountPrivacy{PrivacySandbox: config.PrivacySandbox{TopicsDomain: "ads.pubmatic.com"}}},
  6574  			},
  6575  			wantUser: &openrtb2.User{
  6576  				Data: []openrtb2.Data{
  6577  					{
  6578  						Name: "chrome.com",
  6579  						Segment: []openrtb2.Segment{
  6580  							{ID: "1"},
  6581  						},
  6582  						Ext: json.RawMessage(`{"segtax":603,"segclass":"4"}`),
  6583  					},
  6584  					{
  6585  						Name: "ads.pubmatic.com",
  6586  						Segment: []openrtb2.Segment{
  6587  							{ID: "1"},
  6588  							{ID: "2"},
  6589  							{ID: "3"},
  6590  							{ID: "4"},
  6591  						},
  6592  						Ext: json.RawMessage(`{"segtax":600,"segclass":"2"}`),
  6593  					},
  6594  					{
  6595  						Name: "ads.pubmatic.com",
  6596  						Segment: []openrtb2.Segment{
  6597  							{ID: "1"},
  6598  							{ID: "2"},
  6599  							{ID: "4"},
  6600  							{ID: "6"},
  6601  							{ID: "7"},
  6602  							{ID: "4567"},
  6603  						},
  6604  						Ext: json.RawMessage(`{"segtax":601,"segclass":"3"}`),
  6605  					},
  6606  				},
  6607  			},
  6608  		},
  6609  	}
  6610  	for _, tt := range tests {
  6611  		t.Run(tt.name, func(t *testing.T) {
  6612  			setSecBrowsingTopicsImplicitly(tt.args.httpReq, tt.args.r, tt.args.account)
  6613  
  6614  			// sequence is not guaranteed we're using a map to filter segids
  6615  			sortUserData(tt.wantUser)
  6616  			sortUserData(tt.args.r.User)
  6617  			assert.Equal(t, tt.wantUser, tt.args.r.User, tt.name)
  6618  		})
  6619  	}
  6620  }
  6621  
  6622  func sortUserData(user *openrtb2.User) {
  6623  	if user != nil {
  6624  		sort.Slice(user.Data, func(i, j int) bool {
  6625  			if user.Data[i].Name == user.Data[j].Name {
  6626  				return string(user.Data[i].Ext) < string(user.Data[j].Ext)
  6627  			}
  6628  			return user.Data[i].Name < user.Data[j].Name
  6629  		})
  6630  		for g := range user.Data {
  6631  			sort.Slice(user.Data[g].Segment, func(i, j int) bool {
  6632  				return user.Data[g].Segment[i].ID < user.Data[g].Segment[j].ID
  6633  			})
  6634  		}
  6635  	}
  6636  }