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