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