github.com/prebid/prebid-server@v0.275.0/exchange/targeting_test.go (about) 1 package exchange 2 3 import ( 4 "context" 5 "encoding/json" 6 "net/http" 7 "net/http/httptest" 8 "testing" 9 "time" 10 11 "github.com/prebid/prebid-server/adapters" 12 "github.com/prebid/prebid-server/config" 13 "github.com/prebid/prebid-server/currency" 14 "github.com/prebid/prebid-server/exchange/entities" 15 "github.com/prebid/prebid-server/gdpr" 16 "github.com/prebid/prebid-server/hooks/hookexecution" 17 metricsConfig "github.com/prebid/prebid-server/metrics/config" 18 "github.com/prebid/prebid-server/openrtb_ext" 19 "github.com/prebid/prebid-server/util/ptrutil" 20 21 "github.com/prebid/openrtb/v19/openrtb2" 22 "github.com/stretchr/testify/assert" 23 ) 24 25 // Using this set of bids in more than one test 26 var mockBids = map[openrtb_ext.BidderName][]*openrtb2.Bid{ 27 openrtb_ext.BidderAppnexus: {{ 28 ID: "losing-bid", 29 ImpID: "some-imp", 30 Price: 0.5, 31 CrID: "1", 32 }, { 33 ID: "winning-bid", 34 ImpID: "some-imp", 35 Price: 0.7, 36 CrID: "2", 37 }}, 38 openrtb_ext.BidderRubicon: {{ 39 ID: "contending-bid", 40 ImpID: "some-imp", 41 Price: 0.6, 42 CrID: "3", 43 }}, 44 } 45 46 // Prevents #378. This is not a JSON test because the cache ID values aren't reproducible, which makes them a pain to test in that format. 47 func TestTargetingCache(t *testing.T) { 48 bids := runTargetingAuction(t, mockBids, true, true, true, false) 49 50 // Make sure that the cache keys exist on the bids where they're expected to 51 assertKeyExists(t, bids["winning-bid"], string(openrtb_ext.HbCacheKey), true) 52 assertKeyExists(t, bids["winning-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, MaxKeyLength), true) 53 54 assertKeyExists(t, bids["contending-bid"], string(openrtb_ext.HbCacheKey), false) 55 assertKeyExists(t, bids["contending-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderRubicon, MaxKeyLength), true) 56 57 assertKeyExists(t, bids["losing-bid"], string(openrtb_ext.HbCacheKey), false) 58 assertKeyExists(t, bids["losing-bid"], openrtb_ext.HbCacheKey.BidderKey(openrtb_ext.BidderAppnexus, MaxKeyLength), false) 59 60 //assert hb_cache_host was included 61 assert.Contains(t, string(bids["winning-bid"].Ext), string(openrtb_ext.HbConstantCacheHostKey)) 62 assert.Contains(t, string(bids["winning-bid"].Ext), "www.pbcserver.com") 63 64 //assert hb_cache_path was included 65 assert.Contains(t, string(bids["winning-bid"].Ext), string(openrtb_ext.HbConstantCachePathKey)) 66 assert.Contains(t, string(bids["winning-bid"].Ext), "/pbcache/endpoint") 67 68 } 69 70 func assertKeyExists(t *testing.T, bid *openrtb2.Bid, key string, expected bool) { 71 t.Helper() 72 targets := parseTargets(t, bid) 73 if _, ok := targets[key]; ok != expected { 74 t.Errorf("Bid %s has wrong key: %s. Expected? %t, Exists? %t", bid.ID, key, expected, ok) 75 } 76 } 77 78 // runAuction takes a bunch of mock bids by Bidder and runs an auction. It returns a map of Bids indexed by their ImpID. 79 // If includeCache is true, the auction will be run with cacheing as well, so the cache targeting keys should exist. 80 func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid, includeCache bool, includeWinners bool, includeBidderKeys bool, isApp bool) map[string]*openrtb2.Bid { 81 server := httptest.NewServer(http.HandlerFunc(mockServer)) 82 defer server.Close() 83 84 categoriesFetcher, error := newCategoryFetcher("./test/category-mapping") 85 if error != nil { 86 t.Errorf("Failed to create a category Fetcher: %v", error) 87 } 88 89 gdprPermsBuilder := fakePermissionsBuilder{ 90 permissions: &permissionsMock{ 91 allowAllBidders: true, 92 }, 93 }.Builder 94 95 ex := &exchange{ 96 adapterMap: buildAdapterMap(mockBids, server.URL, server.Client()), 97 me: &metricsConfig.NilMetricsEngine{}, 98 cache: &wellBehavedCache{}, 99 cacheTime: time.Duration(0), 100 gdprPermsBuilder: gdprPermsBuilder, 101 currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), 102 gdprDefaultValue: gdpr.SignalYes, 103 categoriesFetcher: categoriesFetcher, 104 bidIDGenerator: &mockBidIDGenerator{false, false}, 105 } 106 ex.requestSplitter = requestSplitter{ 107 me: ex.me, 108 gdprPermsBuilder: ex.gdprPermsBuilder, 109 } 110 111 imps := buildImps(t, mockBids) 112 113 req := &openrtb2.BidRequest{ 114 Imp: imps, 115 Ext: buildTargetingExt(includeCache, includeWinners, includeBidderKeys), 116 } 117 if isApp { 118 req.App = &openrtb2.App{} 119 } else { 120 req.Site = &openrtb2.Site{} 121 } 122 123 auctionRequest := &AuctionRequest{ 124 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 125 Account: config.Account{}, 126 UserSyncs: &emptyUsersync{}, 127 HookExecutor: &hookexecution.EmptyHookExecutor{}, 128 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 129 } 130 131 debugLog := DebugLog{} 132 bidResp, err := ex.HoldAuction(context.Background(), auctionRequest, &debugLog) 133 134 if err != nil { 135 t.Fatalf("Unexpected errors running auction: %v", err) 136 } 137 if len(bidResp.SeatBid) != len(mockBids) { 138 t.Fatalf("Unexpected number of SeatBids. Expected %d, got %d", len(mockBids), len(bidResp.SeatBid)) 139 } 140 141 return buildBidMap(bidResp.SeatBid, len(mockBids)) 142 } 143 144 func buildAdapterMap(bids map[openrtb_ext.BidderName][]*openrtb2.Bid, mockServerURL string, client *http.Client) map[openrtb_ext.BidderName]AdaptedBidder { 145 adapterMap := make(map[openrtb_ext.BidderName]AdaptedBidder, len(bids)) 146 for bidder, bids := range bids { 147 adapterMap[bidder] = AdaptBidder(&mockTargetingBidder{ 148 mockServerURL: mockServerURL, 149 bids: bids, 150 }, client, &config.Configuration{}, &metricsConfig.NilMetricsEngine{}, openrtb_ext.BidderAppnexus, nil, "") 151 } 152 return adapterMap 153 } 154 155 func buildTargetingExt(includeCache bool, includeWinners bool, includeBidderKeys bool) json.RawMessage { 156 var targeting string 157 if includeWinners && includeBidderKeys { 158 targeting = `{"pricegranularity":{"precision":2,"ranges": [{"min": 0,"max": 20,"increment": 0.1}]},"includewinners": true, "includebidderkeys": true}` 159 } else if !includeWinners && includeBidderKeys { 160 targeting = `{"precision":2,"includewinners": false}` 161 } else if includeWinners && !includeBidderKeys { 162 targeting = `{"precision":2,"includebidderkeys": false}` 163 } else { 164 targeting = `{"precision":2,"includewinners": false, "includebidderkeys": false}` 165 } 166 167 if includeCache { 168 return json.RawMessage(`{"prebid":{"targeting":` + targeting + `,"cache":{"bids":{}}}}`) 169 } 170 171 return json.RawMessage(`{"prebid":{"targeting":` + targeting + `}}`) 172 } 173 174 func buildParams(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid) json.RawMessage { 175 params := make(map[string]interface{}) 176 paramsPrebid := make(map[string]interface{}) 177 paramsPrebidBidders := make(map[string]json.RawMessage) 178 179 for bidder := range mockBids { 180 paramsPrebidBidders[string(bidder)] = json.RawMessage(`{"whatever":true}`) 181 } 182 183 paramsPrebid["bidder"] = paramsPrebidBidders 184 params["prebid"] = paramsPrebid 185 ext, err := json.Marshal(params) 186 if err != nil { 187 t.Fatalf("Failed to make imp exts: %v", err) 188 } 189 return ext 190 } 191 192 func buildImps(t *testing.T, mockBids map[openrtb_ext.BidderName][]*openrtb2.Bid) []openrtb2.Imp { 193 impExt := buildParams(t, mockBids) 194 195 var s struct{} 196 impIds := make(map[string]struct{}, 2*len(mockBids)) 197 for _, bidList := range mockBids { 198 for _, bid := range bidList { 199 impIds[bid.ImpID] = s 200 } 201 } 202 203 imps := make([]openrtb2.Imp, 0, len(impIds)) 204 for impId := range impIds { 205 imps = append(imps, openrtb2.Imp{ 206 ID: impId, 207 Ext: impExt, 208 }) 209 } 210 return imps 211 } 212 213 func buildBidMap(seatBids []openrtb2.SeatBid, numBids int) map[string]*openrtb2.Bid { 214 bids := make(map[string]*openrtb2.Bid, numBids) 215 for _, seatBid := range seatBids { 216 for i := 0; i < len(seatBid.Bid); i++ { 217 bid := seatBid.Bid[i] 218 bids[bid.ID] = &bid 219 } 220 } 221 return bids 222 } 223 224 func parseTargets(t *testing.T, bid *openrtb2.Bid) map[string]string { 225 t.Helper() 226 var parsed openrtb_ext.ExtBid 227 if err := json.Unmarshal(bid.Ext, &parsed); err != nil { 228 t.Fatalf("Unexpected error parsing targeting params: %v", err) 229 } 230 return parsed.Prebid.Targeting 231 } 232 233 type mockTargetingBidder struct { 234 mockServerURL string 235 bids []*openrtb2.Bid 236 } 237 238 func (m *mockTargetingBidder) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 239 return []*adapters.RequestData{{ 240 Method: "POST", 241 Uri: m.mockServerURL, 242 Body: []byte(""), 243 Headers: http.Header{}, 244 }}, nil 245 } 246 247 func (m *mockTargetingBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 248 bidResponse := &adapters.BidderResponse{ 249 Bids: make([]*adapters.TypedBid, len(m.bids)), 250 } 251 for i := 0; i < len(m.bids); i++ { 252 bidResponse.Bids[i] = &adapters.TypedBid{ 253 Bid: m.bids[i], 254 BidType: openrtb_ext.BidTypeBanner, 255 } 256 } 257 return bidResponse, nil 258 } 259 260 func mockServer(w http.ResponseWriter, req *http.Request) { 261 w.Write([]byte("{}")) 262 } 263 264 type TargetingTestData struct { 265 Description string 266 TargetData targetData 267 Auction auction 268 IsApp bool 269 CategoryMapping map[string]string 270 ExpectedPbsBids map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid 271 TruncateTargetAttr *int 272 MultiBidMap map[string]openrtb_ext.ExtMultiBid 273 DefaultBidLimit int 274 } 275 276 type ExpectedPbsBid struct { 277 BidTargets map[string]string 278 TargetBidderCode string 279 } 280 281 var bid123 *openrtb2.Bid = &openrtb2.Bid{ 282 Price: 1.23, 283 } 284 285 var bid111 *openrtb2.Bid = &openrtb2.Bid{ 286 Price: 1.11, 287 DealID: "mydeal", 288 } 289 var bid084 *openrtb2.Bid = &openrtb2.Bid{ 290 Price: 0.84, 291 } 292 293 var bid1p001 *openrtb2.Bid = &openrtb2.Bid{ 294 Price: 0.01, 295 } 296 297 var bid1p077 *openrtb2.Bid = &openrtb2.Bid{ 298 Price: 0.77, 299 } 300 301 var bid1p120 *openrtb2.Bid = &openrtb2.Bid{ 302 Price: 1.20, 303 } 304 305 var bid2p123 *openrtb2.Bid = &openrtb2.Bid{ 306 Price: 1.23, 307 } 308 309 var bid2p144 *openrtb2.Bid = &openrtb2.Bid{ 310 Price: 1.44, 311 } 312 313 var bid2p155 *openrtb2.Bid = &openrtb2.Bid{ 314 Price: 1.55, 315 } 316 317 var bid2p166 *openrtb2.Bid = &openrtb2.Bid{ 318 Price: 1.66, 319 } 320 321 var ( 322 truncateTargetAttrValue10 int = 10 323 truncateTargetAttrValue5 int = 5 324 truncateTargetAttrValue25 int = 25 325 truncateTargetAttrValueNegative int = -1 326 ) 327 328 func lookupPriceGranularity(v string) openrtb_ext.PriceGranularity { 329 priceGranularity, _ := openrtb_ext.NewPriceGranularityFromLegacyID(v) 330 return priceGranularity 331 } 332 333 var TargetingTests []TargetingTestData = []TargetingTestData{ 334 { 335 Description: "Targeting winners only (most basic targeting example)", 336 TargetData: targetData{ 337 priceGranularity: lookupPriceGranularity("med"), 338 includeWinners: true, 339 }, 340 Auction: auction{ 341 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 342 "ImpId-1": { 343 openrtb_ext.BidderAppnexus: {{ 344 Bid: bid123, 345 BidType: openrtb_ext.BidTypeBanner, 346 }}, 347 openrtb_ext.BidderRubicon: {{ 348 Bid: bid084, 349 BidType: openrtb_ext.BidTypeBanner, 350 }}, 351 }, 352 }, 353 }, 354 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 355 "ImpId-1": { 356 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 357 { 358 BidTargets: map[string]string{ 359 "hb_bidder": "appnexus", 360 "hb_pb": "1.20", 361 }, 362 }, 363 }, 364 openrtb_ext.BidderRubicon: []ExpectedPbsBid{}, 365 }, 366 }, 367 TruncateTargetAttr: nil, 368 }, 369 { 370 Description: "Targeting on bidders only", 371 TargetData: targetData{ 372 priceGranularity: lookupPriceGranularity("med"), 373 includeBidderKeys: true, 374 }, 375 Auction: auction{ 376 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 377 "ImpId-1": { 378 openrtb_ext.BidderAppnexus: {{ 379 Bid: bid123, 380 BidType: openrtb_ext.BidTypeBanner, 381 }}, 382 openrtb_ext.BidderRubicon: {{ 383 Bid: bid084, 384 BidType: openrtb_ext.BidTypeBanner, 385 }}, 386 }, 387 }, 388 }, 389 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 390 "ImpId-1": { 391 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 392 { 393 BidTargets: map[string]string{ 394 "hb_bidder_appnexus": "appnexus", 395 "hb_pb_appnexus": "1.20", 396 }, 397 }, 398 }, 399 openrtb_ext.BidderRubicon: []ExpectedPbsBid{ 400 { 401 BidTargets: map[string]string{ 402 "hb_bidder_rubicon": "rubicon", 403 "hb_pb_rubicon": "0.80", 404 }, 405 }, 406 }, 407 }, 408 }, 409 TruncateTargetAttr: nil, 410 }, 411 { 412 Description: "Full basic targeting with hd_format", 413 TargetData: targetData{ 414 priceGranularity: lookupPriceGranularity("med"), 415 includeWinners: true, 416 includeBidderKeys: true, 417 includeFormat: true, 418 }, 419 Auction: auction{ 420 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 421 "ImpId-1": { 422 openrtb_ext.BidderAppnexus: {{ 423 Bid: bid123, 424 BidType: openrtb_ext.BidTypeBanner, 425 }}, 426 openrtb_ext.BidderRubicon: {{ 427 Bid: bid084, 428 BidType: openrtb_ext.BidTypeBanner, 429 }}, 430 }, 431 }, 432 }, 433 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 434 "ImpId-1": { 435 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 436 { 437 BidTargets: map[string]string{ 438 "hb_bidder": "appnexus", 439 "hb_bidder_appnexus": "appnexus", 440 "hb_pb": "1.20", 441 "hb_pb_appnexus": "1.20", 442 "hb_format": "banner", 443 "hb_format_appnexus": "banner", 444 }, 445 }, 446 }, 447 openrtb_ext.BidderRubicon: []ExpectedPbsBid{ 448 { 449 BidTargets: map[string]string{ 450 "hb_bidder_rubicon": "rubicon", 451 "hb_pb_rubicon": "0.80", 452 "hb_format_rubicon": "banner", 453 }, 454 }, 455 }, 456 }, 457 }, 458 TruncateTargetAttr: nil, 459 }, 460 { 461 Description: "Cache and deal targeting test", 462 TargetData: targetData{ 463 priceGranularity: lookupPriceGranularity("med"), 464 includeBidderKeys: true, 465 cacheHost: "cache.prebid.com", 466 cachePath: "cache", 467 }, 468 Auction: auction{ 469 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 470 "ImpId-1": { 471 openrtb_ext.BidderAppnexus: {{ 472 Bid: bid123, 473 BidType: openrtb_ext.BidTypeBanner, 474 }}, 475 openrtb_ext.BidderRubicon: {{ 476 Bid: bid111, 477 BidType: openrtb_ext.BidTypeBanner, 478 }}, 479 }, 480 }, 481 cacheIds: map[*openrtb2.Bid]string{ 482 bid123: "55555", 483 bid111: "cacheme", 484 }, 485 }, 486 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 487 "ImpId-1": { 488 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 489 { 490 BidTargets: map[string]string{ 491 "hb_bidder_appnexus": "appnexus", 492 "hb_pb_appnexus": "1.20", 493 "hb_cache_id_appnexus": "55555", 494 "hb_cache_host_appnex": "cache.prebid.com", 495 "hb_cache_path_appnex": "cache", 496 }, 497 }, 498 }, 499 openrtb_ext.BidderRubicon: []ExpectedPbsBid{ 500 { 501 BidTargets: map[string]string{ 502 "hb_bidder_rubicon": "rubicon", 503 "hb_pb_rubicon": "1.10", 504 "hb_cache_id_rubicon": "cacheme", 505 "hb_deal_rubicon": "mydeal", 506 "hb_cache_host_rubico": "cache.prebid.com", 507 "hb_cache_path_rubico": "cache", 508 }, 509 }, 510 }, 511 }, 512 }, 513 TruncateTargetAttr: nil, 514 }, 515 { 516 Description: "bidder with no dealID should not have deal targeting", 517 TargetData: targetData{ 518 priceGranularity: lookupPriceGranularity("med"), 519 includeBidderKeys: true, 520 }, 521 Auction: auction{ 522 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 523 "ImpId-1": { 524 openrtb_ext.BidderAppnexus: {{ 525 Bid: bid123, 526 BidType: openrtb_ext.BidTypeBanner, 527 }}, 528 }, 529 }, 530 }, 531 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 532 "ImpId-1": { 533 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 534 { 535 BidTargets: map[string]string{ 536 "hb_bidder_appnexus": "appnexus", 537 "hb_pb_appnexus": "1.20", 538 }, 539 }, 540 }, 541 }, 542 }, 543 TruncateTargetAttr: nil, 544 }, 545 { 546 Description: "Truncate Targeting Attribute value is given and is less than const MaxKeyLength", 547 TargetData: targetData{ 548 priceGranularity: lookupPriceGranularity("med"), 549 includeBidderKeys: true, 550 }, 551 Auction: auction{ 552 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 553 "ImpId-1": { 554 openrtb_ext.BidderAppnexus: {{ 555 Bid: bid123, 556 BidType: openrtb_ext.BidTypeBanner, 557 }}, 558 openrtb_ext.BidderRubicon: {{ 559 Bid: bid084, 560 BidType: openrtb_ext.BidTypeBanner, 561 }}, 562 }, 563 }, 564 }, 565 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 566 "ImpId-1": { 567 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 568 { 569 BidTargets: map[string]string{ 570 "hb_bidder_": "appnexus", 571 "hb_pb_appn": "1.20", 572 }, 573 }, 574 }, 575 openrtb_ext.BidderRubicon: []ExpectedPbsBid{ 576 { 577 BidTargets: map[string]string{ 578 "hb_bidder_": "rubicon", 579 "hb_pb_rubi": "0.80", 580 }, 581 }, 582 }, 583 }, 584 }, 585 TruncateTargetAttr: &truncateTargetAttrValue10, 586 }, 587 { 588 Description: "Truncate Targeting Attribute value is given and is greater than const MaxKeyLength", 589 TargetData: targetData{ 590 priceGranularity: lookupPriceGranularity("med"), 591 includeBidderKeys: true, 592 }, 593 Auction: auction{ 594 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 595 "ImpId-1": { 596 openrtb_ext.BidderAppnexus: {{ 597 Bid: bid123, 598 BidType: openrtb_ext.BidTypeBanner, 599 }}, 600 openrtb_ext.BidderRubicon: {{ 601 Bid: bid084, 602 BidType: openrtb_ext.BidTypeBanner, 603 }}, 604 }, 605 }, 606 }, 607 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 608 "ImpId-1": { 609 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 610 { 611 BidTargets: map[string]string{ 612 "hb_bidder_appnexus": "appnexus", 613 "hb_pb_appnexus": "1.20", 614 }, 615 }, 616 }, 617 openrtb_ext.BidderRubicon: []ExpectedPbsBid{ 618 { 619 BidTargets: map[string]string{ 620 "hb_bidder_rubicon": "rubicon", 621 "hb_pb_rubicon": "0.80", 622 }, 623 }, 624 }, 625 }, 626 }, 627 TruncateTargetAttr: &truncateTargetAttrValue25, 628 }, 629 { 630 Description: "Truncate Targeting Attribute value is given and is negative", 631 TargetData: targetData{ 632 priceGranularity: lookupPriceGranularity("med"), 633 includeBidderKeys: true, 634 }, 635 Auction: auction{ 636 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 637 "ImpId-1": { 638 openrtb_ext.BidderAppnexus: {{ 639 Bid: bid123, 640 BidType: openrtb_ext.BidTypeBanner, 641 }}, 642 openrtb_ext.BidderRubicon: {{ 643 Bid: bid084, 644 BidType: openrtb_ext.BidTypeBanner, 645 }}, 646 }, 647 }, 648 }, 649 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 650 "ImpId-1": { 651 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 652 { 653 BidTargets: map[string]string{ 654 "hb_bidder_appnexus": "appnexus", 655 "hb_pb_appnexus": "1.20", 656 }, 657 }, 658 }, 659 openrtb_ext.BidderRubicon: []ExpectedPbsBid{ 660 { 661 BidTargets: map[string]string{ 662 "hb_bidder_rubicon": "rubicon", 663 "hb_pb_rubicon": "0.80", 664 }, 665 }, 666 }, 667 }, 668 }, 669 TruncateTargetAttr: &truncateTargetAttrValueNegative, 670 }, 671 { 672 Description: "Check that key gets truncated properly when value is smaller than key", 673 TargetData: targetData{ 674 priceGranularity: lookupPriceGranularity("med"), 675 includeWinners: true, 676 }, 677 Auction: auction{ 678 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 679 "ImpId-1": { 680 openrtb_ext.BidderAppnexus: {{ 681 Bid: bid123, 682 BidType: openrtb_ext.BidTypeBanner, 683 }}, 684 openrtb_ext.BidderRubicon: {{ 685 Bid: bid084, 686 BidType: openrtb_ext.BidTypeBanner, 687 }}, 688 }, 689 }, 690 }, 691 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 692 "ImpId-1": { 693 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 694 { 695 BidTargets: map[string]string{ 696 "hb_bi": "appnexus", 697 "hb_pb": "1.20", 698 }, 699 }, 700 }, 701 openrtb_ext.BidderRubicon: []ExpectedPbsBid{}, 702 }, 703 }, 704 TruncateTargetAttr: &truncateTargetAttrValue5, 705 }, 706 { 707 Description: "Check that key gets truncated properly when value is greater than key", 708 TargetData: targetData{ 709 priceGranularity: lookupPriceGranularity("med"), 710 includeWinners: true, 711 }, 712 Auction: auction{ 713 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 714 "ImpId-1": { 715 openrtb_ext.BidderAppnexus: {{ 716 Bid: bid123, 717 BidType: openrtb_ext.BidTypeBanner, 718 }}, 719 openrtb_ext.BidderRubicon: {{ 720 Bid: bid084, 721 BidType: openrtb_ext.BidTypeBanner, 722 }}, 723 }, 724 }, 725 }, 726 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 727 "ImpId-1": { 728 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 729 { 730 BidTargets: map[string]string{ 731 "hb_bidder": "appnexus", 732 "hb_pb": "1.20", 733 }, 734 }, 735 }, 736 openrtb_ext.BidderRubicon: []ExpectedPbsBid{}, 737 }, 738 }, 739 TruncateTargetAttr: &truncateTargetAttrValue25, 740 }, 741 { 742 Description: "Check that key gets truncated properly when value is negative", 743 TargetData: targetData{ 744 priceGranularity: lookupPriceGranularity("med"), 745 includeWinners: true, 746 }, 747 Auction: auction{ 748 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 749 "ImpId-1": { 750 openrtb_ext.BidderAppnexus: {{ 751 Bid: bid123, 752 BidType: openrtb_ext.BidTypeBanner, 753 }}, 754 openrtb_ext.BidderRubicon: {{ 755 Bid: bid084, 756 BidType: openrtb_ext.BidTypeBanner, 757 }}, 758 }, 759 }, 760 }, 761 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 762 "ImpId-1": { 763 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 764 { 765 BidTargets: map[string]string{ 766 "hb_bidder": "appnexus", 767 "hb_pb": "1.20", 768 }, 769 }, 770 }, 771 openrtb_ext.BidderRubicon: []ExpectedPbsBid{}, 772 }, 773 }, 774 TruncateTargetAttr: &truncateTargetAttrValueNegative, 775 }, 776 { 777 Description: "Full basic targeting with multibid", 778 TargetData: targetData{ 779 priceGranularity: lookupPriceGranularity("med"), 780 includeWinners: true, 781 includeBidderKeys: true, 782 includeFormat: true, 783 }, 784 Auction: auction{ 785 winningBidsByBidder: map[string]map[openrtb_ext.BidderName][]*entities.PbsOrtbBid{ 786 "ImpId-1": { 787 openrtb_ext.BidderAppnexus: { 788 { 789 Bid: bid1p120, 790 BidType: openrtb_ext.BidTypeBanner, 791 }, 792 { 793 Bid: bid1p077, 794 BidType: openrtb_ext.BidTypeBanner, 795 }, 796 { 797 Bid: bid1p001, 798 BidType: openrtb_ext.BidTypeBanner, 799 }, 800 }, 801 openrtb_ext.BidderRubicon: { 802 { 803 Bid: bid123, 804 BidType: openrtb_ext.BidTypeBanner, 805 }, 806 { 807 Bid: bid111, 808 BidType: openrtb_ext.BidTypeBanner, 809 }, 810 { 811 Bid: bid084, 812 BidType: openrtb_ext.BidTypeBanner, 813 }, 814 }, 815 }, 816 "ImpId-2": { 817 openrtb_ext.BidderPubmatic: { 818 { 819 Bid: bid2p166, 820 BidType: openrtb_ext.BidTypeBanner, 821 }, 822 { 823 Bid: bid2p155, 824 BidType: openrtb_ext.BidTypeBanner, 825 }, 826 { 827 Bid: bid2p144, 828 BidType: openrtb_ext.BidTypeBanner, 829 }, 830 { 831 Bid: bid2p123, 832 BidType: openrtb_ext.BidTypeBanner, 833 }, 834 }, 835 }, 836 }, 837 }, 838 ExpectedPbsBids: map[string]map[openrtb_ext.BidderName][]ExpectedPbsBid{ 839 "ImpId-1": { 840 openrtb_ext.BidderAppnexus: []ExpectedPbsBid{ 841 { 842 BidTargets: map[string]string{ 843 "hb_bidder_appnexus": "appnexus", 844 "hb_pb_appnexus": "1.10", 845 "hb_format_appnexus": "banner", 846 }, 847 TargetBidderCode: "appnexus", 848 }, 849 {}, 850 {}, 851 }, 852 openrtb_ext.BidderRubicon: []ExpectedPbsBid{ 853 { 854 BidTargets: map[string]string{ 855 "hb_bidder": "rubicon", 856 "hb_bidder_rubicon": "rubicon", 857 "hb_pb": "1.20", 858 "hb_pb_rubicon": "1.20", 859 "hb_format": "banner", 860 "hb_format_rubicon": "banner", 861 }, 862 }, 863 {}, 864 {}, 865 }, 866 }, 867 "ImpId-2": { 868 openrtb_ext.BidderPubmatic: []ExpectedPbsBid{ 869 { 870 BidTargets: map[string]string{ 871 "hb_bidder": "pubmatic", 872 "hb_bidder_pubmatic": "pubmatic", 873 "hb_pb": "1.60", 874 "hb_pb_pubmatic": "1.60", 875 "hb_format": "banner", 876 "hb_format_pubmatic": "banner", 877 }, 878 TargetBidderCode: "pubmatic", 879 }, 880 { 881 BidTargets: map[string]string{ 882 "hb_bidder_pm2": "pm2", 883 "hb_pb_pm2": "1.50", 884 "hb_format_pm2": "banner", 885 }, 886 TargetBidderCode: "pm2", 887 }, 888 { 889 BidTargets: map[string]string{ 890 "hb_bidder_pm3": "pm3", 891 "hb_pb_pm3": "1.40", 892 "hb_format_pm3": "banner", 893 }, 894 TargetBidderCode: "pm3", 895 }, 896 {}, 897 }, 898 }, 899 }, 900 TruncateTargetAttr: nil, 901 MultiBidMap: map[string]openrtb_ext.ExtMultiBid{ 902 string(openrtb_ext.BidderPubmatic): { 903 MaxBids: ptrutil.ToPtr(3), 904 TargetBidderCodePrefix: "pm", 905 }, 906 string(openrtb_ext.BidderAppnexus): { 907 MaxBids: ptrutil.ToPtr(2), 908 }, 909 }, 910 }, 911 } 912 913 func TestSetTargeting(t *testing.T) { 914 for _, test := range TargetingTests { 915 auc := &test.Auction 916 // Set rounded prices from the auction data 917 auc.setRoundedPrices(test.TargetData) 918 winningBids := make(map[string]*entities.PbsOrtbBid) 919 // Set winning bids from the auction data 920 for imp, bidsByBidder := range auc.winningBidsByBidder { 921 for _, bids := range bidsByBidder { 922 for _, bid := range bids { 923 if winningBid, ok := winningBids[imp]; ok { 924 if winningBid.Bid.Price < bid.Bid.Price { 925 winningBids[imp] = bid 926 } 927 } else { 928 winningBids[imp] = bid 929 } 930 } 931 } 932 } 933 auc.winningBids = winningBids 934 targData := test.TargetData 935 targData.setTargeting(auc, test.IsApp, test.CategoryMapping, test.TruncateTargetAttr, test.MultiBidMap) 936 for imp, targetsByBidder := range test.ExpectedPbsBids { 937 for bidder, expectedTargets := range targetsByBidder { 938 for i, expected := range expectedTargets { 939 assert.Equal(t, 940 expected.BidTargets, 941 auc.winningBidsByBidder[imp][bidder][i].BidTargets, 942 "Test: %s\nTargeting failed for bidder %s on imp %s.", 943 test.Description, 944 string(bidder), 945 imp) 946 assert.Equal(t, expected.TargetBidderCode, auc.winningBidsByBidder[imp][bidder][i].TargetBidderCode) 947 } 948 } 949 } 950 } 951 }