github.com/prebid/prebid-server@v0.275.0/exchange/bidder_validate_bids_test.go (about) 1 package exchange 2 3 import ( 4 "context" 5 "testing" 6 7 "github.com/prebid/openrtb/v19/openrtb2" 8 "github.com/prebid/prebid-server/adapters" 9 "github.com/prebid/prebid-server/currency" 10 "github.com/prebid/prebid-server/exchange/entities" 11 "github.com/prebid/prebid-server/experiment/adscert" 12 "github.com/prebid/prebid-server/hooks/hookexecution" 13 "github.com/prebid/prebid-server/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 } 138 seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) 139 assert.Len(t, seatBids, 1) 140 assert.Len(t, seatBids[0].Bids, 0) 141 assert.Len(t, errs, 7) 142 } 143 144 func TestMixedBids(t *testing.T) { 145 bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{ 146 bidResponse: []*entities.PbsOrtbSeatBid{{ 147 Bids: []*entities.PbsOrtbBid{ 148 { 149 Bid: &openrtb2.Bid{ 150 ID: "one-bid", 151 ImpID: "thisImp", 152 Price: 0.45, 153 CrID: "thisCreative", 154 }, 155 }, 156 { 157 Bid: &openrtb2.Bid{ 158 ID: "thatBid", 159 ImpID: "thatImp", 160 CrID: "thatCreative", 161 }, 162 }, 163 { 164 Bid: &openrtb2.Bid{ 165 ID: "123", 166 ImpID: "456", 167 Price: 0.44, 168 CrID: "789", 169 }, 170 }, 171 { 172 Bid: &openrtb2.Bid{ 173 ImpID: "456", 174 Price: 0.44, 175 CrID: "blah", 176 }, 177 }, 178 { 179 Bid: &openrtb2.Bid{ 180 ID: "zeroPriceBid", 181 ImpID: "444", 182 Price: 0.00, 183 CrID: "555", 184 DealID: "777", 185 }, 186 }, 187 { 188 Bid: &openrtb2.Bid{ 189 ID: "zeroPriceBidNoDeal", 190 ImpID: "444", 191 Price: 0.00, 192 CrID: "555", 193 DealID: "", 194 }, 195 }, 196 { 197 Bid: &openrtb2.Bid{ 198 ID: "negativePrice", 199 ImpID: "999", 200 Price: -0.10, 201 CrID: "888", 202 }, 203 }, 204 {}, 205 }, 206 }, 207 }}) 208 bidderReq := BidderRequest{ 209 BidRequest: &openrtb2.BidRequest{}, 210 BidderName: openrtb_ext.BidderAppnexus, 211 } 212 bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} 213 bidReqOptions := bidRequestOptions{ 214 accountDebugAllowed: true, 215 headerDebugAllowed: false, 216 addCallSignHeader: false, 217 bidAdjustments: bidAdjustments, 218 } 219 seatBids, _, errs := bidder.requestBid(context.Background(), bidderReq, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) 220 assert.Len(t, seatBids, 1) 221 assert.Len(t, seatBids[0].Bids, 3) 222 assert.Len(t, errs, 5) 223 } 224 225 func TestCurrencyBids(t *testing.T) { 226 currencyTestCases := []struct { 227 brqCur []string 228 brpCur string 229 defaultCur string 230 expectedValidBid bool 231 }{ 232 // Case bid request and bid response don't specify any currencies. 233 // Expected to be valid since both bid request / response will be overridden with default currency (USD). 234 { 235 brqCur: []string{}, 236 brpCur: "", 237 expectedValidBid: true, 238 }, 239 // Case bid request specifies a currency (default one) but bid response doesn't. 240 // Expected to be valid since bid response will be overridden with default currency (USD). 241 { 242 brqCur: []string{"USD"}, 243 brpCur: "", 244 expectedValidBid: true, 245 }, 246 // Case bid request specifies more than 1 currency (default one and another one) but bid response doesn't. 247 // Expected to be valid since bid response will be overridden with default currency (USD). 248 { 249 brqCur: []string{"USD", "EUR"}, 250 brpCur: "", 251 expectedValidBid: true, 252 }, 253 // Case bid request specifies more than 1 currency (default one and another one) and bid response specifies default currency (USD). 254 // Expected to be valid. 255 { 256 brqCur: []string{"USD", "EUR"}, 257 brpCur: "USD", 258 expectedValidBid: true, 259 }, 260 // Case bid request specifies more than 1 currency (default one and another one) and bid response specifies the second currency allowed (not USD). 261 // Expected to be valid. 262 { 263 brqCur: []string{"USD", "EUR"}, 264 brpCur: "EUR", 265 expectedValidBid: true, 266 }, 267 // Case bid request specifies only 1 currency which is not the default one. 268 // Bid response doesn't specify any currency. 269 // Expected to be invalid. 270 { 271 brqCur: []string{"JPY"}, 272 brpCur: "", 273 expectedValidBid: false, 274 }, 275 // Case bid request doesn't specify any currencies. 276 // Bid response specifies a currency which is not the default one. 277 // Expected to be invalid. 278 { 279 brqCur: []string{}, 280 brpCur: "JPY", 281 expectedValidBid: false, 282 }, 283 // Case bid request specifies a currency. 284 // Bid response specifies a currency which is not the one specified in bid request. 285 // Expected to be invalid. 286 { 287 brqCur: []string{"USD"}, 288 brpCur: "EUR", 289 expectedValidBid: false, 290 }, 291 // Case bid request specifies several currencies. 292 // Bid response specifies a currency which is not the one specified in bid request. 293 // Expected to be invalid. 294 { 295 brqCur: []string{"USD", "EUR"}, 296 brpCur: "JPY", 297 expectedValidBid: false, 298 }, 299 } 300 301 for _, tc := range currencyTestCases { 302 bids := []*entities.PbsOrtbBid{ 303 { 304 Bid: &openrtb2.Bid{ 305 ID: "one-bid", 306 ImpID: "thisImp", 307 Price: 0.45, 308 CrID: "thisCreative", 309 }, 310 }, 311 { 312 Bid: &openrtb2.Bid{ 313 ID: "thatBid", 314 ImpID: "thatImp", 315 Price: 0.44, 316 CrID: "thatCreative", 317 }, 318 }, 319 } 320 bidder := addValidatedBidderMiddleware(&mockAdaptedBidder{ 321 bidResponse: []*entities.PbsOrtbSeatBid{{ 322 Currency: tc.brpCur, 323 Bids: bids, 324 }, 325 }}) 326 327 expectedValidBids := len(bids) 328 expectedErrs := 0 329 330 if tc.expectedValidBid != true { 331 // If currency mistmatch, we should have one error 332 expectedErrs = 1 333 expectedValidBids = 0 334 } 335 336 request := &openrtb2.BidRequest{ 337 Cur: tc.brqCur, 338 } 339 bidderRequest := BidderRequest{BidRequest: request, BidderName: openrtb_ext.BidderAppnexus} 340 341 bidAdjustments := map[string]float64{string(openrtb_ext.BidderAppnexus): 1.0} 342 bidReqOptions := bidRequestOptions{ 343 accountDebugAllowed: true, 344 headerDebugAllowed: false, 345 addCallSignHeader: false, 346 bidAdjustments: bidAdjustments, 347 } 348 seatBids, _, errs := bidder.requestBid(context.Background(), bidderRequest, currency.NewConstantRates(), &adapters.ExtraRequestInfo{}, &adscert.NilSigner{}, bidReqOptions, openrtb_ext.ExtAlternateBidderCodes{}, &hookexecution.EmptyHookExecutor{}, nil) 349 assert.Len(t, seatBids, 1) 350 assert.Len(t, seatBids[0].Bids, expectedValidBids) 351 assert.Len(t, errs, expectedErrs) 352 } 353 } 354 355 type mockAdaptedBidder struct { 356 bidResponse []*entities.PbsOrtbSeatBid 357 extraRespInfo extraBidderRespInfo 358 errorResponse []error 359 } 360 361 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) { 362 return b.bidResponse, b.extraRespInfo, b.errorResponse 363 }