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