github.com/prebid/prebid-server@v0.275.0/endpoints/openrtb2/auction_test.go (about) 1 package openrtb2 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "context" 7 "encoding/json" 8 "errors" 9 "io" 10 "io/fs" 11 "net" 12 "net/http" 13 "net/http/httptest" 14 "os" 15 "path/filepath" 16 "strings" 17 "testing" 18 "time" 19 20 "github.com/buger/jsonparser" 21 "github.com/julienschmidt/httprouter" 22 "github.com/prebid/openrtb/v19/native1" 23 nativeRequests "github.com/prebid/openrtb/v19/native1/request" 24 "github.com/prebid/openrtb/v19/openrtb2" 25 "github.com/prebid/prebid-server/analytics" 26 analyticsConf "github.com/prebid/prebid-server/analytics/config" 27 "github.com/prebid/prebid-server/config" 28 "github.com/prebid/prebid-server/errortypes" 29 "github.com/prebid/prebid-server/exchange" 30 "github.com/prebid/prebid-server/hooks" 31 "github.com/prebid/prebid-server/hooks/hookexecution" 32 "github.com/prebid/prebid-server/hooks/hookstage" 33 "github.com/prebid/prebid-server/metrics" 34 metricsConfig "github.com/prebid/prebid-server/metrics/config" 35 "github.com/prebid/prebid-server/openrtb_ext" 36 "github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher" 37 "github.com/prebid/prebid-server/stored_responses" 38 "github.com/prebid/prebid-server/util/iputil" 39 "github.com/prebid/prebid-server/util/ptrutil" 40 "github.com/stretchr/testify/assert" 41 ) 42 43 const jsonFileExtension string = ".json" 44 45 func TestJsonSampleRequests(t *testing.T) { 46 testSuites := []struct { 47 description string 48 sampleRequestsSubDir string 49 }{ 50 { 51 "Assert 200s on all bidRequests from exemplary folder", 52 "valid-whole/exemplary", 53 }, 54 { 55 "Asserts we return 200s on well-formed Native requests.", 56 "valid-native", 57 }, 58 { 59 "Asserts we return 400s on requests that are not supposed to pass validation", 60 "invalid-whole", 61 }, 62 { 63 "Asserts we return 400s on requests with Native requests that don't pass validation", 64 "invalid-native", 65 }, 66 { 67 "Makes sure we handle (default) aliased bidders properly", 68 "aliased", 69 }, 70 { 71 "Asserts we return 500s on requests referencing accounts with malformed configs.", 72 "account-malformed", 73 }, 74 { 75 "Asserts we return 503s on requests with blacklisted accounts and apps.", 76 "blacklisted", 77 }, 78 { 79 "Assert that requests that come with no user id nor app id return error if the `AccountRequired` field in the `config.Configuration` structure is set to true", 80 "account-required/no-account", 81 }, 82 { 83 "Assert requests that come with a valid user id or app id when account is required", 84 "account-required/with-account", 85 }, 86 { 87 "Tests diagnostic messages for invalid stored requests", 88 "invalid-stored", 89 }, 90 { 91 "Make sure requests with disabled bidders will fail", 92 "disabled/bad", 93 }, 94 { 95 "There are both disabled and non-disabled bidders, we expect a 200", 96 "disabled/good", 97 }, 98 { 99 "Assert we correctly use the server conversion rates when needed", 100 "currency-conversion/server-rates/valid", 101 }, 102 { 103 "Assert we correctly throw an error when no conversion rate was found in the server conversions map", 104 "currency-conversion/server-rates/errors", 105 }, 106 { 107 "Assert we correctly use request-defined custom currency rates when present in root.ext", 108 "currency-conversion/custom-rates/valid", 109 }, 110 { 111 "Assert we correctly validate request-defined custom currency rates when present in root.ext", 112 "currency-conversion/custom-rates/errors", 113 }, 114 { 115 "Assert request with ad server targeting is processing correctly", 116 "adservertargeting", 117 }, 118 { 119 "Assert request with bid adjustments defined is processing correctly", 120 "bidadjustments", 121 }, 122 } 123 124 for _, tc := range testSuites { 125 err := filepath.WalkDir(filepath.Join("sample-requests", tc.sampleRequestsSubDir), func(path string, info fs.DirEntry, err error) error { 126 // According to documentation, needed to avoid panics 127 if err != nil { 128 return err 129 } 130 131 // Test suite will traverse the directory tree recursively and will only consider files with `json` extension 132 if !info.IsDir() && filepath.Ext(info.Name()) == jsonFileExtension { 133 t.Run(tc.description, func(t *testing.T) { 134 runJsonBasedTest(t, path, tc.description) 135 }) 136 } 137 138 return nil 139 }) 140 assert.NoError(t, err, "Test case %s. Error reading files from directory %s \n", tc.description, tc.sampleRequestsSubDir) 141 } 142 } 143 144 func runJsonBasedTest(t *testing.T, filename, desc string) { 145 t.Helper() 146 147 fileData, err := os.ReadFile(filename) 148 if !assert.NoError(t, err, "Test case %s. Error reading file %s \n", desc, filename) { 149 return 150 } 151 152 // Retrieve test case input and expected output from JSON file 153 test, err := parseTestData(fileData, filename) 154 if !assert.NoError(t, err) { 155 return 156 } 157 158 // Build endpoint for testing. If no error, run test case 159 cfg := &config.Configuration{MaxRequestSize: maxSize} 160 if test.Config != nil { 161 cfg.BlacklistedApps = test.Config.BlacklistedApps 162 cfg.BlacklistedAppMap = test.Config.getBlacklistedAppMap() 163 cfg.BlacklistedAccts = test.Config.BlacklistedAccounts 164 cfg.BlacklistedAcctMap = test.Config.getBlackListedAccountMap() 165 cfg.AccountRequired = test.Config.AccountRequired 166 } 167 cfg.MarshalAccountDefaults() 168 test.endpointType = OPENRTB_ENDPOINT 169 170 auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) 171 if assert.NoError(t, err) { 172 assert.NotPanics(t, func() { runEndToEndTest(t, auctionEndpointHandler, test, fileData, filename) }, filename) 173 } 174 175 // Close servers regardless if the test case was run or not 176 for _, mockBidServer := range mockBidServers { 177 mockBidServer.Close() 178 } 179 mockCurrencyRatesServer.Close() 180 } 181 182 func runEndToEndTest(t *testing.T, auctionEndpointHandler httprouter.Handle, test testCase, fileData []byte, testFile string) { 183 t.Helper() 184 185 // Hit the auction endpoint with the test case configuration and mockBidRequest 186 request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(test.BidRequest)) 187 recorder := httptest.NewRecorder() 188 auctionEndpointHandler(recorder, request, nil) 189 190 // Assertions 191 actualCode := recorder.Code 192 actualJsonBidResponse := recorder.Body.String() 193 assert.Equal(t, test.ExpectedReturnCode, actualCode, "Test failed. Filename: %s \n", testFile) 194 195 // Either assert bid response or expected error 196 if len(test.ExpectedErrorMessage) > 0 { 197 assert.True(t, strings.HasPrefix(actualJsonBidResponse, test.ExpectedErrorMessage), "Actual: %s \nExpected: %s. Filename: %s \n", actualJsonBidResponse, test.ExpectedErrorMessage, testFile) 198 } 199 200 if len(test.ExpectedBidResponse) > 0 { 201 var expectedBidResponse openrtb2.BidResponse 202 var actualBidResponse openrtb2.BidResponse 203 var err error 204 205 err = json.Unmarshal(test.ExpectedBidResponse, &expectedBidResponse) 206 if assert.NoError(t, err, "Could not unmarshal expected bidResponse taken from test file.\n Test file: %s\n Error:%s\n", testFile, err) { 207 err = json.Unmarshal([]byte(actualJsonBidResponse), &actualBidResponse) 208 if assert.NoError(t, err, "Could not unmarshal actual bidResponse from auction.\n Test file: %s\n Error:%s\n", testFile, err) { 209 assertBidResponseEqual(t, testFile, expectedBidResponse, actualBidResponse) 210 } 211 } 212 } 213 } 214 215 func compareWarnings(t *testing.T, expectedBidResponseExt, actualBidResponseExt []byte, warnPath string) { 216 expectedWarnings, _, _, err := jsonparser.Get(expectedBidResponseExt, warnPath) 217 if err != nil && err != jsonparser.KeyPathNotFoundError { 218 assert.Fail(t, "error getting data from response extension") 219 } 220 if len(expectedWarnings) > 0 { 221 actualWarnings, _, _, err := jsonparser.Get(actualBidResponseExt, warnPath) 222 if err != nil && err != jsonparser.KeyPathNotFoundError { 223 assert.Fail(t, "error getting data from response extension") 224 } 225 226 var expectedWarn []openrtb_ext.ExtBidderMessage 227 err = json.Unmarshal(expectedWarnings, &expectedWarn) 228 if err != nil { 229 assert.Fail(t, "error unmarshalling expected warnings data from response extension") 230 } 231 232 var actualWarn []openrtb_ext.ExtBidderMessage 233 err = json.Unmarshal(actualWarnings, &actualWarn) 234 if err != nil { 235 assert.Fail(t, "error unmarshalling actual warnings data from response extension") 236 } 237 238 // warnings from different bidders may be returned in different order. 239 assert.Equal(t, len(expectedWarn), len(actualWarn), "incorrect warnings number") 240 for i, expWarn := range expectedWarn { 241 actualWarning := actualWarn[i] 242 assert.Contains(t, actualWarning.Message, expWarn.Message, "incorrect warning") 243 } 244 } 245 } 246 247 // Once unmarshalled, bidResponse objects can't simply be compared with an `assert.Equalf()` call 248 // because tests fail if the elements inside the `bidResponse.SeatBid` and `bidResponse.SeatBid.Bid` 249 // arrays, if any, are not listed in the exact same order in the actual version and in the expected version. 250 func assertBidResponseEqual(t *testing.T, testFile string, expectedBidResponse openrtb2.BidResponse, actualBidResponse openrtb2.BidResponse) { 251 252 //Assert non-array BidResponse fields 253 assert.Equalf(t, expectedBidResponse.ID, actualBidResponse.ID, "BidResponse.ID doesn't match expected. Test: %s\n", testFile) 254 assert.Equalf(t, expectedBidResponse.Cur, actualBidResponse.Cur, "BidResponse.Cur doesn't match expected. Test: %s\n", testFile) 255 256 if len(expectedBidResponse.Ext) > 0 { 257 compareWarnings(t, expectedBidResponse.Ext, actualBidResponse.Ext, "warnings.general") 258 } 259 260 //Assert []SeatBid and their Bid elements independently of their order 261 assert.Len(t, actualBidResponse.SeatBid, len(expectedBidResponse.SeatBid), "BidResponse.SeatBid is expected to contain %d elements but contains %d. Test: %s\n", len(expectedBidResponse.SeatBid), len(actualBidResponse.SeatBid), testFile) 262 263 //Given that bidResponses have the same length, compare them in an order-independent way using maps 264 var actualSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0) 265 for _, seatBid := range actualBidResponse.SeatBid { 266 actualSeatBidsMap[seatBid.Seat] = seatBid 267 } 268 269 var expectedSeatBidsMap map[string]openrtb2.SeatBid = make(map[string]openrtb2.SeatBid, 0) 270 for _, seatBid := range expectedBidResponse.SeatBid { 271 expectedSeatBidsMap[seatBid.Seat] = seatBid 272 } 273 274 for bidderName, expectedSeatBid := range expectedSeatBidsMap { 275 if !assert.Contains(t, actualSeatBidsMap, bidderName, "BidResponse.SeatBid[%s] was not found as expected. Test: %s\n", bidderName, testFile) { 276 continue 277 } 278 279 //Assert non-array SeatBid fields 280 assert.Equalf(t, expectedSeatBid.Seat, actualSeatBidsMap[bidderName].Seat, "actualSeatBidsMap[%s].Seat doesn't match expected. Test: %s\n", bidderName, testFile) 281 assert.Equalf(t, expectedSeatBid.Group, actualSeatBidsMap[bidderName].Group, "actualSeatBidsMap[%s].Group doesn't match expected. Test: %s\n", bidderName, testFile) 282 assert.Equalf(t, expectedSeatBid.Ext, actualSeatBidsMap[bidderName].Ext, "actualSeatBidsMap[%s].Ext doesn't match expected. Test: %s\n", bidderName, testFile) 283 284 // Assert Bid arrays 285 assert.Len(t, actualSeatBidsMap[bidderName].Bid, len(expectedSeatBid.Bid), "BidResponse.SeatBid[].Bid array is expected to contain %d elements but has %d. Test: %s\n", len(expectedSeatBid.Bid), len(actualSeatBidsMap[bidderName].Bid), testFile) 286 // Given that actualSeatBidsMap[bidderName].Bid and expectedSeatBid.Bid have the same length, compare them in an order-independent way using maps 287 var expectedBidMap map[string]openrtb2.Bid = make(map[string]openrtb2.Bid, 0) 288 for _, bid := range expectedSeatBid.Bid { 289 expectedBidMap[bid.ID] = bid 290 } 291 292 var actualBidMap map[string]openrtb2.Bid = make(map[string]openrtb2.Bid, 0) 293 for _, bid := range actualSeatBidsMap[bidderName].Bid { 294 actualBidMap[bid.ID] = bid 295 } 296 297 for bidID, expectedBid := range expectedBidMap { 298 if !assert.Contains(t, actualBidMap, bidID, "BidResponse.SeatBid[%s].Bid[%s].ID doesn't match expected. Test: %s\n", bidderName, bidID, testFile) { 299 continue 300 } 301 assert.Equalf(t, expectedBid.ImpID, actualBidMap[bidID].ImpID, "BidResponse.SeatBid[%s].Bid[%s].ImpID doesn't match expected. Test: %s\n", bidderName, bidID, testFile) 302 assert.Equalf(t, expectedBid.Price, actualBidMap[bidID].Price, "BidResponse.SeatBid[%s].Bid[%s].Price doesn't match expected. Test: %s\n", bidderName, bidID, testFile) 303 304 if len(expectedBid.Ext) > 0 { 305 assert.JSONEq(t, string(expectedBid.Ext), string(actualBidMap[bidID].Ext), "Incorrect bid extension") 306 } 307 } 308 } 309 } 310 311 func TestBidRequestAssert(t *testing.T) { 312 appnexusB1 := openrtb2.Bid{ID: "appnexus-bid-1", Price: 5.00} 313 appnexusB2 := openrtb2.Bid{ID: "appnexus-bid-2", Price: 7.00} 314 rubiconB1 := openrtb2.Bid{ID: "rubicon-bid-1", Price: 1.50} 315 rubiconB2 := openrtb2.Bid{ID: "rubicon-bid-2", Price: 4.00} 316 317 sampleSeatBids := []openrtb2.SeatBid{ 318 { 319 Seat: "appnexus-bids", 320 Bid: []openrtb2.Bid{appnexusB1, appnexusB2}, 321 }, 322 { 323 Seat: "rubicon-bids", 324 Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, 325 }, 326 } 327 328 testSuites := []struct { 329 description string 330 expectedBidResponse openrtb2.BidResponse 331 actualBidResponse openrtb2.BidResponse 332 }{ 333 { 334 "identical SeatBids, exact same SeatBid and Bid arrays order", 335 openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, 336 openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, 337 }, 338 { 339 "identical SeatBids but Seatbid array elements come in different order", 340 openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, 341 openrtb2.BidResponse{ID: "anId", BidID: "bidId", 342 SeatBid: []openrtb2.SeatBid{ 343 { 344 Seat: "rubicon-bids", 345 Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, 346 }, 347 { 348 Seat: "appnexus-bids", 349 Bid: []openrtb2.Bid{appnexusB1, appnexusB2}, 350 }, 351 }, 352 }, 353 }, 354 { 355 "SeatBids seem to be identical except for the different order of Bid array elements in one of them", 356 openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, 357 openrtb2.BidResponse{ID: "anId", BidID: "bidId", 358 SeatBid: []openrtb2.SeatBid{ 359 { 360 Seat: "appnexus-bids", 361 Bid: []openrtb2.Bid{appnexusB2, appnexusB1}, 362 }, 363 { 364 Seat: "rubicon-bids", 365 Bid: []openrtb2.Bid{rubiconB1, rubiconB2}, 366 }, 367 }, 368 }, 369 }, 370 { 371 "Both SeatBid elements and bid elements come in different order", 372 openrtb2.BidResponse{ID: "anId", BidID: "bidId", SeatBid: sampleSeatBids}, 373 openrtb2.BidResponse{ID: "anId", BidID: "bidId", 374 SeatBid: []openrtb2.SeatBid{ 375 { 376 Seat: "rubicon-bids", 377 Bid: []openrtb2.Bid{rubiconB2, rubiconB1}, 378 }, 379 { 380 Seat: "appnexus-bids", 381 Bid: []openrtb2.Bid{appnexusB2, appnexusB1}, 382 }, 383 }, 384 }, 385 }, 386 } 387 388 for _, test := range testSuites { 389 assertBidResponseEqual(t, test.description, test.expectedBidResponse, test.actualBidResponse) 390 } 391 } 392 393 // TestExplicitUserId makes sure that the cookie's ID doesn't override an explicit value sent in the request. 394 func TestExplicitUserId(t *testing.T) { 395 cookieName := "userid" 396 mockId := "12345" 397 cfg := &config.Configuration{ 398 MaxRequestSize: maxSize, 399 HostCookie: config.HostCookie{ 400 CookieName: cookieName, 401 }, 402 } 403 ex := &mockExchange{} 404 405 request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(`{ 406 "id": "some-request-id", 407 "site": { 408 "page": "test.somepage.com" 409 }, 410 "user": { 411 "id": "explicit" 412 }, 413 "imp": [ 414 { 415 "id": "my-imp-id", 416 "banner": { 417 "format": [ 418 { 419 "w": 300, 420 "h": 600 421 } 422 ] 423 }, 424 "pmp": { 425 "deals": [ 426 { 427 "id": "some-deal-id" 428 } 429 ] 430 }, 431 "ext": { 432 "appnexus": { 433 "placementId": 12883451 434 } 435 } 436 } 437 ] 438 }`)) 439 request.AddCookie(&http.Cookie{ 440 Name: cookieName, 441 Value: mockId, 442 }) 443 444 endpoint, _ := NewEndpoint( 445 fakeUUIDGenerator{}, 446 ex, 447 mockBidderParamValidator{}, 448 empty_fetcher.EmptyFetcher{}, 449 empty_fetcher.EmptyFetcher{}, 450 cfg, 451 &metricsConfig.NilMetricsEngine{}, 452 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 453 map[string]string{}, 454 []byte{}, 455 openrtb_ext.BuildBidderMap(), 456 empty_fetcher.EmptyFetcher{}, 457 hooks.EmptyPlanBuilder{}, 458 nil, 459 ) 460 461 endpoint(httptest.NewRecorder(), request, nil) 462 463 if ex.lastRequest == nil { 464 t.Fatalf("The request never made it into the Exchange.") 465 } 466 467 if ex.lastRequest.User == nil { 468 t.Fatalf("The exchange should have received a request with a non-nil user.") 469 } 470 471 if ex.lastRequest.User.ID != "explicit" { 472 t.Errorf("Bad User ID. Expected explicit, got %s", ex.lastRequest.User.ID) 473 } 474 } 475 476 // TestBadAliasRequests() reuses two requests that would fail anyway. Here, we 477 // take advantage of our knowledge that processStoredRequests() in auction.go 478 // processes aliases before it processes stored imps. Changing that order 479 // would probably cause this test to fail. 480 func TestBadAliasRequests(t *testing.T) { 481 doBadAliasRequest(t, "sample-requests/invalid-stored/bad_stored_imp.json", "Invalid request: Invalid JSON in Default Request Settings: invalid character '\"' after object key:value pair at offset 51\n") 482 doBadAliasRequest(t, "sample-requests/invalid-stored/bad_incoming_imp.json", "Invalid request: Invalid JSON in Incoming Request: invalid character '\"' after object key:value pair at offset 230\n") 483 } 484 485 // doBadAliasRequest() is a customized variation of doRequest(), above 486 func doBadAliasRequest(t *testing.T, filename string, expectMsg string) { 487 t.Helper() 488 fileData := readFile(t, filename) 489 testBidRequest, _, _, err := jsonparser.Get(fileData, "mockBidRequest") 490 assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err) 491 492 // aliasJSON lacks a comma after the "appnexus" entry so is bad JSON 493 aliasJSON := []byte(`{"ext":{"prebid":{"aliases": {"test1": "appnexus" "test2": "rubicon", "test3": "openx"}}}}`) 494 495 bidderInfos := getBidderInfos(nil, openrtb_ext.CoreBidderNames()) 496 497 bidderMap := exchange.GetActiveBidders(bidderInfos) 498 disabledBidders := exchange.GetDisabledBidderWarningMessages(bidderInfos) 499 500 // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. 501 // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. 502 endpoint, _ := NewEndpoint( 503 fakeUUIDGenerator{}, 504 &nobidExchange{}, 505 mockBidderParamValidator{}, 506 &mockStoredReqFetcher{}, 507 empty_fetcher.EmptyFetcher{}, 508 &config.Configuration{MaxRequestSize: maxSize}, 509 &metricsConfig.NilMetricsEngine{}, 510 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 511 disabledBidders, 512 aliasJSON, 513 bidderMap, 514 empty_fetcher.EmptyFetcher{}, 515 hooks.EmptyPlanBuilder{}, 516 nil, 517 ) 518 519 request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(testBidRequest)) 520 recorder := httptest.NewRecorder() 521 endpoint(recorder, request, nil) 522 523 assertResponseCode(t, filename, recorder.Code, http.StatusBadRequest, recorder.Body.String()) 524 assert.Equal(t, string(expectMsg), recorder.Body.String(), "file %s had bad response body", filename) 525 526 } 527 528 func newParamsValidator(t *testing.T) openrtb_ext.BidderParamValidator { 529 paramValidator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") 530 if err != nil { 531 t.Fatalf("Error creating the param validator: %v", err) 532 } 533 return paramValidator 534 } 535 536 func assertResponseCode(t *testing.T, filename string, actual int, expected int, msg string) { 537 t.Helper() 538 if actual != expected { 539 t.Errorf("Expected a %d response from %v. Got %d: %s", expected, filename, actual, msg) 540 } 541 } 542 543 func getRequestPayload(t *testing.T, example []byte) []byte { 544 t.Helper() 545 if value, _, _, err := jsonparser.Get(example, "requestPayload"); err != nil { 546 t.Fatalf("Error parsing root.requestPayload from request: %v.", err) 547 } else { 548 return value 549 } 550 return nil 551 } 552 553 // TestNilExchange makes sure we fail when given nil for the Exchange. 554 func TestNilExchange(t *testing.T) { 555 // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. 556 // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. 557 _, err := NewEndpoint( 558 fakeUUIDGenerator{}, 559 nil, 560 mockBidderParamValidator{}, 561 empty_fetcher.EmptyFetcher{}, 562 empty_fetcher.EmptyFetcher{}, 563 &config.Configuration{MaxRequestSize: maxSize}, 564 &metricsConfig.NilMetricsEngine{}, 565 analyticsConf.NewPBSAnalytics(&config.Analytics{}), map[string]string{}, 566 []byte{}, 567 openrtb_ext.BuildBidderMap(), 568 empty_fetcher.EmptyFetcher{}, 569 hooks.EmptyPlanBuilder{}, 570 nil, 571 ) 572 573 if err == nil { 574 t.Errorf("NewEndpoint should return an error when given a nil Exchange.") 575 } 576 } 577 578 // TestNilValidator makes sure we fail when given nil for the BidderParamValidator. 579 func TestNilValidator(t *testing.T) { 580 // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. 581 // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. 582 _, err := NewEndpoint( 583 fakeUUIDGenerator{}, 584 &nobidExchange{}, 585 nil, 586 empty_fetcher.EmptyFetcher{}, 587 empty_fetcher.EmptyFetcher{}, 588 &config.Configuration{MaxRequestSize: maxSize}, 589 &metricsConfig.NilMetricsEngine{}, 590 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 591 map[string]string{}, 592 []byte{}, 593 openrtb_ext.BuildBidderMap(), 594 empty_fetcher.EmptyFetcher{}, 595 hooks.EmptyPlanBuilder{}, 596 nil, 597 ) 598 599 if err == nil { 600 t.Errorf("NewEndpoint should return an error when given a nil BidderParamValidator.") 601 } 602 } 603 604 // TestExchangeError makes sure we return a 500 if the exchange auction fails. 605 func TestExchangeError(t *testing.T) { 606 // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. 607 // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. 608 endpoint, _ := NewEndpoint( 609 fakeUUIDGenerator{}, 610 &brokenExchange{}, 611 mockBidderParamValidator{}, 612 empty_fetcher.EmptyFetcher{}, 613 empty_fetcher.EmptyFetcher{}, 614 &config.Configuration{MaxRequestSize: maxSize}, 615 &metricsConfig.NilMetricsEngine{}, 616 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 617 map[string]string{}, 618 []byte{}, 619 openrtb_ext.BuildBidderMap(), 620 empty_fetcher.EmptyFetcher{}, 621 hooks.EmptyPlanBuilder{}, 622 nil, 623 ) 624 625 request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) 626 recorder := httptest.NewRecorder() 627 endpoint(recorder, request, nil) 628 629 if recorder.Code != http.StatusInternalServerError { 630 t.Errorf("Expected status %d. Got %d. Input was: %s", http.StatusInternalServerError, recorder.Code, validRequest(t, "site.json")) 631 } 632 } 633 634 // TestUserAgentSetting makes sure we read the User-Agent header if it wasn't defined on the request. 635 func TestUserAgentSetting(t *testing.T) { 636 httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) 637 httpReq.Header.Set("User-Agent", "foo") 638 bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}} 639 640 setUAImplicitly(httpReq, bidReq) 641 642 if bidReq.Device == nil { 643 t.Fatal("bidrequest.device should have been set implicitly.") 644 } 645 if bidReq.Device.UA != "foo" { 646 t.Errorf("bidrequest.device.ua should have been \"foo\". Got %s", bidReq.Device.UA) 647 } 648 } 649 650 // TestUserAgentOverride makes sure that the explicit UA from the request takes precedence. 651 func TestUserAgentOverride(t *testing.T) { 652 httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) 653 httpReq.Header.Set("User-Agent", "foo") 654 bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 655 Device: &openrtb2.Device{ 656 UA: "bar", 657 }, 658 }} 659 660 setUAImplicitly(httpReq, bidReq) 661 662 if bidReq.Device.UA != "bar" { 663 t.Errorf("bidrequest.device.ua should have been \"bar\". Got %s", bidReq.Device.UA) 664 } 665 } 666 667 func TestAuctionTypeDefault(t *testing.T) { 668 bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}} 669 setAuctionTypeImplicitly(bidReq) 670 671 if bidReq.AT != 1 { 672 t.Errorf("Expected request.at to be 1. Got %d", bidReq.AT) 673 } 674 } 675 676 func TestImplicitIPsEndToEnd(t *testing.T) { 677 testCases := []struct { 678 description string 679 reqJSONFile string 680 xForwardedForHeader string 681 privateNetworksIPv4 []net.IPNet 682 privateNetworksIPv6 []net.IPNet 683 expectedDeviceIPv4 string 684 expectedDeviceIPv6 string 685 }{ 686 { 687 description: "IPv4", 688 reqJSONFile: "site.json", 689 xForwardedForHeader: "1.1.1.1", 690 expectedDeviceIPv4: "1.1.1.1", 691 }, 692 { 693 description: "IPv6", 694 reqJSONFile: "site.json", 695 xForwardedForHeader: "1111::", 696 expectedDeviceIPv6: "1111::", 697 }, 698 { 699 description: "IPv4 - Defined In Request", 700 reqJSONFile: "site-has-ipv4.json", 701 xForwardedForHeader: "1.1.1.1", 702 expectedDeviceIPv4: "8.8.8.8", // Hardcoded value in test file. 703 }, 704 { 705 description: "IPv6 - Defined In Request", 706 reqJSONFile: "site-has-ipv6.json", 707 xForwardedForHeader: "1111::", 708 expectedDeviceIPv6: "8888::", // Hardcoded value in test file. 709 }, 710 { 711 description: "IPv4 - Defined In Request - Private Network", 712 reqJSONFile: "site-has-ipv4.json", 713 xForwardedForHeader: "1.1.1.1", 714 privateNetworksIPv4: []net.IPNet{{IP: net.IP{8, 8, 8, 0}, Mask: net.CIDRMask(24, 32)}}, // Hardcoded value in test file. 715 expectedDeviceIPv4: "1.1.1.1", 716 }, 717 { 718 description: "IPv6 - Defined In Request - Private Network", 719 reqJSONFile: "site-has-ipv6.json", 720 xForwardedForHeader: "1111::", 721 privateNetworksIPv6: []net.IPNet{{IP: net.ParseIP("8800::"), Mask: net.CIDRMask(8, 128)}}, // Hardcoded value in test file. 722 expectedDeviceIPv6: "1111::", 723 }, 724 } 725 726 for _, test := range testCases { 727 exchange := &nobidExchange{} 728 cfg := &config.Configuration{ 729 MaxRequestSize: maxSize, 730 RequestValidation: config.RequestValidation{ 731 IPv4PrivateNetworksParsed: test.privateNetworksIPv4, 732 IPv6PrivateNetworksParsed: test.privateNetworksIPv6, 733 }, 734 } 735 endpoint, _ := NewEndpoint( 736 fakeUUIDGenerator{}, 737 exchange, 738 mockBidderParamValidator{}, 739 &mockStoredReqFetcher{}, 740 empty_fetcher.EmptyFetcher{}, 741 cfg, 742 &metricsConfig.NilMetricsEngine{}, 743 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 744 map[string]string{}, 745 []byte{}, 746 openrtb_ext.BuildBidderMap(), 747 empty_fetcher.EmptyFetcher{}, 748 hooks.EmptyPlanBuilder{}, 749 nil, 750 ) 751 752 httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) 753 httpReq.Header.Set("X-Forwarded-For", test.xForwardedForHeader) 754 755 endpoint(httptest.NewRecorder(), httpReq, nil) 756 757 result := exchange.gotRequest 758 if !assert.NotEmpty(t, result, test.description+"Request received by the exchange.") { 759 t.FailNow() 760 } 761 assert.Equal(t, test.expectedDeviceIPv4, result.Device.IP, test.description+":ipv4") 762 assert.Equal(t, test.expectedDeviceIPv6, result.Device.IPv6, test.description+":ipv6") 763 } 764 } 765 766 func TestImplicitDNT(t *testing.T) { 767 var ( 768 disabled int8 = 0 769 enabled int8 = 1 770 ) 771 testCases := []struct { 772 description string 773 dntHeader string 774 request openrtb2.BidRequest 775 expectedRequest openrtb2.BidRequest 776 }{ 777 { 778 description: "Device Missing - Not Set In Header", 779 dntHeader: "", 780 request: openrtb2.BidRequest{}, 781 expectedRequest: openrtb2.BidRequest{}, 782 }, 783 { 784 description: "Device Missing - Set To 0 In Header", 785 dntHeader: "0", 786 request: openrtb2.BidRequest{}, 787 expectedRequest: openrtb2.BidRequest{ 788 Device: &openrtb2.Device{ 789 DNT: &disabled, 790 }, 791 }, 792 }, 793 { 794 description: "Device Missing - Set To 1 In Header", 795 dntHeader: "1", 796 request: openrtb2.BidRequest{}, 797 expectedRequest: openrtb2.BidRequest{ 798 Device: &openrtb2.Device{ 799 DNT: &enabled, 800 }, 801 }, 802 }, 803 { 804 description: "Not Set In Request - Not Set In Header", 805 dntHeader: "", 806 request: openrtb2.BidRequest{ 807 Device: &openrtb2.Device{}, 808 }, 809 expectedRequest: openrtb2.BidRequest{ 810 Device: &openrtb2.Device{}, 811 }, 812 }, 813 { 814 description: "Not Set In Request - Set To 0 In Header", 815 dntHeader: "0", 816 request: openrtb2.BidRequest{ 817 Device: &openrtb2.Device{}, 818 }, 819 expectedRequest: openrtb2.BidRequest{ 820 Device: &openrtb2.Device{ 821 DNT: &disabled, 822 }, 823 }, 824 }, 825 { 826 description: "Not Set In Request - Set To 1 In Header", 827 dntHeader: "1", 828 request: openrtb2.BidRequest{ 829 Device: &openrtb2.Device{}, 830 }, 831 expectedRequest: openrtb2.BidRequest{ 832 Device: &openrtb2.Device{ 833 DNT: &enabled, 834 }, 835 }, 836 }, 837 { 838 description: "Set In Request - Not Set In Header", 839 dntHeader: "", 840 request: openrtb2.BidRequest{ 841 Device: &openrtb2.Device{ 842 DNT: &enabled, 843 }, 844 }, 845 expectedRequest: openrtb2.BidRequest{ 846 Device: &openrtb2.Device{ 847 DNT: &enabled, 848 }, 849 }, 850 }, 851 { 852 description: "Set In Request - Set To 0 In Header", 853 dntHeader: "0", 854 request: openrtb2.BidRequest{ 855 Device: &openrtb2.Device{ 856 DNT: &enabled, 857 }, 858 }, 859 expectedRequest: openrtb2.BidRequest{ 860 Device: &openrtb2.Device{ 861 DNT: &enabled, 862 }, 863 }, 864 }, 865 { 866 description: "Set In Request - Set To 1 In Header", 867 dntHeader: "1", 868 request: openrtb2.BidRequest{ 869 Device: &openrtb2.Device{ 870 DNT: &enabled, 871 }, 872 }, 873 expectedRequest: openrtb2.BidRequest{ 874 Device: &openrtb2.Device{ 875 DNT: &enabled, 876 }, 877 }, 878 }, 879 } 880 881 for _, test := range testCases { 882 httpReq := httptest.NewRequest("POST", "/openrtb2/auction", nil) 883 httpReq.Header.Set("DNT", test.dntHeader) 884 reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &test.request} 885 setDoNotTrackImplicitly(httpReq, reqWrapper) 886 assert.Equal(t, test.expectedRequest, *reqWrapper.BidRequest, test.description) 887 } 888 } 889 890 func TestImplicitDNTEndToEnd(t *testing.T) { 891 var ( 892 disabled int8 = 0 893 enabled int8 = 1 894 ) 895 testCases := []struct { 896 description string 897 reqJSONFile string 898 dntHeader string 899 expectedDNT *int8 900 }{ 901 { 902 description: "Not Set In Request - Not Set In Header", 903 reqJSONFile: "site.json", 904 dntHeader: "", 905 expectedDNT: nil, 906 }, 907 { 908 description: "Not Set In Request - Set To 0 In Header", 909 reqJSONFile: "site.json", 910 dntHeader: "0", 911 expectedDNT: &disabled, 912 }, 913 { 914 description: "Not Set In Request - Set To 1 In Header", 915 reqJSONFile: "site.json", 916 dntHeader: "1", 917 expectedDNT: &enabled, 918 }, 919 { 920 description: "Set In Request - Not Set In Header", 921 reqJSONFile: "site-has-dnt.json", 922 dntHeader: "", 923 expectedDNT: &enabled, // Hardcoded value in test file. 924 }, 925 { 926 description: "Set In Request - Not Overwritten By Header", 927 reqJSONFile: "site-has-dnt.json", 928 dntHeader: "0", 929 expectedDNT: &enabled, // Hardcoded value in test file. 930 }, 931 } 932 933 for _, test := range testCases { 934 exchange := &nobidExchange{} 935 endpoint, _ := NewEndpoint( 936 fakeUUIDGenerator{}, 937 exchange, 938 mockBidderParamValidator{}, 939 &mockStoredReqFetcher{}, 940 empty_fetcher.EmptyFetcher{}, 941 &config.Configuration{MaxRequestSize: maxSize}, 942 &metricsConfig.NilMetricsEngine{}, 943 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 944 map[string]string{}, 945 []byte{}, 946 openrtb_ext.BuildBidderMap(), 947 empty_fetcher.EmptyFetcher{}, 948 hooks.EmptyPlanBuilder{}, 949 nil, 950 ) 951 952 httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, test.reqJSONFile))) 953 httpReq.Header.Set("DNT", test.dntHeader) 954 955 endpoint(httptest.NewRecorder(), httpReq, nil) 956 957 result := exchange.gotRequest 958 if !assert.NotEmpty(t, result, test.description+"Request received by the exchange.") { 959 t.FailNow() 960 } 961 assert.Equal(t, test.expectedDNT, result.Device.DNT, test.description+":dnt") 962 } 963 } 964 965 func TestReferer(t *testing.T) { 966 testCases := []struct { 967 description string 968 givenReferer string 969 expectedDomain string 970 expectedPage string 971 expectedPublisherDomain string 972 bidReq *openrtb_ext.RequestWrapper 973 }{ 974 { 975 description: "site.page/domain are unchanged when site.page/domain and http referer are not set", 976 expectedDomain: "", 977 expectedPage: "", 978 expectedPublisherDomain: "", 979 bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 980 Site: &openrtb2.Site{ 981 Publisher: &openrtb2.Publisher{}, 982 }, 983 }}, 984 }, 985 { 986 description: "site.page/domain are derived from referer when neither is set and http referer is set", 987 givenReferer: "https://test.somepage.com", 988 expectedDomain: "test.somepage.com", 989 expectedPublisherDomain: "somepage.com", 990 expectedPage: "https://test.somepage.com", 991 bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 992 Site: &openrtb2.Site{ 993 Publisher: &openrtb2.Publisher{}, 994 }, 995 }}, 996 }, 997 { 998 description: "site.domain is derived from site.page when site.page is set and http referer is not set", 999 expectedDomain: "test.somepage.com", 1000 expectedPublisherDomain: "somepage.com", 1001 expectedPage: "https://test.somepage.com", 1002 bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 1003 Site: &openrtb2.Site{ 1004 Page: "https://test.somepage.com", 1005 Publisher: &openrtb2.Publisher{}, 1006 }, 1007 }}, 1008 }, 1009 { 1010 description: "site.domain is derived from http referer when site.page and http referer are set", 1011 givenReferer: "http://test.com", 1012 expectedDomain: "test.com", 1013 expectedPublisherDomain: "test.com", 1014 expectedPage: "https://test.somepage.com", 1015 bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 1016 Site: &openrtb2.Site{ 1017 Page: "https://test.somepage.com", 1018 Publisher: &openrtb2.Publisher{}, 1019 }, 1020 }}, 1021 }, 1022 { 1023 description: "site.page/domain are unchanged when site.page/domain and http referer are set", 1024 givenReferer: "http://test.com", 1025 expectedDomain: "some.domain.com", 1026 expectedPublisherDomain: "test.com", 1027 expectedPage: "https://test.somepage.com", 1028 bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 1029 Site: &openrtb2.Site{ 1030 Domain: "some.domain.com", 1031 Page: "https://test.somepage.com", 1032 Publisher: &openrtb2.Publisher{}, 1033 }, 1034 }}, 1035 }, 1036 { 1037 description: "Publisher domain shouldn't be overrwriten if already set", 1038 expectedDomain: "test.somepage.com", 1039 expectedPublisherDomain: "differentpage.com", 1040 expectedPage: "https://test.somepage.com", 1041 bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 1042 Site: &openrtb2.Site{ 1043 Domain: "", 1044 Page: "https://test.somepage.com", 1045 Publisher: &openrtb2.Publisher{ 1046 Domain: "differentpage.com", 1047 }, 1048 }, 1049 }}, 1050 }, 1051 { 1052 description: "request.site is nil", 1053 givenReferer: "http://test.com", 1054 expectedDomain: "test.com", 1055 expectedPublisherDomain: "test.com", 1056 expectedPage: "http://test.com", 1057 bidReq: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{}}, 1058 }, 1059 } 1060 1061 for _, test := range testCases { 1062 httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) 1063 httpReq.Header.Set("Referer", test.givenReferer) 1064 1065 setSiteImplicitly(httpReq, test.bidReq) 1066 1067 assert.NotNil(t, test.bidReq.Site, test.description) 1068 assert.Equal(t, test.expectedDomain, test.bidReq.Site.Domain, test.description) 1069 assert.Equal(t, test.expectedPage, test.bidReq.Site.Page, test.description) 1070 assert.Equal(t, test.expectedPublisherDomain, test.bidReq.Site.Publisher.Domain, test.description) 1071 } 1072 } 1073 1074 func TestParseImpInfoSingleImpression(t *testing.T) { 1075 1076 expectedRes := []ImpExtPrebidData{ 1077 { 1078 Imp: json.RawMessage(`{"video":{"h":300,"w":200},"ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}}}}`), 1079 ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}}, 1080 }, 1081 { 1082 Imp: json.RawMessage(`{"id": "adUnit2","ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}},"appnexus": {"placementId": "def","trafficSourceCode": "mysite.com","reserve": null},"rubicon": null}}`), 1083 ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}}, 1084 }, 1085 { 1086 Imp: json.RawMessage(`{"ext": {"prebid": {"storedrequest": {"id": "2"},"options": {"echovideoattrs": false}}}}`), 1087 ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "2"}, Options: &openrtb_ext.Options{EchoVideoAttrs: false}}, 1088 }, 1089 { 1090 //in this case impression doesn't have storedrequest so we don't expect any data about this imp will be returned 1091 Imp: json.RawMessage(`{"id": "some-static-imp","video":{"mimes":["video/mp4"]},"ext": {"appnexus": {"placementId": "abc","position": "below"}}}`), 1092 ImpExtPrebid: openrtb_ext.ExtImpPrebid{}, 1093 }, 1094 { 1095 Imp: json.RawMessage(`{"id":"my-imp-id", "video":{"h":300, "w":200}, "ext":{"prebid":{"storedrequest": {"id": "6"}}}}`), 1096 ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "6"}}, 1097 }, 1098 } 1099 1100 for i, requestData := range testStoredRequests { 1101 impInfo, errs := parseImpInfo([]byte(requestData)) 1102 assert.Len(t, errs, 0, "No errors should be returned") 1103 assert.JSONEq(t, string(expectedRes[i].Imp), string(impInfo[0].Imp), "Incorrect impression data") 1104 assert.Equal(t, expectedRes[i].ImpExtPrebid, impInfo[0].ImpExtPrebid, "Incorrect impression ext prebid data") 1105 1106 } 1107 } 1108 1109 func TestParseImpInfoMultipleImpressions(t *testing.T) { 1110 1111 inputData := []byte(`{ 1112 "id": "ThisID", 1113 "imp": [ 1114 { 1115 "id": "imp1", 1116 "ext": { 1117 "prebid": { 1118 "storedrequest": { 1119 "id": "1" 1120 }, 1121 "options": { 1122 "echovideoattrs": true 1123 } 1124 } 1125 } 1126 }, 1127 { 1128 "id": "imp2", 1129 "ext": { 1130 "prebid": { 1131 "storedrequest": { 1132 "id": "2" 1133 }, 1134 "options": { 1135 "echovideoattrs": false 1136 } 1137 } 1138 } 1139 }, 1140 { 1141 "id": "imp3" 1142 } 1143 ] 1144 }`) 1145 1146 expectedRes := []ImpExtPrebidData{ 1147 { 1148 Imp: json.RawMessage(`{"id": "imp1","ext": {"prebid": {"storedrequest": {"id": "1"},"options": {"echovideoattrs": true}}}}`), 1149 ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "1"}, Options: &openrtb_ext.Options{EchoVideoAttrs: true}}, 1150 }, 1151 { 1152 Imp: json.RawMessage(`{"id": "imp2","ext": {"prebid": {"storedrequest": {"id": "2"},"options": {"echovideoattrs": false}}}}`), 1153 ImpExtPrebid: openrtb_ext.ExtImpPrebid{StoredRequest: &openrtb_ext.ExtStoredRequest{ID: "2"}, Options: &openrtb_ext.Options{EchoVideoAttrs: false}}, 1154 }, 1155 { 1156 Imp: json.RawMessage(`{"id": "imp3"}`), 1157 ImpExtPrebid: openrtb_ext.ExtImpPrebid{}, 1158 }, 1159 } 1160 1161 impInfo, errs := parseImpInfo([]byte(inputData)) 1162 assert.Len(t, errs, 0, "No errors should be returned") 1163 for i, res := range expectedRes { 1164 assert.JSONEq(t, string(res.Imp), string(impInfo[i].Imp), "Incorrect impression data") 1165 assert.Equal(t, res.ImpExtPrebid, impInfo[i].ImpExtPrebid, "Incorrect impression ext prebid data") 1166 } 1167 } 1168 1169 // Test the stored request functionality 1170 func TestStoredRequests(t *testing.T) { 1171 deps := &endpointDeps{ 1172 fakeUUIDGenerator{}, 1173 &nobidExchange{}, 1174 mockBidderParamValidator{}, 1175 &mockStoredReqFetcher{}, 1176 empty_fetcher.EmptyFetcher{}, 1177 empty_fetcher.EmptyFetcher{}, 1178 &config.Configuration{MaxRequestSize: maxSize}, 1179 &metricsConfig.NilMetricsEngine{}, 1180 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 1181 map[string]string{}, 1182 false, 1183 []byte{}, 1184 openrtb_ext.BuildBidderMap(), 1185 nil, 1186 nil, 1187 hardcodedResponseIPValidator{response: true}, 1188 empty_fetcher.EmptyFetcher{}, 1189 hooks.EmptyPlanBuilder{}, 1190 nil, 1191 } 1192 1193 testStoreVideoAttr := []bool{true, true, false, false, false} 1194 1195 for i, requestData := range testStoredRequests { 1196 impInfo, errs := parseImpInfo([]byte(requestData)) 1197 assert.Len(t, errs, 0, "No errors should be returned") 1198 storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(context.Background(), json.RawMessage(requestData), impInfo) 1199 assert.Len(t, errs, 0, "No errors should be returned") 1200 newRequest, impExtInfoMap, errList := deps.processStoredRequests(json.RawMessage(requestData), impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest) 1201 if len(errList) != 0 { 1202 for _, err := range errList { 1203 if err != nil { 1204 t.Errorf("processStoredRequests Error: %s", err.Error()) 1205 } else { 1206 t.Error("processStoredRequests Error: received nil error") 1207 } 1208 } 1209 } 1210 expectJson := json.RawMessage(testFinalRequests[i]) 1211 assert.JSONEq(t, string(expectJson), string(newRequest), "Incorrect result request %d", i) 1212 expectedImp := testStoredImpIds[i] 1213 expectedStoredImp := json.RawMessage(testStoredImps[i]) 1214 if len(impExtInfoMap[expectedImp].StoredImp) > 0 { 1215 assert.JSONEq(t, string(expectedStoredImp), string(impExtInfoMap[expectedImp].StoredImp), "Incorrect expected stored imp %d", i) 1216 1217 } 1218 assert.Equalf(t, testStoreVideoAttr[i], impExtInfoMap[expectedImp].EchoVideoAttrs, "EchoVideoAttrs value is incorrect") 1219 } 1220 } 1221 1222 func TestMergeBidderParams(t *testing.T) { 1223 testCases := []struct { 1224 description string 1225 givenRequest openrtb2.BidRequest 1226 expectedRequestImps []openrtb2.Imp 1227 }{ 1228 { 1229 description: "No Request Params", 1230 givenRequest: openrtb2.BidRequest{ 1231 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}}, 1232 }, 1233 expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}}, 1234 }, 1235 { 1236 description: "No Request Params - Empty Object", 1237 givenRequest: openrtb2.BidRequest{ 1238 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}}, 1239 Ext: json.RawMessage(`{"prebid":{"bidderparams":{}}}`), 1240 }, 1241 expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}}, 1242 }, 1243 { 1244 description: "Malformed Request Params", 1245 givenRequest: openrtb2.BidRequest{ 1246 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}}, 1247 Ext: json.RawMessage(`{"prebid":{"bidderparams":malformed}}`), 1248 }, 1249 expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}}, 1250 }, 1251 { 1252 description: "No Imps", 1253 givenRequest: openrtb2.BidRequest{ 1254 Imp: []openrtb2.Imp{}, 1255 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`), 1256 }, 1257 expectedRequestImps: []openrtb2.Imp{}, 1258 }, 1259 { 1260 description: "One Imp - imp.ext Modified", 1261 givenRequest: openrtb2.BidRequest{ 1262 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1}}`)}}, 1263 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`), 1264 }, 1265 expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2}}`)}}, 1266 }, 1267 { 1268 description: "One Imp - imp.ext.prebid.bidder Modified", 1269 givenRequest: openrtb2.BidRequest{ 1270 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidder1":{"a":1}}}}`)}}, 1271 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`), 1272 }, 1273 expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidder1":{"a":1,"b":2}}}}`)}}, 1274 }, 1275 { 1276 description: "One Imp - imp.ext + imp.ext.prebid.bidder Modified - Different Bidders", 1277 givenRequest: openrtb2.BidRequest{ 1278 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)}}, 1279 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2},"bidder2":{"b":"two"}}}}`), 1280 }, 1281 expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2},"prebid":{"bidder":{"bidder2":{"a":"one","b":"two"}}}}`)}}, 1282 }, 1283 { 1284 description: "One Imp - imp.ext + imp.ext.prebid.bidder Modified - Same Bidder", 1285 givenRequest: openrtb2.BidRequest{ 1286 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder1":{"a":"one"}}}}`)}}, 1287 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`), 1288 }, 1289 expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2},"prebid":{"bidder":{"bidder1":{"a":"one","b":2}}}}`)}}, 1290 }, 1291 { 1292 description: "One Imp - No imp.ext", 1293 givenRequest: openrtb2.BidRequest{ 1294 Imp: []openrtb2.Imp{{ID: "1"}}, 1295 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`), 1296 }, 1297 expectedRequestImps: []openrtb2.Imp{{ID: "1"}}, 1298 }, 1299 { 1300 description: "Multiple Imps - Modified Mixed", 1301 givenRequest: openrtb2.BidRequest{ 1302 Imp: []openrtb2.Imp{ 1303 {ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder1":{"a":"one"}}}}`)}, 1304 {ID: "2", Ext: json.RawMessage(`{"bidder2":{"a":1,"b":"existing"},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)}}, 1305 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2},"bidder2":{"b":"two"}}}}`), 1306 }, 1307 expectedRequestImps: []openrtb2.Imp{ 1308 {ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2},"prebid":{"bidder":{"bidder1":{"a":"one","b":2}}}}`)}, 1309 {ID: "2", Ext: json.RawMessage(`{"bidder2":{"a":1,"b":"existing"},"prebid":{"bidder":{"bidder2":{"a":"one","b":"two"}}}}`)}}, 1310 }, 1311 { 1312 description: "Multiple Imps - None Modified Mixed", 1313 givenRequest: openrtb2.BidRequest{ 1314 Imp: []openrtb2.Imp{ 1315 {ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)}, 1316 {ID: "2", Ext: json.RawMessage(`{"bidder1":{"a":2},"prebid":{"bidder":{"bidder2":{"a":"two"}}}}`)}}, 1317 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder3":{"c":3}}}}`), 1318 }, 1319 expectedRequestImps: []openrtb2.Imp{ 1320 {ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)}, 1321 {ID: "2", Ext: json.RawMessage(`{"bidder1":{"a":2},"prebid":{"bidder":{"bidder2":{"a":"two"}}}}`)}}, 1322 }, 1323 { 1324 description: "Multiple Imps - One Malformed", 1325 givenRequest: openrtb2.BidRequest{ 1326 Imp: []openrtb2.Imp{ 1327 {ID: "1", Ext: json.RawMessage(`malformed`)}, 1328 {ID: "2", Ext: json.RawMessage(`{"bidder2":{"a":1,"b":"existing"},"prebid":{"bidder":{"bidder2":{"a":"one"}}}}`)}}, 1329 Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2},"bidder2":{"b":"two"}}}}`), 1330 }, 1331 expectedRequestImps: []openrtb2.Imp{ 1332 {ID: "1", Ext: json.RawMessage(`malformed`)}, 1333 {ID: "2", Ext: json.RawMessage(`{"bidder2":{"a":1,"b":"existing"},"prebid":{"bidder":{"bidder2":{"a":"one","b":"two"}}}}`)}}, 1334 }, 1335 } 1336 1337 for _, test := range testCases { 1338 w := &openrtb_ext.RequestWrapper{BidRequest: &test.givenRequest} 1339 actualErr := mergeBidderParams(w) 1340 1341 // errors are only possible from the marshal operation, which is not testable 1342 assert.NoError(t, actualErr, test.description+":err") 1343 1344 // rebuild request before asserting value 1345 assert.NoError(t, w.RebuildRequest(), test.description+":rebuild_request") 1346 1347 assert.Equal(t, test.givenRequest.Imp, test.expectedRequestImps, test.description+":imps") 1348 } 1349 } 1350 1351 func TestMergeBidderParamsImpExt(t *testing.T) { 1352 testCases := []struct { 1353 description string 1354 givenImpExt map[string]json.RawMessage 1355 givenReqExtParams map[string]map[string]json.RawMessage 1356 expectedModified bool 1357 expectedImpExt map[string]json.RawMessage 1358 }{ 1359 { 1360 description: "One Bidder - Modified (no collision)", 1361 givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`)}, 1362 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1363 expectedModified: true, 1364 expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, 1365 }, 1366 { 1367 description: "One Bidder - Modified (imp.ext bidder empty)", 1368 givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{}`)}, 1369 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1370 expectedModified: true, 1371 expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"b":2}`)}, 1372 }, 1373 { 1374 description: "One Bidder - Not Modified (imp.ext bidder not defined)", 1375 givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, 1376 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder-not-defined": {"b": json.RawMessage(`4`)}}, 1377 expectedModified: false, 1378 expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, 1379 }, 1380 { 1381 description: "One Bidder - Not Modified (imp.ext bidder nil)", 1382 givenImpExt: map[string]json.RawMessage{"bidder1": nil}, 1383 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, 1384 expectedModified: false, 1385 expectedImpExt: map[string]json.RawMessage{"bidder1": nil}, 1386 }, 1387 { 1388 description: "One Bidder - Not Modified (imp.ext wins)", 1389 givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, 1390 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, 1391 expectedModified: false, 1392 expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}, 1393 }, 1394 { 1395 description: "One Bidder - Not Modified (reserved bidder ignored)", 1396 givenImpExt: map[string]json.RawMessage{"gpid": json.RawMessage(`{"a":1}`)}, 1397 givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}}, 1398 expectedModified: false, 1399 expectedImpExt: map[string]json.RawMessage{"gpid": json.RawMessage(`{"a":1}`)}, 1400 }, 1401 { 1402 description: "One Bidder - Not Modified (reserved bidder ignored - not embedded object)", 1403 givenImpExt: map[string]json.RawMessage{"gpid": json.RawMessage(`1`)}, 1404 givenReqExtParams: map[string]map[string]json.RawMessage{"gpid": {"b": json.RawMessage(`2`)}}, 1405 expectedModified: false, 1406 expectedImpExt: map[string]json.RawMessage{"gpid": json.RawMessage(`1`)}, 1407 }, 1408 { 1409 description: "One Bidder - Not Modified (malformed ignored)", 1410 givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}, 1411 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1412 expectedModified: false, 1413 expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}, 1414 }, 1415 { 1416 description: "Multiple Bidders - Mixed", 1417 givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, 1418 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}, "bidder2": {"b": json.RawMessage(`"three"`)}}, 1419 expectedModified: true, 1420 expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, 1421 }, 1422 { 1423 description: "Multiple Bidders - None Modified", 1424 givenImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, 1425 givenReqExtParams: map[string]map[string]json.RawMessage{}, 1426 expectedModified: false, 1427 expectedImpExt: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}, 1428 }, 1429 } 1430 1431 for _, test := range testCases { 1432 impExt := openrtb_ext.CreateImpExtForTesting(test.givenImpExt, nil) 1433 1434 err := mergeBidderParamsImpExt(&impExt, test.givenReqExtParams) 1435 1436 // errors are only possible from the marshal operation, which is not testable 1437 assert.NoError(t, err, test.description+":err") 1438 1439 assert.Equal(t, test.expectedModified, impExt.Dirty(), test.description+":modified") 1440 assert.Equal(t, test.expectedImpExt, impExt.GetExt(), test.description+":imp.ext") 1441 } 1442 } 1443 1444 func TestMergeBidderParamsImpExtPrebid(t *testing.T) { 1445 testCases := []struct { 1446 description string 1447 givenImpExtPrebid *openrtb_ext.ExtImpPrebid 1448 givenReqExtParams map[string]map[string]json.RawMessage 1449 expectedModified bool 1450 expectedImpExtPrebid *openrtb_ext.ExtImpPrebid 1451 }{ 1452 { 1453 description: "No Prebid Section", 1454 givenImpExtPrebid: nil, 1455 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1456 expectedModified: false, 1457 expectedImpExtPrebid: nil, 1458 }, 1459 { 1460 description: "No Prebid Bidder Section", 1461 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: nil}, 1462 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1463 expectedModified: false, 1464 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: nil}, 1465 }, 1466 { 1467 description: "Empty Prebid Bidder Section", 1468 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{}}, 1469 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1470 expectedModified: false, 1471 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{}}, 1472 }, 1473 { 1474 description: "One Bidder - Modified (no collision)", 1475 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`)}}, 1476 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1477 expectedModified: true, 1478 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, 1479 }, 1480 { 1481 description: "One Bidder - Modified (imp.ext bidder empty)", 1482 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{}`)}}, 1483 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1484 expectedModified: true, 1485 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"b":2}`)}}, 1486 }, 1487 { 1488 description: "One Bidder - Not Modified (imp.ext wins)", 1489 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, 1490 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, 1491 expectedModified: false, 1492 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, 1493 }, 1494 { 1495 description: "One Bidder - Not Modified (imp.ext bidder not defined)", 1496 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, 1497 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder-not-defined": {"b": json.RawMessage(`4`)}}, 1498 expectedModified: false, 1499 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`)}}, 1500 }, 1501 { 1502 description: "One Bidder - Not Modified (imp.ext bidder nil)", 1503 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": nil}}, 1504 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`4`)}}, 1505 expectedModified: false, 1506 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": nil}}, 1507 }, 1508 { 1509 description: "One Bidder - Not Modified (malformed ignored)", 1510 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}}, 1511 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}}, 1512 expectedModified: false, 1513 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`malformed`)}}, 1514 }, 1515 { 1516 description: "Multiple Bidders - Mixed", 1517 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}}, 1518 givenReqExtParams: map[string]map[string]json.RawMessage{"bidder1": {"b": json.RawMessage(`2`)}, "bidder2": {"b": json.RawMessage(`"three"`)}}, 1519 expectedModified: true, 1520 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1,"b":2}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}}, 1521 }, 1522 { 1523 description: "Multiple Bidders - None Modified", 1524 givenImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}}, 1525 givenReqExtParams: map[string]map[string]json.RawMessage{}, 1526 expectedModified: false, 1527 expectedImpExtPrebid: &openrtb_ext.ExtImpPrebid{Bidder: map[string]json.RawMessage{"bidder1": json.RawMessage(`{"a":1}`), "bidder2": json.RawMessage(`{"a":"one","b":"two"}`)}}, 1528 }, 1529 } 1530 1531 for _, test := range testCases { 1532 impExt := openrtb_ext.CreateImpExtForTesting(map[string]json.RawMessage{}, test.givenImpExtPrebid) 1533 1534 err := mergeBidderParamsImpExtPrebid(&impExt, test.givenReqExtParams) 1535 1536 // errors are only possible from the marshal operation, which is not testable 1537 assert.NoError(t, err, test.description+":err") 1538 1539 assert.Equal(t, test.expectedModified, impExt.Dirty(), test.description+":modified") 1540 assert.Equal(t, test.expectedImpExtPrebid, impExt.GetPrebid(), test.description+":imp.ext.prebid") 1541 } 1542 } 1543 1544 func TestValidateRequest(t *testing.T) { 1545 deps := &endpointDeps{ 1546 fakeUUIDGenerator{}, 1547 &nobidExchange{}, 1548 mockBidderParamValidator{}, 1549 &mockStoredReqFetcher{}, 1550 empty_fetcher.EmptyFetcher{}, 1551 empty_fetcher.EmptyFetcher{}, 1552 &config.Configuration{MaxRequestSize: maxSize}, 1553 &metricsConfig.NilMetricsEngine{}, 1554 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 1555 map[string]string{}, 1556 false, 1557 []byte{}, 1558 openrtb_ext.BuildBidderMap(), 1559 nil, 1560 nil, 1561 hardcodedResponseIPValidator{response: true}, 1562 empty_fetcher.EmptyFetcher{}, 1563 hooks.EmptyPlanBuilder{}, 1564 nil, 1565 } 1566 1567 testCases := []struct { 1568 description string 1569 givenIsAmp bool 1570 givenRequestWrapper *openrtb_ext.RequestWrapper 1571 expectedErrorList []error 1572 expectedChannelObject *openrtb_ext.ExtRequestPrebidChannel 1573 }{ 1574 { 1575 description: "No errors in bid request with request.ext.prebid.channel info, expect validate request to throw no errors", 1576 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 1577 BidRequest: &openrtb2.BidRequest{ 1578 ID: "Some-ID", 1579 App: &openrtb2.App{}, 1580 Imp: []openrtb2.Imp{ 1581 { 1582 ID: "Some-Imp-ID", 1583 Banner: &openrtb2.Banner{ 1584 Format: []openrtb2.Format{ 1585 { 1586 W: 600, 1587 H: 500, 1588 }, 1589 { 1590 W: 300, 1591 H: 600, 1592 }, 1593 }, 1594 }, 1595 Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), 1596 }, 1597 }, 1598 Ext: []byte(`{"prebid":{"channel": {"name": "nameOfChannel", "version": "1.0"}}}`), 1599 }, 1600 }, 1601 givenIsAmp: false, 1602 expectedErrorList: []error{}, 1603 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: "nameOfChannel", Version: "1.0"}, 1604 }, 1605 { 1606 description: "Error in bid request with request.ext.prebid.channel.name being blank, expect validate request to return error", 1607 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 1608 BidRequest: &openrtb2.BidRequest{ 1609 ID: "Some-ID", 1610 App: &openrtb2.App{}, 1611 Imp: []openrtb2.Imp{ 1612 { 1613 ID: "Some-Imp-ID", 1614 Banner: &openrtb2.Banner{ 1615 Format: []openrtb2.Format{ 1616 { 1617 W: 600, 1618 H: 500, 1619 }, 1620 { 1621 W: 300, 1622 H: 600, 1623 }, 1624 }, 1625 }, 1626 Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), 1627 }, 1628 }, 1629 Ext: []byte(`{"prebid":{"channel": {"name": "", "version": ""}}}`), 1630 }, 1631 }, 1632 givenIsAmp: false, 1633 expectedErrorList: []error{errors.New("ext.prebid.channel.name can't be empty")}, 1634 }, 1635 { 1636 description: "AliasGVLID validation error", 1637 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 1638 BidRequest: &openrtb2.BidRequest{ 1639 ID: "Some-ID", 1640 App: &openrtb2.App{}, 1641 Imp: []openrtb2.Imp{ 1642 { 1643 ID: "Some-Imp-ID", 1644 Banner: &openrtb2.Banner{ 1645 Format: []openrtb2.Format{ 1646 { 1647 W: 600, 1648 H: 500, 1649 }, 1650 { 1651 W: 300, 1652 H: 600, 1653 }, 1654 }, 1655 }, 1656 Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), 1657 }, 1658 }, 1659 Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"pubmatic1":1}}}`), 1660 }, 1661 }, 1662 givenIsAmp: false, 1663 expectedErrorList: []error{errors.New("request.ext.prebid.aliasgvlids. vendorId 1 refers to unknown bidder alias: pubmatic1")}, 1664 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, 1665 }, 1666 { 1667 description: "AliasGVLID validation error as vendorID < 1", 1668 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 1669 BidRequest: &openrtb2.BidRequest{ 1670 ID: "Some-ID", 1671 App: &openrtb2.App{}, 1672 Imp: []openrtb2.Imp{ 1673 { 1674 ID: "Some-Imp-ID", 1675 Banner: &openrtb2.Banner{ 1676 Format: []openrtb2.Format{ 1677 { 1678 W: 600, 1679 H: 500, 1680 }, 1681 { 1682 W: 300, 1683 H: 600, 1684 }, 1685 }, 1686 }, 1687 Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), 1688 }, 1689 }, 1690 Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"yahoossp":0}}}`), 1691 }, 1692 }, 1693 givenIsAmp: false, 1694 expectedErrorList: []error{errors.New("request.ext.prebid.aliasgvlids. Invalid vendorId 0 for alias: yahoossp. Choose a different vendorId, or remove this entry.")}, 1695 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, 1696 }, 1697 { 1698 description: "No errors in bid request with request.ext.prebid but no channel info, expect validate request to throw no errors and fill channel with app", 1699 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 1700 BidRequest: &openrtb2.BidRequest{ 1701 ID: "Some-ID", 1702 App: &openrtb2.App{}, 1703 Imp: []openrtb2.Imp{ 1704 { 1705 ID: "Some-Imp-ID", 1706 Banner: &openrtb2.Banner{ 1707 Format: []openrtb2.Format{ 1708 { 1709 W: 600, 1710 H: 500, 1711 }, 1712 { 1713 W: 300, 1714 H: 600, 1715 }, 1716 }, 1717 }, 1718 Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), 1719 }, 1720 }, 1721 Ext: []byte(`{"prebid":{"aliases":{"yahoossp":"appnexus"}, "aliasgvlids":{"yahoossp":1}}}`), 1722 }, 1723 }, 1724 givenIsAmp: false, 1725 expectedErrorList: []error{}, 1726 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, 1727 }, 1728 } 1729 1730 for _, test := range testCases { 1731 errorList := deps.validateRequest(test.givenRequestWrapper, test.givenIsAmp, false, nil, false) 1732 assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) 1733 1734 if len(errorList) == 0 { 1735 requestExt, err := test.givenRequestWrapper.GetRequestExt() 1736 assert.Empty(t, err, test.description) 1737 requestPrebid := requestExt.GetPrebid() 1738 1739 assert.Equalf(t, test.expectedChannelObject, requestPrebid.Channel, "Channel information isn't correct: %s\n", test.description) 1740 } 1741 } 1742 } 1743 1744 func TestValidateRequestExt(t *testing.T) { 1745 testCases := []struct { 1746 description string 1747 givenRequestExt json.RawMessage 1748 expectedErrors []string 1749 }{ 1750 { 1751 description: "nil", 1752 givenRequestExt: nil, 1753 }, 1754 { 1755 description: "prebid - nil", 1756 givenRequestExt: json.RawMessage(`{}`), 1757 }, 1758 { 1759 description: "prebid - empty", 1760 givenRequestExt: json.RawMessage(`{"prebid":{}}`), 1761 }, 1762 { 1763 description: "prebid cache - empty", 1764 givenRequestExt: json.RawMessage(`{"prebid":{"cache":{}}}`), 1765 expectedErrors: []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`}, 1766 }, 1767 { 1768 description: "prebid cache - bids - null", 1769 givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":null}}}`), 1770 expectedErrors: []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`}, 1771 }, 1772 { 1773 description: "prebid cache - bids - wrong type", 1774 givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":true}}}`), 1775 expectedErrors: []string{`json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.bids of type openrtb_ext.ExtRequestPrebidCacheBids`}, 1776 }, 1777 { 1778 description: "prebid cache - bids - provided", 1779 givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":{}}}}`), 1780 }, 1781 { 1782 description: "prebid cache - vastxml - null", 1783 givenRequestExt: json.RawMessage(`{"prebid": {"cache": {"vastxml": null}}}`), 1784 expectedErrors: []string{`request.ext is invalid: request.ext.prebid.cache requires one of the "bids" or "vastxml" properties`}, 1785 }, 1786 { 1787 description: "prebid cache - vastxml - wrong type", 1788 givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"vastxml":true}}}`), 1789 expectedErrors: []string{`json: cannot unmarshal bool into Go struct field ExtRequestPrebidCache.cache.vastxml of type openrtb_ext.ExtRequestPrebidCacheVAST`}, 1790 }, 1791 { 1792 description: "prebid cache - vastxml - provided", 1793 givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"vastxml":{}}}}`), 1794 }, 1795 { 1796 description: "prebid cache - bids + vastxml - provided", 1797 givenRequestExt: json.RawMessage(`{"prebid":{"cache":{"bids":{},"vastxml":{}}}}`), 1798 }, 1799 { 1800 description: "prebid targeting", // test integration with validateTargeting 1801 givenRequestExt: json.RawMessage(`{"prebid":{"targeting":{}}}`), 1802 expectedErrors: []string{"ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"}, 1803 }, 1804 { 1805 description: "valid multibid", 1806 givenRequestExt: json.RawMessage(`{"prebid": {"multibid": [{"Bidder": "pubmatic", "MaxBids": 2}]}}`), 1807 }, 1808 { 1809 description: "multibid with invalid entries", 1810 givenRequestExt: json.RawMessage(`{"prebid": {"multibid": [{"Bidder": "pubmatic"}, {"Bidder": "pubmatic", "MaxBids": 2}, {"Bidders": ["pubmatic"], "MaxBids": 3}]}}`), 1811 expectedErrors: []string{ 1812 `maxBids not defined for {Bidder:pubmatic, Bidders:[], MaxBids:<nil>, TargetBidderCodePrefix:}`, 1813 `multiBid already defined for pubmatic, ignoring this instance {Bidder:, Bidders:[pubmatic], MaxBids:3, TargetBidderCodePrefix:}`, 1814 }, 1815 }, 1816 } 1817 1818 for _, test := range testCases { 1819 w := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: test.givenRequestExt}} 1820 errs := validateRequestExt(w) 1821 1822 if len(test.expectedErrors) > 0 { 1823 for i, expectedError := range test.expectedErrors { 1824 assert.EqualError(t, errs[i], expectedError, test.description) 1825 } 1826 } else { 1827 assert.Nil(t, errs, test.description) 1828 } 1829 } 1830 } 1831 1832 func TestValidateTargeting(t *testing.T) { 1833 testCases := []struct { 1834 name string 1835 givenTargeting *openrtb_ext.ExtRequestTargeting 1836 expectedError error 1837 }{ 1838 { 1839 name: "nil", 1840 givenTargeting: nil, 1841 expectedError: nil, 1842 }, 1843 { 1844 name: "empty", 1845 givenTargeting: &openrtb_ext.ExtRequestTargeting{}, 1846 expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"), 1847 }, 1848 { 1849 name: "includewinners nil, includebidderkeys false", 1850 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1851 IncludeBidderKeys: ptrutil.ToPtr(false), 1852 }, 1853 expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"), 1854 }, 1855 { 1856 name: "includewinners nil, includebidderkeys true", 1857 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1858 IncludeBidderKeys: ptrutil.ToPtr(true), 1859 }, 1860 expectedError: nil, 1861 }, 1862 { 1863 name: "includewinners false, includebidderkeys nil", 1864 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1865 IncludeWinners: ptrutil.ToPtr(false), 1866 }, 1867 expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"), 1868 }, 1869 { 1870 name: "includewinners true, includebidderkeys nil", 1871 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1872 IncludeWinners: ptrutil.ToPtr(true), 1873 }, 1874 expectedError: nil, 1875 }, 1876 { 1877 name: "all false", 1878 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1879 IncludeWinners: ptrutil.ToPtr(false), 1880 IncludeBidderKeys: ptrutil.ToPtr(false), 1881 }, 1882 expectedError: errors.New("ext.prebid.targeting: At least one of includewinners or includebidderkeys must be enabled to enable targeting support"), 1883 }, 1884 { 1885 name: "includewinners false, includebidderkeys true", 1886 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1887 IncludeWinners: ptrutil.ToPtr(false), 1888 IncludeBidderKeys: ptrutil.ToPtr(true), 1889 }, 1890 expectedError: nil, 1891 }, 1892 { 1893 name: "includewinners false, includebidderkeys true", 1894 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1895 IncludeWinners: ptrutil.ToPtr(true), 1896 IncludeBidderKeys: ptrutil.ToPtr(false), 1897 }, 1898 expectedError: nil, 1899 }, 1900 { 1901 name: "includewinners true, includebidderkeys true", 1902 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1903 IncludeWinners: ptrutil.ToPtr(true), 1904 IncludeBidderKeys: ptrutil.ToPtr(true), 1905 }, 1906 expectedError: nil, 1907 }, 1908 { 1909 name: "price granularity ranges out of order", 1910 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1911 IncludeWinners: ptrutil.ToPtr(true), 1912 PriceGranularity: &openrtb_ext.PriceGranularity{ 1913 Precision: ptrutil.ToPtr(2), 1914 Ranges: []openrtb_ext.GranularityRange{ 1915 {Min: 1.0, Max: 2.0, Increment: 0.2}, 1916 {Min: 0.0, Max: 1.0, Increment: 0.5}, 1917 }, 1918 }, 1919 }, 1920 expectedError: errors.New(`Price granularity error: range list must be ordered with increasing "max"`), 1921 }, 1922 { 1923 name: "media type price granularity video correct", 1924 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1925 IncludeWinners: ptrutil.ToPtr(true), 1926 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 1927 Video: &openrtb_ext.PriceGranularity{ 1928 Precision: ptrutil.ToPtr(2), 1929 Ranges: []openrtb_ext.GranularityRange{ 1930 {Min: 0.0, Max: 10.0, Increment: 1}, 1931 }, 1932 }, 1933 }, 1934 }, 1935 expectedError: nil, 1936 }, 1937 { 1938 name: "media type price granularity banner correct", 1939 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1940 IncludeWinners: ptrutil.ToPtr(true), 1941 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 1942 Banner: &openrtb_ext.PriceGranularity{ 1943 Precision: ptrutil.ToPtr(2), 1944 Ranges: []openrtb_ext.GranularityRange{ 1945 {Min: 0.0, Max: 10.0, Increment: 1}, 1946 }, 1947 }, 1948 }, 1949 }, 1950 expectedError: nil, 1951 }, 1952 { 1953 name: "media type price granularity native correct", 1954 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1955 IncludeWinners: ptrutil.ToPtr(true), 1956 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 1957 Native: &openrtb_ext.PriceGranularity{ 1958 Precision: ptrutil.ToPtr(2), 1959 Ranges: []openrtb_ext.GranularityRange{ 1960 {Min: 0.0, Max: 20.0, Increment: 1}, 1961 }, 1962 }, 1963 }, 1964 }, 1965 expectedError: nil, 1966 }, 1967 { 1968 name: "media type price granularity video and banner correct", 1969 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1970 IncludeWinners: ptrutil.ToPtr(true), 1971 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 1972 Banner: &openrtb_ext.PriceGranularity{ 1973 Precision: ptrutil.ToPtr(2), 1974 Ranges: []openrtb_ext.GranularityRange{ 1975 {Min: 0.0, Max: 10.0, Increment: 1}, 1976 }, 1977 }, 1978 Video: &openrtb_ext.PriceGranularity{ 1979 Precision: ptrutil.ToPtr(2), 1980 Ranges: []openrtb_ext.GranularityRange{ 1981 {Min: 0.0, Max: 10.0, Increment: 1}, 1982 }, 1983 }, 1984 }, 1985 }, 1986 expectedError: nil, 1987 }, 1988 { 1989 name: "media type price granularity video incorrect", 1990 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 1991 IncludeWinners: ptrutil.ToPtr(true), 1992 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 1993 Video: &openrtb_ext.PriceGranularity{ 1994 Precision: ptrutil.ToPtr(2), 1995 Ranges: []openrtb_ext.GranularityRange{ 1996 {Min: 0.0, Max: 10.0, Increment: -1}, 1997 }, 1998 }, 1999 }, 2000 }, 2001 expectedError: errors.New("Price granularity error: increment must be a nonzero positive number"), 2002 }, 2003 { 2004 name: "media type price granularity banner incorrect", 2005 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 2006 IncludeWinners: ptrutil.ToPtr(true), 2007 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 2008 Banner: &openrtb_ext.PriceGranularity{ 2009 Precision: ptrutil.ToPtr(2), 2010 Ranges: []openrtb_ext.GranularityRange{ 2011 {Min: 0.0, Max: 0.0, Increment: 1}, 2012 }, 2013 }, 2014 }, 2015 }, 2016 expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), 2017 }, 2018 { 2019 name: "media type price granularity native incorrect", 2020 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 2021 IncludeWinners: ptrutil.ToPtr(true), 2022 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 2023 Native: &openrtb_ext.PriceGranularity{ 2024 Precision: ptrutil.ToPtr(2), 2025 Ranges: []openrtb_ext.GranularityRange{ 2026 {Min: 0.0, Max: 0.0, Increment: 1}, 2027 }, 2028 }, 2029 }, 2030 }, 2031 expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), 2032 }, 2033 { 2034 name: "media type price granularity video correct and banner incorrect", 2035 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 2036 IncludeWinners: ptrutil.ToPtr(true), 2037 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 2038 Banner: &openrtb_ext.PriceGranularity{ 2039 Precision: ptrutil.ToPtr(2), 2040 Ranges: []openrtb_ext.GranularityRange{ 2041 {Min: 0.0, Max: 10.0, Increment: -1}, 2042 }, 2043 }, 2044 Video: &openrtb_ext.PriceGranularity{ 2045 Precision: ptrutil.ToPtr(2), 2046 Ranges: []openrtb_ext.GranularityRange{ 2047 {Min: 0.0, Max: 0.0, Increment: 1}, 2048 }, 2049 }, 2050 }, 2051 }, 2052 expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), 2053 }, 2054 { 2055 name: "media type price granularity native incorrect and banner correct", 2056 givenTargeting: &openrtb_ext.ExtRequestTargeting{ 2057 IncludeWinners: ptrutil.ToPtr(true), 2058 MediaTypePriceGranularity: openrtb_ext.MediaTypePriceGranularity{ 2059 Native: &openrtb_ext.PriceGranularity{ 2060 Precision: ptrutil.ToPtr(2), 2061 Ranges: []openrtb_ext.GranularityRange{ 2062 {Min: 0.0, Max: 10.0, Increment: -1}, 2063 }, 2064 }, 2065 Video: &openrtb_ext.PriceGranularity{ 2066 Precision: ptrutil.ToPtr(2), 2067 Ranges: []openrtb_ext.GranularityRange{ 2068 {Min: 0.0, Max: 0.0, Increment: 1}, 2069 }, 2070 }, 2071 }, 2072 }, 2073 expectedError: errors.New("Price granularity error: range list must be ordered with increasing \"max\""), 2074 }, 2075 } 2076 2077 for _, tc := range testCases { 2078 t.Run(tc.name, func(t *testing.T) { 2079 assert.Equal(t, tc.expectedError, validateTargeting(tc.givenTargeting), "Targeting") 2080 }) 2081 } 2082 } 2083 2084 func TestValidatePriceGranularity(t *testing.T) { 2085 testCases := []struct { 2086 description string 2087 givenPriceGranularity *openrtb_ext.PriceGranularity 2088 expectedError error 2089 }{ 2090 { 2091 description: "Precision is nil", 2092 givenPriceGranularity: &openrtb_ext.PriceGranularity{ 2093 Precision: nil, 2094 }, 2095 expectedError: errors.New("Price granularity error: precision is required"), 2096 }, 2097 { 2098 description: "Precision is negative", 2099 givenPriceGranularity: &openrtb_ext.PriceGranularity{ 2100 Precision: ptrutil.ToPtr(-1), 2101 }, 2102 expectedError: errors.New("Price granularity error: precision must be non-negative"), 2103 }, 2104 { 2105 description: "Precision is too big", 2106 givenPriceGranularity: &openrtb_ext.PriceGranularity{ 2107 Precision: ptrutil.ToPtr(20), 2108 }, 2109 expectedError: errors.New("Price granularity error: precision of more than 15 significant figures is not supported"), 2110 }, 2111 { 2112 description: "price granularity ranges out of order", 2113 givenPriceGranularity: &openrtb_ext.PriceGranularity{ 2114 Precision: ptrutil.ToPtr(2), 2115 Ranges: []openrtb_ext.GranularityRange{ 2116 {Min: 1.0, Max: 2.0, Increment: 0.2}, 2117 {Min: 0.0, Max: 1.0, Increment: 0.5}, 2118 }, 2119 }, 2120 expectedError: errors.New(`Price granularity error: range list must be ordered with increasing "max"`), 2121 }, 2122 { 2123 description: "price granularity negative increment", 2124 givenPriceGranularity: &openrtb_ext.PriceGranularity{ 2125 Precision: ptrutil.ToPtr(2), 2126 Ranges: []openrtb_ext.GranularityRange{ 2127 {Min: 0.0, Max: 1.0, Increment: -0.1}, 2128 }, 2129 }, 2130 expectedError: errors.New("Price granularity error: increment must be a nonzero positive number"), 2131 }, 2132 { 2133 description: "price granularity correct", 2134 givenPriceGranularity: &openrtb_ext.PriceGranularity{ 2135 Precision: ptrutil.ToPtr(2), 2136 Ranges: []openrtb_ext.GranularityRange{ 2137 {Min: 0.0, Max: 10.0, Increment: 1}, 2138 }, 2139 }, 2140 expectedError: nil, 2141 }, 2142 { 2143 description: "price granularity with correct precision and ranges not specified", 2144 givenPriceGranularity: &openrtb_ext.PriceGranularity{ 2145 Precision: ptrutil.ToPtr(2), 2146 }, 2147 expectedError: nil, 2148 }, 2149 } 2150 2151 for _, tc := range testCases { 2152 t.Run(tc.description, func(t *testing.T) { 2153 assert.Equal(t, tc.expectedError, validatePriceGranularity(tc.givenPriceGranularity)) 2154 }) 2155 } 2156 } 2157 2158 func TestValidateOrFillChannel(t *testing.T) { 2159 testCases := []struct { 2160 description string 2161 givenIsAmp bool 2162 givenRequestWrapper *openrtb_ext.RequestWrapper 2163 expectedError error 2164 expectedChannelObject *openrtb_ext.ExtRequestPrebidChannel 2165 }{ 2166 { 2167 description: "No request.ext info in app request, so we expect channel name to be set to app", 2168 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2169 BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}}, 2170 }, 2171 givenIsAmp: false, 2172 expectedError: nil, 2173 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, 2174 }, 2175 { 2176 description: "No request.ext info in amp request, so we expect channel name to be set to amp", 2177 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2178 BidRequest: &openrtb2.BidRequest{}, 2179 }, 2180 givenIsAmp: true, 2181 expectedError: nil, 2182 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""}, 2183 }, 2184 { 2185 description: "Channel object in request with populated name/version, we expect same name/version in object that's created", 2186 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2187 BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"channel": {"name": "video", "version": "1.0"}}}`)}, 2188 }, 2189 givenIsAmp: false, 2190 expectedError: nil, 2191 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: "video", Version: "1.0"}, 2192 }, 2193 { 2194 description: "No channel object in site request, expect nil", 2195 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2196 BidRequest: &openrtb2.BidRequest{Site: &openrtb2.Site{}, Ext: []byte(`{"prebid":{}}`)}, 2197 }, 2198 givenIsAmp: false, 2199 expectedError: nil, 2200 expectedChannelObject: nil, 2201 }, 2202 { 2203 description: "No channel name given in channel object, we expect error to be thrown", 2204 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2205 BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{"prebid":{"channel": {"name": "", "version": ""}}}`)}, 2206 }, 2207 givenIsAmp: false, 2208 expectedError: errors.New("ext.prebid.channel.name can't be empty"), 2209 expectedChannelObject: nil, 2210 }, 2211 { 2212 description: "App request, has request.ext, no request.ext.prebid, expect channel name to be filled with app", 2213 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2214 BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{}`)}, 2215 }, 2216 givenIsAmp: false, 2217 expectedError: nil, 2218 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, 2219 }, 2220 { 2221 description: "App request, has request.ext.prebid, but no channel object, expect channel name to be filled with app", 2222 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2223 BidRequest: &openrtb2.BidRequest{App: &openrtb2.App{}, Ext: []byte(`{"prebid":{}}`)}, 2224 }, 2225 givenIsAmp: false, 2226 expectedError: nil, 2227 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: appChannel, Version: ""}, 2228 }, 2229 { 2230 description: "Amp request, has request.ext, no request.ext.prebid, expect channel name to be filled with amp", 2231 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2232 BidRequest: &openrtb2.BidRequest{Ext: []byte(`{}`)}, 2233 }, 2234 givenIsAmp: true, 2235 expectedError: nil, 2236 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""}, 2237 }, 2238 { 2239 description: "Amp request, has request.ext.prebid, but no channel object, expect channel name to be filled with amp", 2240 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2241 BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{}}`)}, 2242 }, 2243 givenIsAmp: true, 2244 expectedError: nil, 2245 expectedChannelObject: &openrtb_ext.ExtRequestPrebidChannel{Name: ampChannel, Version: ""}, 2246 }, 2247 } 2248 2249 for _, test := range testCases { 2250 err := validateOrFillChannel(test.givenRequestWrapper, test.givenIsAmp) 2251 assert.Equalf(t, test.expectedError, err, "Error doesn't match: %s\n", test.description) 2252 2253 if err == nil { 2254 requestExt, err := test.givenRequestWrapper.GetRequestExt() 2255 assert.Empty(t, err, test.description) 2256 requestPrebid := requestExt.GetPrebid() 2257 2258 assert.Equalf(t, test.expectedChannelObject, requestPrebid.Channel, "Channel information isn't correct: %s\n", test.description) 2259 } 2260 } 2261 } 2262 2263 func TestSetIntegrationType(t *testing.T) { 2264 deps := &endpointDeps{ 2265 fakeUUIDGenerator{}, 2266 &nobidExchange{}, 2267 mockBidderParamValidator{}, 2268 &mockStoredReqFetcher{}, 2269 empty_fetcher.EmptyFetcher{}, 2270 empty_fetcher.EmptyFetcher{}, 2271 &config.Configuration{}, 2272 &metricsConfig.NilMetricsEngine{}, 2273 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2274 map[string]string{}, 2275 false, 2276 []byte{}, 2277 openrtb_ext.BuildBidderMap(), 2278 nil, 2279 nil, 2280 hardcodedResponseIPValidator{response: true}, 2281 empty_fetcher.EmptyFetcher{}, 2282 hooks.EmptyPlanBuilder{}, 2283 nil, 2284 } 2285 2286 testCases := []struct { 2287 description string 2288 givenRequestWrapper *openrtb_ext.RequestWrapper 2289 givenAccount *config.Account 2290 expectedIntegrationType string 2291 }{ 2292 { 2293 description: "Request has integration type defined, expect that same integration type", 2294 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2295 BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"integration": "TestIntegrationType"}}`)}, 2296 }, 2297 givenAccount: &config.Account{DefaultIntegration: "TestDefaultIntegration"}, 2298 expectedIntegrationType: "TestIntegrationType", 2299 }, 2300 { 2301 description: "Request doesn't have request.ext.prebid path, expect default integration value", 2302 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2303 BidRequest: &openrtb2.BidRequest{Ext: []byte(``)}, 2304 }, 2305 givenAccount: &config.Account{DefaultIntegration: "TestDefaultIntegration"}, 2306 expectedIntegrationType: "TestDefaultIntegration", 2307 }, 2308 { 2309 description: "Request has blank integration in request, expect default integration value ", 2310 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 2311 BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid":{"integration": ""}}`)}, 2312 }, 2313 givenAccount: &config.Account{DefaultIntegration: "TestDefaultIntegration"}, 2314 expectedIntegrationType: "TestDefaultIntegration", 2315 }, 2316 } 2317 2318 for _, test := range testCases { 2319 err := deps.setIntegrationType(test.givenRequestWrapper, test.givenAccount) 2320 assert.Empty(t, err, test.description) 2321 integrationTypeFromReq, err2 := getIntegrationFromRequest(test.givenRequestWrapper) 2322 assert.Empty(t, err2, test.description) 2323 assert.Equalf(t, test.expectedIntegrationType, integrationTypeFromReq, "Integration type information isn't correct: %s\n", test.description) 2324 } 2325 } 2326 2327 func TestStoredRequestGenerateUuid(t *testing.T) { 2328 uuid := "foo" 2329 2330 deps := &endpointDeps{ 2331 fakeUUIDGenerator{id: "foo", err: nil}, 2332 &nobidExchange{}, 2333 mockBidderParamValidator{}, 2334 &mockStoredReqFetcher{}, 2335 empty_fetcher.EmptyFetcher{}, 2336 empty_fetcher.EmptyFetcher{}, 2337 &config.Configuration{MaxRequestSize: maxSize}, 2338 &metricsConfig.NilMetricsEngine{}, 2339 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2340 map[string]string{}, 2341 false, 2342 []byte{}, 2343 openrtb_ext.BuildBidderMap(), 2344 nil, 2345 nil, 2346 hardcodedResponseIPValidator{response: true}, 2347 empty_fetcher.EmptyFetcher{}, 2348 hooks.EmptyPlanBuilder{}, 2349 nil, 2350 } 2351 2352 req := &openrtb2.BidRequest{} 2353 2354 testCases := []struct { 2355 description string 2356 givenRawData string 2357 givenGenerateRequestID bool 2358 expectedID string 2359 expectedCur string 2360 }{ 2361 { 2362 description: "GenerateRequestID is true, rawData is an app request and has stored bid request we should generate uuid", 2363 givenRawData: testBidRequests[2], 2364 givenGenerateRequestID: true, 2365 expectedID: uuid, 2366 }, 2367 { 2368 description: "GenerateRequestID is true, rawData is a site request, has stored bid, and stored bidrequestID is not the macro {{UUID}}, we should not generate uuid", 2369 givenRawData: testBidRequests[3], 2370 givenGenerateRequestID: true, 2371 expectedID: "ThisID", 2372 }, 2373 { 2374 description: "GenerateRequestID is false, rawData is an app request and has stored bid, and stored bidrequestID is the macro {{UUID}}, so we should generate uuid", 2375 givenRawData: testBidRequests[4], 2376 givenGenerateRequestID: false, 2377 expectedID: uuid, 2378 }, 2379 { 2380 description: "GenerateRequestID is true, rawData is an app request, but no stored bid, we should not generate uuid", 2381 givenRawData: testBidRequests[0], 2382 givenGenerateRequestID: true, 2383 expectedID: "ThisID", 2384 }, 2385 { 2386 description: "GenerateRequestID is false and macro ID is not present, so we should not generate uuid", 2387 givenRawData: testBidRequests[0], 2388 givenGenerateRequestID: false, 2389 expectedID: "ThisID", 2390 }, 2391 { 2392 description: "GenerateRequestID is false, and rawData is a site request, and macro {{UUID}} is present, we should generate uuid", 2393 givenRawData: testBidRequests[1], 2394 givenGenerateRequestID: false, 2395 expectedID: uuid, 2396 }, 2397 { 2398 description: "Macro ID {{UUID}} case sensitivity check meaning a macro that is lowercase {{uuid}} shouldn't generate a uuid", 2399 givenRawData: testBidRequests[2], 2400 givenGenerateRequestID: false, 2401 expectedID: "ThisID", 2402 }, 2403 { 2404 description: "Test to check that stored requests are being merged properly when UUID isn't being generated", 2405 givenRawData: testBidRequests[5], 2406 givenGenerateRequestID: false, 2407 expectedID: "ThisID", 2408 expectedCur: "USD", 2409 }, 2410 } 2411 2412 for _, test := range testCases { 2413 deps.cfg.GenerateRequestID = test.givenGenerateRequestID 2414 impInfo, errs := parseImpInfo([]byte(test.givenRawData)) 2415 assert.Empty(t, errs, test.description) 2416 storedBidRequestId, hasStoredBidRequest, storedRequests, storedImps, errs := deps.getStoredRequests(context.Background(), json.RawMessage(test.givenRawData), impInfo) 2417 assert.Empty(t, errs, test.description) 2418 newRequest, _, errList := deps.processStoredRequests(json.RawMessage(test.givenRawData), impInfo, storedRequests, storedImps, storedBidRequestId, hasStoredBidRequest) 2419 assert.Empty(t, errList, test.description) 2420 2421 if err := json.Unmarshal(newRequest, req); err != nil { 2422 t.Errorf("processStoredRequests Error: %s", err.Error()) 2423 } 2424 if test.expectedCur != "" { 2425 assert.Equalf(t, test.expectedCur, req.Cur[0], "The stored request wasn't merged properly: %s\n", test.description) 2426 } 2427 assert.Equalf(t, test.expectedID, req.ID, "The Bid Request ID is incorrect: %s\n", test.description) 2428 } 2429 } 2430 2431 // TestOversizedRequest makes sure we behave properly when the request size exceeds the configured max. 2432 func TestOversizedRequest(t *testing.T) { 2433 reqBody := validRequest(t, "site.json") 2434 deps := &endpointDeps{ 2435 fakeUUIDGenerator{}, 2436 &nobidExchange{}, 2437 mockBidderParamValidator{}, 2438 &mockStoredReqFetcher{}, 2439 empty_fetcher.EmptyFetcher{}, 2440 empty_fetcher.EmptyFetcher{}, 2441 &config.Configuration{MaxRequestSize: int64(len(reqBody) - 1)}, 2442 &metricsConfig.NilMetricsEngine{}, 2443 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2444 map[string]string{}, 2445 false, 2446 []byte{}, 2447 openrtb_ext.BuildBidderMap(), 2448 nil, 2449 nil, 2450 hardcodedResponseIPValidator{response: true}, 2451 empty_fetcher.EmptyFetcher{}, 2452 hooks.EmptyPlanBuilder{}, 2453 nil, 2454 } 2455 2456 req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) 2457 recorder := httptest.NewRecorder() 2458 2459 deps.Auction(recorder, req, nil) 2460 2461 if recorder.Code != http.StatusBadRequest { 2462 t.Errorf("Endpoint should return a 400 if the request exceeds the size max.") 2463 } 2464 2465 if bytesRead, err := req.Body.Read(make([]byte, 1)); bytesRead != 0 || err != io.EOF { 2466 t.Errorf("The request body should still be fully read.") 2467 } 2468 } 2469 2470 // TestRequestSizeEdgeCase makes sure we behave properly when the request size *equals* the configured max. 2471 func TestRequestSizeEdgeCase(t *testing.T) { 2472 reqBody := validRequest(t, "site.json") 2473 deps := &endpointDeps{ 2474 fakeUUIDGenerator{}, 2475 &nobidExchange{}, 2476 mockBidderParamValidator{}, 2477 &mockStoredReqFetcher{}, 2478 empty_fetcher.EmptyFetcher{}, 2479 empty_fetcher.EmptyFetcher{}, 2480 &config.Configuration{MaxRequestSize: int64(len(reqBody))}, 2481 &metricsConfig.NilMetricsEngine{}, 2482 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2483 map[string]string{}, 2484 false, 2485 []byte{}, 2486 openrtb_ext.BuildBidderMap(), 2487 nil, 2488 nil, 2489 hardcodedResponseIPValidator{response: true}, 2490 empty_fetcher.EmptyFetcher{}, 2491 hooks.EmptyPlanBuilder{}, 2492 nil, 2493 } 2494 2495 req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) 2496 recorder := httptest.NewRecorder() 2497 2498 deps.Auction(recorder, req, nil) 2499 2500 if recorder.Code != http.StatusOK { 2501 t.Errorf("Endpoint should return a 200 if the request equals the size max.") 2502 } 2503 2504 if bytesRead, err := req.Body.Read(make([]byte, 1)); bytesRead != 0 || err != io.EOF { 2505 t.Errorf("The request body should have been read to completion.") 2506 } 2507 } 2508 2509 // TestNoEncoding prevents #231. 2510 func TestNoEncoding(t *testing.T) { 2511 endpoint, _ := NewEndpoint( 2512 fakeUUIDGenerator{}, 2513 &mockExchange{}, 2514 mockBidderParamValidator{}, 2515 &mockStoredReqFetcher{}, 2516 empty_fetcher.EmptyFetcher{}, 2517 &config.Configuration{MaxRequestSize: maxSize}, 2518 &metricsConfig.NilMetricsEngine{}, 2519 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2520 map[string]string{}, 2521 []byte{}, 2522 openrtb_ext.BuildBidderMap(), 2523 empty_fetcher.EmptyFetcher{}, 2524 hooks.EmptyPlanBuilder{}, 2525 nil, 2526 ) 2527 request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) 2528 recorder := httptest.NewRecorder() 2529 endpoint(recorder, request, nil) 2530 2531 if !strings.Contains(recorder.Body.String(), "<script></script>") { 2532 t.Errorf("The Response from the exchange should not be html-encoded") 2533 } 2534 } 2535 2536 // TestTimeoutParser makes sure we parse tmax properly. 2537 func TestTimeoutParser(t *testing.T) { 2538 reqJson := json.RawMessage(`{"tmax":22}`) 2539 timeout := parseTimeout(reqJson, 11*time.Millisecond) 2540 if timeout != 22*time.Millisecond { 2541 t.Errorf("Failed to parse tmax properly. Expected %d, got %d", 22*time.Millisecond, timeout) 2542 } 2543 } 2544 2545 func TestImplicitAMPNoExt(t *testing.T) { 2546 httpReq, err := http.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) 2547 if !assert.NoError(t, err) { 2548 return 2549 } 2550 2551 reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 2552 Site: &openrtb2.Site{}, 2553 }} 2554 2555 setSiteImplicitly(httpReq, reqWrapper) 2556 2557 assert.NoError(t, reqWrapper.RebuildRequest()) 2558 assert.JSONEq(t, `{"amp":0}`, string(reqWrapper.Site.Ext)) 2559 } 2560 2561 func TestImplicitAMPOtherExt(t *testing.T) { 2562 httpReq, err := http.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) 2563 if !assert.NoError(t, err) { 2564 return 2565 } 2566 2567 reqWrapper := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 2568 Site: &openrtb2.Site{ 2569 Ext: json.RawMessage(`{"other":true}`), 2570 }, 2571 }} 2572 2573 setSiteImplicitly(httpReq, reqWrapper) 2574 2575 assert.NoError(t, reqWrapper.RebuildRequest()) 2576 assert.JSONEq(t, `{"amp":0,"other":true}`, string(reqWrapper.Site.Ext)) 2577 } 2578 2579 func TestExplicitAMP(t *testing.T) { 2580 httpReq, err := http.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site-amp.json"))) 2581 if !assert.NoError(t, err) { 2582 return 2583 } 2584 2585 bidReq := &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{ 2586 Site: &openrtb2.Site{ 2587 Ext: json.RawMessage(`{"amp":1}`), 2588 }, 2589 }} 2590 setSiteImplicitly(httpReq, bidReq) 2591 assert.JSONEq(t, `{"amp":1}`, string(bidReq.Site.Ext)) 2592 } 2593 2594 // TestContentType prevents #328 2595 func TestContentType(t *testing.T) { 2596 endpoint, _ := NewEndpoint( 2597 fakeUUIDGenerator{}, 2598 &mockExchange{}, 2599 mockBidderParamValidator{}, 2600 &mockStoredReqFetcher{}, 2601 empty_fetcher.EmptyFetcher{}, 2602 &config.Configuration{MaxRequestSize: maxSize}, 2603 &metricsConfig.NilMetricsEngine{}, 2604 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2605 map[string]string{}, 2606 []byte{}, 2607 openrtb_ext.BuildBidderMap(), 2608 empty_fetcher.EmptyFetcher{}, 2609 hooks.EmptyPlanBuilder{}, 2610 nil, 2611 ) 2612 request := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "site.json"))) 2613 recorder := httptest.NewRecorder() 2614 endpoint(recorder, request, nil) 2615 2616 if recorder.Header().Get("Content-Type") != "application/json" { 2617 t.Errorf("Content-Type should be application/json. Got %s", recorder.Header().Get("Content-Type")) 2618 } 2619 } 2620 2621 func TestValidateImpExt(t *testing.T) { 2622 type testCase struct { 2623 description string 2624 impExt json.RawMessage 2625 expectedImpExt string 2626 expectedErrs []error 2627 } 2628 testGroups := []struct { 2629 description string 2630 testCases []testCase 2631 }{ 2632 { 2633 "Empty", 2634 []testCase{ 2635 { 2636 description: "Empty", 2637 impExt: nil, 2638 expectedImpExt: "", 2639 expectedErrs: []error{errors.New("request.imp[0].ext is required")}, 2640 }, 2641 }, 2642 }, 2643 { 2644 "Unknown bidder tests", 2645 []testCase{ 2646 { 2647 description: "Unknown Bidder only", 2648 impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555}}`), 2649 expectedImpExt: `{"unknownbidder":{"placement_id":555}}`, 2650 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2651 }, 2652 { 2653 description: "Unknown Prebid Ext Bidder only", 2654 impExt: json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`), 2655 expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}}}`, 2656 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2657 }, 2658 { 2659 description: "Unknown Prebid Ext Bidder + First Party Data Context", 2660 impExt: json.RawMessage(`{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`), 2661 expectedImpExt: `{"prebid":{"bidder":{"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, 2662 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2663 }, 2664 { 2665 description: "Unknown Bidder + First Party Data Context", 2666 impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555} ,"context":{"data":{"keywords":"prebid server example"}}}`), 2667 expectedImpExt: `{"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, 2668 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2669 }, 2670 { 2671 description: "Unknown Bidder + Disabled Bidder", 2672 impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), 2673 expectedImpExt: `{"unknownbidder":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`, 2674 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2675 }, 2676 { 2677 description: "Unknown Bidder + Disabled Prebid Ext Bidder", 2678 impExt: json.RawMessage(`{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`), 2679 expectedImpExt: `{"unknownbidder":{"placement_id":555},"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, 2680 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2681 }, 2682 }, 2683 }, 2684 { 2685 "Disabled bidder tests", 2686 []testCase{ 2687 { 2688 description: "Disabled Bidder", 2689 impExt: json.RawMessage(`{"disabledbidder":{"foo":"bar"}}`), 2690 expectedImpExt: `{"disabledbidder":{"foo":"bar"}}`, 2691 expectedErrs: []error{ 2692 &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, 2693 errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), 2694 }, 2695 // if only bidder(s) found in request.imp[x].ext.{biddername} or request.imp[x].ext.prebid.bidder.{biddername} are disabled, return error 2696 }, 2697 { 2698 description: "Disabled Prebid Ext Bidder", 2699 impExt: json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`), 2700 expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}}}`, 2701 expectedErrs: []error{ 2702 &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, 2703 errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), 2704 }, 2705 }, 2706 { 2707 description: "Disabled Bidder + First Party Data Context", 2708 impExt: json.RawMessage(`{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), 2709 expectedImpExt: `{"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`, 2710 expectedErrs: []error{ 2711 &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, 2712 errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), 2713 }, 2714 }, 2715 { 2716 description: "Disabled Prebid Ext Bidder + First Party Data Context", 2717 impExt: json.RawMessage(`{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), 2718 expectedImpExt: `{"prebid":{"bidder":{"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`, 2719 expectedErrs: []error{ 2720 &errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}, 2721 errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), 2722 }, 2723 }, 2724 }, 2725 }, 2726 { 2727 "First Party only", 2728 []testCase{ 2729 { 2730 description: "First Party Data Context", 2731 impExt: json.RawMessage(`{"context":{"data":{"keywords":"prebid server example"}}}`), 2732 expectedImpExt: `{"context":{"data":{"keywords":"prebid server example"}}}`, 2733 expectedErrs: []error{ 2734 errors.New("request.imp[0].ext.prebid.bidder must contain at least one bidder"), 2735 }, 2736 }, 2737 }, 2738 }, 2739 { 2740 "Valid bidder tests", 2741 []testCase{ 2742 { 2743 description: "Valid bidder root ext", 2744 impExt: json.RawMessage(`{"appnexus":{"placement_id":555}}`), 2745 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, 2746 expectedErrs: []error{}, 2747 }, 2748 { 2749 description: "Valid bidder in prebid field", 2750 impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`), 2751 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, 2752 expectedErrs: []error{}, 2753 }, 2754 { 2755 description: "Valid Bidder + First Party Data Context", 2756 impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), 2757 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, 2758 expectedErrs: []error{}, 2759 }, 2760 { 2761 description: "Valid Prebid Ext Bidder + First Party Data Context", 2762 impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555}}} ,"context":{"data":{"keywords":"prebid server example"}}}`), 2763 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, 2764 expectedErrs: []error{}, 2765 }, 2766 { 2767 description: "Valid Bidder + Unknown Bidder", 2768 impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`), 2769 expectedImpExt: `{"appnexus":{"placement_id":555},"unknownbidder":{"placement_id":555}}`, 2770 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2771 }, 2772 { 2773 description: "Valid Bidder + Disabled Bidder", 2774 impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}`), 2775 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}}}`, 2776 expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, 2777 }, 2778 { 2779 description: "Valid Bidder + Disabled Bidder + First Party Data Context", 2780 impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"context":{"data":{"keywords":"prebid server example"}}}`), 2781 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, 2782 expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, 2783 }, 2784 { 2785 description: "Valid Bidder + Disabled Bidder + Unknown Bidder + First Party Data Context", 2786 impExt: json.RawMessage(`{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`), 2787 expectedImpExt: `{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555},"context":{"data":{"keywords":"prebid server example"}}}`, 2788 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2789 }, 2790 { 2791 description: "Valid Prebid Ext Bidder + Disabled Bidder Ext", 2792 impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}}}`), 2793 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}}}`, 2794 expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, 2795 }, 2796 { 2797 description: "Valid Prebid Ext Bidder + Disabled Ext Bidder + First Party Data Context", 2798 impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"}}},"context":{"data":{"keywords":"prebid server example"}}}`), 2799 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id": 555}}},"context":{"data":{"keywords":"prebid server example"}}}`, 2800 expectedErrs: []error{&errortypes.BidderTemporarilyDisabled{Message: "The bidder 'disabledbidder' has been disabled."}}, 2801 }, 2802 { 2803 description: "Valid Prebid Ext Bidder + Disabled Ext Bidder + Unknown Ext + First Party Data Context", 2804 impExt: json.RawMessage(`{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`), 2805 expectedImpExt: `{"prebid":{"bidder":{"appnexus":{"placement_id":555},"disabledbidder":{"foo":"bar"},"unknownbidder":{"placement_id":555}}},"context":{"data":{"keywords":"prebid server example"}}}`, 2806 expectedErrs: []error{errors.New("request.imp[0].ext.prebid.bidder contains unknown bidder: unknownbidder. Did you forget an alias in request.ext.prebid.aliases?")}, 2807 }, 2808 }, 2809 }, 2810 } 2811 2812 deps := &endpointDeps{ 2813 fakeUUIDGenerator{}, 2814 &nobidExchange{}, 2815 mockBidderParamValidator{}, 2816 &mockStoredReqFetcher{}, 2817 empty_fetcher.EmptyFetcher{}, 2818 empty_fetcher.EmptyFetcher{}, 2819 &config.Configuration{MaxRequestSize: int64(8096)}, 2820 &metricsConfig.NilMetricsEngine{}, 2821 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2822 map[string]string{"disabledbidder": "The bidder 'disabledbidder' has been disabled."}, 2823 false, 2824 []byte{}, 2825 openrtb_ext.BuildBidderMap(), 2826 nil, 2827 nil, 2828 hardcodedResponseIPValidator{response: true}, 2829 empty_fetcher.EmptyFetcher{}, 2830 hooks.EmptyPlanBuilder{}, 2831 nil, 2832 } 2833 2834 for _, group := range testGroups { 2835 for _, test := range group.testCases { 2836 imp := &openrtb2.Imp{Ext: test.impExt} 2837 impWrapper := &openrtb_ext.ImpWrapper{Imp: imp} 2838 2839 errs := deps.validateImpExt(impWrapper, nil, 0, false, nil) 2840 2841 assert.NoError(t, impWrapper.RebuildImp(), test.description+":rebuild_imp") 2842 2843 if len(test.expectedImpExt) > 0 { 2844 assert.JSONEq(t, test.expectedImpExt, string(imp.Ext), "imp.ext JSON does not match expected. Test: %s. %s\n", group.description, test.description) 2845 } else { 2846 assert.Empty(t, imp.Ext, "imp.ext expected to be empty but was: %s. Test: %s. %s\n", string(imp.Ext), group.description, test.description) 2847 } 2848 assert.Equal(t, test.expectedErrs, errs, "errs slice does not match expected. Test: %s. %s\n", group.description, test.description) 2849 } 2850 } 2851 } 2852 2853 func validRequest(t *testing.T, filename string) string { 2854 requestData, err := os.ReadFile("sample-requests/valid-whole/supplementary/" + filename) 2855 if err != nil { 2856 t.Fatalf("Failed to fetch a valid request: %v", err) 2857 } 2858 testBidRequest, _, _, err := jsonparser.Get(requestData, "mockBidRequest") 2859 assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err) 2860 2861 return string(testBidRequest) 2862 } 2863 2864 func TestCurrencyTrunc(t *testing.T) { 2865 deps := &endpointDeps{ 2866 fakeUUIDGenerator{}, 2867 &nobidExchange{}, 2868 mockBidderParamValidator{}, 2869 &mockStoredReqFetcher{}, 2870 empty_fetcher.EmptyFetcher{}, 2871 empty_fetcher.EmptyFetcher{}, 2872 &config.Configuration{}, 2873 &metricsConfig.NilMetricsEngine{}, 2874 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2875 map[string]string{}, 2876 false, 2877 []byte{}, 2878 openrtb_ext.BuildBidderMap(), 2879 nil, 2880 nil, 2881 hardcodedResponseIPValidator{response: true}, 2882 empty_fetcher.EmptyFetcher{}, 2883 hooks.EmptyPlanBuilder{}, 2884 nil, 2885 } 2886 2887 ui := int64(1) 2888 req := openrtb2.BidRequest{ 2889 ID: "anyRequestID", 2890 Imp: []openrtb2.Imp{ 2891 { 2892 ID: "anyImpID", 2893 Banner: &openrtb2.Banner{ 2894 W: &ui, 2895 H: &ui, 2896 }, 2897 Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), 2898 }, 2899 }, 2900 Site: &openrtb2.Site{ 2901 ID: "anySiteID", 2902 }, 2903 Cur: []string{"USD", "EUR"}, 2904 } 2905 2906 errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) 2907 2908 expectedError := errortypes.Warning{Message: "A prebid request can only process one currency. Taking the first currency in the list, USD, as the active currency"} 2909 assert.ElementsMatch(t, errL, []error{&expectedError}) 2910 } 2911 2912 func TestCCPAInvalid(t *testing.T) { 2913 deps := &endpointDeps{ 2914 fakeUUIDGenerator{}, 2915 &nobidExchange{}, 2916 mockBidderParamValidator{}, 2917 &mockStoredReqFetcher{}, 2918 empty_fetcher.EmptyFetcher{}, 2919 empty_fetcher.EmptyFetcher{}, 2920 &config.Configuration{}, 2921 &metricsConfig.NilMetricsEngine{}, 2922 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2923 map[string]string{}, 2924 false, 2925 []byte{}, 2926 openrtb_ext.BuildBidderMap(), 2927 nil, 2928 nil, 2929 hardcodedResponseIPValidator{response: true}, 2930 empty_fetcher.EmptyFetcher{}, 2931 hooks.EmptyPlanBuilder{}, 2932 nil, 2933 } 2934 2935 ui := int64(1) 2936 req := openrtb2.BidRequest{ 2937 ID: "anyRequestID", 2938 Imp: []openrtb2.Imp{ 2939 { 2940 ID: "anyImpID", 2941 Banner: &openrtb2.Banner{ 2942 W: &ui, 2943 H: &ui, 2944 }, 2945 Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), 2946 }, 2947 }, 2948 Site: &openrtb2.Site{ 2949 ID: "anySiteID", 2950 }, 2951 Regs: &openrtb2.Regs{ 2952 Ext: json.RawMessage(`{"us_privacy": "invalid by length"}`), 2953 }, 2954 } 2955 2956 errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) 2957 2958 expectedWarning := errortypes.Warning{ 2959 Message: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", 2960 WarningCode: errortypes.InvalidPrivacyConsentWarningCode} 2961 assert.ElementsMatch(t, errL, []error{&expectedWarning}) 2962 } 2963 2964 func TestNoSaleInvalid(t *testing.T) { 2965 deps := &endpointDeps{ 2966 fakeUUIDGenerator{}, 2967 &nobidExchange{}, 2968 mockBidderParamValidator{}, 2969 &mockStoredReqFetcher{}, 2970 empty_fetcher.EmptyFetcher{}, 2971 empty_fetcher.EmptyFetcher{}, 2972 &config.Configuration{}, 2973 &metricsConfig.NilMetricsEngine{}, 2974 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 2975 map[string]string{}, 2976 false, 2977 []byte{}, 2978 openrtb_ext.BuildBidderMap(), 2979 nil, 2980 nil, 2981 hardcodedResponseIPValidator{response: true}, 2982 empty_fetcher.EmptyFetcher{}, 2983 hooks.EmptyPlanBuilder{}, 2984 nil, 2985 } 2986 2987 ui := int64(1) 2988 req := openrtb2.BidRequest{ 2989 ID: "anyRequestID", 2990 Imp: []openrtb2.Imp{ 2991 { 2992 ID: "anyImpID", 2993 Banner: &openrtb2.Banner{ 2994 W: &ui, 2995 H: &ui, 2996 }, 2997 Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), 2998 }, 2999 }, 3000 Site: &openrtb2.Site{ 3001 ID: "anySiteID", 3002 }, 3003 Regs: &openrtb2.Regs{ 3004 Ext: json.RawMessage(`{"us_privacy": "1NYN"}`), 3005 }, 3006 Ext: json.RawMessage(`{"prebid": {"nosale": ["*", "appnexus"]} }`), 3007 } 3008 3009 errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) 3010 3011 expectedError := errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided") 3012 assert.ElementsMatch(t, errL, []error{expectedError}) 3013 } 3014 3015 func TestValidateSourceTID(t *testing.T) { 3016 cfg := &config.Configuration{ 3017 AutoGenSourceTID: true, 3018 } 3019 3020 deps := &endpointDeps{ 3021 fakeUUIDGenerator{}, 3022 &nobidExchange{}, 3023 mockBidderParamValidator{}, 3024 &mockStoredReqFetcher{}, 3025 empty_fetcher.EmptyFetcher{}, 3026 empty_fetcher.EmptyFetcher{}, 3027 cfg, 3028 &metricsConfig.NilMetricsEngine{}, 3029 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 3030 map[string]string{}, 3031 false, 3032 []byte{}, 3033 openrtb_ext.BuildBidderMap(), 3034 nil, 3035 nil, 3036 hardcodedResponseIPValidator{response: true}, 3037 empty_fetcher.EmptyFetcher{}, 3038 hooks.EmptyPlanBuilder{}, 3039 nil, 3040 } 3041 3042 ui := int64(1) 3043 req := openrtb2.BidRequest{ 3044 ID: "anyRequestID", 3045 Imp: []openrtb2.Imp{ 3046 { 3047 ID: "anyImpID", 3048 Banner: &openrtb2.Banner{ 3049 W: &ui, 3050 H: &ui, 3051 }, 3052 Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), 3053 }, 3054 }, 3055 Site: &openrtb2.Site{ 3056 ID: "anySiteID", 3057 }, 3058 } 3059 3060 deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) 3061 assert.NotEmpty(t, req.Source.TID, "Expected req.Source.TID to be filled with a randomly generated UID") 3062 } 3063 3064 func TestSChainInvalid(t *testing.T) { 3065 deps := &endpointDeps{ 3066 fakeUUIDGenerator{}, 3067 &nobidExchange{}, 3068 mockBidderParamValidator{}, 3069 &mockStoredReqFetcher{}, 3070 empty_fetcher.EmptyFetcher{}, 3071 empty_fetcher.EmptyFetcher{}, 3072 &config.Configuration{}, 3073 &metricsConfig.NilMetricsEngine{}, 3074 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 3075 map[string]string{}, 3076 false, 3077 []byte{}, 3078 openrtb_ext.BuildBidderMap(), 3079 nil, 3080 nil, 3081 hardcodedResponseIPValidator{response: true}, 3082 empty_fetcher.EmptyFetcher{}, 3083 hooks.EmptyPlanBuilder{}, 3084 nil, 3085 } 3086 3087 ui := int64(1) 3088 req := openrtb2.BidRequest{ 3089 ID: "anyRequestID", 3090 Imp: []openrtb2.Imp{ 3091 { 3092 ID: "anyImpID", 3093 Banner: &openrtb2.Banner{ 3094 W: &ui, 3095 H: &ui, 3096 }, 3097 Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), 3098 }, 3099 }, 3100 Site: &openrtb2.Site{ 3101 ID: "anySiteID", 3102 }, 3103 Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), 3104 } 3105 3106 errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) 3107 3108 expectedError := errors.New("request.ext.prebid.schains contains multiple schains for bidder appnexus; it must contain no more than one per bidder.") 3109 assert.ElementsMatch(t, errL, []error{expectedError}) 3110 } 3111 3112 func TestMapSChains(t *testing.T) { 3113 const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}` 3114 const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}` 3115 3116 seller1SChainUnpacked := openrtb2.SupplyChain{ 3117 Complete: 1, 3118 Nodes: []openrtb2.SupplyChainNode{{ 3119 ASI: "directseller1.com", 3120 SID: "00001", 3121 RID: "BidRequest1", 3122 HP: openrtb2.Int8Ptr(1), 3123 }}, 3124 Ver: "1.0", 3125 } 3126 3127 tests := []struct { 3128 description string 3129 bidRequest openrtb2.BidRequest 3130 wantReqExtSChain *openrtb2.SupplyChain 3131 wantSourceExtSChain *openrtb2.SupplyChain 3132 wantError bool 3133 }{ 3134 { 3135 description: "invalid req.ext", 3136 bidRequest: openrtb2.BidRequest{ 3137 Ext: json.RawMessage(`{"prebid":{"schains":invalid}}`), 3138 Source: &openrtb2.Source{ 3139 Ext: json.RawMessage(`{}`), 3140 }, 3141 }, 3142 wantError: true, 3143 }, 3144 { 3145 description: "invalid source.ext", 3146 bidRequest: openrtb2.BidRequest{ 3147 Ext: json.RawMessage(`{}`), 3148 Source: &openrtb2.Source{ 3149 Ext: json.RawMessage(`{"schain":invalid}}`), 3150 }, 3151 }, 3152 wantError: true, 3153 }, 3154 { 3155 description: "req.ext.prebid.schains, req.source.ext.schain and req.ext.schain are nil", 3156 bidRequest: openrtb2.BidRequest{ 3157 Ext: json.RawMessage(`{}`), 3158 Source: &openrtb2.Source{ 3159 Ext: json.RawMessage(`{}`), 3160 }, 3161 }, 3162 wantReqExtSChain: nil, 3163 wantSourceExtSChain: nil, 3164 }, 3165 { 3166 description: "req.ext.prebid.schains is not nil", 3167 bidRequest: openrtb2.BidRequest{ 3168 Ext: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), 3169 Source: &openrtb2.Source{ 3170 Ext: json.RawMessage(`{}`), 3171 }, 3172 }, 3173 wantReqExtSChain: nil, 3174 wantSourceExtSChain: nil, 3175 }, 3176 { 3177 description: "req.source.ext is not nil", 3178 bidRequest: openrtb2.BidRequest{ 3179 Ext: json.RawMessage(`{}`), 3180 Source: &openrtb2.Source{ 3181 Ext: json.RawMessage(`{` + seller1SChain + `}`), 3182 }, 3183 }, 3184 wantReqExtSChain: nil, 3185 wantSourceExtSChain: &seller1SChainUnpacked, 3186 }, 3187 { 3188 description: "req.ext.schain is not nil", 3189 bidRequest: openrtb2.BidRequest{ 3190 Ext: json.RawMessage(`{` + seller1SChain + `}`), 3191 Source: &openrtb2.Source{ 3192 Ext: json.RawMessage(`{}`), 3193 }, 3194 }, 3195 wantReqExtSChain: nil, 3196 wantSourceExtSChain: &seller1SChainUnpacked, 3197 }, 3198 { 3199 description: "req.source.ext.schain and req.ext.schain are not nil", 3200 bidRequest: openrtb2.BidRequest{ 3201 Ext: json.RawMessage(`{` + seller2SChain + `}`), 3202 Source: &openrtb2.Source{ 3203 Ext: json.RawMessage(`{` + seller1SChain + `}`), 3204 }, 3205 }, 3206 wantReqExtSChain: nil, 3207 wantSourceExtSChain: &seller1SChainUnpacked, 3208 }, 3209 } 3210 3211 for _, test := range tests { 3212 reqWrapper := openrtb_ext.RequestWrapper{ 3213 BidRequest: &test.bidRequest, 3214 } 3215 3216 err := mapSChains(&reqWrapper) 3217 3218 if test.wantError { 3219 assert.NotNil(t, err, test.description) 3220 } else { 3221 assert.Nil(t, err, test.description) 3222 3223 reqExt, err := reqWrapper.GetRequestExt() 3224 if err != nil { 3225 assert.Fail(t, "Error getting request ext from wrapper", test.description) 3226 } 3227 reqExtSChain := reqExt.GetSChain() 3228 assert.Equal(t, test.wantReqExtSChain, reqExtSChain, test.description) 3229 3230 sourceExt, err := reqWrapper.GetSourceExt() 3231 if err != nil { 3232 assert.Fail(t, "Error getting source ext from wrapper", test.description) 3233 } 3234 sourceExtSChain := sourceExt.GetSChain() 3235 assert.Equal(t, test.wantSourceExtSChain, sourceExtSChain, test.description) 3236 } 3237 } 3238 } 3239 3240 func TestGetAccountID(t *testing.T) { 3241 testPubID := "test-pub" 3242 testParentAccount := "test-account" 3243 testPubExt := openrtb_ext.ExtPublisher{ 3244 Prebid: &openrtb_ext.ExtPublisherPrebid{ 3245 ParentAccount: &testParentAccount, 3246 }, 3247 } 3248 testPubExtJSON, err := json.Marshal(testPubExt) 3249 assert.NoError(t, err) 3250 3251 testCases := []struct { 3252 description string 3253 pub *openrtb2.Publisher 3254 expectedAccID string 3255 }{ 3256 { 3257 description: "Publisher.ID and Publisher.Ext.Prebid.ParentAccount both present", 3258 pub: &openrtb2.Publisher{ 3259 ID: testPubID, 3260 Ext: testPubExtJSON, 3261 }, 3262 expectedAccID: testParentAccount, 3263 }, 3264 { 3265 description: "Only Publisher.Ext.Prebid.ParentAccount present", 3266 pub: &openrtb2.Publisher{ 3267 ID: "", 3268 Ext: testPubExtJSON, 3269 }, 3270 expectedAccID: testParentAccount, 3271 }, 3272 { 3273 description: "Only Publisher.ID present", 3274 pub: &openrtb2.Publisher{ 3275 ID: testPubID, 3276 }, 3277 expectedAccID: testPubID, 3278 }, 3279 { 3280 description: "Neither Publisher.ID or Publisher.Ext.Prebid.ParentAccount present", 3281 pub: &openrtb2.Publisher{}, 3282 expectedAccID: metrics.PublisherUnknown, 3283 }, 3284 { 3285 description: "Publisher is nil", 3286 pub: nil, 3287 expectedAccID: metrics.PublisherUnknown, 3288 }, 3289 } 3290 3291 for _, test := range testCases { 3292 acc := getAccountID(test.pub) 3293 assert.Equal(t, test.expectedAccID, acc, "getAccountID should return expected account for test case: %s", test.description) 3294 } 3295 } 3296 3297 func TestSanitizeRequest(t *testing.T) { 3298 testCases := []struct { 3299 description string 3300 req *openrtb2.BidRequest 3301 ipValidator iputil.IPValidator 3302 expectedIPv4 string 3303 expectedIPv6 string 3304 }{ 3305 { 3306 description: "Empty", 3307 req: &openrtb2.BidRequest{ 3308 Device: &openrtb2.Device{ 3309 IP: "", 3310 IPv6: "", 3311 }, 3312 }, 3313 expectedIPv4: "", 3314 expectedIPv6: "", 3315 }, 3316 { 3317 description: "Valid", 3318 req: &openrtb2.BidRequest{ 3319 Device: &openrtb2.Device{ 3320 IP: "1.1.1.1", 3321 IPv6: "1111::", 3322 }, 3323 }, 3324 ipValidator: hardcodedResponseIPValidator{response: true}, 3325 expectedIPv4: "1.1.1.1", 3326 expectedIPv6: "1111::", 3327 }, 3328 { 3329 description: "Invalid", 3330 req: &openrtb2.BidRequest{ 3331 Device: &openrtb2.Device{ 3332 IP: "1.1.1.1", 3333 IPv6: "1111::", 3334 }, 3335 }, 3336 ipValidator: hardcodedResponseIPValidator{response: false}, 3337 expectedIPv4: "", 3338 expectedIPv6: "", 3339 }, 3340 { 3341 description: "Invalid - Wrong IP Types", 3342 req: &openrtb2.BidRequest{ 3343 Device: &openrtb2.Device{ 3344 IP: "1111::", 3345 IPv6: "1.1.1.1", 3346 }, 3347 }, 3348 ipValidator: hardcodedResponseIPValidator{response: true}, 3349 expectedIPv4: "", 3350 expectedIPv6: "", 3351 }, 3352 { 3353 description: "Malformed", 3354 req: &openrtb2.BidRequest{ 3355 Device: &openrtb2.Device{ 3356 IP: "malformed", 3357 IPv6: "malformed", 3358 }, 3359 }, 3360 expectedIPv4: "", 3361 expectedIPv6: "", 3362 }, 3363 } 3364 3365 for _, test := range testCases { 3366 bidReq := &openrtb_ext.RequestWrapper{BidRequest: test.req} 3367 3368 sanitizeRequest(bidReq, test.ipValidator) 3369 assert.Equal(t, test.expectedIPv4, test.req.Device.IP, test.description+":ipv4") 3370 assert.Equal(t, test.expectedIPv6, test.req.Device.IPv6, test.description+":ipv6") 3371 } 3372 } 3373 3374 func TestValidateAndFillSourceTID(t *testing.T) { 3375 testTID := "some-tid" 3376 testCases := []struct { 3377 description string 3378 req *openrtb_ext.RequestWrapper 3379 generateRequestID bool 3380 hasStoredBidRequest bool 3381 isAmp bool 3382 expectRandImpTID bool 3383 expectRandSourceTID bool 3384 expectSourceTid *string 3385 expectImpTid *string 3386 }{ 3387 { 3388 description: "req source.tid not set, expect random value", 3389 req: &openrtb_ext.RequestWrapper{ 3390 BidRequest: &openrtb2.BidRequest{ 3391 ID: "1", 3392 Imp: []openrtb2.Imp{{ID: "1"}}, 3393 Source: &openrtb2.Source{}, 3394 }, 3395 }, 3396 generateRequestID: false, 3397 hasStoredBidRequest: false, 3398 isAmp: false, 3399 expectRandSourceTID: true, 3400 expectRandImpTID: false, 3401 }, 3402 { 3403 description: "req source.tid set to {{UUID}}, expect to be replaced by random value", 3404 req: &openrtb_ext.RequestWrapper{ 3405 BidRequest: &openrtb2.BidRequest{ 3406 ID: "1", 3407 Imp: []openrtb2.Imp{{ID: "1"}}, 3408 Source: &openrtb2.Source{TID: "{{UUID}}"}, 3409 }, 3410 }, 3411 generateRequestID: false, 3412 hasStoredBidRequest: false, 3413 isAmp: false, 3414 expectRandSourceTID: true, 3415 expectRandImpTID: false, 3416 }, 3417 { 3418 description: "req source.tid is set, isAmp = true, generateRequestID = true, expect to be replaced by random value", 3419 req: &openrtb_ext.RequestWrapper{ 3420 BidRequest: &openrtb2.BidRequest{ 3421 ID: "1", 3422 Imp: []openrtb2.Imp{{ID: "1"}}, 3423 Source: &openrtb2.Source{TID: "test-tid"}, 3424 }, 3425 }, 3426 generateRequestID: true, 3427 hasStoredBidRequest: false, 3428 isAmp: true, 3429 expectRandSourceTID: true, 3430 expectRandImpTID: false, 3431 }, 3432 { 3433 description: "req source.tid is set, hasStoredBidRequest = true, generateRequestID = true, expect to be replaced by random value", 3434 req: &openrtb_ext.RequestWrapper{ 3435 BidRequest: &openrtb2.BidRequest{ 3436 ID: "1", 3437 Imp: []openrtb2.Imp{{ID: "1"}}, 3438 Source: &openrtb2.Source{TID: "test-tid"}, 3439 }, 3440 }, 3441 generateRequestID: true, 3442 hasStoredBidRequest: true, 3443 isAmp: false, 3444 expectRandSourceTID: true, 3445 expectRandImpTID: false, 3446 }, 3447 { 3448 description: "req source.tid is set, hasStoredBidRequest = true, generateRequestID = false, expect NOT to be replaced by random value", 3449 req: &openrtb_ext.RequestWrapper{ 3450 BidRequest: &openrtb2.BidRequest{ 3451 ID: "1", 3452 Imp: []openrtb2.Imp{{ID: "1"}}, 3453 Source: &openrtb2.Source{TID: testTID}, 3454 }, 3455 }, 3456 generateRequestID: false, 3457 hasStoredBidRequest: true, 3458 isAmp: false, 3459 expectRandSourceTID: false, 3460 expectRandImpTID: false, 3461 expectSourceTid: &testTID, 3462 }, 3463 { 3464 description: "req imp.ext.tid not set, expect random value", 3465 req: &openrtb_ext.RequestWrapper{ 3466 BidRequest: &openrtb2.BidRequest{ 3467 ID: "1", 3468 Imp: []openrtb2.Imp{{ID: "1"}}, 3469 Source: &openrtb2.Source{}, 3470 }, 3471 }, 3472 generateRequestID: false, 3473 hasStoredBidRequest: false, 3474 isAmp: false, 3475 expectRandSourceTID: false, 3476 expectRandImpTID: true, 3477 }, 3478 { 3479 description: "req imp.ext.tid set to {{UUID}}, expect random value", 3480 req: &openrtb_ext.RequestWrapper{ 3481 BidRequest: &openrtb2.BidRequest{ 3482 ID: "1", 3483 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"tid": "{{UUID}}"}`)}}, 3484 Source: &openrtb2.Source{}, 3485 }, 3486 }, 3487 generateRequestID: false, 3488 hasStoredBidRequest: false, 3489 isAmp: false, 3490 expectRandSourceTID: false, 3491 expectRandImpTID: true, 3492 }, 3493 { 3494 description: "req imp.tid is set, hasStoredBidRequest = true, generateRequestID = true, expect to be replaced by random value", 3495 req: &openrtb_ext.RequestWrapper{ 3496 BidRequest: &openrtb2.BidRequest{ 3497 ID: "1", 3498 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"tid": "some-tid"}`)}}, 3499 Source: &openrtb2.Source{TID: "test-tid"}, 3500 }, 3501 }, 3502 generateRequestID: true, 3503 hasStoredBidRequest: true, 3504 isAmp: false, 3505 expectRandSourceTID: false, 3506 expectRandImpTID: true, 3507 }, 3508 { 3509 description: "req imp.tid is set, isAmp = true, generateRequestID = true, expect to be replaced by random value", 3510 req: &openrtb_ext.RequestWrapper{ 3511 BidRequest: &openrtb2.BidRequest{ 3512 ID: "1", 3513 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"tid": "some-tid"}`)}}, 3514 Source: &openrtb2.Source{TID: "test-tid"}, 3515 }, 3516 }, 3517 generateRequestID: true, 3518 hasStoredBidRequest: false, 3519 isAmp: true, 3520 expectRandSourceTID: false, 3521 expectRandImpTID: true, 3522 }, 3523 { 3524 description: "req imp.tid is set, hasStoredBidRequest = true, generateRequestID = false, expect NOT to be replaced by random value", 3525 req: &openrtb_ext.RequestWrapper{ 3526 BidRequest: &openrtb2.BidRequest{ 3527 ID: "1", 3528 Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"tid": "some-tid"}`)}}, 3529 Source: &openrtb2.Source{TID: testTID}, 3530 }, 3531 }, 3532 generateRequestID: false, 3533 hasStoredBidRequest: true, 3534 isAmp: false, 3535 expectRandSourceTID: false, 3536 expectRandImpTID: false, 3537 expectImpTid: &testTID, 3538 }, 3539 } 3540 3541 for _, test := range testCases { 3542 _ = validateAndFillSourceTID(test.req, test.generateRequestID, test.hasStoredBidRequest, test.isAmp) 3543 impWrapper := &openrtb_ext.ImpWrapper{} 3544 impWrapper.Imp = &test.req.Imp[0] 3545 ie, _ := impWrapper.GetImpExt() 3546 impTID := ie.GetTid() 3547 if test.expectRandSourceTID { 3548 assert.NotEmpty(t, test.req.Source.TID, test.description) 3549 } else if test.expectRandImpTID { 3550 assert.NotEqual(t, testTID, impTID, test.description) 3551 assert.NotEmpty(t, impTID, test.description) 3552 } else if test.expectSourceTid != nil { 3553 assert.Equal(t, test.req.Source.TID, *test.expectSourceTid, test.description) 3554 } else if test.expectImpTid != nil { 3555 assert.Equal(t, impTID, *test.expectImpTid, test.description) 3556 } 3557 } 3558 } 3559 3560 func TestEidPermissionsInvalid(t *testing.T) { 3561 deps := &endpointDeps{ 3562 fakeUUIDGenerator{}, 3563 &nobidExchange{}, 3564 mockBidderParamValidator{}, 3565 &mockStoredReqFetcher{}, 3566 empty_fetcher.EmptyFetcher{}, 3567 empty_fetcher.EmptyFetcher{}, 3568 &config.Configuration{}, 3569 &metricsConfig.NilMetricsEngine{}, 3570 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 3571 map[string]string{}, 3572 false, 3573 []byte{}, 3574 openrtb_ext.BuildBidderMap(), 3575 nil, 3576 nil, 3577 hardcodedResponseIPValidator{response: true}, 3578 empty_fetcher.EmptyFetcher{}, 3579 hooks.EmptyPlanBuilder{}, 3580 nil, 3581 } 3582 3583 ui := int64(1) 3584 req := openrtb2.BidRequest{ 3585 ID: "anyRequestID", 3586 Imp: []openrtb2.Imp{ 3587 { 3588 ID: "anyImpID", 3589 Banner: &openrtb2.Banner{ 3590 W: &ui, 3591 H: &ui, 3592 }, 3593 Ext: json.RawMessage(`{"appnexus": {"placementId": 5667}}`), 3594 }, 3595 }, 3596 Site: &openrtb2.Site{ 3597 ID: "anySiteID", 3598 }, 3599 Ext: json.RawMessage(`{"prebid": {"data": {"eidpermissions": [{"source":"a", "bidders":[]}]} } }`), 3600 } 3601 3602 errL := deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: &req}, false, false, nil, false) 3603 3604 expectedError := errors.New(`request.ext.prebid.data.eidpermissions[0] missing or empty required field: "bidders"`) 3605 assert.ElementsMatch(t, errL, []error{expectedError}) 3606 } 3607 3608 func TestValidateEidPermissions(t *testing.T) { 3609 knownBidders := map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")} 3610 knownAliases := map[string]string{"b": "b"} 3611 3612 testCases := []struct { 3613 description string 3614 request *openrtb_ext.ExtRequest 3615 expectedError error 3616 }{ 3617 { 3618 description: "Valid - Empty ext", 3619 request: &openrtb_ext.ExtRequest{}, 3620 expectedError: nil, 3621 }, 3622 { 3623 description: "Valid - Nil ext.prebid.data", 3624 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{}}, 3625 expectedError: nil, 3626 }, 3627 { 3628 description: "Valid - Empty ext.prebid.data", 3629 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{}}}, 3630 expectedError: nil, 3631 }, 3632 { 3633 description: "Valid - Nil ext.prebid.data.eidpermissions", 3634 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: nil}}}, 3635 expectedError: nil, 3636 }, 3637 { 3638 description: "Valid - None", 3639 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}}}}, 3640 expectedError: nil, 3641 }, 3642 { 3643 description: "Valid - One", 3644 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 3645 {Source: "sourceA", Bidders: []string{"a"}}, 3646 }}}}, 3647 expectedError: nil, 3648 }, 3649 { 3650 description: "Valid - Many", 3651 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 3652 {Source: "sourceA", Bidders: []string{"a"}}, 3653 {Source: "sourceB", Bidders: []string{"a"}}, 3654 }}}}, 3655 expectedError: nil, 3656 }, 3657 { 3658 description: "Invalid - Missing Source", 3659 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 3660 {Source: "sourceA", Bidders: []string{"a"}}, 3661 {Bidders: []string{"a"}}, 3662 }}}}, 3663 expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing required field: "source"`), 3664 }, 3665 { 3666 description: "Invalid - Duplicate Source", 3667 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 3668 {Source: "sourceA", Bidders: []string{"a"}}, 3669 {Source: "sourceA", Bidders: []string{"a"}}, 3670 }}}}, 3671 expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] duplicate entry with field: "source"`), 3672 }, 3673 { 3674 description: "Invalid - Missing Bidders - Nil", 3675 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 3676 {Source: "sourceA", Bidders: []string{"a"}}, 3677 {Source: "sourceB"}, 3678 }}}}, 3679 expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`), 3680 }, 3681 { 3682 description: "Invalid - Missing Bidders - Empty", 3683 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 3684 {Source: "sourceA", Bidders: []string{"a"}}, 3685 {Source: "sourceB", Bidders: []string{}}, 3686 }}}}, 3687 expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] missing or empty required field: "bidders"`), 3688 }, 3689 { 3690 description: "Invalid - Invalid Bidders", 3691 request: &openrtb_ext.ExtRequest{Prebid: openrtb_ext.ExtRequestPrebid{Data: &openrtb_ext.ExtRequestPrebidData{EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 3692 {Source: "sourceA", Bidders: []string{"a"}}, 3693 {Source: "sourceB", Bidders: []string{"z"}}, 3694 }}}}, 3695 expectedError: errors.New(`request.ext.prebid.data.eidpermissions[1] contains unrecognized bidder "z"`), 3696 }, 3697 } 3698 3699 endpoint := &endpointDeps{bidderMap: knownBidders} 3700 for _, test := range testCases { 3701 result := endpoint.validateEidPermissions(test.request.Prebid.Data, knownAliases) 3702 assert.Equal(t, test.expectedError, result, test.description) 3703 } 3704 } 3705 3706 func TestValidateBidders(t *testing.T) { 3707 testCases := []struct { 3708 description string 3709 bidders []string 3710 knownBidders map[string]openrtb_ext.BidderName 3711 knownAliases map[string]string 3712 expectedError error 3713 }{ 3714 { 3715 description: "Valid - No Bidders", 3716 bidders: []string{}, 3717 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3718 knownAliases: map[string]string{"c": "c"}, 3719 expectedError: nil, 3720 }, 3721 { 3722 description: "Valid - All Bidders", 3723 bidders: []string{"*"}, 3724 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3725 knownAliases: map[string]string{"c": "c"}, 3726 expectedError: nil, 3727 }, 3728 { 3729 description: "Valid - One Core Bidder", 3730 bidders: []string{"a"}, 3731 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3732 knownAliases: map[string]string{"c": "c"}, 3733 expectedError: nil, 3734 }, 3735 { 3736 description: "Valid - Many Core Bidders", 3737 bidders: []string{"a", "b"}, 3738 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a"), "b": openrtb_ext.BidderName("b")}, 3739 knownAliases: map[string]string{"c": "c"}, 3740 expectedError: nil, 3741 }, 3742 { 3743 description: "Valid - One Alias Bidder", 3744 bidders: []string{"c"}, 3745 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3746 knownAliases: map[string]string{"c": "c"}, 3747 expectedError: nil, 3748 }, 3749 { 3750 description: "Valid - Many Alias Bidders", 3751 bidders: []string{"c", "d"}, 3752 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3753 knownAliases: map[string]string{"c": "c", "d": "d"}, 3754 expectedError: nil, 3755 }, 3756 { 3757 description: "Valid - Mixed Core + Alias Bidders", 3758 bidders: []string{"a", "c"}, 3759 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3760 knownAliases: map[string]string{"c": "c"}, 3761 expectedError: nil, 3762 }, 3763 { 3764 description: "Invalid - Unknown Bidder", 3765 bidders: []string{"z"}, 3766 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3767 knownAliases: map[string]string{"c": "c"}, 3768 expectedError: errors.New(`unrecognized bidder "z"`), 3769 }, 3770 { 3771 description: "Invalid - Unknown Bidder Case Sensitive", 3772 bidders: []string{"A"}, 3773 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3774 knownAliases: map[string]string{"c": "c"}, 3775 expectedError: errors.New(`unrecognized bidder "A"`), 3776 }, 3777 { 3778 description: "Invalid - Unknown Bidder With Known Bidders", 3779 bidders: []string{"a", "c", "z"}, 3780 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3781 knownAliases: map[string]string{"c": "c"}, 3782 expectedError: errors.New(`unrecognized bidder "z"`), 3783 }, 3784 { 3785 description: "Invalid - All Bidders With Known Bidder", 3786 bidders: []string{"*", "a"}, 3787 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3788 knownAliases: map[string]string{"c": "c"}, 3789 expectedError: errors.New(`bidder wildcard "*" mixed with specific bidders`), 3790 }, 3791 { 3792 description: "Invalid - Returns First Error - All Bidders", 3793 bidders: []string{"*", "z"}, 3794 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3795 knownAliases: map[string]string{"c": "c"}, 3796 expectedError: errors.New(`bidder wildcard "*" mixed with specific bidders`), 3797 }, 3798 { 3799 description: "Invalid - Returns First Error - Unknown Bidder", 3800 bidders: []string{"z", "*"}, 3801 knownBidders: map[string]openrtb_ext.BidderName{"a": openrtb_ext.BidderName("a")}, 3802 knownAliases: map[string]string{"c": "c"}, 3803 expectedError: errors.New(`unrecognized bidder "z"`), 3804 }, 3805 } 3806 3807 for _, test := range testCases { 3808 result := validateBidders(test.bidders, test.knownBidders, test.knownAliases) 3809 assert.Equal(t, test.expectedError, result, test.description) 3810 } 3811 } 3812 3813 func TestIOS14EndToEnd(t *testing.T) { 3814 exchange := &nobidExchange{} 3815 3816 endpoint, _ := NewEndpoint( 3817 fakeUUIDGenerator{}, 3818 exchange, 3819 mockBidderParamValidator{}, 3820 &mockStoredReqFetcher{}, 3821 empty_fetcher.EmptyFetcher{}, 3822 &config.Configuration{MaxRequestSize: maxSize}, 3823 &metricsConfig.NilMetricsEngine{}, 3824 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 3825 map[string]string{}, 3826 []byte{}, 3827 openrtb_ext.BuildBidderMap(), 3828 empty_fetcher.EmptyFetcher{}, 3829 hooks.EmptyPlanBuilder{}, 3830 nil, 3831 ) 3832 3833 httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(validRequest(t, "app-ios140-no-ifa.json"))) 3834 3835 endpoint(httptest.NewRecorder(), httpReq, nil) 3836 3837 result := exchange.gotRequest 3838 if !assert.NotEmpty(t, result, "request received by the exchange.") { 3839 t.FailNow() 3840 } 3841 3842 var lmtOne int8 = 1 3843 assert.Equal(t, &lmtOne, result.Device.Lmt) 3844 } 3845 3846 func TestAuctionWarnings(t *testing.T) { 3847 testCases := []struct { 3848 name string 3849 file string 3850 expectedWarning string 3851 }{ 3852 { 3853 name: "us-privacy-invalid", 3854 file: "us-privacy-invalid.json", 3855 expectedWarning: "CCPA consent is invalid and will be ignored. (request.regs.ext.us_privacy must contain 4 characters)", 3856 }, 3857 { 3858 name: "us-privacy-signals-conflict", 3859 file: "us-privacy-conflict.json", 3860 expectedWarning: "regs.us_privacy consent does not match uspv1 in GPP, using regs.gpp", 3861 }, 3862 { 3863 name: "empty-gppsid-array-conflicts-with-regs-gdpr", // gdpr set to 1, an empty non-nil gpp_sid array doesn't match 3864 file: "empty-gppsid-conflict.json", 3865 expectedWarning: "regs.gdpr signal conflicts with GPP (regs.gpp_sid) and will be ignored", 3866 }, 3867 { 3868 name: "gdpr-signals-conflict", // gdpr signals do not match 3869 file: "gdpr-conflict.json", 3870 expectedWarning: "regs.gdpr signal conflicts with GPP (regs.gpp_sid) and will be ignored", 3871 }, 3872 { 3873 name: "gdpr-signals-conflict2", // gdpr consent strings do not match 3874 file: "gdpr-conflict2.json", 3875 expectedWarning: "user.consent GDPR string conflicts with GPP (regs.gpp) GDPR string, using regs.gpp", 3876 }, 3877 } 3878 deps := &endpointDeps{ 3879 fakeUUIDGenerator{}, 3880 &warningsCheckExchange{}, 3881 mockBidderParamValidator{}, 3882 &mockStoredReqFetcher{}, 3883 empty_fetcher.EmptyFetcher{}, 3884 empty_fetcher.EmptyFetcher{}, 3885 &config.Configuration{MaxRequestSize: maxSize}, 3886 &metricsConfig.NilMetricsEngine{}, 3887 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 3888 map[string]string{}, 3889 false, 3890 []byte{}, 3891 openrtb_ext.BuildBidderMap(), 3892 nil, 3893 nil, 3894 hardcodedResponseIPValidator{response: true}, 3895 empty_fetcher.EmptyFetcher{}, 3896 hooks.EmptyPlanBuilder{}, 3897 nil, 3898 } 3899 for _, test := range testCases { 3900 t.Run(test.name, func(t *testing.T) { 3901 reqBody := validRequest(t, test.file) 3902 req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) 3903 recorder := httptest.NewRecorder() 3904 3905 deps.Auction(recorder, req, nil) 3906 3907 if recorder.Code != http.StatusOK { 3908 t.Errorf("Endpoint should return a 200") 3909 } 3910 warnings := deps.ex.(*warningsCheckExchange).auctionRequest.Warnings 3911 if !assert.Len(t, warnings, 1, "One warning should be returned from exchange") { 3912 t.FailNow() 3913 } 3914 actualWarning := warnings[0].(*errortypes.Warning) 3915 assert.Equal(t, test.expectedWarning, actualWarning.Message, "Warning message is incorrect") 3916 3917 assert.Equal(t, errortypes.InvalidPrivacyConsentWarningCode, actualWarning.WarningCode, "Warning code is incorrect") 3918 }) 3919 } 3920 } 3921 3922 func TestParseRequestParseImpInfoError(t *testing.T) { 3923 reqBody := validRequest(t, "imp-info-invalid.json") 3924 deps := &endpointDeps{ 3925 fakeUUIDGenerator{}, 3926 &warningsCheckExchange{}, 3927 mockBidderParamValidator{}, 3928 &mockStoredReqFetcher{}, 3929 empty_fetcher.EmptyFetcher{}, 3930 empty_fetcher.EmptyFetcher{}, 3931 &config.Configuration{MaxRequestSize: int64(len(reqBody))}, 3932 &metricsConfig.NilMetricsEngine{}, 3933 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 3934 map[string]string{}, 3935 false, 3936 []byte{}, 3937 openrtb_ext.BuildBidderMap(), 3938 nil, 3939 nil, 3940 hardcodedResponseIPValidator{response: true}, 3941 empty_fetcher.EmptyFetcher{}, 3942 hooks.EmptyPlanBuilder{}, 3943 nil, 3944 } 3945 3946 hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) 3947 3948 req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(reqBody)) 3949 3950 resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) 3951 3952 assert.Nil(t, resReq, "Result request should be nil due to incorrect imp") 3953 assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp") 3954 assert.Len(t, errL, 1, "One error should be returned") 3955 assert.Contains(t, errL[0].Error(), "echovideoattrs of type bool", "Incorrect error message") 3956 } 3957 3958 func TestParseGzipedRequest(t *testing.T) { 3959 testCases := 3960 []struct { 3961 desc string 3962 reqContentEnc string 3963 maxReqSize int64 3964 compressionCfg config.Compression 3965 expectedErr string 3966 }{ 3967 { 3968 desc: "Gzip compression enabled, request size exceeds max request size", 3969 reqContentEnc: "gzip", 3970 maxReqSize: 10, 3971 compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: true}}, 3972 expectedErr: "request size exceeded max size of 10 bytes.", 3973 }, 3974 { 3975 desc: "Gzip compression enabled, request size is within max request size", 3976 reqContentEnc: "gzip", 3977 maxReqSize: 2000, 3978 compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: true}}, 3979 expectedErr: "", 3980 }, 3981 { 3982 desc: "Gzip compression enabled, request size is within max request size, content-encoding value not in lower case", 3983 reqContentEnc: "GZIP", 3984 maxReqSize: 2000, 3985 compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: true}}, 3986 expectedErr: "", 3987 }, 3988 { 3989 desc: "Request is Gzip compressed, but Gzip compression is disabled", 3990 reqContentEnc: "gzip", 3991 compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: false}}, 3992 expectedErr: "Content-Encoding of type gzip is not supported", 3993 }, 3994 { 3995 desc: "Request is not Gzip compressed, but Gzip compression is enabled", 3996 reqContentEnc: "", 3997 maxReqSize: 2000, 3998 compressionCfg: config.Compression{Request: config.CompressionInfo{GZIP: true}}, 3999 expectedErr: "", 4000 }, 4001 } 4002 4003 reqBody := []byte(validRequest(t, "site.json")) 4004 deps := &endpointDeps{ 4005 fakeUUIDGenerator{}, 4006 &warningsCheckExchange{}, 4007 mockBidderParamValidator{}, 4008 &mockStoredReqFetcher{}, 4009 empty_fetcher.EmptyFetcher{}, 4010 empty_fetcher.EmptyFetcher{}, 4011 &config.Configuration{MaxRequestSize: int64(50), Compression: config.Compression{Request: config.CompressionInfo{GZIP: false}}}, 4012 &metricsConfig.NilMetricsEngine{}, 4013 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 4014 map[string]string{}, 4015 false, 4016 []byte{}, 4017 openrtb_ext.BuildBidderMap(), 4018 nil, 4019 nil, 4020 hardcodedResponseIPValidator{response: true}, 4021 empty_fetcher.EmptyFetcher{}, 4022 hooks.EmptyPlanBuilder{}, 4023 nil, 4024 } 4025 4026 hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) 4027 for _, test := range testCases { 4028 var req *http.Request 4029 deps.cfg.MaxRequestSize = test.maxReqSize 4030 deps.cfg.Compression = test.compressionCfg 4031 if test.reqContentEnc == "gzip" { 4032 var compressed bytes.Buffer 4033 gw := gzip.NewWriter(&compressed) 4034 _, err := gw.Write(reqBody) 4035 assert.NoError(t, err, "Error writing gzip compressed request body", test.desc) 4036 assert.NoError(t, gw.Close(), "Error closing gzip writer", test.desc) 4037 4038 req = httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(compressed.Bytes())) 4039 req.Header.Set("Content-Encoding", "gzip") 4040 } else { 4041 req = httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(reqBody)) 4042 } 4043 resReq, impExtInfoMap, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) 4044 4045 if test.expectedErr == "" { 4046 assert.Nil(t, errL, "Error list should be nil", test.desc) 4047 assert.NotNil(t, resReq, "Result request should not be nil", test.desc) 4048 assert.NotNil(t, impExtInfoMap, "Impression info map should not be nil", test.desc) 4049 } else { 4050 assert.Nil(t, resReq, "Result request should be nil due to incorrect imp", test.desc) 4051 assert.Nil(t, impExtInfoMap, "Impression info map should be nil due to incorrect imp", test.desc) 4052 assert.Len(t, errL, 1, "One error should be returned", test.desc) 4053 assert.Contains(t, errL[0].Error(), test.expectedErr, "Incorrect error message", test.desc) 4054 } 4055 } 4056 } 4057 4058 func TestValidateNativeContextTypes(t *testing.T) { 4059 impIndex := 4 4060 4061 testCases := []struct { 4062 description string 4063 givenContextType native1.ContextType 4064 givenSubType native1.ContextSubType 4065 expectedError string 4066 }{ 4067 { 4068 description: "No Types Specified", 4069 givenContextType: 0, 4070 givenSubType: 0, 4071 expectedError: "", 4072 }, 4073 { 4074 description: "All Types Exchange Specific", 4075 givenContextType: 500, 4076 givenSubType: 500, 4077 expectedError: "", 4078 }, 4079 { 4080 description: "Context Type Known Value - Sub Type Unspecified", 4081 givenContextType: 1, 4082 givenSubType: 0, 4083 expectedError: "", 4084 }, 4085 { 4086 description: "Context Type Negative", 4087 givenContextType: -1, 4088 givenSubType: 0, 4089 expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4090 }, 4091 { 4092 description: "Context Type Just Above Range", 4093 givenContextType: 4, // Range is currently 1-3 4094 givenSubType: 0, 4095 expectedError: "request.imp[4].native.request.context is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4096 }, 4097 { 4098 description: "Sub Type Negative", 4099 givenContextType: 1, 4100 givenSubType: -1, 4101 expectedError: "request.imp[4].native.request.contextsubtype value can't be less than 0. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4102 }, 4103 { 4104 description: "Content - Sub Type Just Below Range", 4105 givenContextType: 1, // Content constant 4106 givenSubType: 9, // Content range is currently 10-15 4107 expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4108 }, 4109 { 4110 description: "Content - Sub Type In Range", 4111 givenContextType: 1, // Content constant 4112 givenSubType: 10, // Content range is currently 10-15 4113 expectedError: "", 4114 }, 4115 { 4116 description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary", 4117 givenContextType: 500, 4118 givenSubType: 10, // Content range is currently 10-15 4119 expectedError: "", 4120 }, 4121 { 4122 description: "Content - Sub Type In Range - Context Type Exchange Specific Boundary + 1", 4123 givenContextType: 501, 4124 givenSubType: 10, // Content range is currently 10-15 4125 expectedError: "", 4126 }, 4127 { 4128 description: "Content - Sub Type Just Above Range", 4129 givenContextType: 1, // Content constant 4130 givenSubType: 16, // Content range is currently 10-15 4131 expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4132 }, 4133 { 4134 description: "Content - Sub Type Exchange Specific Boundary", 4135 givenContextType: 1, // Content constant 4136 givenSubType: 500, 4137 expectedError: "", 4138 }, 4139 { 4140 description: "Content - Sub Type Exchange Specific Boundary + 1", 4141 givenContextType: 1, // Content constant 4142 givenSubType: 501, 4143 expectedError: "", 4144 }, 4145 { 4146 description: "Content - Invalid Context Type", 4147 givenContextType: 2, // Not content constant 4148 givenSubType: 10, // Content range is currently 10-15 4149 expectedError: "request.imp[4].native.request.context is 2, but contextsubtype is 10. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4150 }, 4151 { 4152 description: "Social - Sub Type Just Below Range", 4153 givenContextType: 2, // Social constant 4154 givenSubType: 19, // Social range is currently 20-22 4155 expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4156 }, 4157 { 4158 description: "Social - Sub Type In Range", 4159 givenContextType: 2, // Social constant 4160 givenSubType: 20, // Social range is currently 20-22 4161 expectedError: "", 4162 }, 4163 { 4164 description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary", 4165 givenContextType: 500, 4166 givenSubType: 20, // Social range is currently 20-22 4167 expectedError: "", 4168 }, 4169 { 4170 description: "Social - Sub Type In Range - Context Type Exchange Specific Boundary + 1", 4171 givenContextType: 501, 4172 givenSubType: 20, // Social range is currently 20-22 4173 expectedError: "", 4174 }, 4175 { 4176 description: "Social - Sub Type Just Above Range", 4177 givenContextType: 2, // Social constant 4178 givenSubType: 23, // Social range is currently 20-22 4179 expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4180 }, 4181 { 4182 description: "Social - Sub Type Exchange Specific Boundary", 4183 givenContextType: 2, // Social constant 4184 givenSubType: 500, 4185 expectedError: "", 4186 }, 4187 { 4188 description: "Social - Sub Type Exchange Specific Boundary + 1", 4189 givenContextType: 2, // Social constant 4190 givenSubType: 501, 4191 expectedError: "", 4192 }, 4193 { 4194 description: "Social - Invalid Context Type", 4195 givenContextType: 3, // Not social constant 4196 givenSubType: 20, // Social range is currently 20-22 4197 expectedError: "request.imp[4].native.request.context is 3, but contextsubtype is 20. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4198 }, 4199 { 4200 description: "Product - Sub Type Just Below Range", 4201 givenContextType: 3, // Product constant 4202 givenSubType: 29, // Product range is currently 30-32 4203 expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4204 }, 4205 { 4206 description: "Product - Sub Type In Range", 4207 givenContextType: 3, // Product constant 4208 givenSubType: 30, // Product range is currently 30-32 4209 expectedError: "", 4210 }, 4211 { 4212 description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary", 4213 givenContextType: 500, 4214 givenSubType: 30, // Product range is currently 30-32 4215 expectedError: "", 4216 }, 4217 { 4218 description: "Product - Sub Type In Range - Context Type Exchange Specific Boundary + 1", 4219 givenContextType: 501, 4220 givenSubType: 30, // Product range is currently 30-32 4221 expectedError: "", 4222 }, 4223 { 4224 description: "Product - Sub Type Just Above Range", 4225 givenContextType: 3, // Product constant 4226 givenSubType: 33, // Product range is currently 30-32 4227 expectedError: "request.imp[4].native.request.contextsubtype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4228 }, 4229 { 4230 description: "Product - Sub Type Exchange Specific Boundary", 4231 givenContextType: 3, // Product constant 4232 givenSubType: 500, 4233 expectedError: "", 4234 }, 4235 { 4236 description: "Product - Sub Type Exchange Specific Boundary + 1", 4237 givenContextType: 3, // Product constant 4238 givenSubType: 501, 4239 expectedError: "", 4240 }, 4241 { 4242 description: "Product - Invalid Context Type", 4243 givenContextType: 1, // Not product constant 4244 givenSubType: 30, // Product range is currently 30-32 4245 expectedError: "request.imp[4].native.request.context is 1, but contextsubtype is 30. This is an invalid combination. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=39", 4246 }, 4247 } 4248 4249 for _, test := range testCases { 4250 err := validateNativeContextTypes(test.givenContextType, test.givenSubType, impIndex) 4251 if test.expectedError == "" { 4252 assert.NoError(t, err, test.description) 4253 } else { 4254 assert.EqualError(t, err, test.expectedError, test.description) 4255 } 4256 } 4257 } 4258 4259 func TestValidateNativePlacementType(t *testing.T) { 4260 impIndex := 4 4261 4262 testCases := []struct { 4263 description string 4264 givenPlacementType native1.PlacementType 4265 expectedError string 4266 }{ 4267 { 4268 description: "Not Specified", 4269 givenPlacementType: 0, 4270 expectedError: "", 4271 }, 4272 { 4273 description: "Known Value", 4274 givenPlacementType: 1, // Range is currently 1-4 4275 expectedError: "", 4276 }, 4277 { 4278 description: "Exchange Specific - Boundary", 4279 givenPlacementType: 500, 4280 expectedError: "", 4281 }, 4282 { 4283 description: "Exchange Specific - Boundary + 1", 4284 givenPlacementType: 501, 4285 expectedError: "", 4286 }, 4287 { 4288 description: "Negative", 4289 givenPlacementType: -1, 4290 expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", 4291 }, 4292 { 4293 description: "Just Above Range", 4294 givenPlacementType: 5, // Range is currently 1-4 4295 expectedError: "request.imp[4].native.request.plcmttype is invalid. See https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", 4296 }, 4297 } 4298 4299 for _, test := range testCases { 4300 err := validateNativePlacementType(test.givenPlacementType, impIndex) 4301 if test.expectedError == "" { 4302 assert.NoError(t, err, test.description) 4303 } else { 4304 assert.EqualError(t, err, test.expectedError, test.description) 4305 } 4306 } 4307 } 4308 4309 func TestValidateNativeEventTracker(t *testing.T) { 4310 impIndex := 4 4311 eventIndex := 8 4312 4313 testCases := []struct { 4314 description string 4315 givenEvent nativeRequests.EventTracker 4316 expectedError string 4317 }{ 4318 { 4319 description: "Valid", 4320 givenEvent: nativeRequests.EventTracker{ 4321 Event: 1, 4322 Methods: []native1.EventTrackingMethod{1}, 4323 }, 4324 expectedError: "", 4325 }, 4326 { 4327 description: "Event - Exchange Specific - Boundary", 4328 givenEvent: nativeRequests.EventTracker{ 4329 Event: 500, 4330 Methods: []native1.EventTrackingMethod{1}, 4331 }, 4332 expectedError: "", 4333 }, 4334 { 4335 description: "Event - Exchange Specific - Boundary + 1", 4336 givenEvent: nativeRequests.EventTracker{ 4337 Event: 501, 4338 Methods: []native1.EventTrackingMethod{1}, 4339 }, 4340 expectedError: "", 4341 }, 4342 { 4343 description: "Event - Negative", 4344 givenEvent: nativeRequests.EventTracker{ 4345 Event: -1, 4346 Methods: []native1.EventTrackingMethod{1}, 4347 }, 4348 expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", 4349 }, 4350 { 4351 description: "Event - Just Above Range", 4352 givenEvent: nativeRequests.EventTracker{ 4353 Event: 5, // Range is currently 1-4 4354 Methods: []native1.EventTrackingMethod{1}, 4355 }, 4356 expectedError: "request.imp[4].native.request.eventtrackers[8].event is invalid. See section 7.6: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", 4357 }, 4358 { 4359 description: "Methods - Many Valid", 4360 givenEvent: nativeRequests.EventTracker{ 4361 Event: 1, 4362 Methods: []native1.EventTrackingMethod{1, 2}, 4363 }, 4364 expectedError: "", 4365 }, 4366 { 4367 description: "Methods - Empty", 4368 givenEvent: nativeRequests.EventTracker{ 4369 Event: 1, 4370 Methods: []native1.EventTrackingMethod{}, 4371 }, 4372 expectedError: "request.imp[4].native.request.eventtrackers[8].method is required. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", 4373 }, 4374 { 4375 description: "Methods - Exchange Specific - Boundary", 4376 givenEvent: nativeRequests.EventTracker{ 4377 Event: 1, 4378 Methods: []native1.EventTrackingMethod{500}, 4379 }, 4380 expectedError: "", 4381 }, 4382 { 4383 description: "Methods - Exchange Specific - Boundary + 1", 4384 givenEvent: nativeRequests.EventTracker{ 4385 Event: 1, 4386 Methods: []native1.EventTrackingMethod{501}, 4387 }, 4388 expectedError: "", 4389 }, 4390 { 4391 description: "Methods - Negative", 4392 givenEvent: nativeRequests.EventTracker{ 4393 Event: 1, 4394 Methods: []native1.EventTrackingMethod{-1}, 4395 }, 4396 expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", 4397 }, 4398 { 4399 description: "Methods - Just Above Range", 4400 givenEvent: nativeRequests.EventTracker{ 4401 Event: 1, 4402 Methods: []native1.EventTrackingMethod{3}, // Known values are currently 1-2 4403 }, 4404 expectedError: "request.imp[4].native.request.eventtrackers[8].methods[0] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", 4405 }, 4406 { 4407 description: "Methods - Mixed Valid + Invalid", 4408 givenEvent: nativeRequests.EventTracker{ 4409 Event: 1, 4410 Methods: []native1.EventTrackingMethod{1, -1}, 4411 }, 4412 expectedError: "request.imp[4].native.request.eventtrackers[8].methods[1] is invalid. See section 7.7: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=43", 4413 }, 4414 } 4415 4416 for _, test := range testCases { 4417 err := validateNativeEventTracker(test.givenEvent, impIndex, eventIndex) 4418 if test.expectedError == "" { 4419 assert.NoError(t, err, test.description) 4420 } else { 4421 assert.EqualError(t, err, test.expectedError, test.description) 4422 } 4423 } 4424 } 4425 4426 func TestValidateNativeAssetData(t *testing.T) { 4427 impIndex := 4 4428 assetIndex := 8 4429 4430 testCases := []struct { 4431 description string 4432 givenData nativeRequests.Data 4433 expectedError string 4434 }{ 4435 { 4436 description: "Valid", 4437 givenData: nativeRequests.Data{Type: 1}, 4438 expectedError: "", 4439 }, 4440 { 4441 description: "Exchange Specific - Boundary", 4442 givenData: nativeRequests.Data{Type: 500}, 4443 expectedError: "", 4444 }, 4445 { 4446 description: "Exchange Specific - Boundary + 1", 4447 givenData: nativeRequests.Data{Type: 501}, 4448 expectedError: "", 4449 }, 4450 { 4451 description: "Not Specified", 4452 givenData: nativeRequests.Data{}, 4453 expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", 4454 }, 4455 { 4456 description: "Negative", 4457 givenData: nativeRequests.Data{Type: -1}, 4458 expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", 4459 }, 4460 { 4461 description: "Just Above Range", 4462 givenData: nativeRequests.Data{Type: 13}, // Range is currently 1-12 4463 expectedError: "request.imp[4].native.request.assets[8].data.type is invalid. See section 7.4: https://iabtechlab.com/wp-content/uploads/2016/07/OpenRTB-Native-Ads-Specification-Final-1.2.pdf#page=40", 4464 }, 4465 } 4466 4467 for _, test := range testCases { 4468 err := validateNativeAssetData(&test.givenData, impIndex, assetIndex) 4469 if test.expectedError == "" { 4470 assert.NoError(t, err, test.description) 4471 } else { 4472 assert.EqualError(t, err, test.expectedError, test.description) 4473 } 4474 } 4475 } 4476 4477 func TestAuctionResponseHeaders(t *testing.T) { 4478 testCases := []struct { 4479 description string 4480 requestBody string 4481 expectedStatus int 4482 expectedHeaders func(http.Header) 4483 }{ 4484 { 4485 description: "Success Response", 4486 requestBody: validRequest(t, "site.json"), 4487 expectedStatus: 200, 4488 expectedHeaders: func(h http.Header) { 4489 h.Set("X-Prebid", "pbs-go/unknown") 4490 h.Set("Content-Type", "application/json") 4491 }, 4492 }, 4493 { 4494 description: "Failure Response", 4495 requestBody: "{}", 4496 expectedStatus: 400, 4497 expectedHeaders: func(h http.Header) { 4498 h.Set("X-Prebid", "pbs-go/unknown") 4499 }, 4500 }, 4501 } 4502 4503 exchange := &nobidExchange{} 4504 endpoint, _ := NewEndpoint( 4505 fakeUUIDGenerator{}, 4506 exchange, 4507 mockBidderParamValidator{}, 4508 empty_fetcher.EmptyFetcher{}, 4509 empty_fetcher.EmptyFetcher{}, 4510 &config.Configuration{MaxRequestSize: maxSize}, 4511 &metricsConfig.NilMetricsEngine{}, 4512 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 4513 map[string]string{}, 4514 []byte{}, 4515 openrtb_ext.BuildBidderMap(), 4516 empty_fetcher.EmptyFetcher{}, 4517 hooks.EmptyPlanBuilder{}, 4518 nil, 4519 ) 4520 4521 for _, test := range testCases { 4522 httpReq := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.requestBody)) 4523 recorder := httptest.NewRecorder() 4524 4525 endpoint(recorder, httpReq, nil) 4526 4527 expectedHeaders := http.Header{} 4528 test.expectedHeaders(expectedHeaders) 4529 4530 assert.Equal(t, test.expectedStatus, recorder.Result().StatusCode, test.description+":statuscode") 4531 assert.Equal(t, expectedHeaders, recorder.Result().Header, test.description+":statuscode") 4532 } 4533 } 4534 4535 // StoredRequest testing 4536 4537 // Test stored request data 4538 4539 func TestValidateBanner(t *testing.T) { 4540 impIndex := 0 4541 4542 testCases := []struct { 4543 description string 4544 banner *openrtb2.Banner 4545 impIndex int 4546 isInterstitial bool 4547 expectedError error 4548 }{ 4549 { 4550 description: "isInterstitial Equals False (not set to 1)", 4551 banner: &openrtb2.Banner{W: nil, H: nil, Format: nil}, 4552 impIndex: impIndex, 4553 isInterstitial: false, 4554 expectedError: errors.New("request.imp[0].banner has no sizes. Define \"w\" and \"h\", or include \"format\" elements."), 4555 }, 4556 { 4557 description: "isInterstitial Equals True (is set to 1)", 4558 banner: &openrtb2.Banner{W: nil, H: nil, Format: nil}, 4559 impIndex: impIndex, 4560 isInterstitial: true, 4561 expectedError: nil, 4562 }, 4563 } 4564 4565 for _, test := range testCases { 4566 result := validateBanner(test.banner, test.impIndex, test.isInterstitial) 4567 assert.Equal(t, test.expectedError, result, test.description) 4568 } 4569 } 4570 4571 func TestParseRequestMergeBidderParams(t *testing.T) { 4572 tests := []struct { 4573 name string 4574 givenRequestBody string 4575 expectedImpExt json.RawMessage 4576 expectedReqExt json.RawMessage 4577 expectedErrorCount int 4578 }{ 4579 { 4580 name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder", 4581 givenRequestBody: validRequest(t, "req-ext-bidder-params.json"), 4582 expectedImpExt: getObject(t, "req-ext-bidder-params.json", "expectedImpExt"), 4583 expectedReqExt: getObject(t, "req-ext-bidder-params.json", "expectedReqExt"), 4584 expectedErrorCount: 0, 4585 }, 4586 { 4587 name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext.prebid.bidder with preference for imp[].ext.prebid.bidder params", 4588 givenRequestBody: validRequest(t, "req-ext-bidder-params-merge.json"), 4589 expectedImpExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedImpExt"), 4590 expectedReqExt: getObject(t, "req-ext-bidder-params-merge.json", "expectedReqExt"), 4591 expectedErrorCount: 0, 4592 }, 4593 { 4594 name: "add missing bidder-params from req.ext.prebid.bidderparams to imp[].ext for backward compatibility", 4595 givenRequestBody: validRequest(t, "req-ext-bidder-params-backward-compatible-merge.json"), 4596 expectedImpExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedImpExt"), 4597 expectedReqExt: getObject(t, "req-ext-bidder-params-backward-compatible-merge.json", "expectedReqExt"), 4598 expectedErrorCount: 0, 4599 }, 4600 } 4601 for _, test := range tests { 4602 t.Run(test.name, func(t *testing.T) { 4603 4604 deps := &endpointDeps{ 4605 fakeUUIDGenerator{}, 4606 &warningsCheckExchange{}, 4607 mockBidderParamValidator{}, 4608 &mockStoredReqFetcher{}, 4609 empty_fetcher.EmptyFetcher{}, 4610 empty_fetcher.EmptyFetcher{}, 4611 &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, 4612 &metricsConfig.NilMetricsEngine{}, 4613 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 4614 map[string]string{}, 4615 false, 4616 []byte{}, 4617 openrtb_ext.BuildBidderMap(), 4618 nil, 4619 nil, 4620 hardcodedResponseIPValidator{response: true}, 4621 empty_fetcher.EmptyFetcher{}, 4622 hooks.EmptyPlanBuilder{}, 4623 nil, 4624 } 4625 4626 hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) 4627 4628 req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) 4629 4630 resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) 4631 4632 assert.NoError(t, resReq.RebuildRequest()) 4633 4634 var expIExt, iExt map[string]interface{} 4635 err := json.Unmarshal(test.expectedImpExt, &expIExt) 4636 assert.Nil(t, err, "unmarshal() should return nil error") 4637 4638 assert.NotNil(t, resReq.BidRequest.Imp[0].Ext, "imp[0].Ext should not be nil") 4639 err = json.Unmarshal(resReq.BidRequest.Imp[0].Ext, &iExt) 4640 assert.Nil(t, err, "unmarshal() should return nil error") 4641 4642 assert.Equal(t, expIExt, iExt, "bidderparams in imp[].Ext should match") 4643 4644 var eReqE, reqE map[string]interface{} 4645 err = json.Unmarshal(test.expectedReqExt, &eReqE) 4646 assert.Nil(t, err, "unmarshal() should return nil error") 4647 4648 err = json.Unmarshal(resReq.BidRequest.Ext, &reqE) 4649 assert.Nil(t, err, "unmarshal() should return nil error") 4650 4651 assert.Equal(t, eReqE, reqE, "req.Ext should match") 4652 4653 assert.Len(t, errL, test.expectedErrorCount, "error length should match") 4654 }) 4655 } 4656 } 4657 4658 func TestParseRequestStoredResponses(t *testing.T) { 4659 mockStoredResponses := map[string]json.RawMessage{ 4660 "6d718149": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`), 4661 "6d715835": json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "appnexus"}]`), 4662 } 4663 4664 tests := []struct { 4665 name string 4666 givenRequestBody string 4667 expectedStoredResponses stored_responses.ImpsWithBidResponses 4668 expectedErrorCount int 4669 expectedError string 4670 }{ 4671 { 4672 name: "req imp has valid stored response", 4673 givenRequestBody: validRequest(t, "req-imp-stored-response.json"), 4674 expectedStoredResponses: map[string]json.RawMessage{ 4675 "imp-id1": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`), 4676 }, 4677 expectedErrorCount: 0, 4678 }, 4679 { 4680 name: "req has two imps valid stored responses", 4681 givenRequestBody: validRequest(t, "req-two-imps-stored-response.json"), 4682 expectedStoredResponses: map[string]json.RawMessage{ 4683 "imp-id1": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`), 4684 "imp-id2": json.RawMessage(`[{"bid": [{"id": "bid_id2"],"seat": "appnexus"}]`), 4685 }, 4686 expectedErrorCount: 0, 4687 }, 4688 { 4689 name: "req has two imps with missing stored responses", 4690 givenRequestBody: validRequest(t, "req-two-imps-missing-stored-response.json"), 4691 expectedStoredResponses: nil, 4692 expectedErrorCount: 2, 4693 }, 4694 { 4695 name: "req has two imps: one with stored response and another imp without stored resp", 4696 givenRequestBody: validRequest(t, "req-two-imps-one-stored-response.json"), 4697 expectedStoredResponses: map[string]json.RawMessage{ 4698 "imp-id1": json.RawMessage(`[{"bid": [{"id": "bid_id1"],"seat": "appnexus"}]`), 4699 }, 4700 expectedErrorCount: 1, 4701 expectedError: `request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[1].ext.prebid`, 4702 }, 4703 } 4704 for _, test := range tests { 4705 t.Run(test.name, func(t *testing.T) { 4706 4707 deps := &endpointDeps{ 4708 fakeUUIDGenerator{}, 4709 &warningsCheckExchange{}, 4710 mockBidderParamValidator{}, 4711 &mockStoredReqFetcher{}, 4712 empty_fetcher.EmptyFetcher{}, 4713 empty_fetcher.EmptyFetcher{}, 4714 &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, 4715 &metricsConfig.NilMetricsEngine{}, 4716 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 4717 map[string]string{}, 4718 false, 4719 []byte{}, 4720 openrtb_ext.BuildBidderMap(), 4721 nil, 4722 nil, 4723 hardcodedResponseIPValidator{response: true}, 4724 &mockStoredResponseFetcher{mockStoredResponses}, 4725 hooks.EmptyPlanBuilder{}, 4726 nil, 4727 } 4728 4729 hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) 4730 4731 req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) 4732 4733 _, _, storedResponses, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) 4734 4735 if test.expectedErrorCount == 0 { 4736 assert.Equal(t, test.expectedStoredResponses, storedResponses, "stored responses should match") 4737 } else { 4738 assert.Contains(t, errL[0].Error(), test.expectedError, "error should match") 4739 } 4740 4741 }) 4742 } 4743 } 4744 4745 func TestParseRequestStoredBidResponses(t *testing.T) { 4746 bidRespId1 := json.RawMessage(`{"id": "resp_id1", "seatbid": [{"bid": [{"id": "bid_id1"}], "seat": "testBidder1"}], "bidid": "123", "cur": "USD"}`) 4747 bidRespId2 := json.RawMessage(`{"id": "resp_id2", "seatbid": [{"bid": [{"id": "bid_id2"}], "seat": "testBidder2"}], "bidid": "124", "cur": "USD"}`) 4748 mockStoredBidResponses := map[string]json.RawMessage{ 4749 "bidResponseId1": bidRespId1, 4750 "bidResponseId2": bidRespId2, 4751 } 4752 4753 tests := []struct { 4754 name string 4755 givenRequestBody string 4756 expectedStoredBidResponses stored_responses.ImpBidderStoredResp 4757 expectedErrorCount int 4758 expectedError string 4759 }{ 4760 { 4761 name: "req imp has valid stored bid response", 4762 givenRequestBody: validRequest(t, "imp-with-stored-bid-resp.json"), 4763 expectedStoredBidResponses: map[string]map[string]json.RawMessage{ 4764 "imp-id1": {"testBidder1": bidRespId1}, 4765 }, 4766 expectedErrorCount: 0, 4767 }, 4768 { 4769 name: "req has two imps with valid stored bid responses", 4770 givenRequestBody: validRequest(t, "req-two-imps-stored-bid-responses.json"), 4771 expectedStoredBidResponses: map[string]map[string]json.RawMessage{ 4772 "imp-id1": {"testBidder1": bidRespId1}, 4773 "imp-id2": {"testBidder2": bidRespId2}, 4774 }, 4775 expectedErrorCount: 0, 4776 }, 4777 { 4778 name: "req has two imps one with valid stored bid responses and another one without stored bid responses", 4779 givenRequestBody: validRequest(t, "req-two-imps-with-and-without-stored-bid-responses.json"), 4780 expectedStoredBidResponses: map[string]map[string]json.RawMessage{ 4781 "imp-id2": {"testBidder2": bidRespId2}, 4782 }, 4783 expectedErrorCount: 0, 4784 }, 4785 { 4786 name: "req has two imps with missing stored bid responses", 4787 givenRequestBody: validRequest(t, "req-two-imps-missing-stored-bid-response.json"), 4788 expectedStoredBidResponses: nil, 4789 expectedErrorCount: 2, 4790 }, 4791 } 4792 for _, test := range tests { 4793 t.Run(test.name, func(t *testing.T) { 4794 4795 deps := &endpointDeps{ 4796 fakeUUIDGenerator{}, 4797 &warningsCheckExchange{}, 4798 mockBidderParamValidator{}, 4799 &mockStoredReqFetcher{}, 4800 empty_fetcher.EmptyFetcher{}, 4801 empty_fetcher.EmptyFetcher{}, 4802 &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, 4803 &metricsConfig.NilMetricsEngine{}, 4804 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 4805 map[string]string{}, 4806 false, 4807 []byte{}, 4808 map[string]openrtb_ext.BidderName{"testBidder1": "testBidder1", "testBidder2": "testBidder2"}, 4809 nil, 4810 nil, 4811 hardcodedResponseIPValidator{response: true}, 4812 &mockStoredResponseFetcher{mockStoredBidResponses}, 4813 hooks.EmptyPlanBuilder{}, 4814 nil, 4815 } 4816 4817 hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) 4818 4819 req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) 4820 _, _, _, storedBidResponses, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) 4821 4822 if test.expectedErrorCount == 0 { 4823 assert.Equal(t, test.expectedStoredBidResponses, storedBidResponses, "stored responses should match") 4824 } else { 4825 assert.Contains(t, errL[0].Error(), test.expectedError, "error should match") 4826 } 4827 }) 4828 } 4829 } 4830 4831 func TestValidateStoredResp(t *testing.T) { 4832 deps := &endpointDeps{ 4833 fakeUUIDGenerator{}, 4834 &nobidExchange{}, 4835 mockBidderParamValidator{}, 4836 &mockStoredReqFetcher{}, 4837 empty_fetcher.EmptyFetcher{}, 4838 empty_fetcher.EmptyFetcher{}, 4839 &config.Configuration{MaxRequestSize: maxSize}, 4840 &metricsConfig.NilMetricsEngine{}, 4841 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 4842 map[string]string{}, 4843 false, 4844 []byte{}, 4845 openrtb_ext.BuildBidderMap(), 4846 nil, 4847 nil, 4848 hardcodedResponseIPValidator{response: true}, 4849 &mockStoredResponseFetcher{}, 4850 hooks.EmptyPlanBuilder{}, 4851 nil, 4852 } 4853 4854 testCases := []struct { 4855 description string 4856 givenRequestWrapper *openrtb_ext.RequestWrapper 4857 expectedErrorList []error 4858 hasStoredAuctionResponses bool 4859 storedBidResponses stored_responses.ImpBidderStoredResp 4860 }{ 4861 { 4862 description: "One imp with stored response, expect validate request to throw no errors", 4863 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 4864 BidRequest: &openrtb2.BidRequest{ 4865 ID: "Some-ID", 4866 App: &openrtb2.App{}, 4867 Imp: []openrtb2.Imp{ 4868 { 4869 ID: "Some-Imp-ID", 4870 Banner: &openrtb2.Banner{ 4871 Format: []openrtb2.Format{ 4872 { 4873 W: 600, 4874 H: 500, 4875 }, 4876 { 4877 W: 300, 4878 H: 600, 4879 }, 4880 }, 4881 }, 4882 Ext: []byte(`{"appnexus":{"placementId": 12345678}, "prebid": {"storedAuctionResponse": {"id": "6d718149-6dfe-25ae-a7d6-305399f77f04"}}}`), 4883 }, 4884 }, 4885 }, 4886 }, 4887 expectedErrorList: []error{}, 4888 hasStoredAuctionResponses: true, 4889 storedBidResponses: nil, 4890 }, 4891 { 4892 description: "Two imps with stored responses, expect validate request to throw no errors", 4893 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 4894 BidRequest: &openrtb2.BidRequest{ 4895 ID: "Some-ID", 4896 App: &openrtb2.App{}, 4897 Imp: []openrtb2.Imp{ 4898 { 4899 ID: "Some-Imp-ID", 4900 Banner: &openrtb2.Banner{ 4901 Format: []openrtb2.Format{ 4902 { 4903 W: 600, 4904 H: 500, 4905 }, 4906 { 4907 W: 300, 4908 H: 600, 4909 }, 4910 }, 4911 }, 4912 Ext: []byte(`{"appnexus":{"placementId": 12345678}, "prebid": {"storedAuctionResponse": {"id": "6d718149-6dfe-25ae-a7d6-305399f77f04"}}}`), 4913 }, 4914 { 4915 ID: "Some-Imp-ID2", 4916 Banner: &openrtb2.Banner{ 4917 Format: []openrtb2.Format{ 4918 { 4919 W: 600, 4920 H: 500, 4921 }, 4922 { 4923 W: 300, 4924 H: 600, 4925 }, 4926 }, 4927 }, 4928 Ext: []byte(`{"appnexus":{"placementId": 12345678}, "prebid": {"storedAuctionResponse": {"id": "6d718149-6dfe-25ae-a7d6-305399f77f04"}}}`), 4929 }, 4930 }, 4931 }, 4932 }, 4933 expectedErrorList: []error{}, 4934 hasStoredAuctionResponses: true, 4935 storedBidResponses: nil, 4936 }, 4937 { 4938 description: "Two imps, one with stored response, expect validate request to throw validation error", 4939 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 4940 BidRequest: &openrtb2.BidRequest{ 4941 ID: "Some-ID", 4942 App: &openrtb2.App{}, 4943 Imp: []openrtb2.Imp{ 4944 { 4945 ID: "Some-Imp-ID", 4946 Banner: &openrtb2.Banner{ 4947 Format: []openrtb2.Format{ 4948 { 4949 W: 600, 4950 H: 500, 4951 }, 4952 { 4953 W: 300, 4954 H: 600, 4955 }, 4956 }, 4957 }, 4958 Ext: []byte(`{"appnexus":{"placementId": 12345678}, "prebid": {"storedAuctionResponse": {"id": "6d718149-6dfe-25ae-a7d6-305399f77f04"}}}`), 4959 }, 4960 { 4961 ID: "Some-Imp-ID2", 4962 Banner: &openrtb2.Banner{ 4963 Format: []openrtb2.Format{ 4964 { 4965 W: 600, 4966 H: 500, 4967 }, 4968 { 4969 W: 300, 4970 H: 600, 4971 }, 4972 }, 4973 }, 4974 Ext: []byte(`{"appnexus":{"placementId": 12345678}}`), 4975 }, 4976 }, 4977 }, 4978 }, 4979 expectedErrorList: []error{errors.New("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[1].ext.prebid \n")}, 4980 hasStoredAuctionResponses: true, 4981 storedBidResponses: nil, 4982 }, 4983 { 4984 description: "One imp with stored bid response and corresponding bidder in imp.ext, expect validate request to throw no errors", 4985 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 4986 BidRequest: &openrtb2.BidRequest{ 4987 ID: "Some-ID", 4988 App: &openrtb2.App{}, 4989 Imp: []openrtb2.Imp{ 4990 { 4991 ID: "Some-Imp-ID", 4992 Banner: &openrtb2.Banner{ 4993 Format: []openrtb2.Format{ 4994 { 4995 W: 600, 4996 H: 500, 4997 }, 4998 { 4999 W: 300, 5000 H: 600, 5001 }, 5002 }, 5003 }, 5004 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"storedbidresponse": []}}`), 5005 }, 5006 }, 5007 }, 5008 }, 5009 expectedErrorList: []error{}, 5010 hasStoredAuctionResponses: false, 5011 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}}, 5012 }, 5013 { 5014 description: "One imp with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors", 5015 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5016 BidRequest: &openrtb2.BidRequest{ 5017 ID: "Some-ID", 5018 App: &openrtb2.App{}, 5019 Imp: []openrtb2.Imp{ 5020 { 5021 ID: "Some-Imp-ID", 5022 Banner: &openrtb2.Banner{ 5023 Format: []openrtb2.Format{ 5024 { 5025 W: 600, 5026 H: 500, 5027 }, 5028 { 5029 W: 300, 5030 H: 600, 5031 }, 5032 }, 5033 }, 5034 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`), 5035 }, 5036 }, 5037 }, 5038 }, 5039 expectedErrorList: []error{}, 5040 hasStoredAuctionResponses: false, 5041 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}}, 5042 }, 5043 { 5044 description: "Two imps, one with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors", 5045 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5046 BidRequest: &openrtb2.BidRequest{ 5047 ID: "Some-ID", 5048 App: &openrtb2.App{}, 5049 Imp: []openrtb2.Imp{ 5050 { 5051 ID: "Some-Imp-ID", 5052 Banner: &openrtb2.Banner{ 5053 Format: []openrtb2.Format{ 5054 { 5055 W: 600, 5056 H: 500, 5057 }, 5058 { 5059 W: 300, 5060 H: 600, 5061 }, 5062 }, 5063 }, 5064 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`), 5065 }, 5066 { 5067 ID: "Some-Imp-ID2", 5068 Banner: &openrtb2.Banner{ 5069 Format: []openrtb2.Format{ 5070 { 5071 W: 600, 5072 H: 500, 5073 }, 5074 { 5075 W: 300, 5076 H: 600, 5077 }, 5078 }, 5079 }, 5080 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`), 5081 }, 5082 }, 5083 }, 5084 }, 5085 expectedErrorList: []error{}, 5086 hasStoredAuctionResponses: false, 5087 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}}, 5088 }, 5089 { 5090 description: "Two imps, both with 2 stored bid responses and 2 corresponding bidders in imp.ext, expect validate request to throw no errors", 5091 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5092 BidRequest: &openrtb2.BidRequest{ 5093 ID: "Some-ID", 5094 App: &openrtb2.App{}, 5095 Imp: []openrtb2.Imp{ 5096 { 5097 ID: "Some-Imp-ID", 5098 Banner: &openrtb2.Banner{ 5099 Format: []openrtb2.Format{ 5100 { 5101 W: 600, 5102 H: 500, 5103 }, 5104 { 5105 W: 300, 5106 H: 600, 5107 }, 5108 }, 5109 }, 5110 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`), 5111 }, 5112 { 5113 ID: "Some-Imp-ID2", 5114 Banner: &openrtb2.Banner{ 5115 Format: []openrtb2.Format{ 5116 { 5117 W: 600, 5118 H: 500, 5119 }, 5120 { 5121 W: 300, 5122 H: 600, 5123 }, 5124 }, 5125 }, 5126 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`), 5127 }, 5128 }, 5129 }, 5130 }, 5131 expectedErrorList: []error{}, 5132 hasStoredAuctionResponses: false, 5133 storedBidResponses: stored_responses.ImpBidderStoredResp{ 5134 "Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}, 5135 "Some-Imp-ID1": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}, 5136 }, 5137 }, 5138 { 5139 description: "One imp with 2 stored bid responses and 1 bidder in imp.ext, expect validate request to throw an errors", 5140 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5141 BidRequest: &openrtb2.BidRequest{ 5142 ID: "Some-ID", 5143 App: &openrtb2.App{}, 5144 Imp: []openrtb2.Imp{ 5145 { 5146 ID: "Some-Imp-ID", 5147 Banner: &openrtb2.Banner{ 5148 Format: []openrtb2.Format{ 5149 { 5150 W: 600, 5151 H: 500, 5152 }, 5153 { 5154 W: 300, 5155 H: 600, 5156 }, 5157 }, 5158 }, 5159 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"storedbidresponse": []}}`), 5160 }, 5161 }, 5162 }, 5163 }, 5164 expectedErrorList: []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")}, 5165 hasStoredAuctionResponses: false, 5166 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}}, 5167 }, 5168 { 5169 description: "One imp with 1 stored bid responses and 2 bidders in imp.ext, expect validate request to throw an errors", 5170 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5171 BidRequest: &openrtb2.BidRequest{ 5172 ID: "Some-ID", 5173 App: &openrtb2.App{}, 5174 Imp: []openrtb2.Imp{ 5175 { 5176 ID: "Some-Imp-ID", 5177 Banner: &openrtb2.Banner{ 5178 Format: []openrtb2.Format{ 5179 { 5180 W: 600, 5181 H: 500, 5182 }, 5183 { 5184 W: 300, 5185 H: 600, 5186 }, 5187 }, 5188 }, 5189 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`), 5190 }, 5191 }, 5192 }, 5193 }, 5194 expectedErrorList: []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")}, 5195 hasStoredAuctionResponses: false, 5196 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}}, 5197 }, 5198 { 5199 description: "One imp with 2 stored bid responses and 2 different bidders in imp.ext, expect validate request to throw an errors", 5200 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5201 BidRequest: &openrtb2.BidRequest{ 5202 ID: "Some-ID", 5203 App: &openrtb2.App{}, 5204 Imp: []openrtb2.Imp{ 5205 { 5206 ID: "Some-Imp-ID", 5207 Banner: &openrtb2.Banner{ 5208 Format: []openrtb2.Format{ 5209 { 5210 W: 600, 5211 H: 500, 5212 }, 5213 { 5214 W: 300, 5215 H: 600, 5216 }, 5217 }, 5218 }, 5219 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "telaria": {"seatCode": "12345678"}, "prebid": {"storedbidresponse": []}}`), 5220 }, 5221 }, 5222 }, 5223 }, 5224 expectedErrorList: []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")}, 5225 hasStoredAuctionResponses: false, 5226 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "rubicon": json.RawMessage(`{"test":true}`)}}, 5227 }, 5228 { 5229 description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder, expect validate request to throw no errors", 5230 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5231 BidRequest: &openrtb2.BidRequest{ 5232 ID: "Some-ID", 5233 App: &openrtb2.App{}, 5234 Imp: []openrtb2.Imp{ 5235 { 5236 ID: "Some-Imp-ID", 5237 Banner: &openrtb2.Banner{ 5238 Format: []openrtb2.Format{ 5239 { 5240 W: 600, 5241 H: 500, 5242 }, 5243 { 5244 W: 300, 5245 H: 600, 5246 }, 5247 }, 5248 }, 5249 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`), 5250 }, 5251 }, 5252 }, 5253 }, 5254 expectedErrorList: []error{}, 5255 hasStoredAuctionResponses: false, 5256 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}}, 5257 }, 5258 { 5259 description: "One imp with 2 stored bid responses and 1 bidders in imp.ext and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error", 5260 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5261 BidRequest: &openrtb2.BidRequest{ 5262 ID: "Some-ID", 5263 App: &openrtb2.App{}, 5264 Imp: []openrtb2.Imp{ 5265 { 5266 ID: "Some-Imp-ID", 5267 Banner: &openrtb2.Banner{ 5268 Format: []openrtb2.Format{ 5269 { 5270 W: 600, 5271 H: 500, 5272 }, 5273 { 5274 W: 300, 5275 H: 600, 5276 }, 5277 }, 5278 }, 5279 Ext: []byte(`{"appnexus": {"placementId": 12345678}, "prebid": {"bidder":{"rubicon": {"seatCode": "12345678"}}, "storedbidresponse": []}}`), 5280 }, 5281 }, 5282 }, 5283 }, 5284 expectedErrorList: []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")}, 5285 hasStoredAuctionResponses: false, 5286 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`), "telaria": json.RawMessage(`{"test":true}`)}}, 5287 }, 5288 { 5289 description: "One imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is defined in stored bid responses, expect validate request to throw no errors", 5290 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5291 BidRequest: &openrtb2.BidRequest{ 5292 ID: "Some-ID", 5293 App: &openrtb2.App{}, 5294 Imp: []openrtb2.Imp{ 5295 { 5296 ID: "Some-Imp-ID", 5297 Banner: &openrtb2.Banner{ 5298 Format: []openrtb2.Format{ 5299 { 5300 W: 600, 5301 H: 500, 5302 }, 5303 { 5304 W: 300, 5305 H: 600, 5306 }, 5307 }, 5308 }, 5309 Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`), 5310 }, 5311 }, 5312 }, 5313 }, 5314 expectedErrorList: []error{}, 5315 hasStoredAuctionResponses: false, 5316 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"telaria": json.RawMessage(`{"test":true}`)}}, 5317 }, 5318 { 5319 description: "One imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error", 5320 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5321 BidRequest: &openrtb2.BidRequest{ 5322 ID: "Some-ID", 5323 App: &openrtb2.App{}, 5324 Imp: []openrtb2.Imp{ 5325 { 5326 ID: "Some-Imp-ID", 5327 Banner: &openrtb2.Banner{ 5328 Format: []openrtb2.Format{ 5329 { 5330 W: 600, 5331 H: 500, 5332 }, 5333 { 5334 W: 300, 5335 H: 600, 5336 }, 5337 }, 5338 }, 5339 Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`), 5340 }, 5341 }, 5342 }, 5343 }, 5344 expectedErrorList: []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")}, 5345 hasStoredAuctionResponses: false, 5346 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID": {"appnexus": json.RawMessage(`{"test":true}`)}}, 5347 }, 5348 { 5349 description: "2 imps, one imp without stored responses, another imp with 1 stored bid response and 1 in imp.ext.prebid.bidder that is not defined in stored bid responses, expect validate request to throw an error", 5350 givenRequestWrapper: &openrtb_ext.RequestWrapper{ 5351 BidRequest: &openrtb2.BidRequest{ 5352 ID: "Some-ID", 5353 App: &openrtb2.App{}, 5354 Imp: []openrtb2.Imp{ 5355 { 5356 ID: "Some-Imp-ID", 5357 Banner: &openrtb2.Banner{ 5358 Format: []openrtb2.Format{ 5359 { 5360 W: 600, 5361 H: 500, 5362 }, 5363 { 5364 W: 300, 5365 H: 600, 5366 }, 5367 }, 5368 }, 5369 Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}}}`), 5370 }, 5371 { 5372 ID: "Some-Imp-ID2", 5373 Banner: &openrtb2.Banner{ 5374 Format: []openrtb2.Format{ 5375 { 5376 W: 600, 5377 H: 500, 5378 }, 5379 { 5380 W: 300, 5381 H: 600, 5382 }, 5383 }, 5384 }, 5385 Ext: []byte(`{"prebid": {"bidder":{"telaria": {"seatCode": "12345678"}}, "storedbidresponse": []}}`), 5386 }, 5387 }, 5388 }, 5389 }, 5390 expectedErrorList: []error{errors.New("request validation failed. Stored bid responses are specified for imp Some-Imp-ID2. Bidders specified in imp.ext should match with bidders specified in imp.ext.prebid.storedbidresponse")}, 5391 hasStoredAuctionResponses: false, 5392 storedBidResponses: stored_responses.ImpBidderStoredResp{"Some-Imp-ID2": {"appnexus": json.RawMessage(`{"test":true}`)}}, 5393 }, 5394 } 5395 5396 for _, test := range testCases { 5397 errorList := deps.validateRequest(test.givenRequestWrapper, false, test.hasStoredAuctionResponses, test.storedBidResponses, false) 5398 assert.Equalf(t, test.expectedErrorList, errorList, "Error doesn't match: %s\n", test.description) 5399 } 5400 } 5401 5402 func TestValidResponseAfterExecutingStages(t *testing.T) { 5403 const nbr int = 123 5404 5405 hooksPlanBuilder := mockPlanBuilder{ 5406 entrypointPlan: hooks.Plan[hookstage.Entrypoint]{ 5407 { 5408 Timeout: 5 * time.Millisecond, 5409 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 5410 entryPointHookUpdateWithErrors, 5411 entryPointHookUpdateWithErrorsAndWarnings, 5412 }, 5413 }, 5414 { 5415 Timeout: 5 * time.Millisecond, 5416 Hooks: []hooks.HookWrapper[hookstage.Entrypoint]{ 5417 entryPointHookUpdate, 5418 }, 5419 }, 5420 }, 5421 rawAuctionPlan: hooks.Plan[hookstage.RawAuctionRequest]{ 5422 { 5423 Timeout: 5 * time.Millisecond, 5424 Hooks: []hooks.HookWrapper[hookstage.RawAuctionRequest]{ 5425 rawAuctionHookNone, 5426 }, 5427 }, 5428 }, 5429 } 5430 5431 testCases := []struct { 5432 description string 5433 file string 5434 planBuilder hooks.ExecutionPlanBuilder 5435 }{ 5436 { 5437 description: "Assert correct BidResponse when request rejected at entrypoint stage", 5438 file: "sample-requests/hooks/auction_entrypoint_reject.json", 5439 planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr, nil})}, 5440 }, 5441 { 5442 description: "Assert correct BidResponse when request rejected at raw-auction stage", 5443 file: "sample-requests/hooks/auction_raw_auction_request_reject.json", 5444 planBuilder: mockPlanBuilder{rawAuctionPlan: makePlan[hookstage.RawAuctionRequest](mockRejectionHook{nbr, nil})}, 5445 }, 5446 { 5447 description: "Assert correct BidResponse when request rejected at processed-auction stage", 5448 file: "sample-requests/hooks/auction_processed_auction_request_reject.json", 5449 planBuilder: mockPlanBuilder{processedAuctionPlan: makePlan[hookstage.ProcessedAuctionRequest](mockRejectionHook{nbr, nil})}, 5450 }, 5451 { 5452 // bidder-request stage doesn't reject whole request, so we do not expect NBR code in response 5453 description: "Assert correct BidResponse when request rejected at bidder-request stage", 5454 file: "sample-requests/hooks/auction_bidder_reject.json", 5455 planBuilder: mockPlanBuilder{bidderRequestPlan: makePlan[hookstage.BidderRequest](mockRejectionHook{nbr, nil})}, 5456 }, 5457 { 5458 description: "Assert correct BidResponse when request rejected at raw-bidder-response stage", 5459 file: "sample-requests/hooks/auction_bidder_response_reject.json", 5460 planBuilder: mockPlanBuilder{rawBidderResponsePlan: makePlan[hookstage.RawBidderResponse](mockRejectionHook{nbr, nil})}, 5461 }, 5462 { 5463 description: "Assert correct BidResponse when request rejected with error from hook", 5464 file: "sample-requests/hooks/auction_reject_with_error.json", 5465 planBuilder: mockPlanBuilder{entrypointPlan: makePlan[hookstage.Entrypoint](mockRejectionHook{nbr, errors.New("dummy")})}, 5466 }, 5467 { 5468 description: "Assert correct BidResponse with debug information from modules added to ext.prebid.modules", 5469 file: "sample-requests/hooks/auction.json", 5470 planBuilder: hooksPlanBuilder, 5471 }, 5472 } 5473 5474 for _, tc := range testCases { 5475 t.Run(tc.description, func(t *testing.T) { 5476 fileData, err := os.ReadFile(tc.file) 5477 assert.NoError(t, err, "Failed to read test file.") 5478 5479 test, err := parseTestData(fileData, tc.file) 5480 assert.NoError(t, err, "Failed to parse test file.") 5481 test.planBuilder = tc.planBuilder 5482 test.endpointType = OPENRTB_ENDPOINT 5483 5484 cfg := &config.Configuration{MaxRequestSize: maxSize, AccountDefaults: config.Account{DebugAllow: true}} 5485 auctionEndpointHandler, _, mockBidServers, mockCurrencyRatesServer, err := buildTestEndpoint(test, cfg) 5486 assert.NoError(t, err, "Failed to build test endpoint.") 5487 5488 recorder := httptest.NewRecorder() 5489 req := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(test.BidRequest)) 5490 auctionEndpointHandler(recorder, req, nil) 5491 assert.Equal(t, recorder.Code, http.StatusOK, "Endpoint should return 200 OK.") 5492 5493 var actualResp openrtb2.BidResponse 5494 var expectedResp openrtb2.BidResponse 5495 var actualExt openrtb_ext.ExtBidResponse 5496 var expectedExt openrtb_ext.ExtBidResponse 5497 5498 assert.NoError(t, json.Unmarshal(test.ExpectedBidResponse, &expectedResp), "Unable to unmarshal expected BidResponse.") 5499 assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &actualResp), "Unable to unmarshal actual BidResponse.") 5500 if expectedResp.Ext != nil { 5501 assert.NoError(t, json.Unmarshal(expectedResp.Ext, &expectedExt), "Unable to unmarshal expected ExtBidResponse.") 5502 assert.NoError(t, json.Unmarshal(actualResp.Ext, &actualExt), "Unable to unmarshal actual ExtBidResponse.") 5503 } 5504 5505 assertBidResponseEqual(t, tc.file, expectedResp, actualResp) 5506 assert.Equal(t, expectedResp.NBR, actualResp.NBR, "Invalid NBR.") 5507 assert.Equal(t, expectedExt.Warnings, actualExt.Warnings, "Wrong bidResponse.ext.warnings.") 5508 5509 if expectedExt.Prebid != nil { 5510 hookexecution.AssertEqualModulesData(t, expectedExt.Prebid.Modules, actualExt.Prebid.Modules) 5511 } else { 5512 assert.Nil(t, actualExt.Prebid, "Invalid BidResponse.ext.prebid") 5513 } 5514 5515 // Close servers regardless if the test case was run or not 5516 for _, mockBidServer := range mockBidServers { 5517 mockBidServer.Close() 5518 } 5519 mockCurrencyRatesServer.Close() 5520 }) 5521 } 5522 } 5523 5524 func TestSendAuctionResponse_LogsErrors(t *testing.T) { 5525 hookExecutor := &mockStageExecutor{ 5526 outcomes: []hookexecution.StageOutcome{ 5527 { 5528 Entity: "bid-request", 5529 Stage: hooks.StageBidderRequest.String(), 5530 Groups: []hookexecution.GroupOutcome{ 5531 { 5532 InvocationResults: []hookexecution.HookOutcome{ 5533 { 5534 HookID: hookexecution.HookID{ 5535 ModuleCode: "foobar", 5536 HookImplCode: "foo", 5537 }, 5538 Status: hookexecution.StatusSuccess, 5539 Action: hookexecution.ActionNone, 5540 Warnings: []string{"warning message"}, 5541 }, 5542 }, 5543 }, 5544 }, 5545 }, 5546 }, 5547 } 5548 5549 testCases := []struct { 5550 description string 5551 expectedErrors []error 5552 expectedStatus int 5553 request *openrtb2.BidRequest 5554 response *openrtb2.BidResponse 5555 hookExecutor hookexecution.HookStageExecutor 5556 }{ 5557 { 5558 description: "Error logged if hook enrichment fails", 5559 expectedErrors: []error{ 5560 errors.New("Failed to enrich Bid Response with hook debug information: Invalid JSON Document"), 5561 errors.New("/openrtb2/auction Failed to send response: json: error calling MarshalJSON for type json.RawMessage: invalid character '.' looking for beginning of value"), 5562 }, 5563 expectedStatus: 0, 5564 request: &openrtb2.BidRequest{ID: "some-id", Test: 1}, 5565 response: &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("...")}, 5566 hookExecutor: hookExecutor, 5567 }, 5568 { 5569 description: "Error logged if hook enrichment returns warnings", 5570 expectedErrors: []error{ 5571 errors.New("Value is not a string: 1"), 5572 errors.New("Value is not a boolean: active"), 5573 }, 5574 expectedStatus: 0, 5575 request: &openrtb2.BidRequest{ID: "some-id", Test: 1, Ext: json.RawMessage(`{"prebid": {"debug": "active", "trace": 1}}`)}, 5576 response: &openrtb2.BidResponse{ID: "some-id", Ext: json.RawMessage("{}")}, 5577 hookExecutor: hookExecutor, 5578 }, 5579 } 5580 5581 for _, test := range testCases { 5582 t.Run(test.description, func(t *testing.T) { 5583 writer := httptest.NewRecorder() 5584 labels := metrics.Labels{} 5585 ao := analytics.AuctionObject{} 5586 account := &config.Account{DebugAllow: true} 5587 5588 labels, ao = sendAuctionResponse(writer, test.hookExecutor, test.response, test.request, account, labels, ao) 5589 5590 assert.Equal(t, ao.Errors, test.expectedErrors, "Invalid errors.") 5591 assert.Equal(t, test.expectedStatus, ao.Status, "Invalid HTTP response status.") 5592 }) 5593 } 5594 } 5595 5596 func TestParseRequestMultiBid(t *testing.T) { 5597 tests := []struct { 5598 name string 5599 givenRequestBody string 5600 expectedReqExt json.RawMessage 5601 expectedErrors []error 5602 }{ 5603 { 5604 name: "validate and build multi-bid extension", 5605 givenRequestBody: validRequest(t, "multi-bid-error.json"), 5606 expectedReqExt: getObject(t, "multi-bid-error.json", "expectedReqExt"), 5607 expectedErrors: []error{ 5608 &errortypes.Warning{ 5609 WarningCode: errortypes.MultiBidWarningCode, 5610 Message: "maxBids not defined for {Bidder:appnexus, Bidders:[], MaxBids:<nil>, TargetBidderCodePrefix:}", 5611 }, 5612 &errortypes.Warning{ 5613 WarningCode: errortypes.MultiBidWarningCode, 5614 Message: "invalid maxBids value, using minimum 1 limit for {Bidder:rubicon, Bidders:[], MaxBids:-1, TargetBidderCodePrefix:rubN}", 5615 }, 5616 &errortypes.Warning{ 5617 WarningCode: errortypes.MultiBidWarningCode, 5618 Message: "invalid maxBids value, using maximum 9 limit for {Bidder:pubmatic, Bidders:[], MaxBids:10, TargetBidderCodePrefix:pm}", 5619 }, 5620 &errortypes.Warning{ 5621 WarningCode: errortypes.MultiBidWarningCode, 5622 Message: "multiBid already defined for pubmatic, ignoring this instance {Bidder:pubmatic, Bidders:[], MaxBids:4, TargetBidderCodePrefix:pubM}", 5623 }, 5624 &errortypes.Warning{ 5625 WarningCode: errortypes.MultiBidWarningCode, 5626 Message: "ignoring bidders from {Bidder:groupm, Bidders:[someBidder], MaxBids:5, TargetBidderCodePrefix:gm}", 5627 }, 5628 &errortypes.Warning{ 5629 WarningCode: errortypes.MultiBidWarningCode, 5630 Message: "multiBid already defined for groupm, ignoring this instance {Bidder:, Bidders:[groupm], MaxBids:6, TargetBidderCodePrefix:}", 5631 }, 5632 &errortypes.Warning{ 5633 WarningCode: errortypes.MultiBidWarningCode, 5634 Message: "ignoring targetbiddercodeprefix for {Bidder:, Bidders:[33across], MaxBids:7, TargetBidderCodePrefix:abc}", 5635 }, 5636 &errortypes.Warning{ 5637 WarningCode: errortypes.MultiBidWarningCode, 5638 Message: "bidder(s) not specified for {Bidder:, Bidders:[], MaxBids:8, TargetBidderCodePrefix:xyz}", 5639 }, 5640 }, 5641 }, 5642 } 5643 for _, test := range tests { 5644 t.Run(test.name, func(t *testing.T) { 5645 deps := &endpointDeps{ 5646 fakeUUIDGenerator{}, 5647 &warningsCheckExchange{}, 5648 mockBidderParamValidator{}, 5649 &mockStoredReqFetcher{}, 5650 empty_fetcher.EmptyFetcher{}, 5651 empty_fetcher.EmptyFetcher{}, 5652 &config.Configuration{MaxRequestSize: int64(len(test.givenRequestBody))}, 5653 &metricsConfig.NilMetricsEngine{}, 5654 analyticsConf.NewPBSAnalytics(&config.Analytics{}), 5655 map[string]string{}, 5656 false, 5657 []byte{}, 5658 openrtb_ext.BuildBidderMap(), 5659 nil, 5660 nil, 5661 hardcodedResponseIPValidator{response: true}, 5662 empty_fetcher.EmptyFetcher{}, 5663 hooks.EmptyPlanBuilder{}, 5664 nil, 5665 } 5666 5667 hookExecutor := hookexecution.NewHookExecutor(deps.hookExecutionPlanBuilder, hookexecution.EndpointAuction, deps.metricsEngine) 5668 5669 req := httptest.NewRequest("POST", "/openrtb2/auction", strings.NewReader(test.givenRequestBody)) 5670 5671 resReq, _, _, _, _, _, errL := deps.parseRequest(req, &metrics.Labels{}, hookExecutor) 5672 5673 assert.NoError(t, resReq.RebuildRequest()) 5674 5675 assert.JSONEq(t, string(test.expectedReqExt), string(resReq.Ext)) 5676 5677 assert.Equal(t, errL, test.expectedErrors, "error length should match") 5678 }) 5679 } 5680 } 5681 5682 type mockStoredResponseFetcher struct { 5683 data map[string]json.RawMessage 5684 } 5685 5686 func (cf *mockStoredResponseFetcher) FetchRequests(ctx context.Context, requestIDs []string, impIDs []string) (requestData map[string]json.RawMessage, impData map[string]json.RawMessage, errs []error) { 5687 return nil, nil, nil 5688 } 5689 5690 func (cf *mockStoredResponseFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) { 5691 return cf.data, nil 5692 } 5693 5694 func getObject(t *testing.T, filename, key string) json.RawMessage { 5695 requestData, err := os.ReadFile("sample-requests/valid-whole/supplementary/" + filename) 5696 if err != nil { 5697 t.Fatalf("Failed to fetch a valid request: %v", err) 5698 } 5699 testBidRequest, _, _, err := jsonparser.Get(requestData, key) 5700 assert.NoError(t, err, "Error jsonparsing root.mockBidRequest from file %s. Desc: %v.", filename, err) 5701 5702 var obj json.RawMessage 5703 err = json.Unmarshal(testBidRequest, &obj) 5704 if err != nil { 5705 t.Fatalf("Failed to fetch object with key '%s' ... got error: %v", key, err) 5706 } 5707 return obj 5708 } 5709 5710 func getIntegrationFromRequest(req *openrtb_ext.RequestWrapper) (string, error) { 5711 reqExt, err := req.GetRequestExt() 5712 if err != nil { 5713 return "", err 5714 } 5715 reqPrebid := reqExt.GetPrebid() 5716 return reqPrebid.Integration, nil 5717 } 5718 5719 type mockStageExecutor struct { 5720 hookexecution.EmptyHookExecutor 5721 5722 outcomes []hookexecution.StageOutcome 5723 } 5724 5725 func (e mockStageExecutor) GetOutcomes() []hookexecution.StageOutcome { 5726 return e.outcomes 5727 } 5728 5729 func TestSetSeatNonBidRaw(t *testing.T) { 5730 type args struct { 5731 request *openrtb_ext.RequestWrapper 5732 auctionResponse *exchange.AuctionResponse 5733 } 5734 tests := []struct { 5735 name string 5736 args args 5737 wantErr bool 5738 }{ 5739 { 5740 name: "nil-auctionResponse", 5741 args: args{auctionResponse: nil}, 5742 wantErr: false, 5743 }, 5744 { 5745 name: "nil-bidResponse", 5746 args: args{auctionResponse: &exchange.AuctionResponse{BidResponse: nil}}, 5747 wantErr: false, 5748 }, 5749 { 5750 name: "invalid-response.Ext", 5751 args: args{auctionResponse: &exchange.AuctionResponse{BidResponse: &openrtb2.BidResponse{Ext: []byte(`invalid_json`)}}}, 5752 wantErr: true, 5753 }, 5754 { 5755 name: "update-seatnonbid-in-ext", 5756 args: args{ 5757 request: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Ext: []byte(`{"prebid": { "returnallbidstatus" : true }}`)}}, 5758 auctionResponse: &exchange.AuctionResponse{ 5759 ExtBidResponse: &openrtb_ext.ExtBidResponse{Prebid: &openrtb_ext.ExtResponsePrebid{SeatNonBid: []openrtb_ext.SeatNonBid{}}}, 5760 BidResponse: &openrtb2.BidResponse{Ext: []byte(`{}`)}, 5761 }, 5762 }, 5763 wantErr: false, 5764 }, 5765 } 5766 for _, tt := range tests { 5767 t.Run(tt.name, func(t *testing.T) { 5768 if err := setSeatNonBidRaw(tt.args.request, tt.args.auctionResponse); (err != nil) != tt.wantErr { 5769 t.Errorf("setSeatNonBidRaw() error = %v, wantErr %v", err, tt.wantErr) 5770 } 5771 }) 5772 } 5773 }