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  }