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