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

     1  package openrtb2
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"os"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/prebid/prebid-server/v2/analytics"
    15  	analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build"
    16  	"github.com/prebid/prebid-server/v2/config"
    17  	"github.com/prebid/prebid-server/v2/errortypes"
    18  	"github.com/prebid/prebid-server/v2/exchange"
    19  	"github.com/prebid/prebid-server/v2/hooks"
    20  	"github.com/prebid/prebid-server/v2/metrics"
    21  	metricsConfig "github.com/prebid/prebid-server/v2/metrics/config"
    22  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    23  	"github.com/prebid/prebid-server/v2/prebid_cache_client"
    24  	"github.com/prebid/prebid-server/v2/privacy"
    25  	"github.com/prebid/prebid-server/v2/stored_requests/backends/empty_fetcher"
    26  	"github.com/prebid/prebid-server/v2/util/jsonutil"
    27  	"github.com/prebid/prebid-server/v2/util/ptrutil"
    28  
    29  	"github.com/prebid/openrtb/v20/adcom1"
    30  	"github.com/prebid/openrtb/v20/openrtb2"
    31  	gometrics "github.com/rcrowley/go-metrics"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  func TestVideoEndpointImpressionsNumber(t *testing.T) {
    37  	ex := &mockExchangeVideo{}
    38  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample.json")
    39  	req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
    40  	recorder := httptest.NewRecorder()
    41  
    42  	deps := mockDeps(t, ex)
    43  	deps.VideoAuctionEndpoint(recorder, req, nil)
    44  
    45  	if ex.lastRequest == nil {
    46  		t.Fatalf("The request never made it into the Exchange.")
    47  	}
    48  
    49  	respBytes := recorder.Body.Bytes()
    50  	resp := &openrtb_ext.BidResponseVideo{}
    51  	if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil {
    52  		t.Fatalf("Unable to unmarshal response.")
    53  	}
    54  
    55  	assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request")
    56  	assert.Equal(t, "prebid.com", string(ex.lastRequest.Site.Page), "Incorrect site page in request")
    57  	assert.Equal(t, "TvName", ex.lastRequest.Site.Content.Series, "Incorrect site content series in request")
    58  
    59  	assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response")
    60  	assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response")
    61  	assert.Len(t, resp.AdPods[1].Targeting, 3, "Incorrect Targeting data in response")
    62  	assert.Len(t, resp.AdPods[2].Targeting, 5, "Incorrect Targeting data in response")
    63  	assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response")
    64  	assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response")
    65  
    66  	assert.Equal(t, "20.00_395_30s", resp.AdPods[4].Targeting[0].HbPbCatDur, "Incorrect number of Ad Pods in response")
    67  	assert.Equal(t, "ABC_123", resp.AdPods[0].Targeting[0].HbDeal, "If DealID exists in bid response, hb_deal targeting needs to be added to resp")
    68  }
    69  
    70  func TestVideoEndpointImpressionsDuration(t *testing.T) {
    71  	ex := &mockExchangeVideo{}
    72  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_different_durations.json")
    73  	req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
    74  	recorder := httptest.NewRecorder()
    75  
    76  	deps := mockDeps(t, ex)
    77  	deps.VideoAuctionEndpoint(recorder, req, nil)
    78  
    79  	if ex.lastRequest == nil {
    80  		t.Fatalf("The request never made it into the Exchange.")
    81  	}
    82  
    83  	var extData openrtb_ext.ExtRequest
    84  	jsonutil.UnmarshalValid(ex.lastRequest.Ext, &extData)
    85  	assert.NotNil(t, extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ")
    86  	assert.True(t, *extData.Prebid.Targeting.IncludeBidderKeys, "Request ext incorrect: IncludeBidderKeys should be true ")
    87  
    88  	assert.Len(t, ex.lastRequest.Imp, 22, "Incorrect number of impressions in request")
    89  	assert.Equal(t, "1_0", ex.lastRequest.Imp[0].ID, "Incorrect impression id in request")
    90  	assert.Equal(t, int64(15), ex.lastRequest.Imp[0].Video.MaxDuration, "Incorrect impression max duration in request")
    91  	assert.Equal(t, int64(15), ex.lastRequest.Imp[0].Video.MinDuration, "Incorrect impression min duration in request")
    92  
    93  	assert.Equal(t, "1_6", ex.lastRequest.Imp[6].ID, "Incorrect impression id in request")
    94  	assert.Equal(t, int64(30), ex.lastRequest.Imp[6].Video.MaxDuration, "Incorrect impression max duration in request")
    95  	assert.Equal(t, int64(30), ex.lastRequest.Imp[6].Video.MinDuration, "Incorrect impression min duration in request")
    96  
    97  	assert.Equal(t, "2_0", ex.lastRequest.Imp[12].ID, "Incorrect impression id in request")
    98  	assert.Equal(t, int64(15), ex.lastRequest.Imp[12].Video.MaxDuration, "Incorrect impression max duration in request")
    99  	assert.Equal(t, int64(15), ex.lastRequest.Imp[12].Video.MinDuration, "Incorrect impression min duration in request")
   100  
   101  	assert.Equal(t, "2_5", ex.lastRequest.Imp[17].ID, "Incorrect impression id in request")
   102  	assert.Equal(t, int64(30), ex.lastRequest.Imp[17].Video.MaxDuration, "Incorrect impression max duration in request")
   103  	assert.Equal(t, int64(30), ex.lastRequest.Imp[17].Video.MinDuration, "Incorrect impression min duration in request")
   104  }
   105  
   106  func TestCreateBidExtension(t *testing.T) {
   107  	durationRange := make([]int, 0)
   108  	durationRange = append(durationRange, 15)
   109  	durationRange = append(durationRange, 30)
   110  
   111  	priceGranRanges := make([]openrtb_ext.GranularityRange, 0)
   112  	priceGranRanges = append(priceGranRanges, openrtb_ext.GranularityRange{
   113  		Max:       30,
   114  		Min:       0,
   115  		Increment: 0.1,
   116  	})
   117  
   118  	translateCategories := true
   119  	videoRequest := openrtb_ext.BidRequestVideo{
   120  		IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{
   121  			PrimaryAdserver:     1,
   122  			Publisher:           "",
   123  			TranslateCategories: &translateCategories,
   124  		},
   125  		PodConfig: openrtb_ext.PodConfig{
   126  			DurationRangeSec:     durationRange,
   127  			RequireExactDuration: false,
   128  		},
   129  		PriceGranularity: &openrtb_ext.PriceGranularity{
   130  			Precision: ptrutil.ToPtr(2),
   131  			Ranges:    priceGranRanges,
   132  		},
   133  	}
   134  	res, err := createBidExtension(&videoRequest)
   135  	assert.NoError(t, err, "Error should be nil")
   136  
   137  	resExt := &openrtb_ext.ExtRequest{}
   138  
   139  	if err := jsonutil.UnmarshalValid(res, &resExt); err != nil {
   140  		assert.Fail(t, "Unable to unmarshal bid extension")
   141  	}
   142  	assert.Equal(t, durationRange, resExt.Prebid.Targeting.DurationRangeSec, "Duration range seconds is incorrect")
   143  	assert.Equal(t, priceGranRanges, resExt.Prebid.Targeting.PriceGranularity.Ranges, "Price granularity is incorrect")
   144  }
   145  
   146  func TestCreateBidExtensionTargeting(t *testing.T) {
   147  	ex := &mockExchangeVideo{}
   148  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample.json")
   149  	req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
   150  	recorder := httptest.NewRecorder()
   151  
   152  	deps := mockDeps(t, ex)
   153  	deps.VideoAuctionEndpoint(recorder, req, nil)
   154  
   155  	require.NotNil(t, ex.lastRequest, "The request never made it into the Exchange.")
   156  
   157  	// assert targeting set to default
   158  	expectedRequestExt := `{"prebid":{"cache":{"vastxml":{}},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":20,"increment":0.1}]},"mediatypepricegranularity":{},"includebidderkeys":true,"includewinners":true,"includebrandcategory":{"primaryadserver":1,"publisher":"","withcategory":true}}}}`
   159  	assert.JSONEq(t, expectedRequestExt, string(ex.lastRequest.Ext))
   160  }
   161  
   162  func TestVideoEndpointDebugQueryTrue(t *testing.T) {
   163  	ex := &mockExchangeVideo{
   164  		cache: &mockCacheClient{},
   165  	}
   166  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample.json")
   167  	req := httptest.NewRequest("POST", "/openrtb2/video?debug=true", strings.NewReader(reqBody))
   168  	recorder := httptest.NewRecorder()
   169  
   170  	deps := mockDeps(t, ex)
   171  	deps.VideoAuctionEndpoint(recorder, req, nil)
   172  
   173  	if ex.lastRequest == nil {
   174  		t.Fatalf("The request never made it into the Exchange.")
   175  	}
   176  	if !ex.cache.called {
   177  		t.Fatalf("Cache was not called when it should have been")
   178  	}
   179  
   180  	respBytes := recorder.Body.Bytes()
   181  	resp := &openrtb_ext.BidResponseVideo{}
   182  	if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil {
   183  		t.Fatalf("Unable to unmarshal response.")
   184  	}
   185  
   186  	assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request")
   187  	assert.Equal(t, "prebid.com", string(ex.lastRequest.Site.Page), "Incorrect site page in request")
   188  	assert.Equal(t, "TvName", ex.lastRequest.Site.Content.Series, "Incorrect site content series in request")
   189  
   190  	assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response")
   191  	assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response")
   192  	assert.Len(t, resp.AdPods[1].Targeting, 3, "Incorrect Targeting data in response")
   193  	assert.Len(t, resp.AdPods[2].Targeting, 5, "Incorrect Targeting data in response")
   194  	assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response")
   195  	assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response")
   196  
   197  	assert.Equal(t, "20.00_395_30s", resp.AdPods[4].Targeting[0].HbPbCatDur, "Incorrect number of Ad Pods in response")
   198  }
   199  
   200  func TestVideoEndpointDebugQueryFalse(t *testing.T) {
   201  	ex := &mockExchangeVideo{
   202  		cache: &mockCacheClient{},
   203  	}
   204  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample.json")
   205  	req := httptest.NewRequest("POST", "/openrtb2/video?debug=123", strings.NewReader(reqBody))
   206  	recorder := httptest.NewRecorder()
   207  
   208  	deps := mockDeps(t, ex)
   209  	deps.VideoAuctionEndpoint(recorder, req, nil)
   210  
   211  	if ex.lastRequest == nil {
   212  		t.Fatalf("The request never made it into the Exchange.")
   213  	}
   214  	if ex.cache.called {
   215  		t.Fatalf("Cache was called when it shouldn't have been")
   216  	}
   217  
   218  	respBytes := recorder.Body.Bytes()
   219  	resp := &openrtb_ext.BidResponseVideo{}
   220  	if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil {
   221  		t.Fatalf("Unable to unmarshal response.")
   222  	}
   223  
   224  	assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request")
   225  	assert.Equal(t, "prebid.com", string(ex.lastRequest.Site.Page), "Incorrect site page in request")
   226  	assert.Equal(t, "TvName", ex.lastRequest.Site.Content.Series, "Incorrect site content series in request")
   227  
   228  	assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response")
   229  	assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response")
   230  	assert.Len(t, resp.AdPods[1].Targeting, 3, "Incorrect Targeting data in response")
   231  	assert.Len(t, resp.AdPods[2].Targeting, 5, "Incorrect Targeting data in response")
   232  	assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response")
   233  	assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response")
   234  
   235  	assert.Equal(t, "20.00_395_30s", resp.AdPods[4].Targeting[0].HbPbCatDur, "Incorrect number of Ad Pods in response")
   236  }
   237  
   238  func TestVideoEndpointDebugError(t *testing.T) {
   239  	ex := &mockExchangeVideo{
   240  		cache: &mockCacheClient{},
   241  	}
   242  	reqBody := readVideoTestFile(t, "sample-requests/video/video_invalid_sample.json")
   243  	req := httptest.NewRequest("POST", "/openrtb2/video?debug=true", strings.NewReader(reqBody))
   244  	recorder := httptest.NewRecorder()
   245  
   246  	deps := mockDeps(t, ex)
   247  	deps.VideoAuctionEndpoint(recorder, req, nil)
   248  
   249  	if !ex.cache.called {
   250  		t.Fatalf("Cache was not called when it should have been")
   251  	}
   252  
   253  	assert.Equal(t, 500, recorder.Code, "Should catch error in request")
   254  }
   255  
   256  func TestVideoEndpointDebugNoAdPods(t *testing.T) {
   257  	ex := &mockExchangeVideoNoBids{
   258  		cache: &mockCacheClient{},
   259  	}
   260  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample.json")
   261  	req := httptest.NewRequest("POST", "/openrtb2/video?debug=true", strings.NewReader(reqBody))
   262  	recorder := httptest.NewRecorder()
   263  
   264  	deps := mockDepsNoBids(t, ex)
   265  	deps.VideoAuctionEndpoint(recorder, req, nil)
   266  
   267  	if ex.lastRequest == nil {
   268  		t.Fatalf("The request never made it into the Exchange.")
   269  	}
   270  	if !ex.cache.called {
   271  		t.Fatalf("Cache was not called when it should have been")
   272  	}
   273  
   274  	respBytes := recorder.Body.Bytes()
   275  	resp := &openrtb_ext.BidResponseVideo{}
   276  	if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil {
   277  		t.Fatalf("Unable to unmarshal response.")
   278  	}
   279  
   280  	assert.Len(t, resp.AdPods, 1, "Debug AdPod should be added to response")
   281  	assert.Empty(t, resp.AdPods[0].Errors, "AdPod Errors should be empty")
   282  	assert.Empty(t, resp.AdPods[0].Targeting[0].HbPb, "Hb_pb should be empty")
   283  	assert.Empty(t, resp.AdPods[0].Targeting[0].HbPbCatDur, "Hb_pb_cat_dur should be empty")
   284  	assert.NotEmpty(t, resp.AdPods[0].Targeting[0].HbCacheID, "Hb_cache_id should not be empty")
   285  	assert.Equal(t, int64(0), resp.AdPods[0].PodId, "Pod ID should be 0")
   286  }
   287  
   288  func TestVideoEndpointNoPods(t *testing.T) {
   289  	ex := &mockExchangeVideo{}
   290  	reqBody := readVideoTestFile(t, "sample-requests/video/video_invalid_sample.json")
   291  	req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
   292  	recorder := httptest.NewRecorder()
   293  
   294  	deps := mockDeps(t, ex)
   295  	deps.VideoAuctionEndpoint(recorder, req, nil)
   296  
   297  	errorMessage := recorder.Body.String()
   298  
   299  	assert.Equal(t, 500, recorder.Code, "Should catch error in request")
   300  	assert.Equal(t, "Critical error while running the video endpoint:  request missing required field: PodConfig.DurationRangeSec request missing required field: PodConfig.Pods", errorMessage, "Incorrect request validation message")
   301  }
   302  
   303  func TestVideoEndpointValidationsPositive(t *testing.T) {
   304  	ex := &mockExchangeVideo{}
   305  	deps := mockDeps(t, ex)
   306  	deps.cfg.VideoStoredRequestRequired = true
   307  
   308  	durationRange := make([]int, 0)
   309  	durationRange = append(durationRange, 15)
   310  	durationRange = append(durationRange, 30)
   311  
   312  	pods := make([]openrtb_ext.Pod, 0)
   313  	pod1 := openrtb_ext.Pod{
   314  		PodId:            1,
   315  		AdPodDurationSec: 30,
   316  		ConfigId:         "qwerty",
   317  	}
   318  	pod2 := openrtb_ext.Pod{
   319  		PodId:            2,
   320  		AdPodDurationSec: 30,
   321  		ConfigId:         "qwerty",
   322  	}
   323  	pods = append(pods, pod1)
   324  	pods = append(pods, pod2)
   325  
   326  	mimes := make([]string, 0)
   327  	mimes = append(mimes, "mp4")
   328  	mimes = append(mimes, "")
   329  
   330  	videoProtocols := []adcom1.MediaCreativeSubtype{15, 30}
   331  
   332  	req := openrtb_ext.BidRequestVideo{
   333  		StoredRequestId: "123",
   334  		PodConfig: openrtb_ext.PodConfig{
   335  			DurationRangeSec:     durationRange,
   336  			RequireExactDuration: true,
   337  			Pods:                 pods,
   338  		},
   339  		App: &openrtb2.App{
   340  			Bundle: "pbs.com",
   341  		},
   342  		IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{
   343  			PrimaryAdserver: 1,
   344  		},
   345  		Video: &openrtb2.Video{
   346  			MIMEs:     mimes,
   347  			Protocols: videoProtocols,
   348  		},
   349  	}
   350  
   351  	errors, podErrors := deps.validateVideoRequest(&req)
   352  	assert.Len(t, errors, 0, "Errors should be empty")
   353  	assert.Len(t, podErrors, 0, "Pod errors should be empty")
   354  }
   355  
   356  func TestVideoEndpointValidationsCritical(t *testing.T) {
   357  	ex := &mockExchangeVideo{}
   358  	deps := mockDeps(t, ex)
   359  	deps.cfg.VideoStoredRequestRequired = true
   360  
   361  	durationRange := make([]int, 0)
   362  	durationRange = append(durationRange, 0)
   363  	durationRange = append(durationRange, -30)
   364  
   365  	pods := make([]openrtb_ext.Pod, 0)
   366  
   367  	mimes := make([]string, 0)
   368  	mimes = append(mimes, "")
   369  	mimes = append(mimes, "")
   370  
   371  	videoProtocols := []adcom1.MediaCreativeSubtype{}
   372  
   373  	req := openrtb_ext.BidRequestVideo{
   374  		StoredRequestId: "",
   375  		PodConfig: openrtb_ext.PodConfig{
   376  			DurationRangeSec:     durationRange,
   377  			RequireExactDuration: true,
   378  			Pods:                 pods,
   379  		},
   380  		IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{
   381  			PrimaryAdserver: 0,
   382  		},
   383  		Video: &openrtb2.Video{
   384  			MIMEs:     mimes,
   385  			Protocols: videoProtocols,
   386  		},
   387  	}
   388  
   389  	errors, podErrors := deps.validateVideoRequest(&req)
   390  	assert.Len(t, podErrors, 0, "Pod errors should be empty")
   391  	assert.Len(t, errors, 6, "Errors array should contain 6 error messages")
   392  
   393  	assert.Equal(t, "request missing required field: storedrequestid", errors[0].Error(), "Errors array should contain 6 error messages")
   394  	assert.Equal(t, "duration array cannot contain negative or zero values", errors[1].Error(), "Errors array should contain 6 error messages")
   395  	assert.Equal(t, "request missing required field: PodConfig.Pods", errors[2].Error(), "Errors array should contain 6 error messages")
   396  	assert.Equal(t, "request missing required field: site or app", errors[3].Error(), "Errors array should contain 6 error messages")
   397  	assert.Equal(t, "request missing required field: Video.Mimes, mime types contains empty strings only", errors[4].Error(), "Errors array should contain 6 error messages")
   398  	assert.Equal(t, "request missing required field: Video.Protocols", errors[5].Error(), "Errors array should contain 6 error messages")
   399  }
   400  
   401  func TestVideoEndpointValidationsPodErrors(t *testing.T) {
   402  	ex := &mockExchangeVideo{}
   403  	deps := mockDeps(t, ex)
   404  	deps.cfg.VideoStoredRequestRequired = true
   405  
   406  	durationRange := make([]int, 0)
   407  	durationRange = append(durationRange, 15)
   408  	durationRange = append(durationRange, 30)
   409  
   410  	pods := make([]openrtb_ext.Pod, 0)
   411  	pod1 := openrtb_ext.Pod{
   412  		PodId:            1,
   413  		AdPodDurationSec: 30,
   414  		ConfigId:         "qwerty",
   415  	}
   416  	pod2 := openrtb_ext.Pod{
   417  		PodId:            2,
   418  		AdPodDurationSec: 30,
   419  		ConfigId:         "qwerty",
   420  	}
   421  	pod3 := openrtb_ext.Pod{
   422  		PodId:            2,
   423  		AdPodDurationSec: 0,
   424  		ConfigId:         "",
   425  	}
   426  	pod4 := openrtb_ext.Pod{
   427  		PodId:            0,
   428  		AdPodDurationSec: -30,
   429  		ConfigId:         "",
   430  	}
   431  	pods = append(pods, pod1)
   432  	pods = append(pods, pod2)
   433  	pods = append(pods, pod3)
   434  	pods = append(pods, pod4)
   435  
   436  	mimes := make([]string, 0)
   437  	mimes = append(mimes, "mp4")
   438  	mimes = append(mimes, "")
   439  
   440  	videoProtocols := []adcom1.MediaCreativeSubtype{15, 30}
   441  
   442  	req := openrtb_ext.BidRequestVideo{
   443  		StoredRequestId: "123",
   444  		PodConfig: openrtb_ext.PodConfig{
   445  			DurationRangeSec:     durationRange,
   446  			RequireExactDuration: true,
   447  			Pods:                 pods,
   448  		},
   449  		App: &openrtb2.App{
   450  			Bundle: "pbs.com",
   451  		},
   452  		IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{
   453  			PrimaryAdserver: 1,
   454  		},
   455  		Video: &openrtb2.Video{
   456  			MIMEs:     mimes,
   457  			Protocols: videoProtocols,
   458  		},
   459  	}
   460  
   461  	errors, podErrors := deps.validateVideoRequest(&req)
   462  	assert.Len(t, errors, 0, "Errors should be empty")
   463  
   464  	assert.Len(t, podErrors, 2, "Pod errors should contain 2 elements")
   465  
   466  	assert.Equal(t, 2, podErrors[0].PodId, "Pod error ind 0, incorrect id should be 2")
   467  	assert.Equal(t, 2, podErrors[0].PodIndex, "Pod error ind 0, incorrect index should be 2")
   468  	assert.Len(t, podErrors[0].ErrMsgs, 3, "Pod error ind 0 should contain 3 errors")
   469  	assert.Equal(t, "request duplicated required field: PodConfig.Pods.PodId, Pod id: 2", podErrors[0].ErrMsgs[0], "Pod error ind 0 should have duplicated pod id")
   470  	assert.Equal(t, "request missing or incorrect required field: PodConfig.Pods.AdPodDurationSec, Pod index: 2", podErrors[0].ErrMsgs[1], "Pod error ind 0 should have missing AdPodDuration")
   471  	assert.Equal(t, "request missing or incorrect required field: PodConfig.Pods.ConfigId, Pod index: 2", podErrors[0].ErrMsgs[2], "Pod error ind 0 should have missing config id")
   472  
   473  	assert.Equal(t, 0, podErrors[1].PodId, "Pod error ind 1, incorrect id should be 0")
   474  	assert.Equal(t, 3, podErrors[1].PodIndex, "Pod error ind 1, incorrect index should be 3")
   475  	assert.Len(t, podErrors[1].ErrMsgs, 3, "Pod error ind 1 should contain 3 errors")
   476  	assert.Equal(t, "request missing required field: PodConfig.Pods.PodId, Pod index: 3", podErrors[1].ErrMsgs[0], "Pod error ind 1 should have missed pod id")
   477  	assert.Equal(t, "request incorrect required field: PodConfig.Pods.AdPodDurationSec is negative, Pod index: 3", podErrors[1].ErrMsgs[1], "Pod error ind 1 should have negative AdPodDurationSec")
   478  	assert.Equal(t, "request missing or incorrect required field: PodConfig.Pods.ConfigId, Pod index: 3", podErrors[1].ErrMsgs[2], "Pod error ind 1 should have missing config id")
   479  }
   480  
   481  func TestVideoEndpointValidationsSiteAndApp(t *testing.T) {
   482  	ex := &mockExchangeVideo{}
   483  	deps := mockDeps(t, ex)
   484  	deps.cfg.VideoStoredRequestRequired = true
   485  
   486  	durationRange := make([]int, 0)
   487  	durationRange = append(durationRange, 15)
   488  	durationRange = append(durationRange, 30)
   489  
   490  	pods := make([]openrtb_ext.Pod, 0)
   491  	pod1 := openrtb_ext.Pod{
   492  		PodId:            1,
   493  		AdPodDurationSec: 30,
   494  		ConfigId:         "qwerty",
   495  	}
   496  	pod2 := openrtb_ext.Pod{
   497  		PodId:            2,
   498  		AdPodDurationSec: 30,
   499  		ConfigId:         "qwerty",
   500  	}
   501  	pods = append(pods, pod1)
   502  	pods = append(pods, pod2)
   503  
   504  	mimes := make([]string, 0)
   505  	mimes = append(mimes, "mp4")
   506  	mimes = append(mimes, "")
   507  
   508  	videoProtocols := []adcom1.MediaCreativeSubtype{15, 30}
   509  
   510  	req := openrtb_ext.BidRequestVideo{
   511  		StoredRequestId: "123",
   512  		PodConfig: openrtb_ext.PodConfig{
   513  			DurationRangeSec:     durationRange,
   514  			RequireExactDuration: true,
   515  			Pods:                 pods,
   516  		},
   517  		App: &openrtb2.App{
   518  			Bundle: "pbs.com",
   519  		},
   520  		Site: &openrtb2.Site{
   521  			ID: "pbs.com",
   522  		},
   523  		IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{
   524  			PrimaryAdserver: 1,
   525  		},
   526  		Video: &openrtb2.Video{
   527  			MIMEs:     mimes,
   528  			Protocols: videoProtocols,
   529  		},
   530  	}
   531  
   532  	errors, podErrors := deps.validateVideoRequest(&req)
   533  	assert.Equal(t, "request.site or request.app must be defined, but not both", errors[0].Error(), "Site and App error should be present")
   534  	assert.Len(t, podErrors, 0, "Pod errors should be empty")
   535  }
   536  
   537  func TestVideoEndpointValidationsSiteMissingRequiredField(t *testing.T) {
   538  	ex := &mockExchangeVideo{}
   539  	deps := mockDeps(t, ex)
   540  	deps.cfg.VideoStoredRequestRequired = true
   541  
   542  	durationRange := make([]int, 0)
   543  	durationRange = append(durationRange, 15)
   544  	durationRange = append(durationRange, 30)
   545  
   546  	pods := make([]openrtb_ext.Pod, 0)
   547  	pod1 := openrtb_ext.Pod{
   548  		PodId:            1,
   549  		AdPodDurationSec: 30,
   550  		ConfigId:         "qwerty",
   551  	}
   552  	pod2 := openrtb_ext.Pod{
   553  		PodId:            2,
   554  		AdPodDurationSec: 30,
   555  		ConfigId:         "qwerty",
   556  	}
   557  	pods = append(pods, pod1)
   558  	pods = append(pods, pod2)
   559  
   560  	mimes := make([]string, 0)
   561  	mimes = append(mimes, "mp4")
   562  	mimes = append(mimes, "")
   563  
   564  	videoProtocols := []adcom1.MediaCreativeSubtype{15, 30}
   565  
   566  	req := openrtb_ext.BidRequestVideo{
   567  		StoredRequestId: "123",
   568  		PodConfig: openrtb_ext.PodConfig{
   569  			DurationRangeSec:     durationRange,
   570  			RequireExactDuration: true,
   571  			Pods:                 pods,
   572  		},
   573  		Site: &openrtb2.Site{
   574  			Domain: "pbs.com",
   575  		},
   576  		IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{
   577  			PrimaryAdserver: 1,
   578  		},
   579  		Video: &openrtb2.Video{
   580  			MIMEs:     mimes,
   581  			Protocols: videoProtocols,
   582  		},
   583  	}
   584  
   585  	errors, podErrors := deps.validateVideoRequest(&req)
   586  	assert.Equal(t, "request.site missing required field: id or page", errors[0].Error(), "Site required fields error should be present")
   587  	assert.Len(t, podErrors, 0, "Pod errors should be empty")
   588  }
   589  
   590  func TestVideoEndpointValidationsMissingVideo(t *testing.T) {
   591  	ex := &mockExchangeVideo{}
   592  	deps := mockDeps(t, ex)
   593  	deps.cfg.VideoStoredRequestRequired = true
   594  
   595  	req := openrtb_ext.BidRequestVideo{
   596  		StoredRequestId: "123",
   597  		PodConfig: openrtb_ext.PodConfig{
   598  			DurationRangeSec:     []int{15, 30},
   599  			RequireExactDuration: true,
   600  			Pods: []openrtb_ext.Pod{
   601  				{
   602  					PodId:            1,
   603  					AdPodDurationSec: 30,
   604  					ConfigId:         "qwerty",
   605  				},
   606  				{
   607  					PodId:            2,
   608  					AdPodDurationSec: 30,
   609  					ConfigId:         "qwerty",
   610  				},
   611  			},
   612  		},
   613  		App: &openrtb2.App{
   614  			Bundle: "pbs.com",
   615  		},
   616  		IncludeBrandCategory: &openrtb_ext.IncludeBrandCategory{
   617  			PrimaryAdserver: 1,
   618  		},
   619  	}
   620  
   621  	errors, podErrors := deps.validateVideoRequest(&req)
   622  	assert.Len(t, podErrors, 0, "Pod errors should be empty")
   623  	assert.Len(t, errors, 1, "Errors array should contain 1 error message")
   624  	assert.Equal(t, "request missing required field: Video", errors[0].Error(), "Errors array should contain message regarding missing Video field")
   625  }
   626  
   627  func TestVideoBuildVideoResponseMissedCacheForOneBid(t *testing.T) {
   628  	openRtbBidResp := openrtb2.BidResponse{}
   629  	podErrors := make([]PodError, 0)
   630  
   631  	seatBids := make([]openrtb2.SeatBid, 0)
   632  	seatBid := openrtb2.SeatBid{}
   633  
   634  	bids := make([]openrtb2.Bid, 0)
   635  	bid1 := openrtb2.Bid{}
   636  	bid2 := openrtb2.Bid{}
   637  	bid3 := openrtb2.Bid{}
   638  
   639  	extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
   640  	extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
   641  	extBid3 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_406_30s","hb_size":"1x1"}}}`)
   642  
   643  	bid1.Ext = extBid1
   644  	bids = append(bids, bid1)
   645  
   646  	bid2.Ext = extBid2
   647  	bids = append(bids, bid2)
   648  
   649  	bid3.Ext = extBid3
   650  	bids = append(bids, bid3)
   651  
   652  	seatBid.Bid = bids
   653  	seatBid.Seat = "appnexus"
   654  	seatBids = append(seatBids, seatBid)
   655  	openRtbBidResp.SeatBid = seatBids
   656  
   657  	bidRespVideo, err := buildVideoResponse(&openRtbBidResp, podErrors)
   658  	assert.NoError(t, err, "Should be no error")
   659  	assert.Len(t, bidRespVideo.AdPods, 1, "AdPods length should be 1")
   660  	assert.Len(t, bidRespVideo.AdPods[0].Targeting, 2, "AdPod Targeting length should be 2")
   661  	assert.Equal(t, "17.00_123_30s", bidRespVideo.AdPods[0].Targeting[0].HbPbCatDur, "AdPod Targeting first element hb_pb_cat_dur should be 17.00_123_30s")
   662  	assert.Equal(t, "17.00_456_30s", bidRespVideo.AdPods[0].Targeting[1].HbPbCatDur, "AdPod Targeting first element hb_pb_cat_dur should be 17.00_456_30s")
   663  }
   664  
   665  func TestVideoBuildVideoResponseMissedCacheForAllBids(t *testing.T) {
   666  	openRtbBidResp := openrtb2.BidResponse{}
   667  	podErrors := make([]PodError, 0)
   668  
   669  	seatBids := make([]openrtb2.SeatBid, 0)
   670  	seatBid := openrtb2.SeatBid{}
   671  
   672  	bids := make([]openrtb2.Bid, 0)
   673  	bid1 := openrtb2.Bid{}
   674  	bid2 := openrtb2.Bid{}
   675  	bid3 := openrtb2.Bid{}
   676  
   677  	extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_123_30s","hb_size":"1x1"}}}`)
   678  	extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_456_30s","hb_size":"1x1"}}}`)
   679  	extBid3 := []byte(`{"prebid":{"targeting":{"hb_bidder":"appnexus","hb_pb":"17.00","hb_pb_cat_dur":"17.00_406_30s","hb_size":"1x1"}}}`)
   680  
   681  	bid1.Ext = extBid1
   682  	bids = append(bids, bid1)
   683  
   684  	bid2.Ext = extBid2
   685  	bids = append(bids, bid2)
   686  
   687  	bid3.Ext = extBid3
   688  	bids = append(bids, bid3)
   689  
   690  	seatBid.Bid = bids
   691  	seatBids = append(seatBids, seatBid)
   692  	openRtbBidResp.SeatBid = seatBids
   693  
   694  	bidRespVideo, err := buildVideoResponse(&openRtbBidResp, podErrors)
   695  	assert.Nil(t, bidRespVideo, "bid response should be nil")
   696  	assert.Equal(t, "caching failed for all bids", err.Error(), "error should be caching failed for all bids")
   697  }
   698  
   699  func TestVideoBuildVideoResponsePodErrors(t *testing.T) {
   700  	openRtbBidResp := openrtb2.BidResponse{}
   701  	podErrors := make([]PodError, 0, 2)
   702  
   703  	seatBids := make([]openrtb2.SeatBid, 0)
   704  	seatBid := openrtb2.SeatBid{}
   705  
   706  	bids := make([]openrtb2.Bid, 0)
   707  	bid1 := openrtb2.Bid{}
   708  	bid2 := openrtb2.Bid{}
   709  
   710  	extBid1 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_123_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
   711  	extBid2 := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"17.00","hb_pb_cat_dur_appnex":"17.00_456_30s","hb_size":"1x1","hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"}}}`)
   712  
   713  	bid1.Ext = extBid1
   714  	bids = append(bids, bid1)
   715  
   716  	bid2.Ext = extBid2
   717  	bids = append(bids, bid2)
   718  
   719  	seatBid.Bid = bids
   720  	seatBid.Seat = "appnexus"
   721  	seatBids = append(seatBids, seatBid)
   722  	openRtbBidResp.SeatBid = seatBids
   723  
   724  	podErr1 := PodError{}
   725  	podErr1.PodId = 222
   726  	podErr1.PodIndex = 1
   727  	podErrors = append(podErrors, podErr1)
   728  
   729  	podErr2 := PodError{}
   730  	podErr2.PodId = 333
   731  	podErr2.PodIndex = 2
   732  	podErrors = append(podErrors, podErr2)
   733  
   734  	bidRespVideo, err := buildVideoResponse(&openRtbBidResp, podErrors)
   735  	assert.NoError(t, err, "Error should be nil")
   736  	assert.Len(t, bidRespVideo.AdPods, 3, "AdPods length should be 3")
   737  	assert.Len(t, bidRespVideo.AdPods[0].Targeting, 2, "First ad pod should be correct and contain 2 targeting elements")
   738  	assert.Equal(t, int64(222), bidRespVideo.AdPods[1].PodId, "AdPods should contain error element at index 1")
   739  	assert.Equal(t, int64(333), bidRespVideo.AdPods[2].PodId, "AdPods should contain error element at index 2")
   740  }
   741  
   742  func TestVideoBuildVideoResponseNoBids(t *testing.T) {
   743  	openRtbBidResp := openrtb2.BidResponse{}
   744  	podErrors := make([]PodError, 0)
   745  	openRtbBidResp.SeatBid = make([]openrtb2.SeatBid, 0)
   746  	bidRespVideo, err := buildVideoResponse(&openRtbBidResp, podErrors)
   747  	assert.NoError(t, err, "Error should be nil")
   748  	assert.Len(t, bidRespVideo.AdPods, 0, "AdPods length should be 0")
   749  }
   750  
   751  func TestMergeOpenRTBToVideoRequest(t *testing.T) {
   752  	var bidReq = &openrtb2.BidRequest{}
   753  	var videoReq = &openrtb_ext.BidRequestVideo{}
   754  
   755  	videoReq.App = &openrtb2.App{
   756  		Domain: "test.com",
   757  		Bundle: "test.bundle",
   758  	}
   759  
   760  	videoReq.Site = &openrtb2.Site{
   761  		Page: "site.com/index",
   762  	}
   763  
   764  	var dnt int8 = 4
   765  	var lmt int8 = 5
   766  	videoReq.Device = openrtb2.Device{
   767  		DNT: &dnt,
   768  		Lmt: &lmt,
   769  	}
   770  
   771  	videoReq.BCat = []string{"test1", "test2"}
   772  	videoReq.BAdv = []string{"test3", "test4"}
   773  
   774  	videoReq.Regs = &openrtb2.Regs{
   775  		Ext: json.RawMessage(`{"gdpr":1,"us_privacy":"1NYY","existing":"any","consent":"anyConsent"}`),
   776  	}
   777  
   778  	videoReq.User = &openrtb2.User{
   779  		BuyerUID: "test UID",
   780  		Yob:      1980,
   781  		Keywords: "test keywords",
   782  		Ext:      json.RawMessage(`{"consent":"test string"}`),
   783  	}
   784  
   785  	mergeData(videoReq, bidReq)
   786  
   787  	assert.Equal(t, videoReq.BCat, bidReq.BCat, "BCat is incorrect")
   788  	assert.Equal(t, videoReq.BAdv, bidReq.BAdv, "BAdv is incorrect")
   789  
   790  	assert.Equal(t, videoReq.App.Domain, bidReq.App.Domain, "App.Domain is incorrect")
   791  	assert.Equal(t, videoReq.App.Bundle, bidReq.App.Bundle, "App.Bundle is incorrect")
   792  
   793  	assert.Equal(t, videoReq.Device.Lmt, bidReq.Device.Lmt, "Device.Lmt is incorrect")
   794  	assert.Equal(t, videoReq.Device.DNT, bidReq.Device.DNT, "Device.DNT is incorrect")
   795  
   796  	assert.Equal(t, videoReq.Site.Page, bidReq.Site.Page, "Device.Site.Page is incorrect")
   797  
   798  	assert.Equal(t, videoReq.Regs, bidReq.Regs, "Regs is incorrect")
   799  
   800  	assert.Equal(t, videoReq.User, bidReq.User, "User is incorrect")
   801  }
   802  
   803  func TestHandleError(t *testing.T) {
   804  	tests := []struct {
   805  		description       string
   806  		giveErrors        []error
   807  		wantCode          int
   808  		wantMetricsStatus metrics.RequestStatus
   809  	}{
   810  		{
   811  			description: "Blocked account - return 503 with blocked metrics status",
   812  			giveErrors: []error{
   813  				&errortypes.AccountDisabled{},
   814  			},
   815  			wantCode:          503,
   816  			wantMetricsStatus: metrics.RequestStatusBlacklisted,
   817  		},
   818  		{
   819  			description: "Blocked app - return 503 with blocked metrics status",
   820  			giveErrors: []error{
   821  				&errortypes.BlacklistedApp{},
   822  			},
   823  			wantCode:          503,
   824  			wantMetricsStatus: metrics.RequestStatusBlacklisted,
   825  		},
   826  		{
   827  			description: "Account required error - return 400 with bad input metrics status",
   828  			giveErrors: []error{
   829  				&errortypes.AcctRequired{},
   830  			},
   831  			wantCode:          400,
   832  			wantMetricsStatus: metrics.RequestStatusBadInput,
   833  		},
   834  		{
   835  			description: "Malformed account config error - return 500 with account config error metrics status",
   836  			giveErrors: []error{
   837  				&errortypes.MalformedAcct{},
   838  			},
   839  			wantCode:          500,
   840  			wantMetricsStatus: metrics.RequestStatusAccountConfigErr,
   841  		},
   842  		{
   843  			description: "Multiple generic errors - return 500 with generic error metrics status",
   844  			giveErrors: []error{
   845  				errors.New("Error for testing handleError 1"),
   846  				errors.New("Error for testing handleError 2"),
   847  			},
   848  			wantCode:          500,
   849  			wantMetricsStatus: metrics.RequestStatusErr,
   850  		},
   851  	}
   852  
   853  	for _, tt := range tests {
   854  		vo := analytics.VideoObject{
   855  			Status: 200,
   856  			Errors: make([]error, 0),
   857  		}
   858  
   859  		labels := metrics.Labels{
   860  			Source:        metrics.DemandUnknown,
   861  			RType:         metrics.ReqTypeVideo,
   862  			PubID:         metrics.PublisherUnknown,
   863  			CookieFlag:    metrics.CookieFlagUnknown,
   864  			RequestStatus: metrics.RequestStatusOK,
   865  		}
   866  
   867  		recorder := httptest.NewRecorder()
   868  		handleError(&labels, recorder, tt.giveErrors, &vo, nil)
   869  
   870  		assert.Equal(t, tt.wantMetricsStatus, labels.RequestStatus, tt.description)
   871  		assert.Equal(t, tt.wantCode, recorder.Code, tt.description)
   872  		assert.Equal(t, tt.wantCode, vo.Status, tt.description)
   873  		assert.ElementsMatch(t, tt.giveErrors, vo.Errors, tt.description)
   874  	}
   875  }
   876  
   877  func TestHandleErrorMetrics(t *testing.T) {
   878  	ex := &mockExchangeVideo{}
   879  	reqBody := readVideoTestFile(t, "sample-requests/video/video_invalid_sample.json")
   880  	req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
   881  	recorder := httptest.NewRecorder()
   882  
   883  	deps, met, mod := mockDepsWithMetrics(t, ex)
   884  	deps.VideoAuctionEndpoint(recorder, req, nil)
   885  
   886  	assert.Equal(t, int64(0), met.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusOK].Count(), "OK requests count should be 0")
   887  	assert.Equal(t, int64(1), met.RequestStatuses[metrics.ReqTypeVideo][metrics.RequestStatusErr].Count(), "Error requests count should be 1")
   888  	assert.Equal(t, 1, len(mod.videoObjects), "Mock AnalyticsModule should have 1 AuctionObject")
   889  	assert.Equal(t, 500, mod.videoObjects[0].Status, "AnalyticsObject should have 500 status")
   890  	assert.Equal(t, 2, len(mod.videoObjects[0].Errors), "AnalyticsObject should have Errors length of 2")
   891  	assert.Equal(t, "request missing required field: PodConfig.DurationRangeSec", mod.videoObjects[0].Errors[0].Error(), "First error in AnalyticsObject should have message regarding DurationRangeSec")
   892  	assert.Equal(t, "request missing required field: PodConfig.Pods", mod.videoObjects[0].Errors[1].Error(), "Second error in AnalyticsObject should have message regarding Pods")
   893  }
   894  
   895  func TestParseVideoRequestWithUserAgentAndHeader(t *testing.T) {
   896  	ex := &mockExchangeVideo{}
   897  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_with_device_user_agent.json")
   898  	headers := http.Header{}
   899  	headers.Add("User-Agent", "TestHeader")
   900  
   901  	deps := mockDeps(t, ex)
   902  	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
   903  
   904  	assert.Equal(t, "TestHeaderSample", req.Device.UA, "Header should be taken from original request")
   905  	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
   906  	assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned")
   907  }
   908  
   909  func TestParseVideoRequestWithUserAgentAndEmptyHeader(t *testing.T) {
   910  	ex := &mockExchangeVideo{}
   911  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_with_device_user_agent.json")
   912  
   913  	headers := http.Header{}
   914  
   915  	deps := mockDeps(t, ex)
   916  	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
   917  
   918  	assert.Equal(t, "TestHeaderSample", req.Device.UA, "Header should be taken from original request")
   919  	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
   920  	assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned")
   921  }
   922  
   923  func TestParseVideoRequestWithoutUserAgentWithHeader(t *testing.T) {
   924  	ex := &mockExchangeVideo{}
   925  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_without_device_user_agent.json")
   926  	headers := http.Header{}
   927  	headers.Add("User-Agent", "TestHeader")
   928  
   929  	deps := mockDeps(t, ex)
   930  	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
   931  
   932  	assert.Equal(t, "TestHeader", req.Device.UA, "Device.ua should be taken from request header")
   933  	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
   934  	assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned")
   935  }
   936  
   937  func TestParseVideoRequestWithoutUserAgentAndEmptyHeader(t *testing.T) {
   938  	ex := &mockExchangeVideo{}
   939  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_without_device_user_agent.json")
   940  
   941  	headers := http.Header{}
   942  
   943  	deps := mockDeps(t, ex)
   944  
   945  	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
   946  
   947  	assert.Equal(t, "", req.Device.UA, "Device.ua should be empty")
   948  	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
   949  	assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned")
   950  }
   951  
   952  func TestParseVideoRequestWithEncodedUserAgentInHeader(t *testing.T) {
   953  	ex := &mockExchangeVideo{}
   954  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_without_device_user_agent.json")
   955  
   956  	uaEncoded := "Mozilla%2F5.0%20%28Macintosh%3B%20Intel%20Mac%20OS%20X%2010_14_6%29%20AppleWebKit%2F537.36%20%28KHTML%2C%20like%20Gecko%29%20Chrome%2F78.0.3904.87%20Safari%2F537.36"
   957  	uaDecoded := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36"
   958  
   959  	headers := http.Header{}
   960  	headers.Add("User-Agent", uaEncoded)
   961  
   962  	deps := mockDeps(t, ex)
   963  	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
   964  
   965  	assert.Equal(t, uaDecoded, req.Device.UA, "Device.ua should be taken from request header")
   966  	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
   967  	assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned")
   968  }
   969  
   970  func TestParseVideoRequestWithDecodedUserAgentInHeader(t *testing.T) {
   971  	ex := &mockExchangeVideo{}
   972  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_without_device_user_agent.json")
   973  
   974  	uaDecoded := "Mozilla/5.0+(Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36"
   975  
   976  	headers := http.Header{}
   977  	headers.Add("User-Agent", uaDecoded)
   978  
   979  	deps := mockDeps(t, ex)
   980  	req, valErr, podErr := deps.parseVideoRequest([]byte(reqBody), headers)
   981  
   982  	assert.Equal(t, uaDecoded, req.Device.UA, "Device.ua should be taken from request header")
   983  	assert.Equal(t, []error(nil), valErr, "No validation errors should be returned")
   984  	assert.Equal(t, make([]PodError, 0), podErr, "No pod errors should be returned")
   985  }
   986  
   987  func TestHandleErrorDebugLog(t *testing.T) {
   988  	vo := analytics.VideoObject{
   989  		Status: 200,
   990  		Errors: make([]error, 0),
   991  	}
   992  
   993  	labels := metrics.Labels{
   994  		Source:        metrics.DemandUnknown,
   995  		RType:         metrics.ReqTypeVideo,
   996  		PubID:         metrics.PublisherUnknown,
   997  		CookieFlag:    metrics.CookieFlagUnknown,
   998  		RequestStatus: metrics.RequestStatusOK,
   999  	}
  1000  
  1001  	recorder := httptest.NewRecorder()
  1002  	err1 := errors.New("Error for testing handleError 1")
  1003  	err2 := errors.New("Error for testing handleError 2")
  1004  	debugLog := exchange.DebugLog{
  1005  		Enabled:   true,
  1006  		CacheType: prebid_cache_client.TypeXML,
  1007  		Data: exchange.DebugData{
  1008  			Request:  "test request string",
  1009  			Headers:  "test headers string",
  1010  			Response: "test response string",
  1011  		},
  1012  		TTL:                      int64(3600),
  1013  		Regexp:                   regexp.MustCompile(`[<>]`),
  1014  		DebugOverride:            false,
  1015  		DebugEnabledOrOverridden: true,
  1016  	}
  1017  	handleError(&labels, recorder, []error{err1, err2}, &vo, &debugLog)
  1018  
  1019  	assert.Equal(t, metrics.RequestStatusErr, labels.RequestStatus, "labels.RequestStatus should indicate an error")
  1020  	assert.Equal(t, 500, recorder.Code, "Error status should be written to writer")
  1021  	assert.Equal(t, 500, vo.Status, "Analytics object should have error status")
  1022  	assert.Equal(t, 3, len(vo.Errors), "New errors including debug cache ID should be appended to Analytics object Errors")
  1023  	assert.Equal(t, "Error for testing handleError 1", vo.Errors[0].Error(), "Error in Analytics object should have test error message for first error")
  1024  	assert.Equal(t, "Error for testing handleError 2", vo.Errors[1].Error(), "Error in Analytics object should have test error message for second error")
  1025  	assert.NotEmpty(t, debugLog.CacheKey, "DebugLog CacheKey value should have been set")
  1026  }
  1027  
  1028  func TestCreateImpressionTemplate(t *testing.T) {
  1029  	imp := openrtb2.Imp{}
  1030  	imp.Video = &openrtb2.Video{}
  1031  	imp.Video.Protocols = []adcom1.MediaCreativeSubtype{1, 2}
  1032  	imp.Video.MIMEs = []string{"video/mp4"}
  1033  	imp.Video.H = ptrutil.ToPtr[int64](200)
  1034  	imp.Video.W = ptrutil.ToPtr[int64](400)
  1035  	imp.Video.PlaybackMethod = []adcom1.PlaybackMethod{5, 6}
  1036  
  1037  	video := openrtb2.Video{}
  1038  	video.Protocols = []adcom1.MediaCreativeSubtype{3, 4}
  1039  	video.MIMEs = []string{"video/flv"}
  1040  	video.H = ptrutil.ToPtr[int64](300)
  1041  	video.W = ptrutil.ToPtr[int64](0)
  1042  	video.PlaybackMethod = []adcom1.PlaybackMethod{7, 8}
  1043  
  1044  	res := createImpressionTemplate(imp, &video)
  1045  	assert.Equal(t, []adcom1.MediaCreativeSubtype{3, 4}, res.Video.Protocols, "Incorrect video protocols")
  1046  	assert.Equal(t, []string{"video/flv"}, res.Video.MIMEs, "Incorrect video MIMEs")
  1047  	assert.Equal(t, ptrutil.ToPtr[int64](300), res.Video.H, "Incorrect video height")
  1048  	assert.Equal(t, ptrutil.ToPtr[int64](0), res.Video.W, "Incorrect video width")
  1049  	assert.Equal(t, []adcom1.PlaybackMethod{7, 8}, res.Video.PlaybackMethod, "Incorrect video playback method")
  1050  }
  1051  
  1052  func TestCCPA(t *testing.T) {
  1053  	testCases := []struct {
  1054  		description         string
  1055  		testFilePath        string
  1056  		expectConsentString bool
  1057  		expectEmptyConsent  bool
  1058  	}{
  1059  		{
  1060  			description:         "Missing Consent",
  1061  			testFilePath:        "sample-requests/video/video_valid_sample.json",
  1062  			expectConsentString: false,
  1063  			expectEmptyConsent:  true,
  1064  		},
  1065  		{
  1066  			description:         "Valid Consent",
  1067  			testFilePath:        "sample-requests/video/video_valid_sample_ccpa_valid.json",
  1068  			expectConsentString: true,
  1069  		},
  1070  		{
  1071  			description:         "Malformed Consent",
  1072  			testFilePath:        "sample-requests/video/video_valid_sample_ccpa_malformed.json",
  1073  			expectConsentString: false,
  1074  		},
  1075  	}
  1076  
  1077  	for _, test := range testCases {
  1078  		reqBody := readVideoTestFile(t, test.testFilePath)
  1079  
  1080  		// Create HTTP Request + Response Recorder
  1081  		httpRequest := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
  1082  		httpResponseRecorder := httptest.NewRecorder()
  1083  
  1084  		// Run Test
  1085  		ex := &mockExchangeVideo{}
  1086  		mockDeps(t, ex).VideoAuctionEndpoint(httpResponseRecorder, httpRequest, nil)
  1087  
  1088  		// Validate Request To Exchange
  1089  		// - An error should never be generated for CCPA problems.
  1090  		if ex.lastRequest == nil {
  1091  			t.Fatalf("%s: The request never made it into the exchange.", test.description)
  1092  		}
  1093  		extRegs := &openrtb_ext.ExtRegs{}
  1094  		if err := jsonutil.UnmarshalValid(ex.lastRequest.Regs.Ext, extRegs); err != nil {
  1095  			t.Fatalf("%s: Failed to unmarshal reg.ext in request to the exchange: %v", test.description, err)
  1096  		}
  1097  		if test.expectConsentString {
  1098  			assert.Len(t, extRegs.USPrivacy, 4, test.description+":consent")
  1099  		} else if test.expectEmptyConsent {
  1100  			assert.Empty(t, extRegs.USPrivacy, test.description+":consent")
  1101  		}
  1102  
  1103  		// Validate HTTP Response
  1104  		responseBytes := httpResponseRecorder.Body.Bytes()
  1105  		response := &openrtb_ext.BidResponseVideo{}
  1106  		if err := jsonutil.UnmarshalValid(responseBytes, response); err != nil {
  1107  			t.Fatalf("%s: Unable to unmarshal response.", test.description)
  1108  		}
  1109  		assert.Len(t, ex.lastRequest.Imp, 11, test.description+":imps")
  1110  		assert.Len(t, response.AdPods, 5, test.description+":adpods")
  1111  	}
  1112  }
  1113  
  1114  func TestVideoEndpointAppendBidderNames(t *testing.T) {
  1115  	ex := &mockExchangeAppendBidderNames{}
  1116  	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_appendbiddernames.json")
  1117  	req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
  1118  	recorder := httptest.NewRecorder()
  1119  
  1120  	deps := mockDepsAppendBidderNames(t, ex)
  1121  	deps.VideoAuctionEndpoint(recorder, req, nil)
  1122  
  1123  	if ex.lastRequest == nil {
  1124  		t.Fatalf("The request never made it into the Exchange.")
  1125  	}
  1126  
  1127  	var extData openrtb_ext.ExtRequest
  1128  	jsonutil.UnmarshalValid(ex.lastRequest.Ext, &extData)
  1129  	assert.True(t, extData.Prebid.Targeting.AppendBidderNames, "Request ext incorrect: AppendBidderNames should be true ")
  1130  
  1131  	respBytes := recorder.Body.Bytes()
  1132  	resp := &openrtb_ext.BidResponseVideo{}
  1133  	if err := jsonutil.UnmarshalValid(respBytes, resp); err != nil {
  1134  		t.Fatalf("Unable to unmarshal response.")
  1135  	}
  1136  
  1137  	assert.Len(t, ex.lastRequest.Imp, 11, "Incorrect number of impressions in request")
  1138  	assert.Equal(t, "prebid.com", string(ex.lastRequest.Site.Page), "Incorrect site page in request")
  1139  	assert.Equal(t, "TvName", ex.lastRequest.Site.Content.Series, "Incorrect site content series in request")
  1140  
  1141  	assert.Len(t, resp.AdPods, 5, "Incorrect number of Ad Pods in response")
  1142  	assert.Len(t, resp.AdPods[0].Targeting, 4, "Incorrect Targeting data in response")
  1143  	assert.Len(t, resp.AdPods[1].Targeting, 3, "Incorrect Targeting data in response")
  1144  	assert.Len(t, resp.AdPods[2].Targeting, 5, "Incorrect Targeting data in response")
  1145  	assert.Len(t, resp.AdPods[3].Targeting, 1, "Incorrect Targeting data in response")
  1146  	assert.Len(t, resp.AdPods[4].Targeting, 3, "Incorrect Targeting data in response")
  1147  
  1148  	assert.Equal(t, "20.00_395_30s_appnexus", resp.AdPods[4].Targeting[0].HbPbCatDur, "Incorrect number of Ad Pods in response")
  1149  }
  1150  
  1151  func TestFormatTargetingKey(t *testing.T) {
  1152  	res := formatTargetingKey(openrtb_ext.HbCategoryDurationKey, "appnexus")
  1153  	assert.Equal(t, "hb_pb_cat_dur_appnex", res, "Tergeting key constructed incorrectly")
  1154  }
  1155  
  1156  func TestFormatTargetingKeyLongKey(t *testing.T) {
  1157  	res := formatTargetingKey(openrtb_ext.HbpbConstantKey, "20.00")
  1158  	assert.Equal(t, "hb_pb_20.00", res, "Tergeting key constructed incorrectly")
  1159  }
  1160  
  1161  func TestVideoAuctionResponseHeaders(t *testing.T) {
  1162  	testCases := []struct {
  1163  		description     string
  1164  		givenTestFile   string
  1165  		givenHeader     map[string]string
  1166  		expectedStatus  int
  1167  		expectedHeaders func(http.Header)
  1168  	}{
  1169  		{
  1170  			description:    "Success Response",
  1171  			givenTestFile:  "sample-requests/video/video_valid_sample.json",
  1172  			expectedStatus: 200,
  1173  			expectedHeaders: func(h http.Header) {
  1174  				h.Set("X-Prebid", "pbs-go/unknown")
  1175  				h.Set("Content-Type", "application/json")
  1176  			},
  1177  		},
  1178  		{
  1179  			description:    "Failure Response",
  1180  			givenTestFile:  "sample-requests/video/video_invalid_sample.json",
  1181  			expectedStatus: 500,
  1182  			expectedHeaders: func(h http.Header) {
  1183  				h.Set("X-Prebid", "pbs-go/unknown")
  1184  			},
  1185  		},
  1186  		{
  1187  			description:    "Success Response with header Observe-Browsing-Topics",
  1188  			givenTestFile:  "sample-requests/video/video_valid_sample.json",
  1189  			givenHeader:    map[string]string{secBrowsingTopics: "anyValue"},
  1190  			expectedStatus: 200,
  1191  			expectedHeaders: func(h http.Header) {
  1192  				h.Set("X-Prebid", "pbs-go/unknown")
  1193  				h.Set("Content-Type", "application/json")
  1194  				h.Set("Observe-Browsing-Topics", "?1")
  1195  			},
  1196  		},
  1197  		{
  1198  			description:    "Failure Response with header Observe-Browsing-Topics",
  1199  			givenTestFile:  "sample-requests/video/video_invalid_sample.json",
  1200  			givenHeader:    map[string]string{secBrowsingTopics: "anyValue"},
  1201  			expectedStatus: 500,
  1202  			expectedHeaders: func(h http.Header) {
  1203  				h.Set("X-Prebid", "pbs-go/unknown")
  1204  				h.Set("Observe-Browsing-Topics", "?1")
  1205  			},
  1206  		},
  1207  	}
  1208  
  1209  	exchange := &mockExchangeVideo{}
  1210  	endpoint := mockDeps(t, exchange)
  1211  
  1212  	for _, test := range testCases {
  1213  		requestBody := readVideoTestFile(t, test.givenTestFile)
  1214  
  1215  		httpReq := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(requestBody))
  1216  		for k, v := range test.givenHeader {
  1217  			httpReq.Header.Add(k, v)
  1218  		}
  1219  		recorder := httptest.NewRecorder()
  1220  
  1221  		endpoint.VideoAuctionEndpoint(recorder, httpReq, nil)
  1222  
  1223  		expectedHeaders := http.Header{}
  1224  		test.expectedHeaders(expectedHeaders)
  1225  
  1226  		assert.Equal(t, test.expectedStatus, recorder.Result().StatusCode, test.description+":statuscode")
  1227  		assert.Equal(t, expectedHeaders, recorder.Result().Header, test.description+":statuscode")
  1228  	}
  1229  }
  1230  
  1231  func mockDepsWithMetrics(t *testing.T, ex *mockExchangeVideo) (*endpointDeps, *metrics.Metrics, *mockAnalyticsModule) {
  1232  	mockModule := &mockAnalyticsModule{}
  1233  
  1234  	metrics := metrics.NewMetrics(gometrics.NewRegistry(), openrtb_ext.CoreBidderNames(), config.DisabledMetrics{}, nil, nil)
  1235  
  1236  	deps := &endpointDeps{
  1237  		fakeUUIDGenerator{},
  1238  		ex,
  1239  		mockBidderParamValidator{},
  1240  		&mockVideoStoredReqFetcher{},
  1241  		&mockVideoStoredReqFetcher{},
  1242  		&mockAccountFetcher{data: mockVideoAccountData},
  1243  		&config.Configuration{MaxRequestSize: maxSize},
  1244  		metrics,
  1245  		mockModule,
  1246  		map[string]string{},
  1247  		false,
  1248  		[]byte{},
  1249  		openrtb_ext.BuildBidderMap(),
  1250  		nil,
  1251  		nil,
  1252  		hardcodedResponseIPValidator{response: true},
  1253  		empty_fetcher.EmptyFetcher{},
  1254  		hooks.EmptyPlanBuilder{},
  1255  		nil,
  1256  		openrtb_ext.NormalizeBidderName,
  1257  	}
  1258  	return deps, metrics, mockModule
  1259  }
  1260  
  1261  type mockAnalyticsModule struct {
  1262  	auctionObjects []*analytics.AuctionObject
  1263  	videoObjects   []*analytics.VideoObject
  1264  }
  1265  
  1266  func (m *mockAnalyticsModule) LogAuctionObject(ao *analytics.AuctionObject, _ privacy.ActivityControl) {
  1267  	m.auctionObjects = append(m.auctionObjects, ao)
  1268  }
  1269  
  1270  func (m *mockAnalyticsModule) LogVideoObject(vo *analytics.VideoObject, _ privacy.ActivityControl) {
  1271  	m.videoObjects = append(m.videoObjects, vo)
  1272  }
  1273  
  1274  func (m *mockAnalyticsModule) LogCookieSyncObject(cso *analytics.CookieSyncObject) {}
  1275  
  1276  func (m *mockAnalyticsModule) LogSetUIDObject(so *analytics.SetUIDObject) {}
  1277  
  1278  func (m *mockAnalyticsModule) LogAmpObject(ao *analytics.AmpObject, _ privacy.ActivityControl) {
  1279  }
  1280  
  1281  func (m *mockAnalyticsModule) LogNotificationEventObject(ne *analytics.NotificationEvent, _ privacy.ActivityControl) {
  1282  }
  1283  
  1284  func mockDeps(t *testing.T, ex *mockExchangeVideo) *endpointDeps {
  1285  	return &endpointDeps{
  1286  		fakeUUIDGenerator{},
  1287  		ex,
  1288  		mockBidderParamValidator{},
  1289  		&mockVideoStoredReqFetcher{},
  1290  		&mockVideoStoredReqFetcher{},
  1291  		&mockAccountFetcher{data: mockVideoAccountData},
  1292  		&config.Configuration{MaxRequestSize: maxSize},
  1293  		&metricsConfig.NilMetricsEngine{},
  1294  		analyticsBuild.New(&config.Analytics{}),
  1295  		map[string]string{},
  1296  		false,
  1297  		[]byte{},
  1298  		openrtb_ext.BuildBidderMap(),
  1299  		ex.cache,
  1300  		regexp.MustCompile(`[<>]`),
  1301  		hardcodedResponseIPValidator{response: true},
  1302  		empty_fetcher.EmptyFetcher{},
  1303  		hooks.EmptyPlanBuilder{},
  1304  		nil,
  1305  		openrtb_ext.NormalizeBidderName,
  1306  	}
  1307  }
  1308  
  1309  func mockDepsAppendBidderNames(t *testing.T, ex *mockExchangeAppendBidderNames) *endpointDeps {
  1310  	deps := &endpointDeps{
  1311  		fakeUUIDGenerator{},
  1312  		ex,
  1313  		mockBidderParamValidator{},
  1314  		&mockVideoStoredReqFetcher{},
  1315  		&mockVideoStoredReqFetcher{},
  1316  		empty_fetcher.EmptyFetcher{},
  1317  		&config.Configuration{MaxRequestSize: maxSize},
  1318  		&metricsConfig.NilMetricsEngine{},
  1319  		analyticsBuild.New(&config.Analytics{}),
  1320  		map[string]string{},
  1321  		false,
  1322  		[]byte{},
  1323  		openrtb_ext.BuildBidderMap(),
  1324  		ex.cache,
  1325  		regexp.MustCompile(`[<>]`),
  1326  		hardcodedResponseIPValidator{response: true},
  1327  		empty_fetcher.EmptyFetcher{},
  1328  		hooks.EmptyPlanBuilder{},
  1329  		nil,
  1330  		openrtb_ext.NormalizeBidderName,
  1331  	}
  1332  
  1333  	return deps
  1334  }
  1335  
  1336  func mockDepsNoBids(t *testing.T, ex *mockExchangeVideoNoBids) *endpointDeps {
  1337  	edep := &endpointDeps{
  1338  		fakeUUIDGenerator{},
  1339  		ex,
  1340  		mockBidderParamValidator{},
  1341  		&mockVideoStoredReqFetcher{},
  1342  		&mockVideoStoredReqFetcher{},
  1343  		empty_fetcher.EmptyFetcher{},
  1344  		&config.Configuration{MaxRequestSize: maxSize},
  1345  		&metricsConfig.NilMetricsEngine{},
  1346  		analyticsBuild.New(&config.Analytics{}),
  1347  		map[string]string{},
  1348  		false,
  1349  		[]byte{},
  1350  		openrtb_ext.BuildBidderMap(),
  1351  		ex.cache,
  1352  		regexp.MustCompile(`[<>]`),
  1353  		hardcodedResponseIPValidator{response: true},
  1354  		empty_fetcher.EmptyFetcher{},
  1355  		hooks.EmptyPlanBuilder{},
  1356  		nil,
  1357  		openrtb_ext.NormalizeBidderName,
  1358  	}
  1359  
  1360  	return edep
  1361  }
  1362  
  1363  type mockCacheClient struct {
  1364  	called bool
  1365  }
  1366  
  1367  func (m *mockCacheClient) PutJson(ctx context.Context, values []prebid_cache_client.Cacheable) ([]string, []error) {
  1368  	if !m.called {
  1369  		m.called = true
  1370  	}
  1371  	return []string{}, []error{}
  1372  }
  1373  
  1374  func (m *mockCacheClient) GetExtCacheData() (scheme string, host string, path string) {
  1375  	return "", "", ""
  1376  }
  1377  
  1378  type mockVideoStoredReqFetcher struct {
  1379  }
  1380  
  1381  func (cf mockVideoStoredReqFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) {
  1382  	return testVideoStoredRequestData, testVideoStoredImpData, nil
  1383  }
  1384  
  1385  func (cf mockVideoStoredReqFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
  1386  	return nil, nil
  1387  }
  1388  
  1389  type mockExchangeVideo struct {
  1390  	lastRequest *openrtb2.BidRequest
  1391  	cache       *mockCacheClient
  1392  }
  1393  
  1394  func (m *mockExchangeVideo) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
  1395  	if err := r.BidRequestWrapper.RebuildRequest(); err != nil {
  1396  		return nil, err
  1397  	}
  1398  
  1399  	m.lastRequest = r.BidRequestWrapper.BidRequest
  1400  	if debugLog != nil && debugLog.Enabled {
  1401  		m.cache.called = true
  1402  	}
  1403  	ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1", "hb_deal_appnexus": "ABC_123"},"type":"video","dealpriority":0,"dealtiersatisfied":false},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`)
  1404  	return &exchange.AuctionResponse{BidResponse: &openrtb2.BidResponse{
  1405  		SeatBid: []openrtb2.SeatBid{{
  1406  			Seat: "appnexus",
  1407  			Bid: []openrtb2.Bid{
  1408  				{ID: "01", ImpID: "1_0", Ext: ext},
  1409  				{ID: "02", ImpID: "1_1", Ext: ext},
  1410  				{ID: "03", ImpID: "1_2", Ext: ext},
  1411  				{ID: "04", ImpID: "1_3", Ext: ext},
  1412  				{ID: "05", ImpID: "2_0", Ext: ext},
  1413  				{ID: "06", ImpID: "2_1", Ext: ext},
  1414  				{ID: "07", ImpID: "2_2", Ext: ext},
  1415  				{ID: "08", ImpID: "3_0", Ext: ext},
  1416  				{ID: "09", ImpID: "3_1", Ext: ext},
  1417  				{ID: "10", ImpID: "3_2", Ext: ext},
  1418  				{ID: "11", ImpID: "3_3", Ext: ext},
  1419  				{ID: "12", ImpID: "3_5", Ext: ext},
  1420  				{ID: "13", ImpID: "4_0", Ext: ext},
  1421  				{ID: "14", ImpID: "5_0", Ext: ext},
  1422  				{ID: "15", ImpID: "5_1", Ext: ext},
  1423  				{ID: "16", ImpID: "5_2", Ext: ext},
  1424  			},
  1425  		}},
  1426  	}}, nil
  1427  }
  1428  
  1429  type mockExchangeAppendBidderNames struct {
  1430  	lastRequest *openrtb2.BidRequest
  1431  	cache       *mockCacheClient
  1432  }
  1433  
  1434  func (m *mockExchangeAppendBidderNames) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
  1435  	m.lastRequest = r.BidRequestWrapper.BidRequest
  1436  	if debugLog != nil && debugLog.Enabled {
  1437  		m.cache.called = true
  1438  	}
  1439  	ext := []byte(`{"prebid":{"targeting":{"hb_bidder_appnexus":"appnexus","hb_pb_appnexus":"20.00","hb_pb_cat_dur_appnex":"20.00_395_30s_appnexus","hb_size":"1x1", "hb_uuid_appnexus":"837ea3b7-5598-4958-8c45-8e9ef2bf7cc1"},"type":"video"},"bidder":{"appnexus":{"brand_id":1,"auction_id":7840037870526938650,"bidder_id":2,"bid_ad_type":1,"creative_info":{"video":{"duration":30,"mimes":["video\/mp4"]}}}}}`)
  1440  	return &exchange.AuctionResponse{BidResponse: &openrtb2.BidResponse{
  1441  		SeatBid: []openrtb2.SeatBid{{
  1442  			Seat: "appnexus",
  1443  			Bid: []openrtb2.Bid{
  1444  				{ID: "01", ImpID: "1_0", Ext: ext},
  1445  				{ID: "02", ImpID: "1_1", Ext: ext},
  1446  				{ID: "03", ImpID: "1_2", Ext: ext},
  1447  				{ID: "04", ImpID: "1_3", Ext: ext},
  1448  				{ID: "05", ImpID: "2_0", Ext: ext},
  1449  				{ID: "06", ImpID: "2_1", Ext: ext},
  1450  				{ID: "07", ImpID: "2_2", Ext: ext},
  1451  				{ID: "08", ImpID: "3_0", Ext: ext},
  1452  				{ID: "09", ImpID: "3_1", Ext: ext},
  1453  				{ID: "10", ImpID: "3_2", Ext: ext},
  1454  				{ID: "11", ImpID: "3_3", Ext: ext},
  1455  				{ID: "12", ImpID: "3_5", Ext: ext},
  1456  				{ID: "13", ImpID: "4_0", Ext: ext},
  1457  				{ID: "14", ImpID: "5_0", Ext: ext},
  1458  				{ID: "15", ImpID: "5_1", Ext: ext},
  1459  				{ID: "16", ImpID: "5_2", Ext: ext},
  1460  			},
  1461  		}}},
  1462  	}, nil
  1463  }
  1464  
  1465  type mockExchangeVideoNoBids struct {
  1466  	lastRequest *openrtb2.BidRequest
  1467  	cache       *mockCacheClient
  1468  }
  1469  
  1470  func (m *mockExchangeVideoNoBids) HoldAuction(ctx context.Context, r *exchange.AuctionRequest, debugLog *exchange.DebugLog) (*exchange.AuctionResponse, error) {
  1471  	m.lastRequest = r.BidRequestWrapper.BidRequest
  1472  	return &exchange.AuctionResponse{BidResponse: &openrtb2.BidResponse{
  1473  		SeatBid: []openrtb2.SeatBid{{}},
  1474  	}}, nil
  1475  }
  1476  
  1477  var mockVideoAccountData = map[string]json.RawMessage{
  1478  	"valid_acct":     json.RawMessage(`{"disabled":false}`),
  1479  	"disabled_acct":  json.RawMessage(`{"disabled":true}`),
  1480  	"malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`),
  1481  }
  1482  
  1483  var testVideoStoredImpData = map[string]json.RawMessage{
  1484  	"fba10607-0c12-43d1-ad07-b8a513bc75d6": json.RawMessage(`{"ext": {"appnexus": {"placementId": 14997137}}}`),
  1485  	"8b452b41-2681-4a20-9086-6f16ffad7773": json.RawMessage(`{"ext": {"appnexus": {"placementId": 15016213}}}`),
  1486  	"87d82a45-35c3-46cc-9315-2e3eeb91d0f2": json.RawMessage(`{"ext": {"appnexus": {"placementId": 15062775}}}`),
  1487  }
  1488  
  1489  var testVideoStoredRequestData = map[string]json.RawMessage{
  1490  	"80ce30c53c16e6ede735f123ef6e32361bfc7b22": json.RawMessage(`{"accountid": "11223344", "site": {"page": "mygame.foo.com"}}`),
  1491  }
  1492  
  1493  func readVideoTestFile(t *testing.T, filename string) string {
  1494  	requestData, err := os.ReadFile(filename)
  1495  	if err != nil {
  1496  		t.Fatalf("Failed to fetch a valid request: %v", err)
  1497  	}
  1498  
  1499  	return string(getRequestPayload(t, requestData))
  1500  }
  1501  
  1502  func TestVideoRequestValidationFailed(t *testing.T) {
  1503  	ex := &mockExchangeVideo{}
  1504  	reqBody := readVideoTestFile(t, "sample-requests/video/video_invalid_sample_negative_tmax.json")
  1505  	req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
  1506  	recorder := httptest.NewRecorder()
  1507  
  1508  	deps := mockDeps(t, ex)
  1509  	deps.VideoAuctionEndpoint(recorder, req, nil)
  1510  
  1511  	errorMessage := recorder.Body.String()
  1512  
  1513  	assert.Equal(t, 500, recorder.Code, "Should catch error in request")
  1514  	assert.Equal(t, "Critical error while running the video endpoint:  request.tmax must be nonnegative. Got -2", errorMessage, "Incorrect request validation message")
  1515  }