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