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

     1  package openrtb2
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"os"
    13  	"strconv"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/buger/jsonparser"
    18  	"github.com/julienschmidt/httprouter"
    19  	"github.com/prebid/openrtb/v20/openrtb2"
    20  	"github.com/prebid/openrtb/v20/openrtb3"
    21  	"github.com/prebid/prebid-server/v2/adapters"
    22  	"github.com/prebid/prebid-server/v2/analytics"
    23  	analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build"
    24  	"github.com/prebid/prebid-server/v2/config"
    25  	"github.com/prebid/prebid-server/v2/currency"
    26  	"github.com/prebid/prebid-server/v2/errortypes"
    27  	"github.com/prebid/prebid-server/v2/exchange"
    28  	"github.com/prebid/prebid-server/v2/experiment/adscert"
    29  	"github.com/prebid/prebid-server/v2/gdpr"
    30  	"github.com/prebid/prebid-server/v2/hooks"
    31  	"github.com/prebid/prebid-server/v2/hooks/hookexecution"
    32  	"github.com/prebid/prebid-server/v2/hooks/hookstage"
    33  	"github.com/prebid/prebid-server/v2/macros"
    34  	"github.com/prebid/prebid-server/v2/metrics"
    35  	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
    36  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    37  	pbc "github.com/prebid/prebid-server/v2/prebid_cache_client"
    38  	"github.com/prebid/prebid-server/v2/stored_requests"
    39  	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
    40  	"github.com/prebid/prebid-server/v2/util/iputil"
    41  	"github.com/prebid/prebid-server/v2/util/jsonutil"
    42  	"github.com/prebid/prebid-server/v2/util/uuidutil"
    43  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
    44  )
    45  
    46  // In this file we define:
    47  //  - Auxiliary types
    48  //  - Unit test interface implementations such as mocks
    49  //  - Other auxiliary functions that don't make assertions and don't take t *testing.T as parameter
    50  //
    51  // All of the above are useful for this package's unit test framework.
    52  
    53  // ----------------------
    54  // test auxiliary types
    55  // ----------------------
    56  const maxSize = 1024 * 256
    57  
    58  const (
    59  	AMP_ENDPOINT = iota
    60  	OPENRTB_ENDPOINT
    61  	VIDEO_ENDPOINT
    62  )
    63  
    64  type testCase struct {
    65  	// Common
    66  	endpointType            int
    67  	Description             string            `json:"description"`
    68  	Config                  *testConfigValues `json:"config"`
    69  	BidRequest              json.RawMessage   `json:"mockBidRequest"`
    70  	ExpectedValidatedBidReq json.RawMessage   `json:"expectedValidatedBidRequest"`
    71  	ExpectedReturnCode      int               `json:"expectedReturnCode,omitempty"`
    72  	ExpectedErrorMessage    string            `json:"expectedErrorMessage"`
    73  	Query                   string            `json:"query"`
    74  	planBuilder             hooks.ExecutionPlanBuilder
    75  
    76  	// "/openrtb2/auction" endpoint JSON test info
    77  	ExpectedBidResponse json.RawMessage `json:"expectedBidResponse"`
    78  
    79  	// "/openrtb2/amp" endpoint JSON test info
    80  	StoredRequest       map[string]json.RawMessage `json:"mockAmpStoredRequest"`
    81  	StoredResponse      map[string]json.RawMessage `json:"mockAmpStoredResponse"`
    82  	ExpectedAmpResponse json.RawMessage            `json:"expectedAmpResponse"`
    83  }
    84  
    85  type testConfigValues struct {
    86  	AccountRequired     bool                          `json:"accountRequired"`
    87  	AliasJSON           string                        `json:"aliases"`
    88  	BlacklistedApps     []string                      `json:"blacklistedApps"`
    89  	DisabledAdapters    []string                      `json:"disabledAdapters"`
    90  	CurrencyRates       map[string]map[string]float64 `json:"currencyRates"`
    91  	MockBidders         []mockBidderHandler           `json:"mockBidders"`
    92  	RealParamsValidator bool                          `json:"realParamsValidator"`
    93  }
    94  
    95  type brokenExchange struct{}
    96  
    97  func (e *brokenExchange) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
    98  	return nil, errors.New("Critical, unrecoverable error.")
    99  }
   100  
   101  // Stored Requests
   102  var testStoredRequestData = map[string]json.RawMessage{
   103  	// Valid JSON
   104  	"1": json.RawMessage(`{"id": "{{UUID}}"}`),
   105  	"2": json.RawMessage(`{
   106  		"id": "{{uuid}}",
   107  		"tmax": 500,
   108  		"ext": {
   109  			"prebid": {
   110  				"targeting": {
   111  					"pricegranularity": "low"
   112  				}
   113  			}
   114  		}
   115  	}`),
   116  	// Invalid JSON because it comes with an extra closing curly brace '}'
   117  	"3": json.RawMessage(`{
   118  		"tmax": 500,
   119  				"ext": {
   120  						"prebid": {
   121  								"targeting": {
   122  										"pricegranularity": "low"
   123  								}
   124  						}
   125  				}}
   126  		}`),
   127  	// Valid JSON
   128  	"4": json.RawMessage(`{"id": "ThisID", "cur": ["USD"]}`),
   129  
   130  	// Stored Request with Root Ext Passthrough
   131  	"5": json.RawMessage(`{
   132  		"ext": {
   133  			"prebid": {
   134  				"passthrough": {
   135  					"root_ext_passthrough": 20
   136  				}
   137  			}
   138  		}
   139  	}`),
   140  }
   141  
   142  // Stored Imp Requests
   143  var testStoredImpData = map[string]json.RawMessage{
   144  	// Has valid JSON and matches schema
   145  	"1": json.RawMessage(`{
   146  			"id": "adUnit1",
   147  			"ext": {
   148  				"appnexus": {
   149  					"placementId": "abc",
   150  					"position": "above",
   151  					"reserve": 0.35
   152  				},
   153  				"rubicon": {
   154  					"accountId": "abc"
   155  				}
   156  			},
   157  			"video":{
   158  				"w":200,
   159  				"h":300
   160  			}
   161  		}`),
   162  	// Has valid JSON, matches schema but is missing video object
   163  	"2": json.RawMessage(`{
   164  			"id": "adUnit1",
   165  			"ext": {
   166  				"appnexus": {
   167  					"placementId": "abc",
   168  					"position": "above",
   169  					"reserve": 0.35
   170  				},
   171  				"rubicon": {
   172  					"accountId": "abc"
   173  				}
   174  			}
   175  		}`),
   176  	// Invalid JSON, is missing a coma after the rubicon's "accountId" field
   177  	"7": json.RawMessage(`{
   178  			"id": "adUnit1",
   179  			"ext": {
   180  				"appnexus": {
   181  					"placementId": 12345678,
   182  					"position": "above",
   183  					"reserve": 0.35
   184  				},
   185  				"rubicon": {
   186  					"accountId": 23456789
   187  					"siteId": 113932,
   188  					"zoneId": 535510
   189  				}
   190  			}
   191  		}`),
   192  	// Valid JSON. Missing video object
   193  	"9": json.RawMessage(`{
   194  			"id": "adUnit1",
   195  			"ext": {
   196  				"appnexus": {
   197  					"placementId": 12345678,
   198  					"position": "above",
   199  					"reserve": 0.35
   200  				},
   201  				"rubicon": {
   202  					"accountId": 23456789,
   203  					"siteId": 113932,
   204  					"zoneId": 535510
   205  				}
   206  			}
   207  		}`),
   208  	// Valid JSON. Missing video object
   209  	"10": json.RawMessage(`{
   210  			"ext": {
   211  				"appnexus": {
   212  					"placementId": 12345678,
   213  					"position": "above",
   214  					"reserve": 0.35
   215  				}
   216  			}
   217  		}`),
   218  	// Stored Imp with Passthrough
   219  	"6": json.RawMessage(`{
   220  		"id": "my-imp-id",
   221  		"ext": {
   222  			"prebid": {
   223  				"passthrough": {
   224  					"imp_passthrough": 30
   225  				}
   226  			}
   227  		}
   228  	}`),
   229  }
   230  
   231  // Incoming requests with stored request IDs
   232  var testStoredRequests = []string{
   233  	`{
   234  		"id": "ThisID",
   235  		"imp": [
   236  			{
   237  				"video":{
   238  					"h":300,
   239  					"w":200
   240  				},
   241  				"ext": {
   242  					"prebid": {
   243  						"storedrequest": {
   244  							"id": "1"
   245  						},
   246  						"options": {
   247  							"echovideoattrs": true
   248  						}
   249  					}
   250  				}
   251  			}
   252  		],
   253  		"ext": {
   254  			"prebid": {
   255  				"cache": {
   256  					"markup": 1
   257  				},
   258  				"targeting": {
   259  				}
   260  			}
   261  		}
   262  	}`,
   263  	`{
   264  		"id": "ThisID",
   265  		"imp": [
   266  			{
   267  				"id": "adUnit2",
   268  				"ext": {
   269  					"prebid": {
   270  						"storedrequest": {
   271  							"id": "1"
   272  						},
   273  						"options": {
   274  							"echovideoattrs": true
   275  						}
   276  					},
   277  					"appnexus": {
   278  						"placementId": "def",
   279  						"trafficSourceCode": "mysite.com",
   280  						"reserve": null
   281  					},
   282  					"rubicon": null
   283  				}
   284  			}
   285  		],
   286  		"ext": {
   287  			"prebid": {
   288  				"cache": {
   289  					"markup": 1
   290  				},
   291  				"targeting": {
   292  				}
   293  			}
   294  		}
   295  	}`,
   296  	`{
   297  		"id": "ThisID",
   298  		"imp": [
   299  			{
   300  				"ext": {
   301  					"prebid": {
   302  						"storedrequest": {
   303  							"id": "2"
   304  						},
   305  						"options": {
   306  							"echovideoattrs": false
   307  						}
   308  					}
   309  				}
   310  			}
   311  		],
   312  		"ext": {
   313  			"prebid": {
   314  				"storedrequest": {
   315  					"id": "2"
   316  				}
   317  			}
   318  		}
   319  	}`,
   320  	`{
   321  		"id": "ThisID",
   322  		"imp": [
   323  			{
   324  				"id": "some-static-imp",
   325  				"video":{
   326  					"mimes":["video/mp4"]
   327  				},
   328  				"ext": {
   329  					"appnexus": {
   330  						"placementId": "abc",
   331  						"position": "below"
   332  					}
   333  				}
   334  			},
   335  			{
   336  				"ext": {
   337  					"prebid": {
   338  						"storedrequest": {
   339  							"id": "1"
   340  						}
   341  					}
   342  				}
   343  			}
   344  		],
   345  		"ext": {
   346  			"prebid": {
   347  				"cache": {
   348  					"markup": 1
   349  				},
   350  				"targeting": {
   351  				}
   352  			}
   353  		}
   354  	}`,
   355  	`{
   356  		"id": "ThisID",
   357  		"imp": [
   358  			{
   359  				"id": "my-imp-id",
   360  				"video":{
   361  					"h":300,
   362  					"w":200
   363  				},
   364  				"ext": {
   365  					"prebid": {
   366  						"storedrequest": {
   367  							"id": "6"
   368  						}
   369  					}
   370  				}
   371  			}
   372  		],
   373  		"ext": {
   374  			"prebid": {
   375  				"storedrequest": {
   376  					"id": "5"
   377  				}
   378  			}
   379  		}
   380  	}`,
   381  }
   382  
   383  // The expected requests after stored request processing
   384  var testFinalRequests = []string{
   385  	`{
   386  		"id": "ThisID",
   387  		"imp": [
   388  			{
   389  				"video":{
   390  					"h":300,
   391  					"w":200
   392  				},
   393  				"ext":{
   394  					"appnexus":{
   395  						"placementId":"abc",
   396  						"position":"above",
   397  						"reserve":0.35
   398  					},
   399  					"prebid":{
   400  						"storedrequest":{
   401  							"id":"1"
   402  						},
   403  					"options":{
   404  						"echovideoattrs":true
   405  					}
   406  				},
   407  				"rubicon":{
   408  					"accountId":"abc"
   409  				}
   410  			},
   411  			"id":"adUnit1"
   412  			}
   413  		],
   414  		"ext": {
   415  			"prebid": {
   416  				"cache": {
   417  					"markup": 1
   418  				},
   419  				"targeting": {
   420  			}
   421  		}
   422  }
   423  	}`,
   424  	`{
   425  		"id": "ThisID",
   426  		"imp": [
   427  			{
   428  				"video":{
   429  					"w":200,
   430  					"h":300
   431  				},
   432  				"ext":{
   433  					"appnexus":{
   434  						"placementId":"def",
   435  						"position":"above",
   436  						"trafficSourceCode":"mysite.com"
   437  					},
   438  					"prebid":{
   439  						"storedrequest":{
   440  							"id":"1"
   441  						},
   442  						"options":{
   443  							"echovideoattrs":true
   444  						}
   445  					}
   446  				},
   447  				"id":"adUnit2"
   448  			}
   449  		],
   450  		"ext": {
   451  			"prebid": {
   452  				"cache": {
   453  					"markup": 1
   454  				},
   455  				"targeting": {
   456  				}
   457  			}
   458  		}
   459  	}`,
   460  	`{
   461    		"ext": {
   462    		  "prebid": {
   463    		    "storedrequest": {
   464    		      "id": "2"
   465    		    },
   466    		    "targeting": {
   467    		      "pricegranularity": "low"
   468    		    }
   469    		  }
   470    		},
   471    		"id": "ThisID",
   472    		"imp": [
   473    		  {
   474    		    "ext": {
   475    		      "appnexus": {
   476    		        "placementId": "abc",
   477    		        "position": "above",
   478    		        "reserve": 0.35
   479    		      },
   480    		      "prebid": {
   481    		        "storedrequest": {
   482    		          "id": "2"
   483    		        },
   484    		        "options":{
   485  					"echovideoattrs":false
   486  				}
   487    		      },
   488    		      "rubicon": {
   489    		        "accountId": "abc"
   490    		      }
   491    		    },
   492    		    "id": "adUnit1"
   493    		  }
   494    		],
   495    		"tmax": 500
   496  	}`,
   497  	`{
   498  	"id": "ThisID",
   499  	"imp": [
   500  		{
   501      		"id": "some-static-imp",
   502      		"video": {
   503      		  "mimes": [
   504      		    "video/mp4"
   505      		  ]
   506      		},
   507      		"ext": {
   508      		  "appnexus": {
   509      		    "placementId": "abc",
   510      		    "position": "below"
   511      		  }
   512      		}
   513    		},
   514    		{
   515    		  "ext": {
   516    		    "appnexus": {
   517    		      "placementId": "abc",
   518    		      "position": "above",
   519    		      "reserve": 0.35
   520    		    },
   521    		    "prebid": {
   522    		      "storedrequest": {
   523    		        "id": "1"
   524    		      }
   525    		    },
   526    		    "rubicon": {
   527    		      "accountId": "abc"
   528    		    }
   529    		  },
   530    		  "id": "adUnit1",
   531  		  "video":{
   532  				"w":200,
   533  				"h":300
   534            }
   535    		}
   536  	],
   537  	"ext": {
   538  		"prebid": {
   539  			"cache": {
   540  				"markup": 1
   541  			},
   542  			"targeting": {
   543  			}
   544  		}
   545  	}
   546  }`,
   547  	`{
   548  	"id": "ThisID",
   549  	"imp": [
   550  		{
   551  			"ext":{
   552  			   "prebid":{
   553  				  "passthrough":{
   554  					 "imp_passthrough":30
   555  				  },
   556  				  "storedrequest":{
   557  					 "id":"6"
   558  				  }
   559  			   }
   560  			},
   561  			"id":"my-imp-id",
   562  			"video":{
   563  			   "h":300,
   564  			   "w":200
   565  			}
   566  		 }
   567  	],
   568  	"ext":{
   569  		"prebid":{
   570  		   "passthrough":{
   571  			  "root_ext_passthrough":20
   572  		   },
   573  		   "storedrequest":{
   574  			  "id":"5"
   575  		   }
   576  		}
   577  	 }
   578  }`,
   579  }
   580  
   581  var testStoredImpIds = []string{
   582  	"adUnit1", "adUnit2", "adUnit1", "some-static-imp", "my-imp-id",
   583  }
   584  
   585  var testStoredImps = []string{
   586  	`{
   587  		"id": "adUnit1",
   588          "ext": {
   589          	"appnexus": {
   590          		"placementId": "abc",
   591          		"position": "above",
   592          		"reserve": 0.35
   593          	},
   594          	"rubicon": {
   595          		"accountId": "abc"
   596          	}
   597          },
   598  		"video":{
   599          	"w":200,
   600          	"h":300
   601  		}
   602  	}`,
   603  	`{
   604  		"id": "adUnit1",
   605          "ext": {
   606          	"appnexus": {
   607          		"placementId": "abc",
   608          		"position": "above",
   609          		"reserve": 0.35
   610          	},
   611          	"rubicon": {
   612          		"accountId": "abc"
   613          	}
   614          },
   615  		"video":{
   616          	"w":200,
   617          	"h":300
   618  		}
   619  	}`,
   620  	`{
   621  			"id": "adUnit1",
   622  			"ext": {
   623  				"appnexus": {
   624  					"placementId": "abc",
   625  					"position": "above",
   626  					"reserve": 0.35
   627  				},
   628  				"rubicon": {
   629  					"accountId": "abc"
   630  				}
   631  			}
   632  		}`,
   633  	``,
   634  	`{
   635  		"id": "my-imp-id",
   636  		"ext": {
   637  			"prebid": {
   638  				"passthrough": {
   639  					"imp_passthrough": 30
   640  				}
   641  			}
   642  		}
   643  	}`,
   644  }
   645  
   646  var testBidRequests = []string{
   647  	`{
   648  		"id": "ThisID",
   649  		"app": {
   650  			"id": "123"
   651  		},
   652  		"imp": [
   653  			{
   654  				"video":{
   655  					"h":300,
   656  					"w":200
   657  				},
   658  				"ext": {
   659  					"prebid": {
   660  						"storedrequest": {
   661  							"id": "1"
   662  						},
   663  						"options": {
   664  							"echovideoattrs": true
   665  						}
   666  					}
   667  				}
   668  			}
   669  		],
   670  		"ext": {
   671  			"prebid": {
   672  				"cache": {
   673  					"markup": 1
   674  				},
   675  				"targeting": {
   676  				}
   677  			}
   678  		}
   679  	}`,
   680  	`{
   681  		"id": "ThisID",
   682  		"site": {
   683  			"page": "prebid.org"
   684  		},
   685  		"imp": [
   686  			{
   687  				"id": "adUnit2",
   688  				"ext": {
   689  					"prebid": {
   690  						"storedrequest": {
   691  							"id": "1"
   692  						},
   693  						"options": {
   694  							"echovideoattrs": true
   695  						}
   696  					},
   697  					"appnexus": {
   698  						"placementId": "def",
   699  						"trafficSourceCode": "mysite.com",
   700  						"reserve": null
   701  					},
   702  					"rubicon": null
   703  				}
   704  			}
   705  		],
   706  		"ext": {
   707  			"prebid": {
   708  				"storedrequest": {
   709  					"id": "1"
   710  				}
   711  			}
   712  		}
   713  	}`,
   714  	`{
   715  		"id": "ThisID",
   716  		"app": {
   717  			"id": "123"
   718  		},
   719  		"imp": [
   720  			{
   721  				"ext": {
   722  					"prebid": {
   723  						"storedrequest": {
   724  							"id": "2"
   725  						},
   726  						"options": {
   727  							"echovideoattrs": false
   728  						}
   729  					}
   730  				}
   731  			}
   732  		],
   733  		"ext": {
   734  			"prebid": {
   735  				"storedrequest": {
   736  					"id": "2"
   737  				}
   738  			}
   739  		}
   740  	}`,
   741  	`{
   742  		"id": "ThisID",
   743  		"site": {
   744  			"page": "prebid.org"
   745  		},
   746  		"imp": [
   747  			{
   748  				"ext": {
   749  					"prebid": {
   750  						"storedrequest": {
   751  							"id": "2"
   752  						},
   753  						"options": {
   754  							"echovideoattrs": false
   755  						}
   756  					}
   757  				}
   758  			}
   759  		],
   760  		"ext": {
   761  			"prebid": {
   762  				"storedrequest": {
   763  					"id": "2"
   764  				}
   765  			}
   766  		}
   767  	}`,
   768  	`{
   769  		"id": "ThisID",
   770  		"app": {
   771  			"id": "123"
   772  		},
   773  		"imp": [
   774  			{
   775  				"ext": {
   776  					"prebid": {
   777  						"storedrequest": {
   778  							"id": "1"
   779  						},
   780  						"options": {
   781  							"echovideoattrs": false
   782  						}
   783  					}
   784  				}
   785  			}
   786  		],
   787  		"ext": {
   788  			"prebid": {
   789  				"storedrequest": {
   790  					"id": "1"
   791  				}
   792  			}
   793  		}
   794  	}`,
   795  	`{
   796  		"id": "ThisID",
   797  		"imp": [{
   798  			"id": "some-impression-id",
   799  			"banner": {
   800  				"format": [{
   801  						"w": 600,
   802  						"h": 500
   803  					},
   804  					{
   805  						"w": 300,
   806  						"h": 600
   807  					}
   808  				]
   809  			},
   810  			"ext": {
   811  				"appnexus": {
   812  					"placementId": 12883451
   813  				}
   814  			}
   815  		}],
   816  		"ext": {
   817  			"prebid": {
   818  				"debug": true,
   819  				"storedrequest": {
   820  					"id": "4"
   821  				}
   822  			}
   823  		},
   824  	  "site": {
   825  		"page": "https://example.com"
   826  	  }
   827  	}`,
   828  }
   829  
   830  // ---------------------------------------------------------
   831  // Some interfaces implemented with the purspose of testing
   832  // ---------------------------------------------------------
   833  
   834  // mockStoredReqFetcher implements the Fetcher interface
   835  type mockStoredReqFetcher struct {
   836  }
   837  
   838  func (cf mockStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
   839  	return testStoredRequestData, testStoredImpData, nil
   840  }
   841  
   842  func (cf mockStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
   843  	return nil, nil
   844  }
   845  
   846  // mockExchange implements the Exchange interface
   847  type mockExchange struct {
   848  	lastRequest *openrtb2.BidRequest
   849  }
   850  
   851  func (m *mockExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
   852  	r := auctionRequest.BidRequestWrapper
   853  	m.lastRequest = r.BidRequest
   854  	return &exchange.AuctionResponse{
   855  		BidResponse: &openrtb2.BidResponse{
   856  			SeatBid: []openrtb2.SeatBid{{
   857  				Bid: []openrtb2.Bid{{
   858  					AdM: "<script></script>",
   859  				}},
   860  			}},
   861  		},
   862  	}, nil
   863  }
   864  
   865  // hardcodedResponseIPValidator implements the IPValidator interface.
   866  type hardcodedResponseIPValidator struct {
   867  	response bool
   868  }
   869  
   870  func (v hardcodedResponseIPValidator) IsValid(net.IP, iputil.IPVersion) bool {
   871  	return v.response
   872  }
   873  
   874  // fakeUUIDGenerator implements the UUIDGenerator interface
   875  type fakeUUIDGenerator struct {
   876  	id  string
   877  	err error
   878  }
   879  
   880  func (f fakeUUIDGenerator) Generate() (string, error) {
   881  	return f.id, f.err
   882  }
   883  
   884  // warningsCheckExchange is a well-behaved exchange which stores all incoming warnings.
   885  // implements the Exchange interface
   886  type warningsCheckExchange struct {
   887  	auctionRequest exchange.AuctionRequest
   888  }
   889  
   890  func (e *warningsCheckExchange) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
   891  	e.auctionRequest = *r
   892  	return nil, nil
   893  }
   894  
   895  // nobidExchange is a well-behaved exchange which always bids "no bid".
   896  // implements the Exchange interface
   897  type nobidExchange struct {
   898  	gotRequest *openrtb2.BidRequest
   899  }
   900  
   901  func (e *nobidExchange) HoldAuction(ctx context.Context, auctionRequest *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
   902  	r := auctionRequest.BidRequestWrapper
   903  	e.gotRequest = r.BidRequest
   904  
   905  	return &exchange.AuctionResponse{
   906  		BidResponse: &openrtb2.BidResponse{
   907  			ID:    r.BidRequest.ID,
   908  			BidID: "test bid id",
   909  			NBR:   openrtb3.NoBidUnknownError.Ptr(),
   910  		},
   911  	}, nil
   912  }
   913  
   914  // mockCurrencyRatesClient is a mock currency rate server and the rates it returns
   915  // are set in the JSON test file
   916  type mockCurrencyRatesClient struct {
   917  	data currencyInfo
   918  }
   919  
   920  type currencyInfo struct {
   921  	Conversions map[string]map[string]float64 `json:"conversions"`
   922  	DataAsOfRaw string                        `json:"dataAsOf"`
   923  }
   924  
   925  func (s mockCurrencyRatesClient) handle(w http.ResponseWriter, req *http.Request) {
   926  	s.data.DataAsOfRaw = "2018-09-12"
   927  
   928  	// Marshal the response and http write it
   929  	currencyServerJsonResponse, err := jsonutil.Marshal(&s.data)
   930  	if err != nil {
   931  		http.Error(w, err.Error(), http.StatusInternalServerError)
   932  		return
   933  	}
   934  	w.Write(currencyServerJsonResponse)
   935  }
   936  
   937  // mockBidderHandler carries mock bidder server information that will be read from JSON test files
   938  // and defines a handle function of a a mock bidder service.
   939  type mockBidderHandler struct {
   940  	BidderName string  `json:"bidderName"`
   941  	Currency   string  `json:"currency"`
   942  	Price      float64 `json:"price"`
   943  	DealID     string  `json:"dealid"`
   944  	Seat       string  `json:"seat"`
   945  }
   946  
   947  func (b mockBidderHandler) bid(w http.ResponseWriter, req *http.Request) {
   948  	// Read request Body
   949  	buf := new(bytes.Buffer)
   950  	buf.ReadFrom(req.Body)
   951  
   952  	// Unmarshal exit if error
   953  	var openrtb2Request openrtb2.BidRequest
   954  	if err := jsonutil.UnmarshalValid(buf.Bytes(), &openrtb2Request); err != nil {
   955  		http.Error(w, err.Error(), http.StatusBadRequest)
   956  		return
   957  	}
   958  
   959  	var openrtb2ImpExt map[string]json.RawMessage
   960  	if err := jsonutil.UnmarshalValid(openrtb2Request.Imp[0].Ext, &openrtb2ImpExt); err != nil {
   961  		http.Error(w, err.Error(), http.StatusBadRequest)
   962  		return
   963  	}
   964  
   965  	_, exists := openrtb2ImpExt["bidder"]
   966  	if !exists {
   967  		http.Error(w, "This request is not meant for this bidder", http.StatusBadRequest)
   968  		return
   969  	}
   970  
   971  	// Create bid service openrtb2.BidResponse with one bid according to JSON test file values
   972  	var serverResponseObject = openrtb2.BidResponse{
   973  		ID:  openrtb2Request.ID,
   974  		Cur: b.Currency,
   975  		SeatBid: []openrtb2.SeatBid{
   976  			{
   977  				Bid: []openrtb2.Bid{
   978  					{
   979  						ID:     b.BidderName + "-bid",
   980  						ImpID:  openrtb2Request.Imp[0].ID,
   981  						Price:  b.Price,
   982  						DealID: b.DealID,
   983  					},
   984  				},
   985  				Seat: b.BidderName,
   986  			},
   987  		},
   988  	}
   989  
   990  	// Marshal the response and http write it
   991  	serverJsonResponse, err := jsonutil.Marshal(&serverResponseObject)
   992  	if err != nil {
   993  		http.Error(w, err.Error(), http.StatusInternalServerError)
   994  		return
   995  	}
   996  	w.Write(serverJsonResponse)
   997  }
   998  
   999  // mockAdapter is a mock impression-splitting adapter
  1000  type mockAdapter struct {
  1001  	mockServerURL string
  1002  	Server        config.Server
  1003  	seat          string
  1004  }
  1005  
  1006  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
  1007  	adapter := &mockAdapter{
  1008  		mockServerURL: config.Endpoint,
  1009  		Server:        server,
  1010  	}
  1011  	return adapter, nil
  1012  }
  1013  
  1014  func (a mockAdapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
  1015  	var requests []*adapters.RequestData
  1016  	var errors []error
  1017  
  1018  	requestCopy := *request
  1019  	for _, imp := range request.Imp {
  1020  		requestCopy.Imp = []openrtb2.Imp{imp}
  1021  
  1022  		requestJSON, err := jsonutil.Marshal(request)
  1023  		if err != nil {
  1024  			errors = append(errors, err)
  1025  			continue
  1026  		}
  1027  
  1028  		requestData := &adapters.RequestData{
  1029  			Method: "POST",
  1030  			Uri:    a.mockServerURL,
  1031  			Body:   requestJSON,
  1032  		}
  1033  		requests = append(requests, requestData)
  1034  	}
  1035  	return requests, errors
  1036  }
  1037  
  1038  func (a mockAdapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
  1039  	if responseData.StatusCode != http.StatusOK {
  1040  		switch responseData.StatusCode {
  1041  		case http.StatusNoContent:
  1042  			return nil, nil
  1043  		case http.StatusBadRequest:
  1044  			return nil, []error{&errortypes.BadInput{
  1045  				Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
  1046  			}}
  1047  		default:
  1048  			return nil, []error{&errortypes.BadServerResponse{
  1049  				Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
  1050  			}}
  1051  		}
  1052  	}
  1053  
  1054  	var publisherResponse openrtb2.BidResponse
  1055  	if err := jsonutil.UnmarshalValid(responseData.Body, &publisherResponse); err != nil {
  1056  		return nil, []error{err}
  1057  	}
  1058  
  1059  	rv := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
  1060  	rv.Currency = publisherResponse.Cur
  1061  	for _, seatBid := range publisherResponse.SeatBid {
  1062  		for i, bid := range seatBid.Bid {
  1063  			for _, imp := range request.Imp {
  1064  				if imp.ID == bid.ImpID {
  1065  					b := &adapters.TypedBid{
  1066  						Bid:     &seatBid.Bid[i],
  1067  						BidType: openrtb_ext.BidTypeBanner,
  1068  					}
  1069  					if len(a.seat) > 0 {
  1070  						b.Seat = openrtb_ext.BidderName(a.seat)
  1071  					}
  1072  					rv.Bids = append(rv.Bids, b)
  1073  				}
  1074  			}
  1075  		}
  1076  	}
  1077  	return rv, nil
  1078  }
  1079  
  1080  // ---------------------------------------------------------
  1081  // Auxiliary functions that don't make assertions and don't
  1082  // take t *testing.T as parameter
  1083  // ---------------------------------------------------------
  1084  func getBidderInfos(disabledAdapters []string, biddersNames []openrtb_ext.BidderName) config.BidderInfos {
  1085  	biddersInfos := make(config.BidderInfos)
  1086  	for _, name := range biddersNames {
  1087  		isDisabled := false
  1088  		for _, disabledAdapter := range disabledAdapters {
  1089  			if string(name) == disabledAdapter {
  1090  				isDisabled = true
  1091  				break
  1092  			}
  1093  		}
  1094  		biddersInfos[string(name)] = newBidderInfo(isDisabled)
  1095  	}
  1096  	return biddersInfos
  1097  }
  1098  
  1099  func enableBidders(bidderInfos config.BidderInfos) {
  1100  	for name, bidderInfo := range bidderInfos {
  1101  		if bidderInfo.Disabled {
  1102  			bidderInfo.Disabled = false
  1103  			bidderInfos[name] = bidderInfo
  1104  		}
  1105  	}
  1106  }
  1107  
  1108  func disableBidders(disabledAdapters []string, bidderInfos config.BidderInfos) {
  1109  	for _, disabledAdapter := range disabledAdapters {
  1110  		if bidderInfo, ok := bidderInfos[disabledAdapter]; ok {
  1111  			bidderInfo.Disabled = true
  1112  			bidderInfos[disabledAdapter] = bidderInfo
  1113  		}
  1114  	}
  1115  }
  1116  
  1117  func newBidderInfo(isDisabled bool) config.BidderInfo {
  1118  	return config.BidderInfo{
  1119  		Disabled: isDisabled,
  1120  	}
  1121  }
  1122  
  1123  func parseTestData(fileData []byte, testFile string) (testCase, error) {
  1124  
  1125  	parsedTestData := testCase{}
  1126  	var err, errEm error
  1127  
  1128  	// Get testCase values
  1129  	parsedTestData.BidRequest, _, _, err = jsonparser.Get(fileData, "mockBidRequest")
  1130  	if err != nil {
  1131  		return parsedTestData, fmt.Errorf("Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", testFile, err)
  1132  	}
  1133  
  1134  	// Get testCaseConfig values
  1135  	parsedTestData.Config = &testConfigValues{}
  1136  	var jsonTestConfig json.RawMessage
  1137  
  1138  	jsonTestConfig, _, _, err = jsonparser.Get(fileData, "config")
  1139  	if err == nil {
  1140  		if err = jsonutil.UnmarshalValid(jsonTestConfig, parsedTestData.Config); err != nil {
  1141  			return parsedTestData, fmt.Errorf("Error unmarshaling root.config from file %s. Desc: %v.", testFile, err)
  1142  		}
  1143  	}
  1144  
  1145  	// Get the return code we expect PBS to throw back given test's bidRequest and config
  1146  	parsedReturnCode, err := jsonparser.GetInt(fileData, "expectedReturnCode")
  1147  	if err != nil {
  1148  		return parsedTestData, fmt.Errorf("Error jsonparsing root.code from file %s. Desc: %v.", testFile, err)
  1149  	}
  1150  
  1151  	// Get both bid response and error message, if any
  1152  	parsedTestData.ExpectedBidResponse, _, _, err = jsonparser.Get(fileData, "expectedBidResponse")
  1153  	parsedTestData.ExpectedErrorMessage, errEm = jsonparser.GetString(fileData, "expectedErrorMessage")
  1154  
  1155  	if err == nil && errEm == nil {
  1156  		return parsedTestData, fmt.Errorf("Test case %s can't have both a valid expectedBidResponse and a valid expectedErrorMessage, fields are mutually exclusive", testFile)
  1157  	} else if err != nil && errEm != nil {
  1158  		return parsedTestData, fmt.Errorf("Test case %s should come with either a valid expectedBidResponse or a valid expectedErrorMessage, not both.", testFile)
  1159  	}
  1160  
  1161  	parsedTestData.ExpectedReturnCode = int(parsedReturnCode)
  1162  
  1163  	return parsedTestData, nil
  1164  }
  1165  
  1166  func (tc *testConfigValues) getBlacklistedAppMap() map[string]bool {
  1167  	var blacklistedAppMap map[string]bool
  1168  
  1169  	if len(tc.BlacklistedApps) > 0 {
  1170  		blacklistedAppMap = make(map[string]bool, len(tc.BlacklistedApps))
  1171  		for _, app := range tc.BlacklistedApps {
  1172  			blacklistedAppMap[app] = true
  1173  		}
  1174  	}
  1175  	return blacklistedAppMap
  1176  }
  1177  
  1178  // exchangeTestWrapper is a wrapper that asserts the openrtb2 bid request just before the HoldAuction call
  1179  type exchangeTestWrapper struct {
  1180  	ex                    exchange.Exchange
  1181  	actualValidatedBidReq *openrtb2.BidRequest
  1182  }
  1183  
  1184  func (te *exchangeTestWrapper) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
  1185  
  1186  	// rebuild/resync the request in the request wrapper.
  1187  	if err := r.BidRequestWrapper.RebuildRequest(); err != nil {
  1188  		return nil, err
  1189  	}
  1190  
  1191  	// Save the validated bidRequest that we are about to feed HoldAuction
  1192  	te.actualValidatedBidReq = r.BidRequestWrapper.BidRequest
  1193  
  1194  	// Call HoldAuction() implementation as written in the exchange package
  1195  	return te.ex.HoldAuction(ctx, r, debugLog)
  1196  }
  1197  
  1198  // buildTestExchange returns an exchange with mock bidder servers and mock currency conversion server
  1199  func buildTestExchange(testCfg *testConfigValues, adapterMap map[openrtb_ext.BidderName]exchange.AdaptedBidder, mockBidServersArray []*httptest.Server, mockCurrencyRatesServer *httptest.Server, bidderInfos config.BidderInfos, cfg *config.Configuration, met metrics.MetricsEngine, mockFetcher stored_requests.CategoryFetcher) (exchange.Exchange, []*httptest.Server) {
  1200  	if len(testCfg.MockBidders) == 0 {
  1201  		testCfg.MockBidders = append(testCfg.MockBidders, mockBidderHandler{BidderName: "appnexus", Currency: "USD", Price: 0.00})
  1202  	}
  1203  	for _, mockBidder := range testCfg.MockBidders {
  1204  		bidServer := httptest.NewServer(http.HandlerFunc(mockBidder.bid))
  1205  		bidderAdapter := mockAdapter{mockServerURL: bidServer.URL, seat: mockBidder.Seat}
  1206  		bidderName := openrtb_ext.BidderName(mockBidder.BidderName)
  1207  
  1208  		adapterMap[bidderName] = exchange.AdaptBidder(bidderAdapter, bidServer.Client(), &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, bidderName, nil, "")
  1209  		mockBidServersArray = append(mockBidServersArray, bidServer)
  1210  	}
  1211  
  1212  	mockCurrencyConverter := currency.NewRateConverter(mockCurrencyRatesServer.Client(), mockCurrencyRatesServer.URL, time.Second)
  1213  	mockCurrencyConverter.Run()
  1214  
  1215  	gdprPermsBuilder := fakePermissionsBuilder{
  1216  		permissions: &fakePermissions{},
  1217  	}.Builder
  1218  
  1219  	testExchange := exchange.NewExchange(adapterMap,
  1220  		&wellBehavedCache{},
  1221  		cfg,
  1222  		nil,
  1223  		met,
  1224  		bidderInfos,
  1225  		gdprPermsBuilder,
  1226  		mockCurrencyConverter,
  1227  		mockFetcher,
  1228  		&adscert.NilSigner{},
  1229  		macros.NewStringIndexBasedReplacer(),
  1230  		nil,
  1231  	)
  1232  
  1233  	testExchange = &exchangeTestWrapper{
  1234  		ex: testExchange,
  1235  	}
  1236  
  1237  	return testExchange, mockBidServersArray
  1238  }
  1239  
  1240  // buildTestEndpoint instantiates an openrtb2 Auction endpoint designed to test endpoints/openrtb2/auction.go
  1241  func buildTestEndpoint(test testCase, cfg *config.Configuration) (httprouter.Handle, *exchangeTestWrapper, []*httptest.Server, *httptest.Server, error) {
  1242  	if test.Config == nil {
  1243  		test.Config = &testConfigValues{}
  1244  	}
  1245  
  1246  	var paramValidator openrtb_ext.BidderParamValidator
  1247  	if test.Config.RealParamsValidator {
  1248  		var err error
  1249  		paramValidator, err = openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
  1250  		if err != nil {
  1251  			return nil, nil, nil, nil, err
  1252  		}
  1253  	} else {
  1254  		paramValidator = mockBidderParamValidator{}
  1255  	}
  1256  
  1257  	bidderInfos, _ := config.LoadBidderInfoFromDisk("../../static/bidder-info")
  1258  	enableBidders(bidderInfos)
  1259  	disableBidders(test.Config.DisabledAdapters, bidderInfos)
  1260  	bidderMap := exchange.GetActiveBidders(bidderInfos)
  1261  	disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos)
  1262  	met := &metricsConfig.NilMetricsEngine{}
  1263  	mockFetcher := empty_fetcher.EmptyFetcher{}
  1264  
  1265  	// Adapter map with mock adapters needed to run JSON test cases
  1266  	adapterMap := make(map[openrtb_ext.BidderName]exchange.AdaptedBidder, 0)
  1267  	mockBidServersArray := make([]*httptest.Server, 0, 3)
  1268  
  1269  	// Mock prebid Server's currency converter, instantiate and start
  1270  	mockCurrencyConversionService := mockCurrencyRatesClient{
  1271  		currencyInfo{
  1272  			Conversions: test.Config.CurrencyRates,
  1273  		},
  1274  	}
  1275  	mockCurrencyRatesServer := httptest.NewServer(http.HandlerFunc(mockCurrencyConversionService.handle))
  1276  
  1277  	testExchange, mockBidServersArray := buildTestExchange(test.Config, adapterMap, mockBidServersArray, mockCurrencyRatesServer, bidderInfos, cfg, met, mockFetcher)
  1278  
  1279  	var storedRequestFetcher stored_requests.Fetcher
  1280  	if len(test.StoredRequest) > 0 {
  1281  		storedRequestFetcher = &mockAmpStoredReqFetcher{test.StoredRequest}
  1282  	} else {
  1283  		storedRequestFetcher = &mockStoredReqFetcher{}
  1284  	}
  1285  
  1286  	var storedResponseFetcher stored_requests.Fetcher
  1287  	if len(test.StoredResponse) > 0 {
  1288  		storedResponseFetcher = &mockAmpStoredResponseFetcher{test.StoredResponse}
  1289  	} else {
  1290  		storedResponseFetcher = empty_fetcher.EmptyFetcher{}
  1291  	}
  1292  
  1293  	accountFetcher := &mockAccountFetcher{
  1294  		data: map[string]json.RawMessage{
  1295  			"malformed_acct":             json.RawMessage(`{"disabled":"invalid type"}`),
  1296  			"disabled_acct":              json.RawMessage(`{"disabled":true}`),
  1297  			"alternate_bidder_code_acct": json.RawMessage(`{"disabled":false,"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}`),
  1298  		},
  1299  	}
  1300  
  1301  	planBuilder := test.planBuilder
  1302  	if planBuilder == nil {
  1303  		planBuilder = hooks.EmptyPlanBuilder{}
  1304  	}
  1305  
  1306  	var endpointBuilder func(uuidutil.UUIDGenerator, exchange.Exchange, openrtb_ext.BidderParamValidator, stored_requests.Fetcher, stored_requests.AccountFetcher, *config.Configuration, metrics.MetricsEngine, analytics.Runner, map[string]string, []byte, map[string]openrtb_ext.BidderName, stored_requests.Fetcher, hooks.ExecutionPlanBuilder, *exchange.TmaxAdjustmentsPreprocessed) (httprouter.Handle, error)
  1307  
  1308  	switch test.endpointType {
  1309  	case AMP_ENDPOINT:
  1310  		endpointBuilder = NewAmpEndpoint
  1311  	default: //case OPENRTB_ENDPOINT:
  1312  		endpointBuilder = NewEndpoint
  1313  	}
  1314  
  1315  	endpoint, err := endpointBuilder(
  1316  		fakeUUIDGenerator{},
  1317  		testExchange,
  1318  		paramValidator,
  1319  		storedRequestFetcher,
  1320  		accountFetcher,
  1321  		cfg,
  1322  		met,
  1323  		analyticsBuild.New(&config.Analytics{}),
  1324  		disabledBidders,
  1325  		[]byte(test.Config.AliasJSON),
  1326  		bidderMap,
  1327  		storedResponseFetcher,
  1328  		planBuilder,
  1329  		nil,
  1330  	)
  1331  
  1332  	return endpoint, testExchange.(*exchangeTestWrapper), mockBidServersArray, mockCurrencyRatesServer, err
  1333  }
  1334  
  1335  type mockBidderParamValidator struct{}
  1336  
  1337  func (v mockBidderParamValidator) Validate(name openrtb_ext.BidderName, ext json.RawMessage) error {
  1338  	return nil
  1339  }
  1340  func (v mockBidderParamValidator) Schema(name openrtb_ext.BidderName) string { return "" }
  1341  
  1342  type mockAccountFetcher struct {
  1343  	data map[string]json.RawMessage
  1344  }
  1345  
  1346  func (af *mockAccountFetcher) FetchAccount(ctx context.Context, defaultAccountJSON json.RawMessage, accountID string) (json.RawMessage, []error) {
  1347  	if account, ok := af.data[accountID]; ok {
  1348  		return account, nil
  1349  	}
  1350  	return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}}
  1351  }
  1352  
  1353  type mockAmpStoredReqFetcher struct {
  1354  	data map[string]json.RawMessage
  1355  }
  1356  
  1357  func (cf *mockAmpStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
  1358  	return cf.data, nil, nil
  1359  }
  1360  
  1361  func (cf *mockAmpStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
  1362  	return nil, nil
  1363  }
  1364  
  1365  type mockAmpStoredResponseFetcher struct {
  1366  	data map[string]json.RawMessage
  1367  }
  1368  
  1369  func (cf *mockAmpStoredResponseFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
  1370  	return nil, nil, nil
  1371  }
  1372  
  1373  func (cf *mockAmpStoredResponseFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
  1374  	for _, storedResponseID := range ids {
  1375  		if storedResponse, exists := cf.data[storedResponseID]; exists {
  1376  			// Found. Unescape string before returning
  1377  			response, err := strconv.Unquote(string(storedResponse))
  1378  			if err != nil {
  1379  				return nil, append([]error{}, err)
  1380  			}
  1381  			cf.data[storedResponseID] = json.RawMessage(response)
  1382  			return cf.data, nil
  1383  		}
  1384  	}
  1385  	return nil, nil
  1386  }
  1387  
  1388  type wellBehavedCache struct{}
  1389  
  1390  func (c *wellBehavedCache) GetExtCacheData() (scheme string, host string, path string) {
  1391  	return "https", "www.pbcserver.com", "/pbcache/endpoint"
  1392  }
  1393  
  1394  func (c *wellBehavedCache) PutJson(ctx context.Context, values []pbc.Cacheable) ([]string, []error) {
  1395  	ids := make([]string, len(values))
  1396  	for i := 0; i < len(values); i++ {
  1397  		ids[i] = strconv.Itoa(i)
  1398  	}
  1399  	return ids, nil
  1400  }
  1401  
  1402  func readFile(t *testing.T, filename string) []byte {
  1403  	data, err := os.ReadFile(filename)
  1404  	if err != nil {
  1405  		t.Fatalf("Failed to read file %s: %v", filename, err)
  1406  	}
  1407  	return data
  1408  }
  1409  
  1410  type fakePermissionsBuilder struct {
  1411  	permissions gdpr.Permissions
  1412  }
  1413  
  1414  func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions {
  1415  	return fpb.permissions
  1416  }
  1417  
  1418  type fakePermissions struct {
  1419  }
  1420  
  1421  func (p *fakePermissions) HostCookiesAllowed(ctx context.Context) (bool, error) {
  1422  	return true, nil
  1423  }
  1424  
  1425  func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) {
  1426  	return true, nil
  1427  }
  1428  
  1429  func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
  1430  	return gdpr.AuctionPermissions{
  1431  		AllowBidRequest: true,
  1432  	}, nil
  1433  }
  1434  
  1435  type mockPlanBuilder struct {
  1436  	entrypointPlan               hooks.Plan[hookstage.Entrypoint]
  1437  	rawAuctionPlan               hooks.Plan[hookstage.RawAuctionRequest]
  1438  	processedAuctionPlan         hooks.Plan[hookstage.ProcessedAuctionRequest]
  1439  	bidderRequestPlan            hooks.Plan[hookstage.BidderRequest]
  1440  	rawBidderResponsePlan        hooks.Plan[hookstage.RawBidderResponse]
  1441  	allProcessedBidResponsesPlan hooks.Plan[hookstage.AllProcessedBidResponses]
  1442  	auctionResponsePlan          hooks.Plan[hookstage.AuctionResponse]
  1443  }
  1444  
  1445  func (m mockPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] {
  1446  	return m.entrypointPlan
  1447  }
  1448  
  1449  func (m mockPlanBuilder) PlanForRawAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawAuctionRequest] {
  1450  	return m.rawAuctionPlan
  1451  }
  1452  
  1453  func (m mockPlanBuilder) PlanForProcessedAuctionStage(_ string, _ *config.Account) hooks.Plan[hookstage.ProcessedAuctionRequest] {
  1454  	return m.processedAuctionPlan
  1455  }
  1456  
  1457  func (m mockPlanBuilder) PlanForBidderRequestStage(_ string, _ *config.Account) hooks.Plan[hookstage.BidderRequest] {
  1458  	return m.bidderRequestPlan
  1459  }
  1460  
  1461  func (m mockPlanBuilder) PlanForRawBidderResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.RawBidderResponse] {
  1462  	return m.rawBidderResponsePlan
  1463  }
  1464  
  1465  func (m mockPlanBuilder) PlanForAllProcessedBidResponsesStage(_ string, _ *config.Account) hooks.Plan[hookstage.AllProcessedBidResponses] {
  1466  	return m.allProcessedBidResponsesPlan
  1467  }
  1468  
  1469  func (m mockPlanBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account) hooks.Plan[hookstage.AuctionResponse] {
  1470  	return m.auctionResponsePlan
  1471  }
  1472  
  1473  func makePlan[H any](hook H) hooks.Plan[H] {
  1474  	return hooks.Plan[H]{
  1475  		{
  1476  			Timeout: 5 * time.Millisecond,
  1477  			Hooks: []hooks.HookWrapper[H]{
  1478  				{
  1479  					Module: "foobar",
  1480  					Code:   "foo",
  1481  					Hook:   hook,
  1482  				},
  1483  			},
  1484  		},
  1485  	}
  1486  }
  1487  
  1488  type mockRejectionHook struct {
  1489  	nbr int
  1490  	err error
  1491  }
  1492  
  1493  func (m mockRejectionHook) HandleEntrypointHook(
  1494  	_ context.Context,
  1495  	_ hookstage.ModuleInvocationContext,
  1496  	_ hookstage.EntrypointPayload,
  1497  ) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
  1498  	return hookstage.HookResult[hookstage.EntrypointPayload]{Reject: true, NbrCode: m.nbr}, m.err
  1499  }
  1500  
  1501  func (m mockRejectionHook) HandleRawAuctionHook(
  1502  	_ context.Context,
  1503  	_ hookstage.ModuleInvocationContext,
  1504  	_ hookstage.RawAuctionRequestPayload,
  1505  ) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) {
  1506  	return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err
  1507  }
  1508  
  1509  func (m mockRejectionHook) HandleProcessedAuctionHook(
  1510  	_ context.Context,
  1511  	_ hookstage.ModuleInvocationContext,
  1512  	_ hookstage.ProcessedAuctionRequestPayload,
  1513  ) (hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload], error) {
  1514  	return hookstage.HookResult[hookstage.ProcessedAuctionRequestPayload]{Reject: true, NbrCode: m.nbr}, m.err
  1515  }
  1516  
  1517  func (m mockRejectionHook) HandleBidderRequestHook(
  1518  	_ context.Context,
  1519  	_ hookstage.ModuleInvocationContext,
  1520  	payload hookstage.BidderRequestPayload,
  1521  ) (hookstage.HookResult[hookstage.BidderRequestPayload], error) {
  1522  	result := hookstage.HookResult[hookstage.BidderRequestPayload]{}
  1523  	if payload.Bidder == "appnexus" {
  1524  		result.Reject = true
  1525  		result.NbrCode = m.nbr
  1526  	}
  1527  
  1528  	return result, m.err
  1529  }
  1530  
  1531  func (m mockRejectionHook) HandleRawBidderResponseHook(
  1532  	_ context.Context,
  1533  	_ hookstage.ModuleInvocationContext,
  1534  	payload hookstage.RawBidderResponsePayload,
  1535  ) (hookstage.HookResult[hookstage.RawBidderResponsePayload], error) {
  1536  	result := hookstage.HookResult[hookstage.RawBidderResponsePayload]{}
  1537  	if payload.Bidder == "appnexus" {
  1538  		result.Reject = true
  1539  		result.NbrCode = m.nbr
  1540  	}
  1541  
  1542  	return result, nil
  1543  }
  1544  
  1545  var entryPointHookUpdateWithErrors = hooks.HookWrapper[hookstage.Entrypoint]{
  1546  	Module: "foobar",
  1547  	Code:   "foo",
  1548  	Hook: mockUpdateHook{
  1549  		entrypointHandler: func(
  1550  			_ hookstage.ModuleInvocationContext,
  1551  			payload hookstage.EntrypointPayload,
  1552  		) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
  1553  			ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{}
  1554  			ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) {
  1555  				payload.Request.Header.Add("foo", "bar")
  1556  				return payload, nil
  1557  			}, hookstage.MutationUpdate, "header", "foo")
  1558  
  1559  			return hookstage.HookResult[hookstage.EntrypointPayload]{
  1560  				ChangeSet: ch,
  1561  				Errors:    []string{"error 1"},
  1562  			}, nil
  1563  		},
  1564  	},
  1565  }
  1566  
  1567  var entryPointHookUpdateWithErrorsAndWarnings = hooks.HookWrapper[hookstage.Entrypoint]{
  1568  	Module: "foobar",
  1569  	Code:   "bar",
  1570  	Hook: mockUpdateHook{
  1571  		entrypointHandler: func(
  1572  			_ hookstage.ModuleInvocationContext,
  1573  			payload hookstage.EntrypointPayload,
  1574  		) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
  1575  			ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{}
  1576  			ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) {
  1577  				params := payload.Request.URL.Query()
  1578  				params.Add("foo", "baz")
  1579  				payload.Request.URL.RawQuery = params.Encode()
  1580  				return payload, nil
  1581  			}, hookstage.MutationUpdate, "param", "foo")
  1582  
  1583  			return hookstage.HookResult[hookstage.EntrypointPayload]{
  1584  				ChangeSet: ch,
  1585  				Errors:    []string{"error 1"},
  1586  				Warnings:  []string{"warning 1"},
  1587  			}, nil
  1588  		},
  1589  	},
  1590  }
  1591  
  1592  var entryPointHookUpdate = hooks.HookWrapper[hookstage.Entrypoint]{
  1593  	Module: "foobar",
  1594  	Code:   "baz",
  1595  	Hook: mockUpdateHook{
  1596  		entrypointHandler: func(
  1597  			ctx hookstage.ModuleInvocationContext,
  1598  			payload hookstage.EntrypointPayload,
  1599  		) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
  1600  			result := hookstage.HookResult[hookstage.EntrypointPayload]{}
  1601  			if ctx.Endpoint != hookexecution.EndpointAuction {
  1602  				result.Warnings = []string{fmt.Sprintf("Endpoint %s is not supported by hook.", ctx.Endpoint)}
  1603  				return result, nil
  1604  			}
  1605  
  1606  			ch := hookstage.ChangeSet[hookstage.EntrypointPayload]{}
  1607  			ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) {
  1608  				body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"tmax":600}`))
  1609  				if err == nil {
  1610  					payload.Body = body
  1611  				}
  1612  				return payload, err
  1613  			}, hookstage.MutationUpdate, "body", "tmax")
  1614  			ch.AddMutation(func(payload hookstage.EntrypointPayload) (hookstage.EntrypointPayload, error) {
  1615  				body, err := jsonpatch.MergePatch(payload.Body, []byte(`{"regs": {"ext": {"gdpr": 1, "us_privacy": "1NYN"}}}`))
  1616  				if err == nil {
  1617  					payload.Body = body
  1618  				}
  1619  				return payload, err
  1620  			}, hookstage.MutationAdd, "body", "regs", "ext", "us_privacy")
  1621  			result.ChangeSet = ch
  1622  
  1623  			return result, nil
  1624  		},
  1625  	},
  1626  }
  1627  
  1628  var rawAuctionHookNone = hooks.HookWrapper[hookstage.RawAuctionRequest]{
  1629  	Module: "vendor.module",
  1630  	Code:   "foobar",
  1631  	Hook:   mockUpdateHook{},
  1632  }
  1633  
  1634  type mockUpdateHook struct {
  1635  	entrypointHandler func(
  1636  		hookstage.ModuleInvocationContext,
  1637  		hookstage.EntrypointPayload,
  1638  	) (hookstage.HookResult[hookstage.EntrypointPayload], error)
  1639  }
  1640  
  1641  func (m mockUpdateHook) HandleEntrypointHook(
  1642  	_ context.Context,
  1643  	miCtx hookstage.ModuleInvocationContext,
  1644  	payload hookstage.EntrypointPayload,
  1645  ) (hookstage.HookResult[hookstage.EntrypointPayload], error) {
  1646  	return m.entrypointHandler(miCtx, payload)
  1647  }
  1648  
  1649  func (m mockUpdateHook) HandleRawAuctionHook(
  1650  	_ context.Context,
  1651  	_ hookstage.ModuleInvocationContext,
  1652  	_ hookstage.RawAuctionRequestPayload,
  1653  ) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) {
  1654  	return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{}, nil
  1655  }