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 }