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  }