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

     1  package openrtb2
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"net/url"
    11  	"os"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/julienschmidt/httprouter"
    18  	"github.com/prebid/openrtb/v20/openrtb2"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  
    22  	"github.com/prebid/prebid-server/v2/amp"
    23  	"github.com/prebid/prebid-server/v2/analytics"
    24  	analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build"
    25  	"github.com/prebid/prebid-server/v2/config"
    26  	"github.com/prebid/prebid-server/v2/errortypes"
    27  	"github.com/prebid/prebid-server/v2/exchange"
    28  	"github.com/prebid/prebid-server/v2/hooks"
    29  	"github.com/prebid/prebid-server/v2/hooks/hookexecution"
    30  	"github.com/prebid/prebid-server/v2/hooks/hookstage"
    31  	"github.com/prebid/prebid-server/v2/metrics"
    32  	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
    33  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    34  	"github.com/prebid/prebid-server/v2/privacy"
    35  	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
    36  	"github.com/prebid/prebid-server/v2/util/jsonutil"
    37  )
    38  
    39  // TestGoodRequests makes sure that the auction runs properly-formatted stored bids correctly.
    40  func TestGoodAmpRequests(t *testing.T) {
    41  	testGroups := []struct {
    42  		desc      string
    43  		dir       string
    44  		testFiles []string
    45  	}{
    46  		{
    47  			desc: "Valid supplementary, tag_id param only",
    48  			dir:  "sample-requests/amp/valid-supplementary/",
    49  			testFiles: []string{
    50  				"aliased-buyeruids.json",
    51  				"aliases.json",
    52  				"imp-with-stored-resp.json",
    53  				"gdpr-no-consentstring.json",
    54  				"gdpr.json",
    55  				"buyeruids-case-insensitive.json",
    56  				"buyeruids-camel-case.json",
    57  				"aliased-buyeruids-case-insensitive.json",
    58  			},
    59  		},
    60  		{
    61  			desc: "Valid, consent handling in query",
    62  			dir:  "sample-requests/amp/consent-through-query/",
    63  			testFiles: []string{
    64  				"addtl-consent-through-query.json",
    65  				"gdpr-tcf1-consent-through-query.json",
    66  				"gdpr-tcf2-consent-through-query.json",
    67  				"gdpr-legacy-tcf2-consent-through-query.json",
    68  				"gdpr-ccpa-through-query.json",
    69  			},
    70  		},
    71  	}
    72  
    73  	for _, tgroup := range testGroups {
    74  		for _, filename := range tgroup.testFiles {
    75  			// Read test case and unmarshal
    76  			fileJsonData, err := os.ReadFile(tgroup.dir + filename)
    77  			if !assert.NoError(t, err, "Failed to fetch a valid request: %v. Test file: %s", err, filename) {
    78  				continue
    79  			}
    80  
    81  			test := testCase{}
    82  			if !assert.NoError(t, jsonutil.UnmarshalValid(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", filename, err) {
    83  				continue
    84  			}
    85  
    86  			// build http request
    87  			request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil)
    88  			recorder := httptest.NewRecorder()
    89  
    90  			// build the stored requests and configure endpoint conf
    91  			query := request.URL.Query()
    92  			tagID := query.Get("tag_id")
    93  			if !assert.Greater(t, len(tagID), 0, "AMP test %s file is missing tag_id field", filename) {
    94  				continue
    95  			}
    96  
    97  			test.StoredRequest = map[string]json.RawMessage{tagID: test.BidRequest}
    98  			test.endpointType = AMP_ENDPOINT
    99  
   100  			cfg := &config.Configuration{
   101  				MaxRequestSize: maxSize,
   102  				GDPR:           config.GDPR{Enabled: true},
   103  			}
   104  			if test.Config != nil {
   105  				cfg.BlacklistedApps = test.Config.BlacklistedApps
   106  				cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap()
   107  				cfg.AccountRequired = test.Config.AccountRequired
   108  			}
   109  
   110  			// Set test up
   111  			ampEndpoint, ex, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
   112  			if !assert.NoError(t, err) {
   113  				continue
   114  			}
   115  
   116  			// runTestCase
   117  			ampEndpoint(recorder, request, nil)
   118  
   119  			// Close servers
   120  			for _, mockBidServer := range mockBidServers {
   121  				mockBidServer.Close()
   122  			}
   123  			mockCurrencyRatesServer.Close()
   124  
   125  			// Assertions
   126  			if assert.Equal(t, test.ExpectedReturnCode, recorder.Code, "Expected status %d. Got %d. Amp test file: %s", http.StatusOK, recorder.Code, filename) {
   127  				if test.ExpectedReturnCode == http.StatusOK {
   128  					assert.JSONEq(t, string(test.ExpectedAmpResponse), recorder.Body.String(), "Not the expected response. Test file: %s", filename)
   129  				} else {
   130  					assert.Equal(t, test.ExpectedErrorMessage, recorder.Body.String(), filename)
   131  				}
   132  			}
   133  			if test.ExpectedValidatedBidReq != nil {
   134  				// compare as json to ignore whitespace and ext field ordering
   135  				actualJson, err := jsonutil.Marshal(ex.actualValidatedBidReq)
   136  				if assert.NoError(t, err, "Error converting actual bid request to json. Test file: %s", filename) {
   137  					assert.JSONEq(t, string(test.ExpectedValidatedBidReq), string(actualJson), "Not the expected validated request. Test file: %s", filename)
   138  				}
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  func TestAccountErrors(t *testing.T) {
   145  	tests := []struct {
   146  		description string
   147  		storedReqID string
   148  		filename    string
   149  	}{
   150  		{
   151  			description: "Malformed account config",
   152  			storedReqID: "1",
   153  			filename:    "account-malformed/malformed-acct.json",
   154  		},
   155  	}
   156  
   157  	for _, tt := range tests {
   158  		fileJsonData, err := os.ReadFile("sample-requests/" + tt.filename)
   159  		if !assert.NoError(t, err, "Failed to fetch a valid request: %v. Test file: %s", err, tt.filename) {
   160  			continue
   161  		}
   162  
   163  		test := testCase{}
   164  		if !assert.NoError(t, jsonutil.UnmarshalValid(fileJsonData, &test), "Failed to unmarshal data from file: %s. Error: %v", tt.filename, err) {
   165  			continue
   166  		}
   167  		test.StoredRequest = map[string]json.RawMessage{tt.storedReqID: test.BidRequest}
   168  		test.endpointType = AMP_ENDPOINT
   169  
   170  		cfg := &config.Configuration{
   171  			MaxRequestSize: maxSize,
   172  		}
   173  		cfg.MarshalAccountDefaults()
   174  
   175  		ampEndpoint, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
   176  		if !assert.NoError(t, err) {
   177  			continue
   178  		}
   179  
   180  		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&", tt.storedReqID), nil)
   181  		recorder := httptest.NewRecorder()
   182  		ampEndpoint(recorder, request, nil)
   183  
   184  		for _, mockBidServer := range mockBidServers {
   185  			mockBidServer.Close()
   186  		}
   187  		mockCurrencyRatesServer.Close()
   188  
   189  		assert.Equal(t, test.ExpectedReturnCode, recorder.Code, "%s: %s", tt.description, tt.filename)
   190  		assert.Equal(t, test.ExpectedErrorMessage, recorder.Body.String(), "%s: %s", tt.description, tt.filename)
   191  	}
   192  }
   193  
   194  // Prevents #683
   195  func TestAMPPageInfo(t *testing.T) {
   196  	const page = "http://test.somepage.co.uk:1234?myquery=1&other=2"
   197  	stored := map[string]json.RawMessage{
   198  		"1": json.RawMessage(validRequest(t, "site.json")),
   199  	}
   200  	exchange := &mockAmpExchange{}
   201  
   202  	endpoint, _ := NewAmpEndpoint(
   203  		fakeUUIDGenerator{},
   204  		exchange,
   205  		newParamsValidator(t),
   206  		&mockAmpStoredReqFetcher{stored},
   207  		empty_fetcher.EmptyFetcher{},
   208  		&config.Configuration{MaxRequestSize: maxSize},
   209  		&metricsConfig.NilMetricsEngine{},
   210  		analyticsBuild.New(&config.Analytics{}),
   211  		map[string]string{},
   212  		[]byte{},
   213  		openrtb_ext.BuildBidderMap(),
   214  		empty_fetcher.EmptyFetcher{},
   215  		hooks.EmptyPlanBuilder{},
   216  		nil,
   217  	)
   218  	request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&curl=%s", url.QueryEscape(page)), nil)
   219  	recorder := httptest.NewRecorder()
   220  	endpoint(recorder, request, nil)
   221  
   222  	if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) {
   223  		return
   224  	}
   225  	if !assert.NotNil(t, exchange.lastRequest.Site) {
   226  		return
   227  	}
   228  	assert.Equal(t, page, exchange.lastRequest.Site.Page)
   229  	assert.Equal(t, "test.somepage.co.uk", exchange.lastRequest.Site.Domain)
   230  }
   231  
   232  func TestGDPRConsent(t *testing.T) {
   233  	consent := "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"
   234  	existingConsent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
   235  
   236  	testCases := []struct {
   237  		description     string
   238  		consent         string
   239  		userExt         *openrtb_ext.ExtUser
   240  		nilUser         bool
   241  		expectedUserExt openrtb_ext.ExtUser
   242  	}{
   243  		{
   244  			description: "Nil User",
   245  			consent:     consent,
   246  			nilUser:     true,
   247  			expectedUserExt: openrtb_ext.ExtUser{
   248  				Consent: consent,
   249  			},
   250  		},
   251  		{
   252  			description: "Nil User Ext",
   253  			consent:     consent,
   254  			userExt:     nil,
   255  			expectedUserExt: openrtb_ext.ExtUser{
   256  				Consent: consent,
   257  			},
   258  		},
   259  		{
   260  			description: "Overrides Existing Consent",
   261  			consent:     consent,
   262  			userExt: &openrtb_ext.ExtUser{
   263  				Consent: existingConsent,
   264  			},
   265  			expectedUserExt: openrtb_ext.ExtUser{
   266  				Consent: consent,
   267  			},
   268  		},
   269  		{
   270  			description: "Overrides Existing Consent - With Sibling Data",
   271  			consent:     consent,
   272  			userExt: &openrtb_ext.ExtUser{
   273  				Consent: existingConsent,
   274  			},
   275  			expectedUserExt: openrtb_ext.ExtUser{
   276  				Consent: consent,
   277  			},
   278  		},
   279  		{
   280  			description: "Does Not Override Existing Consent If Empty",
   281  			consent:     "",
   282  			userExt: &openrtb_ext.ExtUser{
   283  				Consent: existingConsent,
   284  			},
   285  			expectedUserExt: openrtb_ext.ExtUser{
   286  				Consent: existingConsent,
   287  			},
   288  		},
   289  	}
   290  
   291  	for _, test := range testCases {
   292  		// Build Request
   293  		bid, err := getTestBidRequest(test.nilUser, test.userExt, true, nil)
   294  		if err != nil {
   295  			t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err)
   296  		}
   297  
   298  		// Simulated Stored Request Backend
   299  		stored := map[string]json.RawMessage{"1": json.RawMessage(bid)}
   300  
   301  		// Build Exchange Endpoint
   302  		mockExchange := &mockAmpExchange{}
   303  		endpoint, _ := NewAmpEndpoint(
   304  			fakeUUIDGenerator{},
   305  			mockExchange,
   306  			newParamsValidator(t),
   307  			&mockAmpStoredReqFetcher{stored},
   308  			empty_fetcher.EmptyFetcher{},
   309  			&config.Configuration{
   310  				MaxRequestSize: maxSize,
   311  				GDPR:           config.GDPR{Enabled: true},
   312  			},
   313  			&metricsConfig.NilMetricsEngine{},
   314  			analyticsBuild.New(&config.Analytics{}),
   315  			map[string]string{},
   316  			[]byte{},
   317  			openrtb_ext.BuildBidderMap(),
   318  			empty_fetcher.EmptyFetcher{},
   319  			hooks.EmptyPlanBuilder{},
   320  			nil,
   321  		)
   322  
   323  		// Invoke Endpoint
   324  		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_type=2&consent_string=%s", test.consent), nil)
   325  		responseRecorder := httptest.NewRecorder()
   326  		endpoint(responseRecorder, request, nil)
   327  
   328  		// Parse Response
   329  		var response AmpResponse
   330  		if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil {
   331  			t.Fatalf("Error unmarshalling response: %s", err.Error())
   332  		}
   333  
   334  		// Assert Result
   335  		result := mockExchange.lastRequest
   336  		if !assert.NotNil(t, result, test.description+":lastRequest") {
   337  			return
   338  		}
   339  		if !assert.NotNil(t, result.User, test.description+":lastRequest.User") {
   340  			return
   341  		}
   342  		if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") {
   343  			return
   344  		}
   345  		var ue openrtb_ext.ExtUser
   346  		err = jsonutil.UnmarshalValid(result.User.Ext, &ue)
   347  		if !assert.NoError(t, err, test.description+":deserialize") {
   348  			return
   349  		}
   350  		assert.Equal(t, test.expectedUserExt, ue, test.description)
   351  		assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors, test.description+":errors")
   352  		assert.Empty(t, response.ORTB2.Ext.Warnings, test.description+":warnings")
   353  
   354  		// Invoke Endpoint With Legacy Param
   355  		requestLegacy := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_type=2&gdpr_consent=%s", test.consent), nil)
   356  		responseRecorderLegacy := httptest.NewRecorder()
   357  		endpoint(responseRecorderLegacy, requestLegacy, nil)
   358  
   359  		// Parse Resonse
   360  		var responseLegacy AmpResponse
   361  		if err := jsonutil.UnmarshalValid(responseRecorderLegacy.Body.Bytes(), &responseLegacy); err != nil {
   362  			t.Fatalf("Error unmarshalling response: %s", err.Error())
   363  		}
   364  
   365  		// Assert Result With Legacy Param
   366  		resultLegacy := mockExchange.lastRequest
   367  		if !assert.NotNil(t, resultLegacy, test.description+":legacy:lastRequest") {
   368  			return
   369  		}
   370  		if !assert.NotNil(t, resultLegacy.User, test.description+":legacy:lastRequest.User") {
   371  			return
   372  		}
   373  		if !assert.NotNil(t, resultLegacy.User.Ext, test.description+":legacy:lastRequest.User.Ext") {
   374  			return
   375  		}
   376  		var ueLegacy openrtb_ext.ExtUser
   377  		err = jsonutil.UnmarshalValid(resultLegacy.User.Ext, &ueLegacy)
   378  		if !assert.NoError(t, err, test.description+":legacy:deserialize") {
   379  			return
   380  		}
   381  		assert.Equal(t, test.expectedUserExt, ueLegacy, test.description+":legacy")
   382  		assert.Equal(t, expectedErrorsFromHoldAuction, responseLegacy.ORTB2.Ext.Errors, test.description+":legacy:errors")
   383  		assert.Empty(t, responseLegacy.ORTB2.Ext.Warnings, test.description+":legacy:warnings")
   384  	}
   385  }
   386  
   387  func TestOverrideWithParams(t *testing.T) {
   388  	e := &endpointDeps{
   389  		cfg: &config.Configuration{
   390  			GDPR: config.GDPR{
   391  				Enabled: true,
   392  			},
   393  		},
   394  	}
   395  
   396  	type testInput struct {
   397  		ampParams  amp.Params
   398  		bidRequest *openrtb2.BidRequest
   399  	}
   400  	type testOutput struct {
   401  		bidRequest        *openrtb2.BidRequest
   402  		errorMsgs         []string
   403  		expectFatalErrors bool
   404  	}
   405  	testCases := []struct {
   406  		desc     string
   407  		given    testInput
   408  		expected testOutput
   409  	}{
   410  		{
   411  			desc: "bid request with no Site field - amp.Params empty - expect Site to be added",
   412  			given: testInput{
   413  				ampParams: amp.Params{},
   414  				bidRequest: &openrtb2.BidRequest{
   415  					Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   416  				},
   417  			},
   418  			expected: testOutput{
   419  				bidRequest: &openrtb2.BidRequest{
   420  					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   421  					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
   422  				},
   423  				errorMsgs: nil,
   424  			},
   425  		},
   426  		{
   427  			desc: "amp.Params with Size field - expect Site and Banner format fields to be added",
   428  			given: testInput{
   429  				ampParams: amp.Params{
   430  					Size: amp.Size{
   431  						Width:  480,
   432  						Height: 320,
   433  					},
   434  				},
   435  				bidRequest: &openrtb2.BidRequest{
   436  					Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   437  				},
   438  			},
   439  			expected: testOutput{
   440  				bidRequest: &openrtb2.BidRequest{
   441  					Imp: []openrtb2.Imp{
   442  						{
   443  							Banner: &openrtb2.Banner{
   444  								Format: []openrtb2.Format{
   445  									{
   446  										W: 480,
   447  										H: 320,
   448  									},
   449  								},
   450  							},
   451  						},
   452  					},
   453  					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
   454  				},
   455  				errorMsgs: nil,
   456  			},
   457  		},
   458  		{
   459  			desc: "amp.Params with CanonicalURL field - expect Site to be aded with Page and Domain fields",
   460  			given: testInput{
   461  				ampParams:  amp.Params{CanonicalURL: "http://www.foobar.com"},
   462  				bidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}},
   463  			},
   464  			expected: testOutput{
   465  				bidRequest: &openrtb2.BidRequest{
   466  					Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   467  					Site: &openrtb2.Site{
   468  						Page:   "http://www.foobar.com",
   469  						Domain: "www.foobar.com",
   470  						Ext:    json.RawMessage(`{"amp":1}`),
   471  					},
   472  				},
   473  				errorMsgs: nil,
   474  			},
   475  		},
   476  		{
   477  			desc: "amp.Params with Trace field - expect ext.prebid.trace to be added",
   478  			given: testInput{
   479  				ampParams:  amp.Params{Trace: "verbose"},
   480  				bidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}}},
   481  			},
   482  			expected: testOutput{
   483  				bidRequest: &openrtb2.BidRequest{
   484  					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   485  					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
   486  					Ext:  json.RawMessage(`{"prebid":{"trace":"verbose"}}`),
   487  				},
   488  				errorMsgs: nil,
   489  			},
   490  		},
   491  		{
   492  			desc: "amp.Params with Trace field - expect ext.prebid.trace to be merged with existing ext fields",
   493  			given: testInput{
   494  				ampParams: amp.Params{Trace: "verbose"},
   495  				bidRequest: &openrtb2.BidRequest{
   496  					Imp: []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   497  					Ext: json.RawMessage(`{"prebid":{"debug":true}}`),
   498  				},
   499  			},
   500  			expected: testOutput{
   501  				bidRequest: &openrtb2.BidRequest{
   502  					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   503  					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
   504  					Ext:  json.RawMessage(`{"prebid":{"debug":true,"trace":"verbose"}}`),
   505  				},
   506  				errorMsgs: nil,
   507  			},
   508  		},
   509  		{
   510  			desc: "bid request with malformed User.Ext - amp.Params with AdditionalConsent - expect error",
   511  			given: testInput{
   512  				ampParams: amp.Params{AdditionalConsent: "1~X.X.X.X"},
   513  				bidRequest: &openrtb2.BidRequest{
   514  					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   515  					User: &openrtb2.User{Ext: json.RawMessage(`malformed`)},
   516  				},
   517  			},
   518  			expected: testOutput{
   519  				bidRequest: &openrtb2.BidRequest{
   520  					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   521  					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
   522  					User: &openrtb2.User{Ext: json.RawMessage(`malformed`)},
   523  				},
   524  				errorMsgs:         []string{"expect { or n, but found m"},
   525  				expectFatalErrors: true,
   526  			},
   527  		},
   528  		{
   529  			desc: "bid request with valid imp[0].ext - amp.Params with malformed targeting value - expect error because imp[0].ext won't be unable to get merged with targeting values",
   530  			given: testInput{
   531  				ampParams: amp.Params{Targeting: "{123,}"},
   532  				bidRequest: &openrtb2.BidRequest{
   533  					Imp: []openrtb2.Imp{
   534  						{
   535  							Banner: &openrtb2.Banner{Format: []openrtb2.Format{}},
   536  							Ext:    []byte(`{"appnexus":{"placementId":123}}`),
   537  						},
   538  					},
   539  				},
   540  			},
   541  			expected: testOutput{
   542  				bidRequest: &openrtb2.BidRequest{
   543  					Imp: []openrtb2.Imp{
   544  						{
   545  							Banner: &openrtb2.Banner{Format: []openrtb2.Format{}},
   546  							Ext:    json.RawMessage(`{"appnexus":{"placementId":123}}`),
   547  						},
   548  					},
   549  					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
   550  				},
   551  				errorMsgs: []string{"unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Patch"},
   552  			},
   553  		},
   554  		{
   555  			desc: "bid request with malformed user.ext.prebid - amp.Params with GDPR consent values - expect policy writer to return error",
   556  			given: testInput{
   557  				ampParams: amp.Params{
   558  					ConsentType: amp.ConsentTCF2,
   559  					Consent:     "CPdECS0PdECS0ACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA",
   560  				},
   561  				bidRequest: &openrtb2.BidRequest{
   562  					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   563  					User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)},
   564  				},
   565  			},
   566  			expected: testOutput{
   567  				bidRequest: &openrtb2.BidRequest{
   568  					Imp:  []openrtb2.Imp{{Banner: &openrtb2.Banner{Format: []openrtb2.Format{}}}},
   569  					User: &openrtb2.User{Ext: json.RawMessage(`{"prebid":{malformed}}`)},
   570  					Site: &openrtb2.Site{Ext: json.RawMessage(`{"amp":1}`)},
   571  				},
   572  				errorMsgs:         []string{"expect \" after {, but found m"},
   573  				expectFatalErrors: true,
   574  			},
   575  		},
   576  	}
   577  
   578  	for _, test := range testCases {
   579  		errs := e.overrideWithParams(test.given.ampParams, test.given.bidRequest)
   580  
   581  		assert.Equal(t, test.expected.bidRequest, test.given.bidRequest, test.desc)
   582  		assert.Len(t, errs, len(test.expected.errorMsgs), test.desc)
   583  		if len(test.expected.errorMsgs) > 0 {
   584  			assert.Equal(t, test.expected.errorMsgs[0], errs[0].Error(), test.desc)
   585  			assert.Equal(t, test.expected.expectFatalErrors, errortypes.ContainsFatalError(errs), test.desc)
   586  		}
   587  	}
   588  }
   589  
   590  func TestSetConsentedProviders(t *testing.T) {
   591  
   592  	sampleBidRequest := &openrtb2.BidRequest{}
   593  
   594  	testCases := []struct {
   595  		description            string
   596  		givenAdditionalConsent string
   597  		givenBidRequest        *openrtb2.BidRequest
   598  		expectedBidRequest     *openrtb2.BidRequest
   599  		expectedError          bool
   600  	}{
   601  		{
   602  			description:            "empty additional consent bid request unmodified",
   603  			givenAdditionalConsent: "",
   604  			givenBidRequest:        sampleBidRequest,
   605  			expectedBidRequest:     sampleBidRequest,
   606  			expectedError:          false,
   607  		},
   608  		{
   609  			description:            "nil bid request, expect error",
   610  			givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING",
   611  			givenBidRequest:        nil,
   612  			expectedBidRequest:     nil,
   613  			expectedError:          true,
   614  		},
   615  		{
   616  			description:            "malformed user.ext, expect error",
   617  			givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING",
   618  			givenBidRequest: &openrtb2.BidRequest{
   619  				User: &openrtb2.User{
   620  					Ext: json.RawMessage(`malformed`),
   621  				},
   622  			},
   623  			expectedBidRequest: &openrtb2.BidRequest{
   624  				User: &openrtb2.User{
   625  					Ext: json.RawMessage(`malformed`),
   626  				},
   627  			},
   628  			expectedError: true,
   629  		},
   630  		{
   631  			description:            "non-empty additional consent bid request will carry this value in user.ext.ConsentedProvidersSettings.consented_providers",
   632  			givenAdditionalConsent: "ADDITIONAL_CONSENT_STRING",
   633  			givenBidRequest:        sampleBidRequest,
   634  			expectedBidRequest: &openrtb2.BidRequest{
   635  				User: &openrtb2.User{
   636  					Ext: json.RawMessage(`{"ConsentedProvidersSettings":{"consented_providers":"ADDITIONAL_CONSENT_STRING"}}`),
   637  				},
   638  			},
   639  			expectedError: false,
   640  		},
   641  	}
   642  
   643  	for _, test := range testCases {
   644  		err := setConsentedProviders(test.givenBidRequest, amp.Params{AdditionalConsent: test.givenAdditionalConsent})
   645  
   646  		if test.expectedError {
   647  			assert.Error(t, err, test.description)
   648  		} else {
   649  			assert.NoError(t, err, test.description)
   650  		}
   651  		assert.Equal(t, test.expectedBidRequest, test.givenBidRequest, test.description)
   652  	}
   653  }
   654  
   655  func TestCCPAConsent(t *testing.T) {
   656  	consent := "1NYN"
   657  	existingConsent := "1NNN"
   658  
   659  	var gdpr int8 = 1
   660  
   661  	testCases := []struct {
   662  		description    string
   663  		consent        string
   664  		regsExt        *openrtb_ext.ExtRegs
   665  		nilRegs        bool
   666  		expectedRegExt openrtb_ext.ExtRegs
   667  	}{
   668  		{
   669  			description: "Nil Regs",
   670  			consent:     consent,
   671  			nilRegs:     true,
   672  			expectedRegExt: openrtb_ext.ExtRegs{
   673  				USPrivacy: consent,
   674  			},
   675  		},
   676  		{
   677  			description: "Nil Regs Ext",
   678  			consent:     consent,
   679  			regsExt:     nil,
   680  			expectedRegExt: openrtb_ext.ExtRegs{
   681  				USPrivacy: consent,
   682  			},
   683  		},
   684  		{
   685  			description: "Overrides Existing Consent",
   686  			consent:     consent,
   687  			regsExt: &openrtb_ext.ExtRegs{
   688  				USPrivacy: existingConsent,
   689  			},
   690  			expectedRegExt: openrtb_ext.ExtRegs{
   691  				USPrivacy: consent,
   692  			},
   693  		},
   694  		{
   695  			description: "Overrides Existing Consent - With Sibling Data",
   696  			consent:     consent,
   697  			regsExt: &openrtb_ext.ExtRegs{
   698  				USPrivacy: existingConsent,
   699  				GDPR:      &gdpr,
   700  			},
   701  			expectedRegExt: openrtb_ext.ExtRegs{
   702  				USPrivacy: consent,
   703  				GDPR:      &gdpr,
   704  			},
   705  		},
   706  		{
   707  			description: "Does Not Override Existing Consent If Empty",
   708  			consent:     "",
   709  			regsExt: &openrtb_ext.ExtRegs{
   710  				USPrivacy: existingConsent,
   711  			},
   712  			expectedRegExt: openrtb_ext.ExtRegs{
   713  				USPrivacy: existingConsent,
   714  			},
   715  		},
   716  	}
   717  
   718  	for _, test := range testCases {
   719  		// Build Request
   720  		bid, err := getTestBidRequest(true, nil, test.nilRegs, test.regsExt)
   721  		if err != nil {
   722  			t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err)
   723  		}
   724  
   725  		// Simulated Stored Request Backend
   726  		stored := map[string]json.RawMessage{"1": json.RawMessage(bid)}
   727  
   728  		// Build Exchange Endpoint
   729  		mockExchange := &mockAmpExchange{}
   730  		endpoint, _ := NewAmpEndpoint(
   731  			fakeUUIDGenerator{},
   732  			mockExchange,
   733  			newParamsValidator(t),
   734  			&mockAmpStoredReqFetcher{stored},
   735  			empty_fetcher.EmptyFetcher{},
   736  			&config.Configuration{MaxRequestSize: maxSize},
   737  			&metricsConfig.NilMetricsEngine{},
   738  			analyticsBuild.New(&config.Analytics{}),
   739  			map[string]string{},
   740  			[]byte{},
   741  			openrtb_ext.BuildBidderMap(),
   742  			empty_fetcher.EmptyFetcher{},
   743  			hooks.EmptyPlanBuilder{},
   744  			nil,
   745  		)
   746  
   747  		// Invoke Endpoint
   748  		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_type=3&consent_string=%s", test.consent), nil)
   749  		responseRecorder := httptest.NewRecorder()
   750  		endpoint(responseRecorder, request, nil)
   751  
   752  		// Parse Response
   753  		var response AmpResponse
   754  		if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil {
   755  			t.Fatalf("Error unmarshalling response: %s", err.Error())
   756  		}
   757  
   758  		// Assert Result
   759  		result := mockExchange.lastRequest
   760  		if !assert.NotNil(t, result, test.description+":lastRequest") {
   761  			return
   762  		}
   763  		if !assert.NotNil(t, result.Regs, test.description+":lastRequest.Regs") {
   764  			return
   765  		}
   766  		if !assert.NotNil(t, result.Regs.Ext, test.description+":lastRequest.Regs.Ext") {
   767  			return
   768  		}
   769  		var re openrtb_ext.ExtRegs
   770  		err = jsonutil.UnmarshalValid(result.Regs.Ext, &re)
   771  		if !assert.NoError(t, err, test.description+":deserialize") {
   772  			return
   773  		}
   774  		assert.Equal(t, test.expectedRegExt, re, test.description)
   775  		assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors)
   776  		assert.Empty(t, response.ORTB2.Ext.Warnings)
   777  	}
   778  }
   779  
   780  func TestConsentWarnings(t *testing.T) {
   781  	type inputTest struct {
   782  		regs              *openrtb_ext.ExtRegs
   783  		invalidConsentURL bool
   784  		expectedWarnings  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage
   785  	}
   786  	invalidConsent := "invalid"
   787  
   788  	bidderWarning := openrtb_ext.ExtBidderMessage{
   789  		Code:    10003,
   790  		Message: "debug turned off for bidder",
   791  	}
   792  	invalidCCPAWarning := openrtb_ext.ExtBidderMessage{
   793  		Code:    10001,
   794  		Message: "Consent string '" + invalidConsent + "' is not a valid CCPA consent string.",
   795  	}
   796  	invalidConsentWarning := openrtb_ext.ExtBidderMessage{
   797  		Code:    10001,
   798  		Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)",
   799  	}
   800  
   801  	testData := []inputTest{
   802  		{
   803  			regs:              nil,
   804  			invalidConsentURL: false,
   805  			expectedWarnings:  nil,
   806  		},
   807  		{
   808  			regs:              nil,
   809  			invalidConsentURL: true,
   810  			expectedWarnings:  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderReservedGeneral: {invalidCCPAWarning}},
   811  		},
   812  		{
   813  			regs:              &openrtb_ext.ExtRegs{USPrivacy: "invalid"},
   814  			invalidConsentURL: true,
   815  			expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{
   816  				openrtb_ext.BidderReservedGeneral:  {invalidCCPAWarning, invalidConsentWarning},
   817  				openrtb_ext.BidderName("appnexus"): {bidderWarning},
   818  			},
   819  		},
   820  		{
   821  			regs:              &openrtb_ext.ExtRegs{USPrivacy: "1NYN"},
   822  			invalidConsentURL: false,
   823  			expectedWarnings:  map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{openrtb_ext.BidderName("appnexus"): {bidderWarning}},
   824  		},
   825  	}
   826  
   827  	for _, testCase := range testData {
   828  
   829  		bid, err := getTestBidRequest(true, nil, testCase.regs == nil, testCase.regs)
   830  		if err != nil {
   831  			t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err)
   832  		}
   833  
   834  		// Simulated Stored Request Backend
   835  		stored := map[string]json.RawMessage{"1": json.RawMessage(bid)}
   836  
   837  		// Build Exchange Endpoint
   838  		var mockExchange exchange.Exchange
   839  		if testCase.regs != nil {
   840  			mockExchange = &mockAmpExchangeWarnings{}
   841  		} else {
   842  			mockExchange = &mockAmpExchange{}
   843  		}
   844  		endpoint, _ := NewAmpEndpoint(
   845  			fakeUUIDGenerator{},
   846  			mockExchange,
   847  			newParamsValidator(t),
   848  			&mockAmpStoredReqFetcher{stored},
   849  			empty_fetcher.EmptyFetcher{},
   850  			&config.Configuration{MaxRequestSize: maxSize},
   851  			&metricsConfig.NilMetricsEngine{},
   852  			analyticsBuild.New(&config.Analytics{}),
   853  			map[string]string{},
   854  			[]byte{},
   855  			openrtb_ext.BuildBidderMap(),
   856  			empty_fetcher.EmptyFetcher{},
   857  			hooks.EmptyPlanBuilder{},
   858  			nil,
   859  		)
   860  
   861  		// Invoke Endpoint
   862  		var request *http.Request
   863  
   864  		if testCase.invalidConsentURL {
   865  			request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1&consent_type=3&consent_string="+invalidConsent, nil)
   866  
   867  		} else {
   868  			request = httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil)
   869  		}
   870  
   871  		responseRecorder := httptest.NewRecorder()
   872  		endpoint(responseRecorder, request, nil)
   873  
   874  		// Parse Response
   875  		var response AmpResponse
   876  		if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil {
   877  			t.Fatalf("Error unmarshalling response: %s", err.Error())
   878  		}
   879  
   880  		// Assert Result
   881  		if testCase.regs == nil {
   882  			result := mockExchange.(*mockAmpExchange).lastRequest
   883  			assert.NotNil(t, result, "lastRequest")
   884  			assert.Nil(t, result.User, "lastRequest.User")
   885  			assert.Nil(t, result.Regs, "lastRequest.Regs")
   886  			assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors)
   887  			if testCase.invalidConsentURL {
   888  				assert.Equal(t, testCase.expectedWarnings, response.ORTB2.Ext.Warnings)
   889  			} else {
   890  				assert.Empty(t, response.ORTB2.Ext.Warnings)
   891  			}
   892  
   893  		} else {
   894  			assert.Equal(t, testCase.expectedWarnings, response.ORTB2.Ext.Warnings)
   895  		}
   896  	}
   897  }
   898  
   899  func TestNewAndLegacyConsentBothProvided(t *testing.T) {
   900  	validConsentGDPR1 := "COwGVJOOwGVJOADACHENAOCAAO6as_-AAAhoAFNLAAoAAAA"
   901  	validConsentGDPR2 := "CPdiPIJPdiPIJACABBENAzCv_____3___wAAAQNd_X9cAAAAAAAA"
   902  
   903  	testCases := []struct {
   904  		description     string
   905  		consent         string
   906  		consentLegacy   string
   907  		userExt         *openrtb_ext.ExtUser
   908  		expectedUserExt openrtb_ext.ExtUser
   909  	}{
   910  		{
   911  			description:   "New Consent Wins",
   912  			consent:       validConsentGDPR1,
   913  			consentLegacy: validConsentGDPR2,
   914  			expectedUserExt: openrtb_ext.ExtUser{
   915  				Consent: validConsentGDPR1,
   916  			},
   917  		},
   918  		{
   919  			description:   "New Consent Wins - Reverse",
   920  			consent:       validConsentGDPR2,
   921  			consentLegacy: validConsentGDPR1,
   922  			expectedUserExt: openrtb_ext.ExtUser{
   923  				Consent: validConsentGDPR2,
   924  			},
   925  		},
   926  	}
   927  
   928  	for _, test := range testCases {
   929  		// Build Request
   930  		bid, err := getTestBidRequest(false, nil, true, nil)
   931  		if err != nil {
   932  			t.Fatalf("Failed to marshal the complete openrtb2.BidRequest object %v", err)
   933  		}
   934  
   935  		// Simulated Stored Request Backend
   936  		stored := map[string]json.RawMessage{"1": json.RawMessage(bid)}
   937  
   938  		// Build Exchange Endpoint
   939  		mockExchange := &mockAmpExchange{}
   940  		endpoint, _ := NewAmpEndpoint(
   941  			fakeUUIDGenerator{},
   942  			mockExchange,
   943  			newParamsValidator(t),
   944  			&mockAmpStoredReqFetcher{stored},
   945  			empty_fetcher.EmptyFetcher{},
   946  			&config.Configuration{
   947  				MaxRequestSize: maxSize,
   948  				GDPR:           config.GDPR{Enabled: true},
   949  			},
   950  			&metricsConfig.NilMetricsEngine{},
   951  			analyticsBuild.New(&config.Analytics{}),
   952  			map[string]string{},
   953  			[]byte{},
   954  			openrtb_ext.BuildBidderMap(),
   955  			empty_fetcher.EmptyFetcher{},
   956  			hooks.EmptyPlanBuilder{},
   957  			nil,
   958  		)
   959  
   960  		// Invoke Endpoint
   961  		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&consent_type=2&consent_string=%s&gdpr_consent=%s", test.consent, test.consentLegacy), nil)
   962  		responseRecorder := httptest.NewRecorder()
   963  		endpoint(responseRecorder, request, nil)
   964  
   965  		// Parse Response
   966  		var response AmpResponse
   967  		if err := jsonutil.UnmarshalValid(responseRecorder.Body.Bytes(), &response); err != nil {
   968  			t.Fatalf("Error unmarshalling response: %s", err.Error())
   969  		}
   970  
   971  		// Assert Result
   972  		result := mockExchange.lastRequest
   973  		if !assert.NotNil(t, result, test.description+":lastRequest") {
   974  			return
   975  		}
   976  		if !assert.NotNil(t, result.User, test.description+":lastRequest.User") {
   977  			return
   978  		}
   979  		if !assert.NotNil(t, result.User.Ext, test.description+":lastRequest.User.Ext") {
   980  			return
   981  		}
   982  		var ue openrtb_ext.ExtUser
   983  		err = jsonutil.UnmarshalValid(result.User.Ext, &ue)
   984  		if !assert.NoError(t, err, test.description+":deserialize") {
   985  			return
   986  		}
   987  		assert.Equal(t, test.expectedUserExt, ue, test.description)
   988  		assert.Equal(t, expectedErrorsFromHoldAuction, response.ORTB2.Ext.Errors)
   989  		assert.Empty(t, response.ORTB2.Ext.Warnings)
   990  	}
   991  }
   992  
   993  func TestAMPSiteExt(t *testing.T) {
   994  	stored := map[string]json.RawMessage{
   995  		"1": json.RawMessage(validRequest(t, "site.json")),
   996  	}
   997  	exchange := &mockAmpExchange{}
   998  	endpoint, _ := NewAmpEndpoint(
   999  		fakeUUIDGenerator{},
  1000  		exchange,
  1001  		newParamsValidator(t),
  1002  		&mockAmpStoredReqFetcher{stored},
  1003  		empty_fetcher.EmptyFetcher{},
  1004  		&config.Configuration{MaxRequestSize: maxSize},
  1005  		&metricsConfig.NilMetricsEngine{},
  1006  		analyticsBuild.New(&config.Analytics{}),
  1007  		nil,
  1008  		nil,
  1009  		openrtb_ext.BuildBidderMap(),
  1010  		empty_fetcher.EmptyFetcher{},
  1011  		hooks.EmptyPlanBuilder{},
  1012  		nil,
  1013  	)
  1014  	request, err := http.NewRequest("GET", "/openrtb2/auction/amp?tag_id=1", nil)
  1015  	if !assert.NoError(t, err) {
  1016  		return
  1017  	}
  1018  	recorder := httptest.NewRecorder()
  1019  	endpoint(recorder, request, nil)
  1020  
  1021  	if !assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) {
  1022  		return
  1023  	}
  1024  	if !assert.NotNil(t, exchange.lastRequest.Site) {
  1025  		return
  1026  	}
  1027  	assert.JSONEq(t, `{"amp":1}`, string(exchange.lastRequest.Site.Ext))
  1028  }
  1029  
  1030  // TestBadRequests makes sure we return 400's on bad requests.
  1031  func TestAmpBadRequests(t *testing.T) {
  1032  	dir := "sample-requests/invalid-whole/"
  1033  	files, err := os.ReadDir(dir)
  1034  	assert.NoError(t, err, "Failed to read folder: %s", dir)
  1035  
  1036  	mockAmpStoredReq := make(map[string]json.RawMessage, len(files))
  1037  	badRequests := make(map[string]testCase, len(files))
  1038  	for index, file := range files {
  1039  		filename := file.Name()
  1040  		fileData := readFile(t, dir+filename)
  1041  
  1042  		test, err := parseTestData(fileData, filename)
  1043  		if !assert.NoError(t, err) {
  1044  			return
  1045  		}
  1046  
  1047  		if skipAmpTest(test) {
  1048  			continue
  1049  		}
  1050  
  1051  		requestID := strconv.Itoa(100 + index)
  1052  		test.Query = fmt.Sprintf("account=test_pub&tag_id=%s", requestID)
  1053  
  1054  		badRequests[requestID] = test
  1055  		mockAmpStoredReq[requestID] = test.BidRequest
  1056  	}
  1057  
  1058  	addAmpBadRequests(badRequests, mockAmpStoredReq)
  1059  
  1060  	endpoint, _ := NewAmpEndpoint(
  1061  		fakeUUIDGenerator{},
  1062  		&mockAmpExchange{},
  1063  		newParamsValidator(t),
  1064  		&mockAmpStoredReqFetcher{data: mockAmpStoredReq},
  1065  		empty_fetcher.EmptyFetcher{},
  1066  		&config.Configuration{MaxRequestSize: maxSize},
  1067  		&metricsConfig.NilMetricsEngine{},
  1068  		analyticsBuild.New(&config.Analytics{}),
  1069  		map[string]string{},
  1070  		[]byte{},
  1071  		openrtb_ext.BuildBidderMap(),
  1072  		empty_fetcher.EmptyFetcher{},
  1073  		hooks.EmptyPlanBuilder{},
  1074  		nil,
  1075  	)
  1076  
  1077  	for _, test := range badRequests {
  1078  		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil)
  1079  		recorder := httptest.NewRecorder()
  1080  
  1081  		endpoint(recorder, request, nil)
  1082  
  1083  		response := recorder.Body.String()
  1084  		assert.Equal(t, test.ExpectedReturnCode, recorder.Code, test.Description)
  1085  		assert.Contains(t, response, test.ExpectedErrorMessage, "Actual: %s \nExpected: %s. Description: %s \n", response, test.ExpectedErrorMessage, test.Description)
  1086  	}
  1087  }
  1088  
  1089  func skipAmpTest(test testCase) bool {
  1090  	bidRequest := openrtb2.BidRequest{}
  1091  	if err := json.Unmarshal(test.BidRequest, &bidRequest); err == nil {
  1092  		// request.app must not exist in AMP
  1093  		if bidRequest.App != nil {
  1094  			return true
  1095  		}
  1096  
  1097  		// data for tag_id='%s' does not define the required imp array
  1098  		// Invalid request: data for tag_id '%s' includes %d imp elements. Only one is allowed
  1099  		if len(bidRequest.Imp) == 0 || len(bidRequest.Imp) > 1 {
  1100  			return true
  1101  		}
  1102  
  1103  		if bidRequest.Device != nil && strings.Contains(string(bidRequest.Device.Ext), "interstitial") {
  1104  			return true
  1105  		}
  1106  	}
  1107  
  1108  	// request.ext.prebid.cache is initialised in AMP if it is not present in request
  1109  	if strings.Contains(test.ExpectedErrorMessage, `Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`) ||
  1110  		strings.Contains(test.ExpectedErrorMessage, `Invalid request: ext.prebid.storedrequest.id must be a string`) {
  1111  		return true
  1112  	}
  1113  
  1114  	return false
  1115  }
  1116  
  1117  func addAmpBadRequests(mapBadRequests map[string]testCase, mockAmpStoredReq map[string]json.RawMessage) {
  1118  	mapBadRequests["201"] = testCase{
  1119  		Description:          "missing-tag-id",
  1120  		Query:                "account=test_pub",
  1121  		ExpectedReturnCode:   http.StatusBadRequest,
  1122  		ExpectedErrorMessage: "Invalid request: AMP requests require an AMP tag_id\n",
  1123  	}
  1124  	mockAmpStoredReq["201"] = json.RawMessage(`{}`)
  1125  
  1126  	mapBadRequests["202"] = testCase{
  1127  		Description:          "request.app-present",
  1128  		Query:                "account=test_pub&tag_id=202",
  1129  		ExpectedReturnCode:   http.StatusBadRequest,
  1130  		ExpectedErrorMessage: "Invalid request: request.app must not exist in AMP stored requests.\n",
  1131  	}
  1132  	mockAmpStoredReq["202"] = json.RawMessage(`{"imp":[{}],"app":{}}`)
  1133  
  1134  	mapBadRequests["203"] = testCase{
  1135  		Description:          "request-with-2-imps",
  1136  		Query:                "account=test_pub&tag_id=203",
  1137  		ExpectedReturnCode:   http.StatusBadRequest,
  1138  		ExpectedErrorMessage: "Invalid request: data for tag_id '203' includes 2 imp elements. Only one is allowed",
  1139  	}
  1140  	mockAmpStoredReq["203"] = json.RawMessage(`{"imp":[{},{}]}`)
  1141  }
  1142  
  1143  // TestAmpDebug makes sure we get debug information back when requested
  1144  func TestAmpDebug(t *testing.T) {
  1145  	requests := map[string]json.RawMessage{
  1146  		"2": json.RawMessage(validRequest(t, "site.json")),
  1147  	}
  1148  
  1149  	endpoint, _ := NewAmpEndpoint(
  1150  		fakeUUIDGenerator{},
  1151  		&mockAmpExchange{},
  1152  		newParamsValidator(t),
  1153  		&mockAmpStoredReqFetcher{requests},
  1154  		empty_fetcher.EmptyFetcher{},
  1155  		&config.Configuration{MaxRequestSize: maxSize},
  1156  		&metricsConfig.NilMetricsEngine{},
  1157  		analyticsBuild.New(&config.Analytics{}),
  1158  		map[string]string{},
  1159  		[]byte{},
  1160  		openrtb_ext.BuildBidderMap(),
  1161  		empty_fetcher.EmptyFetcher{},
  1162  		hooks.EmptyPlanBuilder{},
  1163  		nil,
  1164  	)
  1165  
  1166  	for requestID := range requests {
  1167  		request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&debug=1", requestID), nil)
  1168  		recorder := httptest.NewRecorder()
  1169  		endpoint(recorder, request, nil)
  1170  
  1171  		if recorder.Code != http.StatusOK {
  1172  			t.Errorf("Expected status %d. Got %d. Request config ID was %s", http.StatusOK, recorder.Code, requestID)
  1173  			t.Errorf("Response body was: %s", recorder.Body)
  1174  			t.Errorf("Request was: %s", string(requests[requestID]))
  1175  		}
  1176  
  1177  		var response AmpResponse
  1178  		if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil {
  1179  			t.Fatalf("Error unmarshalling response: %s", err.Error())
  1180  		}
  1181  
  1182  		if response.Targeting == nil || len(response.Targeting) == 0 {
  1183  			t.Errorf("Bad response, no targeting data.\n Response was: %v", recorder.Body)
  1184  		}
  1185  		if len(response.Targeting) != 3 {
  1186  			t.Errorf("Bad targeting data. Expected 3 keys, got %d.", len(response.Targeting))
  1187  		}
  1188  
  1189  		if response.ORTB2.Ext.Debug == nil {
  1190  			t.Errorf("Debug requested but not present")
  1191  		}
  1192  	}
  1193  }
  1194  
  1195  func TestInitAmpTargetingAndCache(t *testing.T) {
  1196  	trueVal := true
  1197  	emptyTargetingAndCache := &openrtb_ext.ExtRequestPrebid{
  1198  		Targeting: &openrtb_ext.ExtRequestTargeting{},
  1199  		Cache: &openrtb_ext.ExtRequestPrebidCache{
  1200  			Bids: &openrtb_ext.ExtRequestPrebidCacheBids{},
  1201  		},
  1202  	}
  1203  
  1204  	testCases := []struct {
  1205  		name           string
  1206  		request        *openrtb2.BidRequest
  1207  		expectedPrebid *openrtb_ext.ExtRequestPrebid
  1208  		expectedErrs   []string
  1209  	}{
  1210  		{
  1211  			name:         "malformed",
  1212  			request:      &openrtb2.BidRequest{Ext: json.RawMessage("malformed")},
  1213  			expectedErrs: []string{"expect { or n, but found m"},
  1214  		},
  1215  		{
  1216  			name:           "nil",
  1217  			request:        &openrtb2.BidRequest{},
  1218  			expectedPrebid: emptyTargetingAndCache,
  1219  		},
  1220  		{
  1221  			name:           "empty",
  1222  			request:        &openrtb2.BidRequest{Ext: json.RawMessage(`{"ext":{}}`)},
  1223  			expectedPrebid: emptyTargetingAndCache,
  1224  		},
  1225  		{
  1226  			name:           "missing targeting + cache",
  1227  			request:        &openrtb2.BidRequest{Ext: json.RawMessage(`{"ext":{"prebid":{}}}`)},
  1228  			expectedPrebid: emptyTargetingAndCache,
  1229  		},
  1230  		{
  1231  			name:    "missing targeting",
  1232  			request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"cache":{"bids":{"returnCreative":true}}}}`)},
  1233  			expectedPrebid: &openrtb_ext.ExtRequestPrebid{
  1234  				Targeting: &openrtb_ext.ExtRequestTargeting{},
  1235  				Cache: &openrtb_ext.ExtRequestPrebidCache{
  1236  					Bids: &openrtb_ext.ExtRequestPrebidCacheBids{
  1237  						ReturnCreative: &trueVal,
  1238  					},
  1239  				},
  1240  			},
  1241  		},
  1242  		{
  1243  			name:    "missing cache",
  1244  			request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"targeting":{"includewinners":true}}}`)},
  1245  			expectedPrebid: &openrtb_ext.ExtRequestPrebid{
  1246  				Targeting: &openrtb_ext.ExtRequestTargeting{
  1247  					IncludeWinners: &trueVal,
  1248  				},
  1249  				Cache: &openrtb_ext.ExtRequestPrebidCache{
  1250  					Bids: &openrtb_ext.ExtRequestPrebidCacheBids{},
  1251  				},
  1252  			},
  1253  		},
  1254  	}
  1255  
  1256  	for _, tc := range testCases {
  1257  		t.Run(tc.name, func(t *testing.T) {
  1258  			// setup
  1259  			req := &openrtb_ext.RequestWrapper{BidRequest: tc.request}
  1260  
  1261  			// run
  1262  			actualErrs := initAmpTargetingAndCache(req)
  1263  
  1264  			// assertions
  1265  			require.NoError(t, req.RebuildRequest(), "rebuild request")
  1266  
  1267  			actualErrsMsgs := make([]string, len(actualErrs))
  1268  			for i, v := range actualErrs {
  1269  				actualErrsMsgs[i] = v.Error()
  1270  			}
  1271  			assert.ElementsMatch(t, tc.expectedErrs, actualErrsMsgs, "errors")
  1272  
  1273  			actualReqExt, _ := req.GetRequestExt()
  1274  			actualPrebid := actualReqExt.GetPrebid()
  1275  			assert.Equal(t, tc.expectedPrebid, actualPrebid, "prebid ext")
  1276  		})
  1277  	}
  1278  }
  1279  
  1280  func TestQueryParamOverrides(t *testing.T) {
  1281  	requests := map[string]json.RawMessage{
  1282  		"1": json.RawMessage(validRequest(t, "site.json")),
  1283  	}
  1284  
  1285  	endpoint, _ := NewAmpEndpoint(
  1286  		fakeUUIDGenerator{},
  1287  		&mockAmpExchange{},
  1288  		newParamsValidator(t),
  1289  		&mockAmpStoredReqFetcher{requests},
  1290  		empty_fetcher.EmptyFetcher{},
  1291  		&config.Configuration{MaxRequestSize: maxSize},
  1292  		&metricsConfig.NilMetricsEngine{},
  1293  		analyticsBuild.New(&config.Analytics{}),
  1294  		map[string]string{},
  1295  		[]byte{},
  1296  		openrtb_ext.BuildBidderMap(),
  1297  		empty_fetcher.EmptyFetcher{},
  1298  		hooks.EmptyPlanBuilder{},
  1299  		nil,
  1300  	)
  1301  
  1302  	requestID := "1"
  1303  	curl := "http://example.com"
  1304  	slot := "1234"
  1305  	timeout := int64(500)
  1306  	account := "12345"
  1307  
  1308  	request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?tag_id=%s&debug=1&curl=%s&slot=%s&timeout=%d&account=%s", requestID, curl, slot, timeout, account), nil)
  1309  	recorder := httptest.NewRecorder()
  1310  	endpoint(recorder, request, nil)
  1311  
  1312  	if recorder.Code != http.StatusOK {
  1313  		t.Errorf("Expected status %d. Got %d. Request config ID was %s", http.StatusOK, recorder.Code, requestID)
  1314  		t.Errorf("Response body was: %s", recorder.Body)
  1315  		t.Errorf("Request was: %s", string(requests[requestID]))
  1316  	}
  1317  
  1318  	var response AmpResponse
  1319  	if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil {
  1320  		t.Fatalf("Error unmarshalling response: %s", err.Error())
  1321  	}
  1322  
  1323  	var resolvedRequest openrtb2.BidRequest
  1324  	err := jsonutil.UnmarshalValid(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest)
  1325  	assert.NoError(t, err, "resolved request should have a correct format")
  1326  	if resolvedRequest.TMax != timeout {
  1327  		t.Errorf("Expected TMax to equal timeout (%d), got: %d", timeout, resolvedRequest.TMax)
  1328  	}
  1329  
  1330  	resolvedImp := resolvedRequest.Imp[0]
  1331  	if resolvedImp.TagID != slot {
  1332  		t.Errorf("Expected Imp.TagId to equal slot (%s), got: %s", slot, resolvedImp.TagID)
  1333  	}
  1334  
  1335  	if resolvedRequest.Site == nil || resolvedRequest.Site.Page != curl {
  1336  		t.Errorf("Expected Site.Page to equal curl (%s), got: %s", curl, resolvedRequest.Site.Page)
  1337  	}
  1338  
  1339  	if resolvedRequest.Site == nil || resolvedRequest.Site.Publisher == nil || resolvedRequest.Site.Publisher.ID != account {
  1340  		t.Errorf("Expected Site.Publisher.ID to equal (%s), got: %s", account, resolvedRequest.Site.Publisher.ID)
  1341  	}
  1342  }
  1343  
  1344  func TestOverrideDimensions(t *testing.T) {
  1345  	formatOverrideSpec{
  1346  		overrideWidth:  20,
  1347  		overrideHeight: 40,
  1348  		expect: []openrtb2.Format{{
  1349  			W: 20,
  1350  			H: 40,
  1351  		}},
  1352  	}.execute(t)
  1353  }
  1354  
  1355  func TestOverrideHeightNormalWidth(t *testing.T) {
  1356  	formatOverrideSpec{
  1357  		width:          20,
  1358  		overrideHeight: 40,
  1359  		expect: []openrtb2.Format{{
  1360  			W: 20,
  1361  			H: 40,
  1362  		}},
  1363  	}.execute(t)
  1364  }
  1365  
  1366  func TestOverrideWidthNormalHeight(t *testing.T) {
  1367  	formatOverrideSpec{
  1368  		overrideWidth: 20,
  1369  		height:        40,
  1370  		expect: []openrtb2.Format{{
  1371  			W: 20,
  1372  			H: 40,
  1373  		}},
  1374  	}.execute(t)
  1375  }
  1376  
  1377  func TestMultisize(t *testing.T) {
  1378  	formatOverrideSpec{
  1379  		multisize: "200x50,100x60",
  1380  		expect: []openrtb2.Format{{
  1381  			W: 200,
  1382  			H: 50,
  1383  		}, {
  1384  			W: 100,
  1385  			H: 60,
  1386  		}},
  1387  	}.execute(t)
  1388  }
  1389  
  1390  func TestSizeWithMultisize(t *testing.T) {
  1391  	formatOverrideSpec{
  1392  		width:     20,
  1393  		height:    40,
  1394  		multisize: "200x50,100x60",
  1395  		expect: []openrtb2.Format{{
  1396  			W: 20,
  1397  			H: 40,
  1398  		}, {
  1399  			W: 200,
  1400  			H: 50,
  1401  		}, {
  1402  			W: 100,
  1403  			H: 60,
  1404  		}},
  1405  	}.execute(t)
  1406  }
  1407  
  1408  func TestHeightOnly(t *testing.T) {
  1409  	formatOverrideSpec{
  1410  		height: 200,
  1411  		expect: []openrtb2.Format{{
  1412  			W: 300,
  1413  			H: 200,
  1414  		}},
  1415  	}.execute(t)
  1416  }
  1417  
  1418  func TestWidthOnly(t *testing.T) {
  1419  	formatOverrideSpec{
  1420  		width: 150,
  1421  		expect: []openrtb2.Format{{
  1422  			W: 150,
  1423  			H: 600,
  1424  		}},
  1425  	}.execute(t)
  1426  }
  1427  
  1428  type formatOverrideSpec struct {
  1429  	width          uint64
  1430  	height         uint64
  1431  	overrideWidth  uint64
  1432  	overrideHeight uint64
  1433  	multisize      string
  1434  	account        string
  1435  	expect         []openrtb2.Format
  1436  }
  1437  
  1438  func (s formatOverrideSpec) execute(t *testing.T) {
  1439  	requests := map[string]json.RawMessage{
  1440  		"1": json.RawMessage(validRequest(t, "site.json")),
  1441  	}
  1442  
  1443  	endpoint, _ := NewAmpEndpoint(
  1444  		fakeUUIDGenerator{},
  1445  		&mockAmpExchange{},
  1446  		newParamsValidator(t),
  1447  		&mockAmpStoredReqFetcher{requests},
  1448  		empty_fetcher.EmptyFetcher{},
  1449  		&config.Configuration{MaxRequestSize: maxSize},
  1450  		&metricsConfig.NilMetricsEngine{},
  1451  		analyticsBuild.New(&config.Analytics{}),
  1452  		map[string]string{},
  1453  		[]byte{},
  1454  		openrtb_ext.BuildBidderMap(),
  1455  		empty_fetcher.EmptyFetcher{},
  1456  		hooks.EmptyPlanBuilder{},
  1457  		nil,
  1458  	)
  1459  
  1460  	url := fmt.Sprintf("/openrtb2/auction/amp?tag_id=1&debug=1&w=%d&h=%d&ow=%d&oh=%d&ms=%s&account=%s", s.width, s.height, s.overrideWidth, s.overrideHeight, s.multisize, s.account)
  1461  	request := httptest.NewRequest("GET", url, nil)
  1462  	recorder := httptest.NewRecorder()
  1463  	endpoint(recorder, request, nil)
  1464  	if recorder.Code != http.StatusOK {
  1465  		t.Errorf("Expected status %d. Got %d. Request config ID was 1", http.StatusOK, recorder.Code)
  1466  		t.Errorf("Response body was: %s", recorder.Body)
  1467  		t.Errorf("Request was: %s", string(requests["1"]))
  1468  	}
  1469  	var response AmpResponse
  1470  	if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil {
  1471  		t.Fatalf("Error unmarshalling response: %s", err.Error())
  1472  	}
  1473  	var resolvedRequest openrtb2.BidRequest
  1474  	err := jsonutil.UnmarshalValid(response.ORTB2.Ext.Debug.ResolvedRequest, &resolvedRequest)
  1475  	assert.NoError(t, err, "resolved request should have the correct format")
  1476  	formats := resolvedRequest.Imp[0].Banner.Format
  1477  	if len(formats) != len(s.expect) {
  1478  		t.Fatalf("Bad formats length. Expected %v, got %v", s.expect, formats)
  1479  	}
  1480  	for i := 0; i < len(formats); i++ {
  1481  		if formats[i].W != s.expect[i].W {
  1482  			t.Errorf("format[%d].W were not equal. Expected %d, got %d", i, s.expect[i].W, formats[i].W)
  1483  		}
  1484  		if formats[i].H != s.expect[i].H {
  1485  			t.Errorf("format[%d].H were not equal. Expected %d, got %d", i, s.expect[i].H, formats[i].H)
  1486  		}
  1487  	}
  1488  }
  1489  
  1490  type mockAmpExchange struct {
  1491  	lastRequest *openrtb2.BidRequest
  1492  	requestExt  json.RawMessage
  1493  }
  1494  
  1495  var expectedErrorsFromHoldAuction map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage = map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{
  1496  	openrtb_ext.BidderName("openx"): {
  1497  		{
  1498  			Code:    1,
  1499  			Message: "The request exceeded the timeout allocated",
  1500  		},
  1501  	},
  1502  }
  1503  
  1504  func (m *mockAmpExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
  1505  	r := auctionRequest.BidRequestWrapper
  1506  	m.lastRequest = r.BidRequest
  1507  
  1508  	response := &openrtb2.BidResponse{
  1509  		SeatBid: []openrtb2.SeatBid{{
  1510  			Bid: []openrtb2.Bid{{
  1511  				AdM: "<script></script>",
  1512  				Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`),
  1513  			}},
  1514  		}},
  1515  		Ext: json.RawMessage(`{ "errors": {"openx":[ { "code": 1, "message": "The request exceeded the timeout allocated" } ] } }`),
  1516  	}
  1517  
  1518  	if m.requestExt != nil {
  1519  		response.Ext = m.requestExt
  1520  	}
  1521  	if len(auctionRequest.StoredAuctionResponses) > 0 {
  1522  		var seatBids []openrtb2.SeatBid
  1523  
  1524  		if err := jsonutil.UnmarshalValid(auctionRequest.StoredAuctionResponses[r.BidRequest.Imp[0].ID], &seatBids); err != nil {
  1525  			return nil, err
  1526  		}
  1527  		response.SeatBid = seatBids
  1528  	}
  1529  
  1530  	if r.BidRequest.Test == 1 {
  1531  		resolvedRequest, err := jsonutil.Marshal(r.BidRequest)
  1532  		if err != nil {
  1533  			resolvedRequest = json.RawMessage("{}")
  1534  		}
  1535  		response.Ext = json.RawMessage(fmt.Sprintf(`{"debug": {"httpcalls": {}, "resolvedrequest": %s}}`, resolvedRequest))
  1536  	}
  1537  
  1538  	return &exchange.AuctionResponse{BidResponse: response}, nil
  1539  }
  1540  
  1541  type mockAmpExchangeWarnings struct{}
  1542  
  1543  func (m *mockAmpExchangeWarnings) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
  1544  	response := &openrtb2.BidResponse{
  1545  		SeatBid: []openrtb2.SeatBid{{
  1546  			Bid: []openrtb2.Bid{{
  1547  				AdM: "<script></script>",
  1548  				Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`),
  1549  			}},
  1550  		}},
  1551  		Ext: json.RawMessage(`{ "warnings": {"appnexus": [{"code": 10003, "message": "debug turned off for bidder"}] }}`),
  1552  	}
  1553  	return &exchange.AuctionResponse{BidResponse: response}, nil
  1554  }
  1555  
  1556  func getTestBidRequest(nilUser bool, userExt *openrtb_ext.ExtUser, nilRegs bool, regsExt *openrtb_ext.ExtRegs) ([]byte, error) {
  1557  	var width int64 = 300
  1558  	var height int64 = 300
  1559  	bidRequest := &openrtb2.BidRequest{
  1560  		ID: "test-request-id",
  1561  		Imp: []openrtb2.Imp{
  1562  			{
  1563  				ID:  "/19968336/header-bid-tag-0",
  1564  				Ext: json.RawMessage(`{"appnexus": { "placementId":12883451 }}`),
  1565  				Banner: &openrtb2.Banner{
  1566  					Format: []openrtb2.Format{
  1567  						{
  1568  							W: width,
  1569  							H: 250,
  1570  						},
  1571  						{
  1572  							W: width,
  1573  							H: 240,
  1574  						},
  1575  					},
  1576  					W: &width,
  1577  					H: &height,
  1578  				},
  1579  			},
  1580  		},
  1581  		Site: &openrtb2.Site{
  1582  			ID:   "site-id",
  1583  			Page: "some-page",
  1584  		},
  1585  	}
  1586  
  1587  	var userExtData []byte
  1588  	if userExt != nil {
  1589  		var err error
  1590  		userExtData, err = jsonutil.Marshal(userExt)
  1591  		if err != nil {
  1592  			return nil, err
  1593  		}
  1594  	}
  1595  
  1596  	if !nilUser {
  1597  		bidRequest.User = &openrtb2.User{
  1598  			ID:       "aUserId",
  1599  			BuyerUID: "aBuyerID",
  1600  			Ext:      userExtData,
  1601  		}
  1602  	}
  1603  
  1604  	var regsExtData []byte
  1605  	if regsExt != nil {
  1606  		var err error
  1607  		regsExtData, err = jsonutil.Marshal(regsExt)
  1608  		if err != nil {
  1609  			return nil, err
  1610  		}
  1611  	}
  1612  
  1613  	if !nilRegs {
  1614  		bidRequest.Regs = &openrtb2.Regs{
  1615  			COPPA: 1,
  1616  			Ext:   regsExtData,
  1617  		}
  1618  	}
  1619  	return jsonutil.Marshal(bidRequest)
  1620  }
  1621  
  1622  func TestSetEffectiveAmpPubID(t *testing.T) {
  1623  	testPubID := "test-pub"
  1624  
  1625  	testCases := []struct {
  1626  		description   string
  1627  		req           *openrtb2.BidRequest
  1628  		account       string
  1629  		expectedPubID string
  1630  	}{
  1631  		{
  1632  			description: "No publisher ID provided",
  1633  			req: &openrtb2.BidRequest{
  1634  				App: &openrtb2.App{
  1635  					Publisher: nil,
  1636  				},
  1637  			},
  1638  			expectedPubID: "",
  1639  		},
  1640  		{
  1641  			description: "Publisher ID present in req.App.Publisher.ID",
  1642  			req: &openrtb2.BidRequest{
  1643  				App: &openrtb2.App{
  1644  					Publisher: &openrtb2.Publisher{
  1645  						ID: testPubID,
  1646  					},
  1647  				},
  1648  			},
  1649  			expectedPubID: testPubID,
  1650  		},
  1651  		{
  1652  			description: "Publisher ID present in req.Site.Publisher.ID",
  1653  			req: &openrtb2.BidRequest{
  1654  				Site: &openrtb2.Site{
  1655  					Publisher: &openrtb2.Publisher{
  1656  						ID: testPubID,
  1657  					},
  1658  				},
  1659  			},
  1660  			expectedPubID: testPubID,
  1661  		},
  1662  		{
  1663  			description: "Publisher ID present in account parameter",
  1664  			req: &openrtb2.BidRequest{
  1665  				App: &openrtb2.App{
  1666  					Publisher: &openrtb2.Publisher{
  1667  						ID: "",
  1668  					},
  1669  				},
  1670  			},
  1671  			account:       testPubID,
  1672  			expectedPubID: testPubID,
  1673  		},
  1674  		{
  1675  			description: "req.Site.Publisher present but ID set to empty string",
  1676  			req: &openrtb2.BidRequest{
  1677  				Site: &openrtb2.Site{
  1678  					Publisher: &openrtb2.Publisher{
  1679  						ID: "",
  1680  					},
  1681  				},
  1682  			},
  1683  			expectedPubID: "",
  1684  		},
  1685  	}
  1686  
  1687  	for _, test := range testCases {
  1688  		setEffectiveAmpPubID(test.req, test.account)
  1689  		if test.req.Site != nil {
  1690  			if test.req.Site.Publisher == nil {
  1691  				assert.Empty(t, test.expectedPubID,
  1692  					"should return the expected Publisher ID for test case: %s", test.description)
  1693  			} else {
  1694  				assert.Equal(t, test.expectedPubID, test.req.Site.Publisher.ID,
  1695  					"should return the expected Publisher ID for test case: %s", test.description)
  1696  			}
  1697  		} else {
  1698  			if test.req.App.Publisher == nil {
  1699  				assert.Empty(t, test.expectedPubID,
  1700  					"should return the expected Publisher ID for test case: %s", test.description)
  1701  			} else {
  1702  				assert.Equal(t, test.expectedPubID, test.req.App.Publisher.ID,
  1703  					"should return the expected Publisher ID for test case: %s", test.description)
  1704  			}
  1705  		}
  1706  	}
  1707  }
  1708  
  1709  type mockLogger struct {
  1710  	ampObject     *analytics.AmpObject
  1711  	auctionObject *analytics.AuctionObject
  1712  }
  1713  
  1714  func newMockLogger(ao *analytics.AmpObject, aucObj *analytics.AuctionObject) analytics.Runner {
  1715  	return &mockLogger{
  1716  		ampObject:     ao,
  1717  		auctionObject: aucObj,
  1718  	}
  1719  }
  1720  
  1721  func (logger mockLogger) LogAuctionObject(ao *analytics.AuctionObject, _ privacy.ActivityControl) {
  1722  	*logger.auctionObject = *ao
  1723  }
  1724  func (logger mockLogger) LogVideoObject(vo *analytics.VideoObject, _ privacy.ActivityControl) {
  1725  }
  1726  func (logger mockLogger) LogCookieSyncObject(cookieObject *analytics.CookieSyncObject) {
  1727  }
  1728  func (logger mockLogger) LogSetUIDObject(uuidObj *analytics.SetUIDObject) {
  1729  }
  1730  func (logger mockLogger) LogNotificationEventObject(uuidObj *analytics.NotificationEvent, _ privacy.ActivityControl) {
  1731  }
  1732  func (logger mockLogger) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) {
  1733  	*logger.ampObject = *ao
  1734  }
  1735  
  1736  func TestBuildAmpObject(t *testing.T) {
  1737  	testCases := []struct {
  1738  		description       string
  1739  		inTagId           string
  1740  		exchange          *mockAmpExchange
  1741  		inStoredRequest   json.RawMessage
  1742  		expectedAmpObject *analytics.AmpObject
  1743  	}{
  1744  		{
  1745  			description:     "Stored Amp request with nil body. Only the error gets logged",
  1746  			inTagId:         "test",
  1747  			inStoredRequest: nil,
  1748  			expectedAmpObject: &analytics.AmpObject{
  1749  				Status: http.StatusOK,
  1750  				Errors: []error{fmt.Errorf("unexpected end of JSON input")},
  1751  			},
  1752  		},
  1753  		{
  1754  			description:     "Stored Amp request with no imps that should return error. Only the error gets logged",
  1755  			inTagId:         "test",
  1756  			inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[],"tmax":500}`),
  1757  			expectedAmpObject: &analytics.AmpObject{
  1758  				Status: http.StatusOK,
  1759  				Errors: []error{fmt.Errorf("data for tag_id='test' does not define the required imp array")},
  1760  			},
  1761  		},
  1762  		{
  1763  			description:     "Wrong tag_id, error gets logged",
  1764  			inTagId:         "unknown",
  1765  			inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`),
  1766  			expectedAmpObject: &analytics.AmpObject{
  1767  				Status: http.StatusOK,
  1768  				Errors: []error{fmt.Errorf("unexpected end of JSON input")},
  1769  			},
  1770  		},
  1771  		{
  1772  			description:     "Valid stored Amp request, correct tag_id, a valid response should be logged",
  1773  			inTagId:         "test",
  1774  			inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`),
  1775  			expectedAmpObject: &analytics.AmpObject{
  1776  				Status: http.StatusOK,
  1777  				Errors: nil,
  1778  				RequestWrapper: &openrtb_ext.RequestWrapper{
  1779  					BidRequest: &openrtb2.BidRequest{
  1780  						ID: "some-request-id",
  1781  						Device: &openrtb2.Device{
  1782  							IP: "192.0.2.1",
  1783  						},
  1784  						Site: &openrtb2.Site{
  1785  							Page: "prebid.org",
  1786  							Ext:  json.RawMessage(`{"amp":1}`),
  1787  						},
  1788  						Imp: []openrtb2.Imp{
  1789  							{
  1790  								ID: "some-impression-id",
  1791  								Banner: &openrtb2.Banner{
  1792  									Format: []openrtb2.Format{
  1793  										{
  1794  											W: 300,
  1795  											H: 250,
  1796  										},
  1797  									},
  1798  								},
  1799  								Secure: func(val int8) *int8 { return &val }(1), //(*int8)(1),
  1800  								Ext:    json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}`),
  1801  							},
  1802  						},
  1803  						AT:   1,
  1804  						TMax: 500,
  1805  						Ext:  json.RawMessage(`{"prebid":{"cache":{"bids":{}},"channel":{"name":"amp","version":""},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true}}}`),
  1806  					},
  1807  				},
  1808  				AuctionResponse: &openrtb2.BidResponse{
  1809  					SeatBid: []openrtb2.SeatBid{{
  1810  						Bid: []openrtb2.Bid{{
  1811  							AdM: "<script></script>",
  1812  							Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`),
  1813  						}},
  1814  						Seat: "",
  1815  					}},
  1816  					Ext: json.RawMessage(`{ "errors": {"openx":[ { "code": 1, "message": "The request exceeded the timeout allocated" } ] } }`),
  1817  				},
  1818  				AmpTargetingValues: map[string]string{
  1819  					"hb_appnexus_pb": "1.20",
  1820  					"hb_cache_id":    "some_id",
  1821  					"hb_pb":          "1.20",
  1822  				},
  1823  				Origin: "",
  1824  			},
  1825  		},
  1826  		{
  1827  			description:     "Global targeting from bid response should be applied for Amp",
  1828  			inTagId:         "test",
  1829  			inStoredRequest: json.RawMessage(`{"id":"some-request-id","site":{"page":"prebid.org"},"imp":[{"id":"some-impression-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}}],"tmax":500}`),
  1830  			exchange:        &mockAmpExchange{requestExt: json.RawMessage(`{ "prebid": {"targeting": { "test_key": "test_value", "hb_appnexus_pb": "9999" } }, "errors": {"openx":[ { "code": 1, "message": "The request exceeded the timeout allocated" } ] } }`)},
  1831  			expectedAmpObject: &analytics.AmpObject{
  1832  				Status: http.StatusOK,
  1833  				Errors: nil,
  1834  				RequestWrapper: &openrtb_ext.RequestWrapper{
  1835  					BidRequest: &openrtb2.BidRequest{
  1836  						ID: "some-request-id",
  1837  						Device: &openrtb2.Device{
  1838  							IP: "192.0.2.1",
  1839  						},
  1840  						Site: &openrtb2.Site{
  1841  							Page: "prebid.org",
  1842  							Ext:  json.RawMessage(`{"amp":1}`),
  1843  						},
  1844  						Imp: []openrtb2.Imp{
  1845  							{
  1846  								ID: "some-impression-id",
  1847  								Banner: &openrtb2.Banner{
  1848  									Format: []openrtb2.Format{
  1849  										{
  1850  											W: 300,
  1851  											H: 250,
  1852  										},
  1853  									},
  1854  								},
  1855  								Secure: func(val int8) *int8 { return &val }(1), //(*int8)(1),
  1856  								Ext:    json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}}`),
  1857  							},
  1858  						},
  1859  						AT:   1,
  1860  						TMax: 500,
  1861  						Ext:  json.RawMessage(`{"prebid":{"cache":{"bids":{}},"channel":{"name":"amp","version":""},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true}}}`),
  1862  					},
  1863  				},
  1864  				AuctionResponse: &openrtb2.BidResponse{
  1865  					SeatBid: []openrtb2.SeatBid{{
  1866  						Bid: []openrtb2.Bid{{
  1867  							AdM: "<script></script>",
  1868  							Ext: json.RawMessage(`{ "prebid": {"targeting": { "hb_pb": "1.20", "hb_appnexus_pb": "1.20", "hb_cache_id": "some_id"}}}`),
  1869  						}},
  1870  						Seat: "",
  1871  					}},
  1872  					Ext: json.RawMessage(`{ "prebid": {"targeting": { "test_key": "test_value", "hb_appnexus_pb": "9999" } }, "errors": {"openx":[ { "code": 1, "message": "The request exceeded the timeout allocated" } ] } }`),
  1873  				},
  1874  				AmpTargetingValues: map[string]string{
  1875  					"hb_appnexus_pb": "1.20", // Bid level has higher priority than global
  1876  					"hb_cache_id":    "some_id",
  1877  					"hb_pb":          "1.20",
  1878  					"test_key":       "test_value", // New global key added
  1879  				},
  1880  				Origin: "",
  1881  			},
  1882  		},
  1883  	}
  1884  
  1885  	request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=test", nil)
  1886  	recorder := httptest.NewRecorder()
  1887  
  1888  	for _, test := range testCases {
  1889  		// Set up test, declare a new mock logger every time
  1890  		exchange := test.exchange
  1891  		if exchange == nil {
  1892  			exchange = &mockAmpExchange{}
  1893  		}
  1894  		actualAmpObject, endpoint := ampObjectTestSetup(t, test.inTagId, test.inStoredRequest, false, exchange)
  1895  		// Run test
  1896  		endpoint(recorder, request, nil)
  1897  
  1898  		// assert AmpObject
  1899  		assert.Equalf(t, test.expectedAmpObject.Status, actualAmpObject.Status, "Amp Object Status field doesn't match expected: %s\n", test.description)
  1900  		assert.Lenf(t, actualAmpObject.Errors, len(test.expectedAmpObject.Errors), "Amp Object Errors array doesn't match expected: %s\n", test.description)
  1901  		var expectedRequest *openrtb2.BidRequest
  1902  		var actualRequest *openrtb2.BidRequest
  1903  		if test.expectedAmpObject.RequestWrapper != nil {
  1904  			expectedRequest = test.expectedAmpObject.RequestWrapper.BidRequest
  1905  		}
  1906  		if actualAmpObject.RequestWrapper != nil {
  1907  			actualRequest = test.expectedAmpObject.RequestWrapper.BidRequest
  1908  		}
  1909  		assert.Equalf(t, expectedRequest, actualRequest, "Amp Object BidRequest doesn't match expected: %s\n", test.description)
  1910  		assert.Equalf(t, test.expectedAmpObject.AuctionResponse, actualAmpObject.AuctionResponse, "Amp Object BidResponse doesn't match expected: %s\n", test.description)
  1911  		assert.Equalf(t, test.expectedAmpObject.AmpTargetingValues, actualAmpObject.AmpTargetingValues, "Amp Object AmpTargetingValues doesn't match expected: %s\n", test.description)
  1912  		assert.Equalf(t, test.expectedAmpObject.Origin, actualAmpObject.Origin, "Amp Object Origin field doesn't match expected: %s\n", test.description)
  1913  	}
  1914  }
  1915  
  1916  func TestIdGeneration(t *testing.T) {
  1917  	uuid := "foo"
  1918  
  1919  	testCases := []struct {
  1920  		description            string
  1921  		givenInStoredRequest   json.RawMessage
  1922  		givenGenerateRequestID bool
  1923  		expectedID             string
  1924  	}{
  1925  		{
  1926  			description:            "The givenGenerateRequestID flag is set to true, so even though the stored amp request already has an id, we should still generate a new uuid",
  1927  			givenInStoredRequest:   json.RawMessage(`{"id":"ThisID","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`),
  1928  			givenGenerateRequestID: true,
  1929  			expectedID:             uuid,
  1930  		},
  1931  		{
  1932  			description:            "The givenGenerateRequestID flag is set to true and the stored amp request ID is blank, so we should generate a new uuid for the request",
  1933  			givenInStoredRequest:   json.RawMessage(`{"id":"","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`),
  1934  			givenGenerateRequestID: true,
  1935  			expectedID:             uuid,
  1936  		},
  1937  		{
  1938  			description:            "The givenGenerateRequestID flag is false, so the ID shouldn't change",
  1939  			givenInStoredRequest:   json.RawMessage(`{"id":"ThisID","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`),
  1940  			givenGenerateRequestID: false,
  1941  			expectedID:             "ThisID",
  1942  		},
  1943  		{
  1944  			description:            "The givenGenerateRequestID flag is true, and the id field isn't included in the stored request, we should still generate a uuid",
  1945  			givenInStoredRequest:   json.RawMessage(`{"site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`),
  1946  			givenGenerateRequestID: true,
  1947  			expectedID:             uuid,
  1948  		},
  1949  		{
  1950  			description:            "The givenGenerateRequestID flag is false, but id field is the macro option {{UUID}}, we should generate a uuid",
  1951  			givenInStoredRequest:   json.RawMessage(`{"id":"{{UUID}}","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`),
  1952  			givenGenerateRequestID: false,
  1953  			expectedID:             uuid,
  1954  		},
  1955  		{
  1956  			description:            "Macro ID case sensitivity check. The id is {{uuid}}, but we should only generate an id if it's all uppercase {{UUID}}. So the ID shouldn't change.",
  1957  			givenInStoredRequest:   json.RawMessage(`{"id":"{{uuid}}","site":{"page":"prebid.org"},"imp":[{"id":"some-imp-id","banner":{"format":[{"w":300,"h":250}]},"ext":{"appnexus":{"placementId":1}}}],"tmax":1}`),
  1958  			givenGenerateRequestID: false,
  1959  			expectedID:             "{{uuid}}",
  1960  		},
  1961  	}
  1962  
  1963  	request := httptest.NewRequest("GET", "/openrtb2/auction/amp?tag_id=test", nil)
  1964  	recorder := httptest.NewRecorder()
  1965  
  1966  	for _, test := range testCases {
  1967  		// Set up and run test
  1968  		actualAmpObject, endpoint := ampObjectTestSetup(t, "test", test.givenInStoredRequest, test.givenGenerateRequestID, &mockAmpExchange{})
  1969  		endpoint(recorder, request, nil)
  1970  		assert.Equalf(t, test.expectedID, actualAmpObject.RequestWrapper.ID, "Bid Request ID is incorrect: %s\n", test.description)
  1971  	}
  1972  }
  1973  
  1974  func ampObjectTestSetup(t *testing.T, inTagId string, inStoredRequest json.RawMessage, generateRequestID bool, exchange *mockAmpExchange) (*analytics.AmpObject, httprouter.Handle) {
  1975  	actualAmpObject := analytics.AmpObject{}
  1976  	logger := newMockLogger(&actualAmpObject, nil)
  1977  
  1978  	mockAmpFetcher := &mockAmpStoredReqFetcher{
  1979  		data: map[string]json.RawMessage{
  1980  			inTagId: json.RawMessage(inStoredRequest),
  1981  		},
  1982  	}
  1983  
  1984  	endpoint, _ := NewAmpEndpoint(
  1985  		fakeUUIDGenerator{id: "foo", err: nil},
  1986  		exchange,
  1987  		newParamsValidator(t),
  1988  		mockAmpFetcher,
  1989  		empty_fetcher.EmptyFetcher{},
  1990  		&config.Configuration{MaxRequestSize: maxSize, GenerateRequestID: generateRequestID},
  1991  		&metricsConfig.NilMetricsEngine{},
  1992  		logger,
  1993  		map[string]string{},
  1994  		[]byte{},
  1995  		openrtb_ext.BuildBidderMap(),
  1996  		empty_fetcher.EmptyFetcher{},
  1997  		hooks.EmptyPlanBuilder{},
  1998  		nil,
  1999  	)
  2000  	return &actualAmpObject, endpoint
  2001  }
  2002  
  2003  func TestAmpAuctionResponseHeaders(t *testing.T) {
  2004  	testCases := []struct {
  2005  		description         string
  2006  		requestURLArguments string
  2007  		expectedStatus      int
  2008  		expectedHeaders     func(http.Header)
  2009  	}{
  2010  		{
  2011  			description:         "Success Response",
  2012  			requestURLArguments: "?tag_id=1&__amp_source_origin=foo",
  2013  			expectedStatus:      200,
  2014  			expectedHeaders: func(h http.Header) {
  2015  				h.Set("AMP-Access-Control-Allow-Source-Origin", "foo")
  2016  				h.Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin")
  2017  				h.Set("X-Prebid", "pbs-go/unknown")
  2018  				h.Set("Content-Type", "text/plain; charset=utf-8")
  2019  			},
  2020  		},
  2021  		{
  2022  			description:         "Failure Response",
  2023  			requestURLArguments: "?tag_id=invalid&__amp_source_origin=foo",
  2024  			expectedStatus:      400,
  2025  			expectedHeaders: func(h http.Header) {
  2026  				h.Set("AMP-Access-Control-Allow-Source-Origin", "foo")
  2027  				h.Set("Access-Control-Expose-Headers", "AMP-Access-Control-Allow-Source-Origin")
  2028  				h.Set("X-Prebid", "pbs-go/unknown")
  2029  			},
  2030  		},
  2031  	}
  2032  
  2033  	storedRequests := map[string]json.RawMessage{
  2034  		"1": json.RawMessage(validRequest(t, "site.json")),
  2035  	}
  2036  	exchange := &nobidExchange{}
  2037  	endpoint, _ := NewAmpEndpoint(
  2038  		fakeUUIDGenerator{},
  2039  		exchange,
  2040  		newParamsValidator(t),
  2041  		&mockAmpStoredReqFetcher{storedRequests},
  2042  		empty_fetcher.EmptyFetcher{},
  2043  		&config.Configuration{MaxRequestSize: maxSize},
  2044  		&metricsConfig.NilMetricsEngine{},
  2045  		analyticsBuild.New(&config.Analytics{}),
  2046  		map[string]string{},
  2047  		[]byte{},
  2048  		openrtb_ext.BuildBidderMap(),
  2049  		empty_fetcher.EmptyFetcher{},
  2050  		hooks.EmptyPlanBuilder{},
  2051  		nil,
  2052  	)
  2053  
  2054  	for _, test := range testCases {
  2055  		httpReq := httptest.NewRequest("GET", "/openrtb2/auction/amp"+test.requestURLArguments, nil)
  2056  		recorder := httptest.NewRecorder()
  2057  
  2058  		endpoint(recorder, httpReq, nil)
  2059  
  2060  		expectedHeaders := http.Header{}
  2061  		test.expectedHeaders(expectedHeaders)
  2062  
  2063  		assert.Equal(t, test.expectedStatus, recorder.Result().StatusCode, test.description+":statuscode")
  2064  		assert.Equal(t, expectedHeaders, recorder.Result().Header, test.description+":statuscode")
  2065  	}
  2066  }
  2067  
  2068  func TestRequestWithTargeting(t *testing.T) {
  2069  	stored := map[string]json.RawMessage{
  2070  		"1": json.RawMessage(validRequest(t, "site.json")),
  2071  	}
  2072  	exchange := &mockAmpExchange{}
  2073  	endpoint, _ := NewAmpEndpoint(
  2074  		fakeUUIDGenerator{},
  2075  		exchange,
  2076  		newParamsValidator(t),
  2077  		&mockAmpStoredReqFetcher{stored},
  2078  		empty_fetcher.EmptyFetcher{},
  2079  		&config.Configuration{MaxRequestSize: maxSize},
  2080  		&metricsConfig.NilMetricsEngine{},
  2081  		analyticsBuild.New(&config.Analytics{}),
  2082  		nil,
  2083  		nil,
  2084  		openrtb_ext.BuildBidderMap(),
  2085  		empty_fetcher.EmptyFetcher{},
  2086  		hooks.EmptyPlanBuilder{},
  2087  		nil,
  2088  	)
  2089  	url, err := url.Parse("/openrtb2/auction/amp")
  2090  	assert.NoError(t, err, "unexpected error received while parsing url")
  2091  	values := url.Query()
  2092  	values.Add("targeting", `{"gam-key1":"val1", "gam-key2":"val2"}`)
  2093  	values.Add("tag_id", "1")
  2094  	url.RawQuery = values.Encode()
  2095  
  2096  	request, err := http.NewRequest("GET", url.String(), nil)
  2097  	if !assert.NoError(t, err) {
  2098  		return
  2099  	}
  2100  	recorder := httptest.NewRecorder()
  2101  	endpoint(recorder, request, nil)
  2102  
  2103  	if assert.NotNil(t, exchange.lastRequest, "Endpoint responded with %d: %s", recorder.Code, recorder.Body.String()) {
  2104  		assert.JSONEq(t, `{"prebid":{"bidder":{"appnexus":{"placementId":12883451}}}, "data":{"gam-key1":"val1", "gam-key2":"val2"}}`, string(exchange.lastRequest.Imp[0].Ext))
  2105  	}
  2106  }
  2107  
  2108  func TestSetTargeting(t *testing.T) {
  2109  	tests := []struct {
  2110  		description    string
  2111  		bidRequest     openrtb2.BidRequest
  2112  		targeting      string
  2113  		expectedImpExt string
  2114  		wantError      bool
  2115  		errorMessage   string
  2116  	}{
  2117  		{
  2118  			description:    "valid imp ext, valid targeting data",
  2119  			bidRequest:     openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"appnexus":{"placementId":123}}`)}}},
  2120  			targeting:      `{"gam-key1":"val1", "gam-key2":"val2"}`,
  2121  			expectedImpExt: `{"appnexus":{"placementId":123}, "data": {"gam-key1":"val1", "gam-key2":"val2"}}`,
  2122  			wantError:      false,
  2123  			errorMessage:   "",
  2124  		},
  2125  		{
  2126  			description:    "valid imp ext, empty targeting data",
  2127  			bidRequest:     openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"appnexus":{"placementId":123}}`)}}},
  2128  			targeting:      ``,
  2129  			expectedImpExt: `{"appnexus":{"placementId":123}}`,
  2130  			wantError:      false,
  2131  			errorMessage:   "",
  2132  		},
  2133  		{
  2134  			description:    "empty imp ext, valid targeting data",
  2135  			bidRequest:     openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{}`)}}},
  2136  			targeting:      `{"gam-key1":"val1", "gam-key2":"val2"}`,
  2137  			expectedImpExt: `{"data": {"gam-key1":"val1", "gam-key2":"val2"}}`,
  2138  			wantError:      false,
  2139  			errorMessage:   "",
  2140  		},
  2141  		{
  2142  			description:    "nil imp ext, valid targeting data",
  2143  			bidRequest:     openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: nil}}},
  2144  			targeting:      `{"gam-key1":"val1", "gam-key2":"val2"}`,
  2145  			expectedImpExt: `{"data": {"gam-key1":"val1", "gam-key2":"val2"}}`,
  2146  			wantError:      false,
  2147  			errorMessage:   "",
  2148  		},
  2149  		{
  2150  			description:    "imp ext has data, valid targeting data",
  2151  			bidRequest:     openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"data":{"placementId":123}}`)}}},
  2152  			targeting:      `{"gam-key1":"val1", "gam-key2":"val2"}`,
  2153  			expectedImpExt: `{"data": {"gam-key1":"val1", "gam-key2":"val2", "placementId":123}}`,
  2154  			wantError:      false,
  2155  			errorMessage:   "",
  2156  		},
  2157  		{
  2158  			description:    "imp ext has data and other fields, valid targeting data",
  2159  			bidRequest:     openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"data":{"placementId":123}, "prebid": 123}`)}}},
  2160  			targeting:      `{"gam-key1":"val1", "gam-key2":"val2"}`,
  2161  			expectedImpExt: `{"data": {"gam-key1":"val1", "gam-key2":"val2", "placementId":123}, "prebid":123}`,
  2162  			wantError:      false,
  2163  			errorMessage:   "",
  2164  		},
  2165  		{
  2166  			description:    "imp ext has invalid format, valid targeting data",
  2167  			bidRequest:     openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{123:{}`)}}},
  2168  			targeting:      `{"gam-key1":"val1", "gam-key2":"val2"}`,
  2169  			expectedImpExt: ``,
  2170  			wantError:      true,
  2171  			errorMessage:   "unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Document",
  2172  		},
  2173  		{
  2174  			description:    "valid imp ext, invalid targeting data",
  2175  			bidRequest:     openrtb2.BidRequest{Imp: []openrtb2.Imp{{Ext: []byte(`{"appnexus":{"placementId":123}}`)}}},
  2176  			targeting:      `{123,}`,
  2177  			expectedImpExt: ``,
  2178  			wantError:      true,
  2179  			errorMessage:   "unable to merge imp.ext with targeting data, check targeting data is correct: Invalid JSON Patch",
  2180  		},
  2181  	}
  2182  
  2183  	for _, test := range tests {
  2184  		req := &test.bidRequest
  2185  		err := setTargeting(req, test.targeting)
  2186  		if test.wantError {
  2187  			assert.EqualErrorf(t, err, test.errorMessage, "error is incorrect for test case: %s", test.description)
  2188  		} else {
  2189  			assert.NoError(t, err, "error should be nil for test case: %s", test.description)
  2190  			assert.JSONEq(t, test.expectedImpExt, string(req.Imp[0].Ext), "incorrect impression extension returned for test %s", test.description)
  2191  		}
  2192  
  2193  	}
  2194  }
  2195  
  2196  func TestValidAmpResponseWhenRequestRejected(t *testing.T) {
  2197  	const nbr int = 123
  2198  
  2199  	testCases := []struct {
  2200  		description string
  2201  		file        string
  2202  		planBuilder hooks.ExecutionPlanBuilder
  2203  	}{
  2204  		{
  2205  			description: "Assert correct AmpResponse when request rejected at entrypoint stage",
  2206  			file:        "sample-requests/hooks/amp_entrypoint_reject.json",
  2207  			planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr, nil})},
  2208  		},
  2209  		{
  2210  			// raw_auction stage not executed for AMP endpoint, so we expect full response
  2211  			description: "Assert correct AmpResponse when request rejected at raw_auction stage",
  2212  			file:        "sample-requests/amp/valid-supplementary/aliased-buyeruids.json",
  2213  			planBuilder: mockPlanBuilder{rawAuctionPlan: makePlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr, nil})},
  2214  		},
  2215  		{
  2216  			description: "Assert correct AmpResponse when request rejected at processed_auction stage",
  2217  			file:        "sample-requests/hooks/amp_processed_auction_request_reject.json",
  2218  			planBuilder: mockPlanBuilder{processedAuctionPlan: makePlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr, nil})},
  2219  		},
  2220  		{
  2221  			// bidder_request stage rejects only bidder, so we expect bidder rejection warning added
  2222  			description: "Assert correct AmpResponse when request rejected at bidder-request stage",
  2223  			file:        "sample-requests/hooks/amp_bidder_reject.json",
  2224  			planBuilder: mockPlanBuilder{bidderRequestPlan: makePlan[hookstage.BidderRequest](mockRejectionHook{nbr, nil})},
  2225  		},
  2226  		{
  2227  			// raw_bidder_response stage rejects only bidder, so we expect bidder rejection warning added
  2228  			description: "Assert correct AmpResponse when request rejected at raw_bidder_response stage",
  2229  			file:        "sample-requests/hooks/amp_bidder_response_reject.json",
  2230  			planBuilder: mockPlanBuilder{rawBidderResponsePlan: makePlan[hookstage.RawBidderResponse](mockRejectionHook{nbr, nil})},
  2231  		},
  2232  		{
  2233  			// no debug information should be added for raw_auction stage because it's not executed for amp endpoint
  2234  			description: "Assert correct AmpResponse with debug information from modules added to ext.prebid.modules",
  2235  			file:        "sample-requests/hooks/amp.json",
  2236  			planBuilder: mockPlanBuilder{
  2237  				entrypointPlan: hooks.Plan[hookstage.Entrypoint]{
  2238  					{
  2239  						Timeout: 5 * time.Millisecond,
  2240  						Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{
  2241  							entryPointHookUpdateWithErrors,
  2242  							entryPointHookUpdateWithErrorsAndWarnings,
  2243  						},
  2244  					},
  2245  					{
  2246  						Timeout: 5 * time.Millisecond,
  2247  						Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{
  2248  							entryPointHookUpdate,
  2249  						},
  2250  					},
  2251  				},
  2252  				rawAuctionPlan: hooks.Plan[hookstage.RawAuctionRequest]{
  2253  					{
  2254  						Timeout: 5 * time.Millisecond,
  2255  						Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{
  2256  							rawAuctionHookNone,
  2257  						},
  2258  					},
  2259  				},
  2260  			},
  2261  		},
  2262  	}
  2263  
  2264  	for _, tc := range testCases {
  2265  		t.Run(tc.description, func(t *testing.T) {
  2266  			fileData, err := os.ReadFile(tc.file)
  2267  			assert.NoError(t, err, "Failed to read test file.")
  2268  
  2269  			test := testCase{}
  2270  			assert.NoError(t, jsonutil.UnmarshalValid(fileData, &test), "Failed to parse test file.")
  2271  
  2272  			request := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp?%s", test.Query), nil)
  2273  			recorder := httptest.NewRecorder()
  2274  			query := request.URL.Query()
  2275  			tagID := query.Get("tag_id")
  2276  
  2277  			test.StoredRequest = map[string]json.RawMessage{tagID: test.BidRequest}
  2278  			test.planBuilder = tc.planBuilder
  2279  			test.endpointType = AMP_ENDPOINT
  2280  
  2281  			cfg := &config.Configuration{MaxRequestSize: maxSize, AccountDefaults: config.Account{DebugAllow: true}}
  2282  			ampEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg)
  2283  			assert.NoError(t, err, "Failed to build test endpoint.")
  2284  
  2285  			ampEndpointHandler(recorder, request, nil)
  2286  			assert.Equal(t, recorder.Code, http.StatusOK, "Endpoint should return 200 OK.")
  2287  
  2288  			var actualAmpResp AmpResponse
  2289  			var expectedAmpResp AmpResponse
  2290  			assert.NoError(t, jsonutil.UnmarshalValid(recorder.Body.Bytes(), &actualAmpResp), "Unable to unmarshal actual AmpResponse.")
  2291  			assert.NoError(t, jsonutil.UnmarshalValid(test.ExpectedAmpResponse, &expectedAmpResp), "Unable to unmarshal expected AmpResponse.")
  2292  
  2293  			// validate modules data separately, because it has dynamic data
  2294  			if expectedAmpResp.ORTB2.Ext.Prebid == nil {
  2295  				assert.Nil(t, actualAmpResp.ORTB2.Ext.Prebid, "AmpResponse.ortb2.ext.prebid expected to be nil.")
  2296  			} else {
  2297  				hookexecution.AssertEqualModulesData(t, expectedAmpResp.ORTB2.Ext.Prebid.Modules, actualAmpResp.ORTB2.Ext.Prebid.Modules)
  2298  			}
  2299  
  2300  			// reset modules to validate amp responses
  2301  			actualAmpResp.ORTB2.Ext.Prebid = nil
  2302  			expectedAmpResp.ORTB2.Ext.Prebid = nil
  2303  			assert.Equal(t, expectedAmpResp, actualAmpResp, "Invalid AMP Response.")
  2304  
  2305  			// Close servers regardless if the test case was run or not
  2306  			for _, mockBidServer := range mockBidServers {
  2307  				mockBidServer.Close()
  2308  			}
  2309  			mockCurrencyRatesServer.Close()
  2310  		})
  2311  	}
  2312  }
  2313  
  2314  func TestSendAmpResponse_LogsErrors(t *testing.T) {
  2315  	testCases := []struct {
  2316  		description    string
  2317  		expectedErrors []error
  2318  		expectedStatus int
  2319  		writer         http.ResponseWriter
  2320  		request        *openrtb2.BidRequest
  2321  		response       *openrtb2.BidResponse
  2322  		hookExecutor   hookexecution.HookStageExecutor
  2323  	}{
  2324  		{
  2325  			description: "Error logged when bid.ext unmarshal fails",
  2326  			expectedErrors: []error{
  2327  				errors.New("Critical error while unpacking AMP targets: expect { or n, but found \""),
  2328  			},
  2329  			expectedStatus: http.StatusInternalServerError,
  2330  			writer:         httptest.NewRecorder(),
  2331  			request:        &openrtb2.BidRequest{ID: "some-id", Test: 1},
  2332  			response: &openrtb2.BidResponse{ID: "some-id", SeatBid: []openrtb2.SeatBid{
  2333  				{Bid: []openrtb2.Bid{{Ext: json.RawMessage(`"hb_cache_id`)}}},
  2334  			}},
  2335  			hookExecutor: &hookexecution.EmptyHookExecutor{},
  2336  		},
  2337  		{
  2338  			description: "Error logged when test mode activated but no debug present in response",
  2339  			expectedErrors: []error{
  2340  				errors.New("test set on request but debug not present in response"),
  2341  			},
  2342  			expectedStatus: 0,
  2343  			writer:         httptest.NewRecorder(),
  2344  			request:        &openrtb2.BidRequest{ID: "some-id", Test: 1},
  2345  			response:       &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("{}")},
  2346  			hookExecutor:   &hookexecution.EmptyHookExecutor{},
  2347  		},
  2348  		{
  2349  			description: "Error logged when response encoding fails",
  2350  			expectedErrors: []error{
  2351  				errors.New("/openrtb2/amp Failed to send response: failed writing response"),
  2352  			},
  2353  			expectedStatus: 0,
  2354  			writer:         errorResponseWriter{},
  2355  			request:        &openrtb2.BidRequest{ID: "some-id", Test: 1},
  2356  			response:       &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage(`{"debug": {}}`)},
  2357  			hookExecutor:   &hookexecution.EmptyHookExecutor{},
  2358  		},
  2359  		{
  2360  			description: "Error logged if hook enrichment returns warnings",
  2361  			expectedErrors: []error{
  2362  				errors.New("Value is not a string: 1"),
  2363  				errors.New("Value is not a boolean: active"),
  2364  			},
  2365  			expectedStatus: 0,
  2366  			writer:         httptest.NewRecorder(),
  2367  			request:        &openrtb2.BidRequest{ID: "some-id", Ext: json.RawMessage(`{"prebid": {"debug": "active", "trace": 1}}`)},
  2368  			response:       &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("{}")},
  2369  			hookExecutor: &mockStageExecutor{
  2370  				outcomes: []hookexecution.StageOutcome{
  2371  					{
  2372  						Entity: "bid-request",
  2373  						Stage:  hooks.StageBidderRequest.String(),
  2374  						Groups: []hookexecution.GroupOutcome{
  2375  							{
  2376  								InvocationResults: []hookexecution.HookOutcome{
  2377  									{
  2378  										HookID: hookexecution.HookID{
  2379  											ModuleCode:   "foobar",
  2380  											HookImplCode: "foo",
  2381  										},
  2382  										Status:   hookexecution.StatusSuccess,
  2383  										Action:   hookexecution.ActionNone,
  2384  										Warnings: []string{"warning message"},
  2385  									},
  2386  								},
  2387  							},
  2388  						},
  2389  					},
  2390  				},
  2391  			},
  2392  		},
  2393  	}
  2394  
  2395  	for _, test := range testCases {
  2396  		t.Run(test.description, func(t *testing.T) {
  2397  			labels := metrics.Labels{}
  2398  			ao := analytics.AmpObject{}
  2399  			account := &config.Account{DebugAllow: true}
  2400  			reqWrapper := openrtb_ext.RequestWrapper{BidRequest: test.request}
  2401  
  2402  			_, ao = sendAmpResponse(test.writer, test.hookExecutor, &exchange.AuctionResponse{BidResponse: test.response}, &reqWrapper, account, labels, ao, nil)
  2403  
  2404  			assert.Equal(t, test.expectedErrors, ao.Errors, "Invalid errors.")
  2405  			assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.")
  2406  		})
  2407  	}
  2408  }
  2409  
  2410  type errorResponseWriter struct{}
  2411  
  2412  func (e errorResponseWriter) Header() http.Header {
  2413  	return http.Header{}
  2414  }
  2415  
  2416  func (e errorResponseWriter) Write(bytes []byte) (int, error) {
  2417  	return 0, errors.New("failed writing response")
  2418  }
  2419  
  2420  func (e errorResponseWriter) WriteHeader(statusCode int) {}
  2421  
  2422  func TestSetSeatNonBid(t *testing.T) {
  2423  	type args struct {
  2424  		finalExtBidResponse *openrtb_ext.ExtBidResponse
  2425  		request             *openrtb_ext.RequestWrapper
  2426  		auctionResponse     *exchange.AuctionResponse
  2427  	}
  2428  	tests := []struct {
  2429  		name string
  2430  		args args
  2431  		want bool
  2432  	}{
  2433  		{
  2434  			name: "nil-auctionResponse",
  2435  			args: args{auctionResponse: nil},
  2436  			want: false,
  2437  		},
  2438  		{
  2439  			name: "nil-request",
  2440  			args: args{auctionResponse: &exchange.AuctionResponse{}, request: nil},
  2441  			want: false,
  2442  		},
  2443  		{
  2444  			name: "invalid-req-ext",
  2445  			args: args{auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`invalid json`)}}},
  2446  			want: false,
  2447  		},
  2448  		{
  2449  			name: "nil-prebid",
  2450  			args: args{auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: nil}}},
  2451  			want: false,
  2452  		},
  2453  		{
  2454  			name: "returnallbidstatus-is-false",
  2455  			args: args{auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid" : {"returnallbidstatus" : false}}`)}}},
  2456  			want: false,
  2457  		},
  2458  		{
  2459  			name: "finalExtBidResponse-is-nil",
  2460  			args: args{finalExtBidResponse: nil},
  2461  			want: false,
  2462  		},
  2463  		{
  2464  			name: "returnallbidstatus-is-true-and-responseExt.Prebid-is-nil",
  2465  			args: args{finalExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: nil}, auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid" : {"returnallbidstatus" : true}}`)}}},
  2466  			want: true,
  2467  		},
  2468  		{
  2469  			name: "returnallbidstatus-is-true-and-responseExt.Prebid-is-not-nil",
  2470  			args: args{finalExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: nil}, auctionResponse: &exchange.AuctionResponse{}, request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid" : {"returnallbidstatus" : true}}`)}}},
  2471  			want: true,
  2472  		},
  2473  	}
  2474  	for _, tt := range tests {
  2475  		t.Run(tt.name, func(t *testing.T) {
  2476  			if got := setSeatNonBid(tt.args.finalExtBidResponse, tt.args.request, tt.args.auctionResponse); got != tt.want {
  2477  				t.Errorf("setSeatNonBid() = %v, want %v", got, tt.want)
  2478  			}
  2479  		})
  2480  	}
  2481  }
  2482  
  2483  func TestAmpAuctionDebugWarningsOnly(t *testing.T) {
  2484  	testCases := []struct {
  2485  		description         string
  2486  		requestURLArguments string
  2487  		addRequestHeaders   func(r *http.Request)
  2488  		expectedStatus      int
  2489  		expectedWarnings    map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage
  2490  	}{
  2491  		{
  2492  			description:         "debug_enabled_request_with_invalid_Sec-Browsing-Topics_header",
  2493  			requestURLArguments: "?tag_id=1&debug=1",
  2494  			addRequestHeaders: func(r *http.Request) {
  2495  				r.Header.Add("Sec-Browsing-Topics", "foo")
  2496  			},
  2497  			expectedStatus: 200,
  2498  			expectedWarnings: map[openrtb_ext.BidderName][]openrtb_ext.ExtBidderMessage{
  2499  				"general": {
  2500  					{
  2501  						Code:    10012,
  2502  						Message: "Invalid field in Sec-Browsing-Topics header: foo",
  2503  					},
  2504  				},
  2505  			},
  2506  		},
  2507  		{
  2508  			description:         "debug_disabled_request_with_invalid_Sec-Browsing-Topics_header",
  2509  			requestURLArguments: "?tag_id=1",
  2510  			addRequestHeaders: func(r *http.Request) {
  2511  				r.Header.Add("Sec-Browsing-Topics", "foo")
  2512  			},
  2513  			expectedStatus:   200,
  2514  			expectedWarnings: nil,
  2515  		},
  2516  	}
  2517  
  2518  	storedRequests := map[string]json.RawMessage{
  2519  		"1": json.RawMessage(validRequest(t, "site.json")),
  2520  	}
  2521  	exchange := &nobidExchange{}
  2522  	endpoint, _ := NewAmpEndpoint(
  2523  		fakeUUIDGenerator{},
  2524  		exchange,
  2525  		newParamsValidator(t),
  2526  		&mockAmpStoredReqFetcher{storedRequests},
  2527  		empty_fetcher.EmptyFetcher{},
  2528  		&config.Configuration{
  2529  			MaxRequestSize: maxSize,
  2530  			AccountDefaults: config.Account{
  2531  				Privacy: config.AccountPrivacy{
  2532  					PrivacySandbox: config.PrivacySandbox{
  2533  						TopicsDomain: "abc",
  2534  					},
  2535  				},
  2536  			},
  2537  		},
  2538  		&metricsConfig.NilMetricsEngine{},
  2539  		analyticsBuild.New(&config.Analytics{}),
  2540  		map[string]string{},
  2541  		[]byte{},
  2542  		openrtb_ext.BuildBidderMap(),
  2543  		empty_fetcher.EmptyFetcher{},
  2544  		hooks.EmptyPlanBuilder{},
  2545  		nil,
  2546  	)
  2547  
  2548  	for _, test := range testCases {
  2549  		httpReq := httptest.NewRequest("GET", fmt.Sprintf("/openrtb2/auction/amp"+test.requestURLArguments), nil)
  2550  		test.addRequestHeaders(httpReq)
  2551  		recorder := httptest.NewRecorder()
  2552  
  2553  		endpoint(recorder, httpReq, nil)
  2554  
  2555  		assert.Equal(t, test.expectedStatus, recorder.Result().StatusCode)
  2556  
  2557  		// Parse Response
  2558  		var response AmpResponse
  2559  		if err := jsonutil.UnmarshalValid(recorder.Body.Bytes(), &response); err != nil {
  2560  			t.Fatalf("Error unmarshalling response: %s", err.Error())
  2561  		}
  2562  
  2563  		assert.Equal(t, test.expectedWarnings, response.ORTB2.Ext.Warnings)
  2564  	}
  2565  }