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 }