github.com/prebid/prebid-server/v2@v2.18.0/exchange/bidder_validate_bids_test.go (about)

     1  package exchange
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/prebid/openrtb/v20/openrtb2"
     8  	"github.com/prebid/prebid-server/v2/adapters"
     9  	"github.com/prebid/prebid-server/v2/currency"
    10  	"github.com/prebid/prebid-server/v2/exchange/entities"
    11  	"github.com/prebid/prebid-server/v2/experiment/adscert"
    12  	"github.com/prebid/prebid-server/v2/hooks/hookexecution"
    13  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    14  	"github.com/stretchr/testify/assert"
    15  )
    16  
    17  func TestAllValidBids(t *testing.T) {
    18  	var bidder AdaptedBidder = addValidatedBidderMiddleware(&mockAdaptedBidder{
    19  		bidResponse: []*entities.PbsOrtbSeatBid{{
    20  			Bids: []*entities.PbsOrtbBid{
    21  				{
    22  					Bid: &openrtb2.Bid{
    23  						ID:    "one-bid",
    24  						ImpID: "thisImp",
    25  						Price: 0.45,
    26  						CrID:  "thisCreative",
    27  					},
    28  				},
    29  				{
    30  					Bid: &openrtb2.Bid{
    31  						ID:    "thatBid",
    32  						ImpID: "thatImp",
    33  						Price: 0.40,
    34  						CrID:  "thatCreative",
    35  					},
    36  				},
    37  				{
    38  					Bid: &openrtb2.Bid{
    39  						ID:    "123",
    40  						ImpID: "456",
    41  						Price: 0.44,
    42  						CrID:  "789",
    43  					},
    44  				},
    45  				{
    46  					Bid: &openrtb2.Bid{
    47  						ID:     "zeroPriceBid",
    48  						ImpID:  "444",
    49  						Price:  0.00,
    50  						CrID:   "555",
    51  						DealID: "777",
    52  					},
    53  				},
    54  			},
    55  		},
    56  		}})
    57  	bidderReq := BidderRequest{
    58  		BidRequest: &openrtb2.BidRequest{},
    59  		BidderName: openrtb_ext.BidderAppnexus,
    60  	}
    61  	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
    62  	bidReqOptions := bidRequestOptions{
    63  		accountDebugAllowed: true,
    64  		headerDebugAllowed:  false,
    65  		addCallSignHeader:   false,
    66  		bidAdjustments:      bidAdjustments,
    67  	}
    68  	seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
    69  	assert.Len(t, seatBids, 1)
    70  	assert.Len(t, seatBids[0].Bids, 4)
    71  	assert.Len(t, errs, 0)
    72  }
    73  
    74  func TestAllBadBids(t *testing.T) {
    75  	bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
    76  		bidResponse: []*entities.PbsOrtbSeatBid{{
    77  			Bids: []*entities.PbsOrtbBid{
    78  				{
    79  					Bid: &openrtb2.Bid{
    80  						ID:    "one-bid",
    81  						Price: 0.45,
    82  						CrID:  "thisCreative",
    83  					},
    84  				},
    85  				{
    86  					Bid: &openrtb2.Bid{
    87  						ID:    "thatBid",
    88  						ImpID: "thatImp",
    89  						CrID:  "thatCreative",
    90  					},
    91  				},
    92  				{
    93  					Bid: &openrtb2.Bid{
    94  						ID:    "123",
    95  						ImpID: "456",
    96  						Price: 0.44,
    97  					},
    98  				},
    99  				{
   100  					Bid: &openrtb2.Bid{
   101  						ImpID: "456",
   102  						Price: 0.44,
   103  						CrID:  "blah",
   104  					},
   105  				},
   106  				{
   107  					Bid: &openrtb2.Bid{
   108  						ID:     "zeroPriceBidNoDeal",
   109  						ImpID:  "444",
   110  						Price:  0.00,
   111  						CrID:   "555",
   112  						DealID: "",
   113  					},
   114  				},
   115  				{
   116  					Bid: &openrtb2.Bid{
   117  						ID:    "negativePrice",
   118  						ImpID: "999",
   119  						Price: -0.10,
   120  						CrID:  "888",
   121  					},
   122  				},
   123  				{},
   124  			},
   125  		},
   126  		}})
   127  	bidderReq := BidderRequest{
   128  		BidRequest: &openrtb2.BidRequest{},
   129  		BidderName: openrtb_ext.BidderAppnexus,
   130  	}
   131  	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
   132  	bidReqOptions := bidRequestOptions{
   133  		accountDebugAllowed:  true,
   134  		headerDebugAllowed:   false,
   135  		addCallSignHeader:    false,
   136  		bidAdjustments:       bidAdjustments,
   137  		responseDebugAllowed: true,
   138  	}
   139  	seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   140  	assert.Len(t, seatBids, 1)
   141  	assert.Len(t, seatBids[0].Bids, 0)
   142  	assert.Len(t, errs, 7)
   143  }
   144  
   145  func TestMixedBids(t *testing.T) {
   146  	bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
   147  		bidResponse: []*entities.PbsOrtbSeatBid{{
   148  			Bids: []*entities.PbsOrtbBid{
   149  				{
   150  					Bid: &openrtb2.Bid{
   151  						ID:    "one-bid",
   152  						ImpID: "thisImp",
   153  						Price: 0.45,
   154  						CrID:  "thisCreative",
   155  					},
   156  				},
   157  				{
   158  					Bid: &openrtb2.Bid{
   159  						ID:    "thatBid",
   160  						ImpID: "thatImp",
   161  						CrID:  "thatCreative",
   162  					},
   163  				},
   164  				{
   165  					Bid: &openrtb2.Bid{
   166  						ID:    "123",
   167  						ImpID: "456",
   168  						Price: 0.44,
   169  						CrID:  "789",
   170  					},
   171  				},
   172  				{
   173  					Bid: &openrtb2.Bid{
   174  						ImpID: "456",
   175  						Price: 0.44,
   176  						CrID:  "blah",
   177  					},
   178  				},
   179  				{
   180  					Bid: &openrtb2.Bid{
   181  						ID:     "zeroPriceBid",
   182  						ImpID:  "444",
   183  						Price:  0.00,
   184  						CrID:   "555",
   185  						DealID: "777",
   186  					},
   187  				},
   188  				{
   189  					Bid: &openrtb2.Bid{
   190  						ID:     "zeroPriceBidNoDeal",
   191  						ImpID:  "444",
   192  						Price:  0.00,
   193  						CrID:   "555",
   194  						DealID: "",
   195  					},
   196  				},
   197  				{
   198  					Bid: &openrtb2.Bid{
   199  						ID:    "negativePrice",
   200  						ImpID: "999",
   201  						Price: -0.10,
   202  						CrID:  "888",
   203  					},
   204  				},
   205  				{},
   206  			},
   207  		},
   208  		}})
   209  	bidderReq := BidderRequest{
   210  		BidRequest: &openrtb2.BidRequest{},
   211  		BidderName: openrtb_ext.BidderAppnexus,
   212  	}
   213  	bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
   214  	bidReqOptions := bidRequestOptions{
   215  		accountDebugAllowed:  true,
   216  		headerDebugAllowed:   false,
   217  		addCallSignHeader:    false,
   218  		bidAdjustments:       bidAdjustments,
   219  		responseDebugAllowed: false,
   220  	}
   221  	seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   222  	assert.Len(t, seatBids, 1)
   223  	assert.Len(t, seatBids[0].Bids, 3)
   224  	assert.Len(t, errs, 2)
   225  }
   226  
   227  func TestCurrencyBids(t *testing.T) {
   228  	currencyTestCases := []struct {
   229  		brqCur           []string
   230  		brpCur           string
   231  		defaultCur       string
   232  		expectedValidBid bool
   233  	}{
   234  		// Case bid request and bid response don't specify any currencies.
   235  		// Expected to be valid since both bid request / response will be overridden with default currency (USD).
   236  		{
   237  			brqCur:           []string{},
   238  			brpCur:           "",
   239  			expectedValidBid: true,
   240  		},
   241  		// Case bid request specifies a currency (default one) but bid response doesn't.
   242  		// Expected to be valid since bid response will be overridden with default currency (USD).
   243  		{
   244  			brqCur:           []string{"USD"},
   245  			brpCur:           "",
   246  			expectedValidBid: true,
   247  		},
   248  		// Case bid request specifies more than 1 currency (default one and another one) but bid response doesn't.
   249  		// Expected to be valid since bid response will be overridden with default currency (USD).
   250  		{
   251  			brqCur:           []string{"USD", "EUR"},
   252  			brpCur:           "",
   253  			expectedValidBid: true,
   254  		},
   255  		// Case bid request specifies more than 1 currency (default one and another one) and bid response specifies default currency (USD).
   256  		// Expected to be valid.
   257  		{
   258  			brqCur:           []string{"USD", "EUR"},
   259  			brpCur:           "USD",
   260  			expectedValidBid: true,
   261  		},
   262  		// Case bid request specifies more than 1 currency (default one and another one) and bid response specifies the second currency allowed (not USD).
   263  		// Expected to be valid.
   264  		{
   265  			brqCur:           []string{"USD", "EUR"},
   266  			brpCur:           "EUR",
   267  			expectedValidBid: true,
   268  		},
   269  		// Case bid request specifies only 1 currency which is not the default one.
   270  		// Bid response doesn't specify any currency.
   271  		// Expected to be invalid.
   272  		{
   273  			brqCur:           []string{"JPY"},
   274  			brpCur:           "",
   275  			expectedValidBid: false,
   276  		},
   277  		// Case bid request doesn't specify any currencies.
   278  		// Bid response specifies a currency which is not the default one.
   279  		// Expected to be invalid.
   280  		{
   281  			brqCur:           []string{},
   282  			brpCur:           "JPY",
   283  			expectedValidBid: false,
   284  		},
   285  		// Case bid request specifies a currency.
   286  		// Bid response specifies a currency which is not the one specified in bid request.
   287  		// Expected to be invalid.
   288  		{
   289  			brqCur:           []string{"USD"},
   290  			brpCur:           "EUR",
   291  			expectedValidBid: false,
   292  		},
   293  		// Case bid request specifies several currencies.
   294  		// Bid response specifies a currency which is not the one specified in bid request.
   295  		// Expected to be invalid.
   296  		{
   297  			brqCur:           []string{"USD", "EUR"},
   298  			brpCur:           "JPY",
   299  			expectedValidBid: false,
   300  		},
   301  	}
   302  
   303  	for _, tc := range currencyTestCases {
   304  		bids := []*entities.PbsOrtbBid{
   305  			{
   306  				Bid: &openrtb2.Bid{
   307  					ID:    "one-bid",
   308  					ImpID: "thisImp",
   309  					Price: 0.45,
   310  					CrID:  "thisCreative",
   311  				},
   312  			},
   313  			{
   314  				Bid: &openrtb2.Bid{
   315  					ID:    "thatBid",
   316  					ImpID: "thatImp",
   317  					Price: 0.44,
   318  					CrID:  "thatCreative",
   319  				},
   320  			},
   321  		}
   322  		bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{
   323  			bidResponse: []*entities.PbsOrtbSeatBid{{
   324  				Currency: tc.brpCur,
   325  				Bids:     bids,
   326  			},
   327  			}})
   328  
   329  		expectedValidBids := len(bids)
   330  		expectedErrs := 0
   331  
   332  		if tc.expectedValidBid != true {
   333  			// If currency mistmatch, we should have one error
   334  			expectedErrs = 1
   335  			expectedValidBids = 0
   336  		}
   337  
   338  		request := &openrtb2.BidRequest{
   339  			Cur: tc.brqCur,
   340  		}
   341  		bidderRequest := BidderRequest{BidRequest: request, BidderName: openrtb_ext.BidderAppnexus}
   342  
   343  		bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0}
   344  		bidReqOptions := bidRequestOptions{
   345  			accountDebugAllowed: true,
   346  			headerDebugAllowed:  false,
   347  			addCallSignHeader:   false,
   348  			bidAdjustments:      bidAdjustments,
   349  		}
   350  		seatBids, _, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil)
   351  		assert.Len(t, seatBids, 1)
   352  		assert.Len(t, seatBids[0].Bids, expectedValidBids)
   353  		assert.Len(t, errs, expectedErrs)
   354  	}
   355  }
   356  
   357  type mockAdaptedBidder struct {
   358  	bidResponse   []*entities.PbsOrtbSeatBid
   359  	extraRespInfo extraBidderRespInfo
   360  	errorResponse []error
   361  }
   362  
   363  func (b *mockAdaptedBidder) requestBid(ctx context.Context, bidderRequest BidderRequest, conversions currency.Conversions, reqInfo *adapters.ExtraRequestInfo, adsCertSigner adscert.Signer, bidRequestMetadata bidRequestOptions, alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes, executor hookexecution.StageExecutor, ruleToAdjustments openrtb_ext.AdjustmentsByDealID) ([]*entities.PbsOrtbSeatBid, extraBidderRespInfo, []error) {
   364  	return b.bidResponse, b.extraRespInfo, b.errorResponse
   365  }