github.com/prebid/prebid-server/v2@v2.18.0/exchange/utils_test.go (about) 1 package exchange 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "sort" 9 "testing" 10 11 "github.com/prebid/prebid-server/v2/stored_responses" 12 13 gpplib "github.com/prebid/go-gpp" 14 "github.com/prebid/go-gpp/constants" 15 "github.com/prebid/openrtb/v20/openrtb2" 16 "github.com/prebid/prebid-server/v2/config" 17 "github.com/prebid/prebid-server/v2/errortypes" 18 "github.com/prebid/prebid-server/v2/firstpartydata" 19 "github.com/prebid/prebid-server/v2/gdpr" 20 "github.com/prebid/prebid-server/v2/metrics" 21 "github.com/prebid/prebid-server/v2/openrtb_ext" 22 "github.com/prebid/prebid-server/v2/privacy" 23 "github.com/prebid/prebid-server/v2/util/jsonutil" 24 "github.com/prebid/prebid-server/v2/util/ptrutil" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/mock" 27 "github.com/stretchr/testify/require" 28 ) 29 30 const deviceUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" 31 32 // permissionsMock mocks the Permissions interface for tests 33 type permissionsMock struct { 34 allowAllBidders bool 35 allowedBidders []openrtb_ext.BidderName 36 passGeo bool 37 passID bool 38 activitiesError error 39 } 40 41 func (p *permissionsMock) HostCookiesAllowed(ctx context.Context) (bool, error) { 42 return true, nil 43 } 44 45 func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { 46 return true, nil 47 } 48 49 func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (gdpr.AuctionPermissions, error) { 50 permissions := gdpr.AuctionPermissions{ 51 PassGeo: p.passGeo, 52 PassID: p.passID, 53 } 54 55 if p.allowAllBidders { 56 permissions.AllowBidRequest = true 57 return permissions, p.activitiesError 58 } 59 60 for _, allowedBidder := range p.allowedBidders { 61 if bidder == allowedBidder { 62 permissions.AllowBidRequest = true 63 } 64 } 65 66 return permissions, p.activitiesError 67 } 68 69 type fakePermissionsBuilder struct { 70 permissions gdpr.Permissions 71 } 72 73 func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions { 74 return fpb.permissions 75 } 76 77 func assertReq(t *testing.T, bidderRequests []BidderRequest, 78 applyCOPPA bool, consentedVendors map[string]bool) { 79 // assert individual bidder requests 80 assert.NotEqual(t, bidderRequests, 0, "cleanOpenRTBRequest should split request into individual bidder requests") 81 82 // assert for PI data 83 for _, req := range bidderRequests { 84 if !applyCOPPA && consentedVendors[req.BidderName.String()] { 85 assert.NotEqual(t, req.BidRequest.User.BuyerUID, "", "cleanOpenRTBRequest shouldn't clean PI data as per COPPA or for a consented vendor as per GDPR or per CCPA") 86 assert.NotEqual(t, req.BidRequest.Device.DIDMD5, "", "cleanOpenRTBRequest shouldn't clean PI data as per COPPA or for a consented vendor as per GDPR or per CCPA") 87 } else { 88 assert.Equal(t, req.BidRequest.User.BuyerUID, "", "cleanOpenRTBRequest should clean PI data as per COPPA or for a non-consented vendor as per GDPR or per CCPA", req.BidderName.String()) 89 assert.Equal(t, req.BidRequest.Device.DIDMD5, "", "cleanOpenRTBRequest should clean PI data as per COPPA or for a non-consented vendor as per GDPR or per CCPA", req.BidderName.String()) 90 } 91 } 92 } 93 94 func TestSplitImps(t *testing.T) { 95 testCases := []struct { 96 description string 97 givenImps []openrtb2.Imp 98 expectedImps map[string][]openrtb2.Imp 99 expectedError string 100 }{ 101 { 102 description: "Nil", 103 givenImps: nil, 104 expectedImps: map[string][]openrtb2.Imp{}, 105 expectedError: "", 106 }, 107 { 108 description: "Empty", 109 givenImps: []openrtb2.Imp{}, 110 expectedImps: map[string][]openrtb2.Imp{}, 111 expectedError: "", 112 }, 113 { 114 description: "1 Imp, 1 Bidder", 115 givenImps: []openrtb2.Imp{ 116 {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"}}}}`)}, 117 }, 118 expectedImps: map[string][]openrtb2.Imp{ 119 "bidderA": { 120 {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, 121 }, 122 }, 123 expectedError: "", 124 }, 125 { 126 description: "1 Imp, 2 Bidders", 127 givenImps: []openrtb2.Imp{ 128 {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"},"bidderB":{"imp1ParamB":"imp1ValueB"}}}}`)}, 129 }, 130 expectedImps: map[string][]openrtb2.Imp{ 131 "bidderA": { 132 {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, 133 }, 134 "bidderB": { 135 {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamB":"imp1ValueB"}}`)}, 136 }, 137 }, 138 expectedError: "", 139 }, 140 { 141 description: "2 Imps, 1 Bidders Each", 142 givenImps: []openrtb2.Imp{ 143 {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1ParamA":"imp1ValueA"}}}}`)}, 144 {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2ParamA":"imp2ValueA"}}}}`)}, 145 }, 146 expectedImps: map[string][]openrtb2.Imp{ 147 "bidderA": { 148 {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1ParamA":"imp1ValueA"}}`)}, 149 {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2ParamA":"imp2ValueA"}}`)}, 150 }, 151 }, 152 expectedError: "", 153 }, 154 { 155 description: "2 Imps, 2 Bidders Each", 156 givenImps: []openrtb2.Imp{ 157 {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}}}`)}, 158 {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}}}`)}, 159 }, 160 expectedImps: map[string][]openrtb2.Imp{ 161 "bidderA": { 162 {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"}}`)}, 163 {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"}}`)}, 164 }, 165 "bidderB": { 166 {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"}}`)}, 167 {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"}}`)}, 168 }, 169 }, 170 expectedError: "", 171 }, 172 { 173 // This is a "happy path" integration test. Functionality is covered in detail by TestCreateSanitizedImpExt. 174 description: "Other Fields - 2 Imps, 2 Bidders Each", 175 givenImps: []openrtb2.Imp{ 176 {ID: "imp1", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp1paramA":"imp1valueA"},"bidderB":{"imp1paramB":"imp1valueB"}}},"skadn":"imp1SkAdN"}`)}, 177 {ID: "imp2", Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"imp2paramA":"imp2valueA"},"bidderB":{"imp2paramB":"imp2valueB"}}},"skadn":"imp2SkAdN"}`)}, 178 }, 179 expectedImps: map[string][]openrtb2.Imp{ 180 "bidderA": { 181 {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramA":"imp1valueA"},"skadn":"imp1SkAdN"}`)}, 182 {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramA":"imp2valueA"},"skadn":"imp2SkAdN"}`)}, 183 }, 184 "bidderB": { 185 {ID: "imp1", Ext: json.RawMessage(`{"bidder":{"imp1paramB":"imp1valueB"},"skadn":"imp1SkAdN"}`)}, 186 {ID: "imp2", Ext: json.RawMessage(`{"bidder":{"imp2paramB":"imp2valueB"},"skadn":"imp2SkAdN"}`)}, 187 }, 188 }, 189 expectedError: "", 190 }, 191 { 192 description: "Malformed imp.ext", 193 givenImps: []openrtb2.Imp{ 194 {ID: "imp1", Ext: json.RawMessage(`malformed`)}, 195 }, 196 expectedError: "invalid json for imp[0]: expect { or n, but found m", 197 }, 198 { 199 description: "Malformed imp.ext.prebid", 200 givenImps: []openrtb2.Imp{ 201 {ID: "imp1", Ext: json.RawMessage(`{"prebid": malformed}`)}, 202 }, 203 expectedError: "invalid json for imp[0]: do not know how to skip: 109", 204 }, 205 { 206 description: "Malformed imp.ext.prebid.bidder", 207 givenImps: []openrtb2.Imp{ 208 {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": malformed}}`)}, 209 }, 210 expectedError: "invalid json for imp[0]: do not know how to skip: 109", 211 }, 212 } 213 214 for _, test := range testCases { 215 imps, err := splitImps(test.givenImps) 216 217 if test.expectedError == "" { 218 assert.NoError(t, err, test.description+":err") 219 } else { 220 assert.EqualError(t, err, test.expectedError, test.description+":err") 221 } 222 223 assert.Equal(t, test.expectedImps, imps, test.description+":imps") 224 } 225 } 226 227 func TestCreateSanitizedImpExt(t *testing.T) { 228 testCases := []struct { 229 description string 230 givenImpExt map[string]json.RawMessage 231 givenImpExtPrebid map[string]json.RawMessage 232 expected map[string]json.RawMessage 233 expectedError string 234 }{ 235 { 236 description: "Nil", 237 givenImpExt: nil, 238 givenImpExtPrebid: nil, 239 expected: map[string]json.RawMessage{}, 240 expectedError: "", 241 }, 242 { 243 description: "Empty", 244 givenImpExt: map[string]json.RawMessage{}, 245 givenImpExtPrebid: map[string]json.RawMessage{}, 246 expected: map[string]json.RawMessage{}, 247 expectedError: "", 248 }, 249 { 250 description: "imp.ext.prebid - Bidder Only", 251 givenImpExt: map[string]json.RawMessage{ 252 "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), 253 "data": json.RawMessage(`"anyData"`), 254 "context": json.RawMessage(`"anyContext"`), 255 "skadn": json.RawMessage(`"anySKAdNetwork"`), 256 "gpid": json.RawMessage(`"anyGPID"`), 257 "tid": json.RawMessage(`"anyTID"`), 258 }, 259 givenImpExtPrebid: map[string]json.RawMessage{ 260 "bidder": json.RawMessage(`"anyBidder"`), 261 }, 262 expected: map[string]json.RawMessage{ 263 "data": json.RawMessage(`"anyData"`), 264 "context": json.RawMessage(`"anyContext"`), 265 "skadn": json.RawMessage(`"anySKAdNetwork"`), 266 "gpid": json.RawMessage(`"anyGPID"`), 267 "tid": json.RawMessage(`"anyTID"`), 268 }, 269 expectedError: "", 270 }, 271 { 272 description: "imp.ext.prebid - Bidder + Other Forbidden Value", 273 givenImpExt: map[string]json.RawMessage{ 274 "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), 275 "data": json.RawMessage(`"anyData"`), 276 "context": json.RawMessage(`"anyContext"`), 277 "skadn": json.RawMessage(`"anySKAdNetwork"`), 278 "gpid": json.RawMessage(`"anyGPID"`), 279 "tid": json.RawMessage(`"anyTID"`), 280 }, 281 givenImpExtPrebid: map[string]json.RawMessage{ 282 "bidder": json.RawMessage(`"anyBidder"`), 283 "forbidden": json.RawMessage(`"anyValue"`), 284 }, 285 expected: map[string]json.RawMessage{ 286 "data": json.RawMessage(`"anyData"`), 287 "context": json.RawMessage(`"anyContext"`), 288 "skadn": json.RawMessage(`"anySKAdNetwork"`), 289 "gpid": json.RawMessage(`"anyGPID"`), 290 "tid": json.RawMessage(`"anyTID"`), 291 }, 292 expectedError: "", 293 }, 294 { 295 description: "imp.ext.prebid - Bidder + Other Allowed Values", 296 givenImpExt: map[string]json.RawMessage{ 297 "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), 298 "data": json.RawMessage(`"anyData"`), 299 "context": json.RawMessage(`"anyContext"`), 300 "skadn": json.RawMessage(`"anySKAdNetwork"`), 301 "gpid": json.RawMessage(`"anyGPID"`), 302 "tid": json.RawMessage(`"anyTID"`), 303 }, 304 givenImpExtPrebid: map[string]json.RawMessage{ 305 "bidder": json.RawMessage(`"anyBidder"`), 306 "is_rewarded_inventory": json.RawMessage(`"anyIsRewardedInventory"`), 307 "options": json.RawMessage(`"anyOptions"`), 308 }, 309 expected: map[string]json.RawMessage{ 310 "prebid": json.RawMessage(`{"is_rewarded_inventory":"anyIsRewardedInventory","options":"anyOptions"}`), 311 "data": json.RawMessage(`"anyData"`), 312 "context": json.RawMessage(`"anyContext"`), 313 "skadn": json.RawMessage(`"anySKAdNetwork"`), 314 "gpid": json.RawMessage(`"anyGPID"`), 315 "tid": json.RawMessage(`"anyTID"`), 316 }, 317 expectedError: "", 318 }, 319 { 320 description: "imp.ext", 321 givenImpExt: map[string]json.RawMessage{ 322 "anyBidder": json.RawMessage(`"anyBidderValues"`), 323 "data": json.RawMessage(`"anyData"`), 324 "context": json.RawMessage(`"anyContext"`), 325 "skadn": json.RawMessage(`"anySKAdNetwork"`), 326 "gpid": json.RawMessage(`"anyGPID"`), 327 "tid": json.RawMessage(`"anyTID"`), 328 }, 329 givenImpExtPrebid: map[string]json.RawMessage{}, 330 expected: map[string]json.RawMessage{ 331 "data": json.RawMessage(`"anyData"`), 332 "context": json.RawMessage(`"anyContext"`), 333 "skadn": json.RawMessage(`"anySKAdNetwork"`), 334 "gpid": json.RawMessage(`"anyGPID"`), 335 "tid": json.RawMessage(`"anyTID"`), 336 }, 337 expectedError: "", 338 }, 339 { 340 description: "imp.ext + imp.ext.prebid - Prebid Bidder Only", 341 givenImpExt: map[string]json.RawMessage{ 342 "anyBidder": json.RawMessage(`"anyBidderValues"`), 343 "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), 344 "data": json.RawMessage(`"anyData"`), 345 "context": json.RawMessage(`"anyContext"`), 346 "skadn": json.RawMessage(`"anySKAdNetwork"`), 347 "gpid": json.RawMessage(`"anyGPID"`), 348 "tid": json.RawMessage(`"anyTID"`), 349 }, 350 givenImpExtPrebid: map[string]json.RawMessage{ 351 "bidder": json.RawMessage(`"anyBidder"`), 352 }, 353 expected: map[string]json.RawMessage{ 354 "data": json.RawMessage(`"anyData"`), 355 "context": json.RawMessage(`"anyContext"`), 356 "skadn": json.RawMessage(`"anySKAdNetwork"`), 357 "gpid": json.RawMessage(`"anyGPID"`), 358 "tid": json.RawMessage(`"anyTID"`), 359 }, 360 expectedError: "", 361 }, 362 { 363 description: "imp.ext + imp.ext.prebid - Prebid Bidder + Other Forbidden Value", 364 givenImpExt: map[string]json.RawMessage{ 365 "anyBidder": json.RawMessage(`"anyBidderValues"`), 366 "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), 367 "data": json.RawMessage(`"anyData"`), 368 "context": json.RawMessage(`"anyContext"`), 369 "skadn": json.RawMessage(`"anySKAdNetwork"`), 370 "gpid": json.RawMessage(`"anyGPID"`), 371 "tid": json.RawMessage(`"anyTID"`), 372 }, 373 givenImpExtPrebid: map[string]json.RawMessage{ 374 "bidder": json.RawMessage(`"anyBidder"`), 375 "forbidden": json.RawMessage(`"anyValue"`), 376 }, 377 expected: map[string]json.RawMessage{ 378 "data": json.RawMessage(`"anyData"`), 379 "context": json.RawMessage(`"anyContext"`), 380 "skadn": json.RawMessage(`"anySKAdNetwork"`), 381 "gpid": json.RawMessage(`"anyGPID"`), 382 "tid": json.RawMessage(`"anyTID"`), 383 }, 384 expectedError: "", 385 }, 386 { 387 description: "imp.ext + imp.ext.prebid - Prebid Bidder + Other Allowed Values", 388 givenImpExt: map[string]json.RawMessage{ 389 "anyBidder": json.RawMessage(`"anyBidderValues"`), 390 "prebid": json.RawMessage(`"ignoredInFavorOfSeparatelyUnmarshalledImpExtPrebid"`), 391 "data": json.RawMessage(`"anyData"`), 392 "context": json.RawMessage(`"anyContext"`), 393 "skadn": json.RawMessage(`"anySKAdNetwork"`), 394 "gpid": json.RawMessage(`"anyGPID"`), 395 "tid": json.RawMessage(`"anyTID"`), 396 }, 397 givenImpExtPrebid: map[string]json.RawMessage{ 398 "bidder": json.RawMessage(`"anyBidder"`), 399 "is_rewarded_inventory": json.RawMessage(`"anyIsRewardedInventory"`), 400 "options": json.RawMessage(`"anyOptions"`), 401 }, 402 expected: map[string]json.RawMessage{ 403 "prebid": json.RawMessage(`{"is_rewarded_inventory":"anyIsRewardedInventory","options":"anyOptions"}`), 404 "data": json.RawMessage(`"anyData"`), 405 "context": json.RawMessage(`"anyContext"`), 406 "skadn": json.RawMessage(`"anySKAdNetwork"`), 407 "gpid": json.RawMessage(`"anyGPID"`), 408 "tid": json.RawMessage(`"anyTID"`), 409 }, 410 expectedError: "", 411 }, 412 } 413 414 for _, test := range testCases { 415 result, err := createSanitizedImpExt(test.givenImpExt, test.givenImpExtPrebid) 416 417 if test.expectedError == "" { 418 assert.NoError(t, err, test.description+":err") 419 } else { 420 assert.EqualError(t, err, test.expectedError, test.description+":err") 421 } 422 423 assert.Equal(t, test.expected, result, test.description) 424 } 425 } 426 427 func TestCleanOpenRTBRequests(t *testing.T) { 428 emptyTCF2Config := gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}) 429 430 testCases := []struct { 431 req AuctionRequest 432 bidReqAssertions func(t *testing.T, bidderRequests []BidderRequest, 433 applyCOPPA bool, consentedVendors map[string]bool) 434 hasError bool 435 applyCOPPA bool 436 consentedVendors map[string]bool 437 }{ 438 { 439 req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: getTestBuildRequest(t)}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, 440 bidReqAssertions: assertReq, 441 hasError: false, 442 applyCOPPA: true, 443 consentedVendors: map[string]bool{"appnexus": true}, 444 }, 445 { 446 req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, 447 bidReqAssertions: assertReq, 448 hasError: false, 449 applyCOPPA: false, 450 consentedVendors: map[string]bool{"appnexus": true}, 451 }, 452 } 453 454 privacyConfig := config.Privacy{ 455 CCPA: config.CCPA{ 456 Enforce: true, 457 }, 458 LMT: config.LMT{ 459 Enforce: true, 460 }, 461 } 462 463 for _, test := range testCases { 464 465 gdprPermsBuilder := fakePermissionsBuilder{ 466 permissions: &permissionsMock{ 467 allowAllBidders: true, 468 }, 469 }.Builder 470 471 reqSplitter := &requestSplitter{ 472 bidderToSyncerKey: map[string]string{}, 473 me: &metrics.MetricsEngineMock{}, 474 privacyConfig: privacyConfig, 475 gdprPermsBuilder: gdprPermsBuilder, 476 hostSChainNode: nil, 477 bidderInfo: config.BidderInfos{}, 478 } 479 bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{}) 480 if test.hasError { 481 assert.NotNil(t, err, "Error shouldn't be nil") 482 } else { 483 assert.Nil(t, err, "Err should be nil") 484 test.bidReqAssertions(t, bidderRequests, test.applyCOPPA, test.consentedVendors) 485 } 486 } 487 } 488 489 func TestCleanOpenRTBRequestsWithFPD(t *testing.T) { 490 fpd := make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData) 491 492 apnFpd := firstpartydata.ResolvedFirstPartyData{ 493 Site: &openrtb2.Site{Name: "fpdApnSite"}, 494 App: &openrtb2.App{Name: "fpdApnApp"}, 495 User: &openrtb2.User{Keywords: "fpdApnUser"}, 496 } 497 fpd[openrtb_ext.BidderName("rubicon")] = &apnFpd 498 499 emptyTCF2Config := gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}) 500 501 testCases := []struct { 502 description string 503 req AuctionRequest 504 fpdExpected bool 505 }{ 506 { 507 description: "Pass valid FPD data for bidder not found in the request", 508 req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: getTestBuildRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: fpd, TCF2Config: emptyTCF2Config}, 509 fpdExpected: false, 510 }, 511 { 512 description: "Pass valid FPD data for bidders specified in request", 513 req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: fpd, TCF2Config: emptyTCF2Config}, 514 fpdExpected: true, 515 }, 516 { 517 description: "Bidders specified in request but there is no fpd data for this bidder", 518 req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: make(map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData), TCF2Config: emptyTCF2Config}, 519 fpdExpected: false, 520 }, 521 { 522 description: "No FPD data passed", 523 req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: newAdapterAliasBidRequest(t)}, UserSyncs: &emptyUsersync{}, FirstPartyData: nil, TCF2Config: emptyTCF2Config}, 524 fpdExpected: false, 525 }, 526 } 527 528 for _, test := range testCases { 529 530 gdprPermissionsBuilder := fakePermissionsBuilder{ 531 permissions: &permissionsMock{ 532 allowAllBidders: true, 533 }, 534 }.Builder 535 536 reqSplitter := &requestSplitter{ 537 bidderToSyncerKey: map[string]string{}, 538 me: &metrics.MetricsEngineMock{}, 539 privacyConfig: config.Privacy{}, 540 gdprPermsBuilder: gdprPermissionsBuilder, 541 hostSChainNode: nil, 542 bidderInfo: config.BidderInfos{}, 543 } 544 545 bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{}) 546 assert.Empty(t, err, "No errors should be returned") 547 for _, bidderRequest := range bidderRequests { 548 bidderName := bidderRequest.BidderName 549 if test.fpdExpected { 550 assert.Equal(t, fpd[bidderName].Site.Name, bidderRequest.BidRequest.Site.Name, "Incorrect FPD site name") 551 assert.Equal(t, fpd[bidderName].App.Name, bidderRequest.BidRequest.App.Name, "Incorrect FPD app name") 552 assert.Equal(t, fpd[bidderName].User.Keywords, bidderRequest.BidRequest.User.Keywords, "Incorrect FPD user keywords") 553 assert.Equal(t, test.req.BidRequestWrapper.User.BuyerUID, bidderRequest.BidRequest.User.BuyerUID, "Incorrect FPD user buyerUID") 554 } else { 555 assert.Equal(t, "", bidderRequest.BidRequest.Site.Name, "Incorrect FPD site name") 556 assert.Equal(t, "", bidderRequest.BidRequest.User.Keywords, "Incorrect FPD user keywords") 557 } 558 } 559 } 560 } 561 562 func TestExtractAdapterReqBidderParamsMap(t *testing.T) { 563 tests := []struct { 564 name string 565 givenBidRequest *openrtb2.BidRequest 566 want map[string]json.RawMessage 567 wantErr error 568 }{ 569 { 570 name: "nil req", 571 givenBidRequest: nil, 572 want: nil, 573 wantErr: errors.New("error bidRequest should not be nil"), 574 }, 575 { 576 name: "nil req.ext", 577 givenBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{}}`)}, 578 want: nil, 579 wantErr: nil, 580 }, 581 { 582 name: "malformed req.ext", 583 givenBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage("malformed")}, 584 want: nil, 585 wantErr: errors.New("error decoding Request.ext : expect { or n, but found m"), 586 }, 587 { 588 name: "extract bidder params from req.Ext for input request in adapter code", 589 givenBidRequest: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams": {"profile": 1234, "version": 1}}}`)}, 590 want: map[string]json.RawMessage{"profile": json.RawMessage(`1234`), "version": json.RawMessage(`1`)}, 591 wantErr: nil, 592 }, 593 } 594 for _, tt := range tests { 595 t.Run(tt.name, func(t *testing.T) { 596 got, err := ExtractReqExtBidderParamsMap(tt.givenBidRequest) 597 assert.Equal(t, tt.wantErr, err, "err") 598 assert.Equal(t, tt.want, got, "result") 599 }) 600 } 601 } 602 603 func TestCleanOpenRTBRequestsWithBidResponses(t *testing.T) { 604 bidRespId1 := json.RawMessage(`{"id": "resp_id1"}`) 605 bidRespId2 := json.RawMessage(`{"id": "resp_id2"}`) 606 607 testCases := []struct { 608 description string 609 storedBidResponses map[string]map[string]json.RawMessage 610 imps []openrtb2.Imp 611 expectedBidderRequests map[string]BidderRequest 612 }{ 613 { 614 description: "Request with imp with one bidder stored bid response", 615 storedBidResponses: map[string]map[string]json.RawMessage{ 616 "imp-id1": {"bidderA": bidRespId1}, 617 }, 618 imps: []openrtb2.Imp{ 619 { 620 ID: "imp-id1", 621 Video: &openrtb2.Video{ 622 W: ptrutil.ToPtr[int64](300), 623 H: ptrutil.ToPtr[int64](250), 624 }, 625 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 626 }, 627 }, 628 expectedBidderRequests: map[string]BidderRequest{ 629 "bidderA": { 630 BidRequest: &openrtb2.BidRequest{Imp: nil}, 631 BidderName: "bidderA", 632 BidderStoredResponses: map[string]json.RawMessage{ 633 "imp-id1": bidRespId1}, 634 }, 635 }, 636 }, 637 { 638 description: "Request with imps with and without stored bid response for one bidder", 639 storedBidResponses: map[string]map[string]json.RawMessage{ 640 "imp-id1": {"bidderA": bidRespId1}, 641 }, 642 imps: []openrtb2.Imp{ 643 { 644 ID: "imp-id1", 645 Video: &openrtb2.Video{ 646 W: ptrutil.ToPtr[int64](300), 647 H: ptrutil.ToPtr[int64](250), 648 }, 649 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 650 }, 651 { 652 ID: "imp-id2", 653 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 654 }, 655 }, 656 expectedBidderRequests: map[string]BidderRequest{ 657 "bidderA": { 658 BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ 659 {ID: "imp-id2", Ext: json.RawMessage(`{"bidder":{"placementId":"123"}}`)}, 660 }}, 661 BidderName: "bidderA", 662 BidderStoredResponses: map[string]json.RawMessage{ 663 "imp-id1": bidRespId1}, 664 }, 665 }, 666 }, 667 { 668 description: "Request with imp with 2 bidders stored bid response", 669 storedBidResponses: map[string]map[string]json.RawMessage{ 670 "imp-id1": {"bidderA": bidRespId1, "bidderB": bidRespId2}, 671 }, 672 imps: []openrtb2.Imp{ 673 { 674 ID: "imp-id1", 675 Video: &openrtb2.Video{ 676 W: ptrutil.ToPtr[int64](300), 677 H: ptrutil.ToPtr[int64](250), 678 }, 679 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 680 }, 681 }, 682 expectedBidderRequests: map[string]BidderRequest{ 683 "bidderA": { 684 BidRequest: &openrtb2.BidRequest{Imp: nil}, 685 BidderName: "bidderA", 686 BidderStoredResponses: map[string]json.RawMessage{ 687 "imp-id1": bidRespId1, 688 }, 689 }, 690 "bidderB": { 691 BidRequest: &openrtb2.BidRequest{Imp: nil}, 692 BidderName: "bidderB", 693 BidderStoredResponses: map[string]json.RawMessage{ 694 "imp-id1": bidRespId2}, 695 }, 696 }, 697 }, 698 { 699 description: "Request with 2 imps: with 2 bidders stored bid response and imp without stored responses", 700 storedBidResponses: map[string]map[string]json.RawMessage{ 701 "imp-id1": {"bidderA": bidRespId1, "bidderB": bidRespId2}, 702 }, 703 imps: []openrtb2.Imp{ 704 { 705 ID: "imp-id1", 706 Video: &openrtb2.Video{ 707 W: ptrutil.ToPtr[int64](300), 708 H: ptrutil.ToPtr[int64](250), 709 }, 710 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 711 }, 712 { 713 ID: "imp-id2", 714 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 715 }, 716 }, 717 expectedBidderRequests: map[string]BidderRequest{ 718 "bidderA": { 719 BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ 720 {ID: "imp-id2", Ext: json.RawMessage(`{"bidder":{"placementId":"123"}}`)}, 721 }}, 722 BidderName: "bidderA", 723 BidderStoredResponses: map[string]json.RawMessage{ 724 "imp-id1": bidRespId1}, 725 }, 726 "bidderB": { 727 BidRequest: &openrtb2.BidRequest{Imp: nil}, 728 BidderName: "bidderB", 729 BidderStoredResponses: map[string]json.RawMessage{ 730 "imp-id1": bidRespId2}, 731 }, 732 }, 733 }, 734 { 735 description: "Request with 3 imps: with 2 bidders stored bid response and 2 imps without stored responses", 736 storedBidResponses: map[string]map[string]json.RawMessage{ 737 "imp-id1": {"bidderA": bidRespId1, "bidderB": bidRespId2}, 738 }, 739 imps: []openrtb2.Imp{ 740 { 741 ID: "imp-id3", 742 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderC":{"placementId":"1234"}}}}`), 743 }, 744 { 745 ID: "imp-id1", 746 Video: &openrtb2.Video{ 747 W: ptrutil.ToPtr[int64](300), 748 H: ptrutil.ToPtr[int64](250), 749 }, 750 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 751 }, 752 { 753 ID: "imp-id2", 754 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 755 }, 756 }, 757 expectedBidderRequests: map[string]BidderRequest{ 758 "bidderA": { 759 BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ 760 {ID: "imp-id2", Ext: json.RawMessage(`{"bidder":{"placementId":"123"}}`)}, 761 }}, 762 BidderName: "bidderA", 763 BidderStoredResponses: map[string]json.RawMessage{ 764 "imp-id1": bidRespId1}, 765 }, 766 "bidderB": { 767 BidRequest: &openrtb2.BidRequest{Imp: nil}, 768 BidderName: "bidderB", 769 BidderStoredResponses: map[string]json.RawMessage{ 770 "imp-id1": bidRespId2}, 771 }, 772 "bidderC": { 773 BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ 774 {ID: "imp-id3", Ext: json.RawMessage(`{"bidder":{"placementId":"1234"}}`)}, 775 }}, 776 BidderName: "bidderC", 777 BidderStoredResponses: nil, 778 }, 779 }, 780 }, 781 { 782 description: "Request with 2 imps: with 1 bidders stored bid response and imp without stored responses and with the same bidder", 783 storedBidResponses: map[string]map[string]json.RawMessage{ 784 "imp-id2": {"bidderA": bidRespId2}, 785 }, 786 imps: []openrtb2.Imp{ 787 { 788 ID: "imp-id1", 789 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 790 }, 791 { 792 ID: "imp-id2", 793 Ext: json.RawMessage(`{"prebid":{"bidder":{"bidderA":{"placementId":"123"}}}}`), 794 }, 795 }, 796 expectedBidderRequests: map[string]BidderRequest{ 797 "bidderA": { 798 BidRequest: &openrtb2.BidRequest{Imp: []openrtb2.Imp{ 799 {ID: "imp-id1", Ext: json.RawMessage(`{"bidder":{"placementId":"123"}}`)}, 800 }}, 801 BidderName: "bidderA", 802 BidderStoredResponses: map[string]json.RawMessage{ 803 "imp-id2": bidRespId2}, 804 }, 805 }, 806 }, 807 { 808 description: "Request with 2 imps with stored responses and with the same bidder", 809 storedBidResponses: map[string]map[string]json.RawMessage{ 810 "imp-id1": {"bidderA": bidRespId1}, 811 "imp-id2": {"bidderA": bidRespId2}, 812 }, 813 imps: []openrtb2.Imp{ 814 { 815 ID: "imp-id1", 816 Ext: json.RawMessage(`"prebid": {}`), 817 }, 818 { 819 ID: "imp-id2", 820 Ext: json.RawMessage(`"prebid": {}`), 821 }, 822 }, 823 expectedBidderRequests: map[string]BidderRequest{ 824 "bidderA": { 825 BidRequest: &openrtb2.BidRequest{Imp: nil}, 826 BidderName: "bidderA", 827 BidderStoredResponses: map[string]json.RawMessage{ 828 "imp-id1": bidRespId1, 829 "imp-id2": bidRespId2, 830 }, 831 }, 832 }, 833 }, 834 } 835 836 for _, test := range testCases { 837 838 gdprPermissionsBuilder := fakePermissionsBuilder{ 839 permissions: &permissionsMock{ 840 allowAllBidders: true, 841 }, 842 }.Builder 843 844 auctionReq := AuctionRequest{ 845 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &openrtb2.BidRequest{Imp: test.imps}}, 846 UserSyncs: &emptyUsersync{}, 847 StoredBidResponses: test.storedBidResponses, 848 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 849 } 850 851 reqSplitter := &requestSplitter{ 852 bidderToSyncerKey: map[string]string{}, 853 me: &metrics.MetricsEngineMock{}, 854 privacyConfig: config.Privacy{}, 855 gdprPermsBuilder: gdprPermissionsBuilder, 856 hostSChainNode: nil, 857 bidderInfo: config.BidderInfos{}, 858 } 859 860 actualBidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) 861 assert.Empty(t, err, "No errors should be returned") 862 assert.Len(t, actualBidderRequests, len(test.expectedBidderRequests), "result len doesn't match for testCase %s", test.description) 863 for _, actualBidderRequest := range actualBidderRequests { 864 bidderName := string(actualBidderRequest.BidderName) 865 assert.Equal(t, test.expectedBidderRequests[bidderName].BidRequest.Imp, actualBidderRequest.BidRequest.Imp, "incorrect Impressions for testCase %s", test.description) 866 assert.Equal(t, test.expectedBidderRequests[bidderName].BidderStoredResponses, actualBidderRequest.BidderStoredResponses, "incorrect Bidder Stored Responses for testCase %s", test.description) 867 } 868 } 869 } 870 871 func TestCleanOpenRTBRequestsCCPA(t *testing.T) { 872 trueValue, falseValue := true, false 873 874 testCases := []struct { 875 description string 876 reqExt json.RawMessage 877 ccpaConsent string 878 ccpaHostEnabled bool 879 ccpaAccountEnabled *bool 880 expectDataScrub bool 881 expectPrivacyLabels metrics.PrivacyLabels 882 }{ 883 { 884 description: "Feature Flags Enabled - Opt Out", 885 ccpaConsent: "1-Y-", 886 ccpaHostEnabled: true, 887 ccpaAccountEnabled: &trueValue, 888 expectDataScrub: true, 889 expectPrivacyLabels: metrics.PrivacyLabels{ 890 CCPAProvided: true, 891 CCPAEnforced: true, 892 }, 893 }, 894 { 895 description: "Feature Flags Enabled - Opt In", 896 ccpaConsent: "1-N-", 897 ccpaHostEnabled: true, 898 ccpaAccountEnabled: &trueValue, 899 expectDataScrub: false, 900 expectPrivacyLabels: metrics.PrivacyLabels{ 901 CCPAProvided: true, 902 CCPAEnforced: false, 903 }, 904 }, 905 { 906 description: "Feature Flags Enabled - No Sale Star - Doesn't Scrub", 907 reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), 908 ccpaConsent: "1-Y-", 909 ccpaHostEnabled: true, 910 ccpaAccountEnabled: &trueValue, 911 expectDataScrub: false, 912 expectPrivacyLabels: metrics.PrivacyLabels{ 913 CCPAProvided: true, 914 CCPAEnforced: false, 915 }, 916 }, 917 { 918 description: "Feature Flags Enabled - No Sale Specific Bidder - Doesn't Scrub", 919 reqExt: json.RawMessage(`{"prebid":{"nosale":["appnexus"]}}`), 920 ccpaConsent: "1-Y-", 921 ccpaHostEnabled: true, 922 ccpaAccountEnabled: &trueValue, 923 expectDataScrub: false, 924 expectPrivacyLabels: metrics.PrivacyLabels{ 925 CCPAProvided: true, 926 CCPAEnforced: true, 927 }, 928 }, 929 { 930 description: "Feature Flags Enabled - No Sale Different Bidder - Scrubs", 931 reqExt: json.RawMessage(`{"prebid":{"nosale":["rubicon"]}}`), 932 ccpaConsent: "1-Y-", 933 ccpaHostEnabled: true, 934 ccpaAccountEnabled: &trueValue, 935 expectDataScrub: true, 936 expectPrivacyLabels: metrics.PrivacyLabels{ 937 CCPAProvided: true, 938 CCPAEnforced: true, 939 }, 940 }, 941 { 942 description: "Feature flags Account CCPA enabled, host CCPA disregarded - Opt Out", 943 ccpaConsent: "1-Y-", 944 ccpaHostEnabled: false, 945 ccpaAccountEnabled: &trueValue, 946 expectDataScrub: true, 947 expectPrivacyLabels: metrics.PrivacyLabels{ 948 CCPAProvided: true, 949 CCPAEnforced: true, 950 }, 951 }, 952 { 953 description: "Feature flags Account CCPA disabled, host CCPA disregarded", 954 ccpaConsent: "1-Y-", 955 ccpaHostEnabled: true, 956 ccpaAccountEnabled: &falseValue, 957 expectDataScrub: false, 958 expectPrivacyLabels: metrics.PrivacyLabels{ 959 CCPAProvided: true, 960 CCPAEnforced: false, 961 }, 962 }, 963 { 964 description: "Feature flags Account CCPA not specified, host CCPA enabled - Opt Out", 965 ccpaConsent: "1-Y-", 966 ccpaHostEnabled: true, 967 ccpaAccountEnabled: nil, 968 expectDataScrub: true, 969 expectPrivacyLabels: metrics.PrivacyLabels{ 970 CCPAProvided: true, 971 CCPAEnforced: true, 972 }, 973 }, 974 { 975 description: "Feature flags Account CCPA not specified, host CCPA disabled", 976 ccpaConsent: "1-Y-", 977 ccpaHostEnabled: false, 978 ccpaAccountEnabled: nil, 979 expectDataScrub: false, 980 expectPrivacyLabels: metrics.PrivacyLabels{ 981 CCPAProvided: true, 982 CCPAEnforced: false, 983 }, 984 }, 985 } 986 987 for _, test := range testCases { 988 req := newBidRequest(t) 989 req.Ext = test.reqExt 990 req.Regs = &openrtb2.Regs{ 991 Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`), 992 } 993 994 privacyConfig := config.Privacy{ 995 CCPA: config.CCPA{ 996 Enforce: test.ccpaHostEnabled, 997 }, 998 } 999 1000 accountConfig := config.Account{ 1001 CCPA: config.AccountCCPA{ 1002 Enabled: test.ccpaAccountEnabled, 1003 }, 1004 } 1005 1006 auctionReq := AuctionRequest{ 1007 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 1008 UserSyncs: &emptyUsersync{}, 1009 Account: accountConfig, 1010 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, accountConfig.GDPR), 1011 } 1012 1013 gdprPermissionsBuilder := fakePermissionsBuilder{ 1014 permissions: &permissionsMock{ 1015 allowAllBidders: true, 1016 }, 1017 }.Builder 1018 1019 metricsMock := metrics.MetricsEngineMock{} 1020 metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return() 1021 1022 bidderToSyncerKey := map[string]string{} 1023 reqSplitter := &requestSplitter{ 1024 bidderToSyncerKey: bidderToSyncerKey, 1025 me: &metricsMock, 1026 privacyConfig: privacyConfig, 1027 gdprPermsBuilder: gdprPermissionsBuilder, 1028 hostSChainNode: nil, 1029 bidderInfo: config.BidderInfos{}, 1030 } 1031 1032 bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) 1033 result := bidderRequests[0] 1034 1035 assert.Nil(t, errs) 1036 if test.expectDataScrub { 1037 assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") 1038 assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") 1039 metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) 1040 } else { 1041 assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") 1042 assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") 1043 metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) 1044 } 1045 assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") 1046 } 1047 } 1048 1049 func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { 1050 testCases := []struct { 1051 description string 1052 reqExt json.RawMessage 1053 reqRegsExt json.RawMessage 1054 expectError error 1055 }{ 1056 { 1057 description: "Invalid Consent", 1058 reqExt: json.RawMessage(`{"prebid":{"nosale":["*"]}}`), 1059 reqRegsExt: json.RawMessage(`{"us_privacy":"malformed"}`), 1060 expectError: &errortypes.Warning{ 1061 Message: "request.regs.ext.us_privacy must contain 4 characters", 1062 WarningCode: errortypes.InvalidPrivacyConsentWarningCode, 1063 }, 1064 }, 1065 { 1066 description: "Invalid No Sale Bidders", 1067 reqExt: json.RawMessage(`{"prebid":{"nosale":["*", "another"]}}`), 1068 reqRegsExt: json.RawMessage(`{"us_privacy":"1NYN"}`), 1069 expectError: errors.New("request.ext.prebid.nosale is invalid: can only specify all bidders if no other bidders are provided"), 1070 }, 1071 } 1072 1073 for _, test := range testCases { 1074 req := newBidRequest(t) 1075 req.Ext = test.reqExt 1076 req.Regs = &openrtb2.Regs{Ext: test.reqRegsExt} 1077 1078 var reqExtStruct openrtb_ext.ExtRequest 1079 err := jsonutil.UnmarshalValid(req.Ext, &reqExtStruct) 1080 assert.NoError(t, err, test.description+":marshal_ext") 1081 1082 auctionReq := AuctionRequest{ 1083 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 1084 UserSyncs: &emptyUsersync{}, 1085 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 1086 } 1087 1088 gdprPermissionsBuilder := fakePermissionsBuilder{ 1089 permissions: &permissionsMock{ 1090 allowAllBidders: true, 1091 }, 1092 }.Builder 1093 1094 privacyConfig := config.Privacy{ 1095 CCPA: config.CCPA{ 1096 Enforce: true, 1097 }, 1098 } 1099 bidderToSyncerKey := map[string]string{} 1100 metrics := metrics.MetricsEngineMock{} 1101 1102 reqSplitter := &requestSplitter{ 1103 bidderToSyncerKey: bidderToSyncerKey, 1104 me: &metrics, 1105 privacyConfig: privacyConfig, 1106 gdprPermsBuilder: gdprPermissionsBuilder, 1107 hostSChainNode: nil, 1108 bidderInfo: config.BidderInfos{}, 1109 } 1110 1111 _, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, gdpr.SignalNo, false, map[string]float64{}) 1112 1113 assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) 1114 } 1115 } 1116 1117 func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { 1118 testCases := []struct { 1119 description string 1120 coppa int8 1121 expectDataScrub bool 1122 expectPrivacyLabels metrics.PrivacyLabels 1123 }{ 1124 { 1125 description: "Enabled", 1126 coppa: 1, 1127 expectDataScrub: true, 1128 expectPrivacyLabels: metrics.PrivacyLabels{ 1129 COPPAEnforced: true, 1130 }, 1131 }, 1132 { 1133 description: "Disabled", 1134 coppa: 0, 1135 expectDataScrub: false, 1136 expectPrivacyLabels: metrics.PrivacyLabels{ 1137 COPPAEnforced: false, 1138 }, 1139 }, 1140 } 1141 1142 for _, test := range testCases { 1143 req := newBidRequest(t) 1144 req.Regs = &openrtb2.Regs{COPPA: test.coppa} 1145 1146 auctionReq := AuctionRequest{ 1147 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 1148 UserSyncs: &emptyUsersync{}, 1149 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 1150 } 1151 1152 gdprPermissionsBuilder := fakePermissionsBuilder{ 1153 permissions: &permissionsMock{ 1154 allowAllBidders: true, 1155 }, 1156 }.Builder 1157 1158 bidderToSyncerKey := map[string]string{} 1159 metrics := metrics.MetricsEngineMock{} 1160 1161 reqSplitter := &requestSplitter{ 1162 bidderToSyncerKey: bidderToSyncerKey, 1163 me: &metrics, 1164 privacyConfig: config.Privacy{}, 1165 gdprPermsBuilder: gdprPermissionsBuilder, 1166 hostSChainNode: nil, 1167 bidderInfo: config.BidderInfos{}, 1168 } 1169 1170 bidderRequests, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) 1171 result := bidderRequests[0] 1172 1173 assert.Nil(t, errs) 1174 if test.expectDataScrub { 1175 assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") 1176 assert.Equal(t, result.BidRequest.User.Yob, int64(0), test.description+":User.Yob") 1177 } else { 1178 assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") 1179 assert.NotEqual(t, result.BidRequest.User.Yob, int64(0), test.description+":User.Yob") 1180 } 1181 assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") 1182 } 1183 } 1184 1185 func TestCleanOpenRTBRequestsSChain(t *testing.T) { 1186 const seller1SChain string = `"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}` 1187 const seller2SChain string = `"schain":{"complete":2,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":2}],"ver":"2.0"}` 1188 1189 testCases := []struct { 1190 description string 1191 inExt json.RawMessage 1192 inSourceExt json.RawMessage 1193 outRequestExt json.RawMessage 1194 outSourceExt json.RawMessage 1195 hasError bool 1196 }{ 1197 { 1198 description: "nil", 1199 inExt: nil, 1200 inSourceExt: nil, 1201 outRequestExt: nil, 1202 outSourceExt: nil, 1203 }, 1204 { 1205 description: "ORTB 2.5 chain at source.ext.schain", 1206 inExt: nil, 1207 inSourceExt: json.RawMessage(`{` + seller1SChain + `}`), 1208 outRequestExt: nil, 1209 outSourceExt: json.RawMessage(`{` + seller1SChain + `}`), 1210 }, 1211 { 1212 description: "ORTB 2.5 schain at request.ext.prebid.schains", 1213 inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `}]}}`), 1214 inSourceExt: nil, 1215 outRequestExt: nil, 1216 outSourceExt: json.RawMessage(`{` + seller1SChain + `}`), 1217 }, 1218 { 1219 description: "schainwriter instantation error -- multiple bidder schains in ext.prebid.schains.", 1220 inExt: json.RawMessage(`{"prebid":{"schains":[{"bidders":["appnexus"],` + seller1SChain + `},{"bidders":["appnexus"],` + seller2SChain + `}]}}`), 1221 inSourceExt: json.RawMessage(`{` + seller1SChain + `}`), 1222 outRequestExt: nil, 1223 outSourceExt: nil, 1224 hasError: true, 1225 }, 1226 } 1227 1228 for _, test := range testCases { 1229 req := newBidRequest(t) 1230 if test.inSourceExt != nil { 1231 req.Source.Ext = test.inSourceExt 1232 } 1233 1234 var extRequest *openrtb_ext.ExtRequest 1235 if test.inExt != nil { 1236 req.Ext = test.inExt 1237 extRequest = &openrtb_ext.ExtRequest{} 1238 err := jsonutil.UnmarshalValid(req.Ext, extRequest) 1239 assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") 1240 } 1241 1242 auctionReq := AuctionRequest{ 1243 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 1244 UserSyncs: &emptyUsersync{}, 1245 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 1246 } 1247 1248 gdprPermissionsBuilder := fakePermissionsBuilder{ 1249 permissions: &permissionsMock{ 1250 allowAllBidders: true, 1251 }, 1252 }.Builder 1253 1254 reqSplitter := &requestSplitter{ 1255 bidderToSyncerKey: map[string]string{}, 1256 me: &metrics.MetricsEngineMock{}, 1257 privacyConfig: config.Privacy{}, 1258 gdprPermsBuilder: gdprPermissionsBuilder, 1259 hostSChainNode: nil, 1260 bidderInfo: config.BidderInfos{}, 1261 } 1262 1263 bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) 1264 if test.hasError == true { 1265 assert.NotNil(t, errs) 1266 assert.Len(t, bidderRequests, 0) 1267 } else { 1268 result := bidderRequests[0] 1269 assert.Nil(t, errs) 1270 assert.Equal(t, test.outSourceExt, result.BidRequest.Source.Ext, test.description+":Source.Ext") 1271 assert.Equal(t, test.outRequestExt, result.BidRequest.Ext, test.description+":Ext") 1272 } 1273 } 1274 } 1275 1276 func TestCleanOpenRTBRequestsBidderParams(t *testing.T) { 1277 testCases := []struct { 1278 description string 1279 inExt json.RawMessage 1280 expectedExt map[string]json.RawMessage 1281 hasError bool 1282 }{ 1283 { 1284 description: "Nil Bidder params", 1285 inExt: nil, 1286 expectedExt: getExpectedReqExt(true, false, false), 1287 hasError: false, 1288 }, 1289 { 1290 description: "Bidder params for single partner", 1291 inExt: json.RawMessage(`{"prebid":{"bidderparams":{"pubmatic":{"profile":1234,"version":2}}}}`), 1292 expectedExt: getExpectedReqExt(false, true, false), 1293 hasError: false, 1294 }, 1295 { 1296 description: "Bidder params for two partners", 1297 inExt: json.RawMessage(`{"prebid":{"bidderparams":{"pubmatic":{"profile":1234,"version":2},"appnexus":{"key1":123,"key2":{"innerKey1":"innerValue1"}}}}}`), 1298 expectedExt: getExpectedReqExt(false, true, true), 1299 hasError: false, 1300 }, 1301 } 1302 1303 for _, test := range testCases { 1304 req := newBidRequestWithBidderParams(t) 1305 var extRequest *openrtb_ext.ExtRequest 1306 if test.inExt != nil { 1307 req.Ext = test.inExt 1308 extRequest = &openrtb_ext.ExtRequest{} 1309 err := jsonutil.UnmarshalValid(req.Ext, extRequest) 1310 assert.NoErrorf(t, err, test.description+":Error unmarshaling inExt") 1311 } 1312 1313 auctionReq := AuctionRequest{ 1314 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 1315 UserSyncs: &emptyUsersync{}, 1316 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 1317 } 1318 1319 gdprPermissionsBuilder := fakePermissionsBuilder{ 1320 permissions: &permissionsMock{ 1321 allowAllBidders: true, 1322 }, 1323 }.Builder 1324 1325 reqSplitter := &requestSplitter{ 1326 bidderToSyncerKey: map[string]string{}, 1327 me: &metrics.MetricsEngineMock{}, 1328 privacyConfig: config.Privacy{}, 1329 gdprPermsBuilder: gdprPermissionsBuilder, 1330 hostSChainNode: nil, 1331 bidderInfo: config.BidderInfos{}, 1332 } 1333 1334 bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) 1335 if test.hasError == true { 1336 assert.NotNil(t, errs) 1337 assert.Len(t, bidderRequests, 0) 1338 } else { 1339 assert.Nil(t, errs) 1340 for _, r := range bidderRequests { 1341 expected := test.expectedExt[r.BidderName.String()] 1342 actual := r.BidRequest.Ext 1343 assert.Equal(t, expected, actual, test.description+" Req:Ext.Prebid.BidderParams") 1344 } 1345 } 1346 } 1347 } 1348 1349 func getExpectedReqExt(nilExt, includePubmaticParams, includeAppnexusParams bool) map[string]json.RawMessage { 1350 bidderParamsMap := make(map[string]json.RawMessage) 1351 1352 if nilExt { 1353 bidderParamsMap["pubmatic"] = nil 1354 bidderParamsMap["appnexus"] = nil 1355 return bidderParamsMap 1356 } 1357 1358 if includePubmaticParams { 1359 bidderParamsMap["pubmatic"] = json.RawMessage(`{"prebid":{"bidderparams":{"profile":1234,"version":2}}}`) 1360 } else { 1361 bidderParamsMap["pubmatic"] = nil 1362 } 1363 1364 if includeAppnexusParams { 1365 bidderParamsMap["appnexus"] = json.RawMessage(`{"prebid":{"bidderparams":{"key1":123,"key2":{"innerKey1":"innerValue1"}}}}`) 1366 } else { 1367 bidderParamsMap["appnexus"] = nil 1368 } 1369 1370 return bidderParamsMap 1371 } 1372 1373 func TestGetExtCacheInstructions(t *testing.T) { 1374 var boolFalse, boolTrue *bool = new(bool), new(bool) 1375 *boolFalse = false 1376 *boolTrue = true 1377 1378 testCases := []struct { 1379 desc string 1380 requestExtPrebid *openrtb_ext.ExtRequestPrebid 1381 outCacheInstructions extCacheInstructions 1382 }{ 1383 { 1384 desc: "Nil request ext, all cache flags false except for returnCreative that defaults to true", 1385 requestExtPrebid: nil, 1386 outCacheInstructions: extCacheInstructions{ 1387 cacheBids: false, 1388 cacheVAST: false, 1389 returnCreative: true, 1390 }, 1391 }, 1392 { 1393 desc: "Non-nil request ext, nil Cache field, all cache flags false except for returnCreative that defaults to true", 1394 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1395 Cache: nil, 1396 }, 1397 outCacheInstructions: extCacheInstructions{ 1398 cacheBids: false, 1399 cacheVAST: false, 1400 returnCreative: true, 1401 }, 1402 }, 1403 { 1404 desc: "Non-nil Cache field, both ExtRequestPrebidCacheBids and ExtRequestPrebidCacheVAST nil returnCreative that defaults to true", 1405 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1406 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1407 Bids: nil, 1408 VastXML: nil, 1409 }, 1410 }, 1411 outCacheInstructions: extCacheInstructions{ 1412 cacheBids: false, 1413 cacheVAST: false, 1414 returnCreative: true, 1415 }, 1416 }, 1417 { 1418 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheVAST with unspecified ReturnCreative field, cacheVAST = true and returnCreative defaults to true", 1419 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1420 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1421 Bids: nil, 1422 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{}, 1423 }, 1424 }, 1425 outCacheInstructions: extCacheInstructions{ 1426 cacheBids: false, 1427 cacheVAST: true, 1428 returnCreative: true, // default value 1429 }, 1430 }, 1431 { 1432 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheVAST where ReturnCreative is set to false, cacheVAST = true and returnCreative = false", 1433 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1434 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1435 Bids: nil, 1436 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{ReturnCreative: boolFalse}, 1437 }, 1438 }, 1439 outCacheInstructions: extCacheInstructions{ 1440 cacheBids: false, 1441 cacheVAST: true, 1442 returnCreative: false, 1443 }, 1444 }, 1445 { 1446 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheVAST where ReturnCreative is set to true, cacheVAST = true and returnCreative = true", 1447 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1448 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1449 Bids: nil, 1450 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{ReturnCreative: boolTrue}, 1451 }, 1452 }, 1453 outCacheInstructions: extCacheInstructions{ 1454 cacheBids: false, 1455 cacheVAST: true, 1456 returnCreative: true, 1457 }, 1458 }, 1459 { 1460 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheBids with unspecified ReturnCreative field, cacheBids = true and returnCreative defaults to true", 1461 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1462 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1463 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{}, 1464 VastXML: nil, 1465 }, 1466 }, 1467 outCacheInstructions: extCacheInstructions{ 1468 cacheBids: true, 1469 cacheVAST: false, 1470 returnCreative: true, // default value 1471 }, 1472 }, 1473 { 1474 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheBids where ReturnCreative is set to false, cacheBids = true and returnCreative = false", 1475 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1476 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1477 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{ReturnCreative: boolFalse}, 1478 VastXML: nil, 1479 }, 1480 }, 1481 outCacheInstructions: extCacheInstructions{ 1482 cacheBids: true, 1483 cacheVAST: false, 1484 returnCreative: false, 1485 }, 1486 }, 1487 { 1488 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheBids where ReturnCreative is set to true, cacheBids = true and returnCreative = true", 1489 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1490 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1491 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{ReturnCreative: boolTrue}, 1492 VastXML: nil, 1493 }, 1494 }, 1495 outCacheInstructions: extCacheInstructions{ 1496 cacheBids: true, 1497 cacheVAST: false, 1498 returnCreative: true, 1499 }, 1500 }, 1501 { 1502 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheBids and ExtRequest.Cache.ExtRequestPrebidCacheVAST, neither specify a ReturnCreative field value, all extCacheInstructions fields set to true", 1503 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1504 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1505 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{}, 1506 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{}, 1507 }, 1508 }, 1509 outCacheInstructions: extCacheInstructions{ 1510 cacheBids: true, 1511 cacheVAST: true, 1512 returnCreative: true, 1513 }, 1514 }, 1515 { 1516 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheBids and ExtRequest.Cache.ExtRequestPrebidCacheVAST sets ReturnCreative to true, all extCacheInstructions fields set to true", 1517 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1518 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1519 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{}, 1520 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{ReturnCreative: boolTrue}, 1521 }, 1522 }, 1523 outCacheInstructions: extCacheInstructions{ 1524 cacheBids: true, 1525 cacheVAST: true, 1526 returnCreative: true, 1527 }, 1528 }, 1529 { 1530 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheBids and ExtRequest.Cache.ExtRequestPrebidCacheVAST sets ReturnCreative to false, returnCreative = false", 1531 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1532 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1533 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{}, 1534 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{ReturnCreative: boolFalse}, 1535 }, 1536 }, 1537 outCacheInstructions: extCacheInstructions{ 1538 cacheBids: true, 1539 cacheVAST: true, 1540 returnCreative: false, 1541 }, 1542 }, 1543 { 1544 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheVAST and ExtRequest.Cache.ExtRequestPrebidCacheBids sets ReturnCreative to true, all extCacheInstructions fields set to true", 1545 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1546 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1547 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{ReturnCreative: boolTrue}, 1548 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{}, 1549 }, 1550 }, 1551 outCacheInstructions: extCacheInstructions{ 1552 cacheBids: true, 1553 cacheVAST: true, 1554 returnCreative: true, 1555 }, 1556 }, 1557 { 1558 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheVAST and ExtRequest.Cache.ExtRequestPrebidCacheBids sets ReturnCreative to false, returnCreative = false", 1559 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1560 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1561 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{ReturnCreative: boolFalse}, 1562 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{}, 1563 }, 1564 }, 1565 outCacheInstructions: extCacheInstructions{ 1566 cacheBids: true, 1567 cacheVAST: true, 1568 returnCreative: false, 1569 }, 1570 }, 1571 { 1572 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheVAST and ExtRequest.Cache.ExtRequestPrebidCacheBids set different ReturnCreative values, returnCreative = true because one of them is true", 1573 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1574 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1575 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{ReturnCreative: boolFalse}, 1576 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{ReturnCreative: boolTrue}, 1577 }, 1578 }, 1579 outCacheInstructions: extCacheInstructions{ 1580 cacheBids: true, 1581 cacheVAST: true, 1582 returnCreative: true, 1583 }, 1584 }, 1585 { 1586 desc: "Non-nil ExtRequest.Cache.ExtRequestPrebidCacheVAST and ExtRequest.Cache.ExtRequestPrebidCacheBids set different ReturnCreative values, returnCreative = true because one of them is true", 1587 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1588 Cache: &openrtb_ext.ExtRequestPrebidCache{ 1589 Bids: &openrtb_ext.ExtRequestPrebidCacheBids{ReturnCreative: boolTrue}, 1590 VastXML: &openrtb_ext.ExtRequestPrebidCacheVAST{ReturnCreative: boolFalse}, 1591 }, 1592 }, 1593 outCacheInstructions: extCacheInstructions{ 1594 cacheBids: true, 1595 cacheVAST: true, 1596 returnCreative: true, 1597 }, 1598 }, 1599 } 1600 1601 for _, test := range testCases { 1602 cacheInstructions := getExtCacheInstructions(test.requestExtPrebid) 1603 1604 assert.Equal(t, test.outCacheInstructions.cacheBids, cacheInstructions.cacheBids, "%s. Unexpected shouldCacheBids value. \n", test.desc) 1605 assert.Equal(t, test.outCacheInstructions.cacheVAST, cacheInstructions.cacheVAST, "%s. Unexpected shouldCacheVAST value. \n", test.desc) 1606 assert.Equal(t, test.outCacheInstructions.returnCreative, cacheInstructions.returnCreative, "%s. Unexpected returnCreative value. \n", test.desc) 1607 } 1608 } 1609 1610 func TestGetExtTargetData(t *testing.T) { 1611 type inTest struct { 1612 requestExtPrebid *openrtb_ext.ExtRequestPrebid 1613 cacheInstructions extCacheInstructions 1614 } 1615 type outTest struct { 1616 targetData *targetData 1617 nilTargetData bool 1618 } 1619 testCases := []struct { 1620 desc string 1621 in inTest 1622 out outTest 1623 }{ 1624 { 1625 "nil requestExt, nil outTargetData", 1626 inTest{ 1627 requestExtPrebid: nil, 1628 cacheInstructions: extCacheInstructions{ 1629 cacheBids: true, 1630 cacheVAST: true, 1631 }, 1632 }, 1633 outTest{targetData: nil, nilTargetData: true}, 1634 }, 1635 { 1636 "Valid requestExt, nil Targeting field, nil outTargetData", 1637 inTest{ 1638 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1639 Targeting: nil, 1640 }, 1641 cacheInstructions: extCacheInstructions{ 1642 cacheBids: true, 1643 cacheVAST: true, 1644 }, 1645 }, 1646 outTest{targetData: nil, nilTargetData: true}, 1647 }, 1648 { 1649 "Valid targeting data in requestExt, valid outTargetData", 1650 inTest{ 1651 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{ 1652 Targeting: &openrtb_ext.ExtRequestTargeting{ 1653 PriceGranularity: &openrtb_ext.PriceGranularity{ 1654 Precision: ptrutil.ToPtr(2), 1655 Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, 1656 }, 1657 IncludeWinners: ptrutil.ToPtr(true), 1658 IncludeBidderKeys: ptrutil.ToPtr(true), 1659 }, 1660 }, 1661 cacheInstructions: extCacheInstructions{ 1662 cacheBids: true, 1663 cacheVAST: true, 1664 }, 1665 }, 1666 outTest{ 1667 targetData: &targetData{ 1668 priceGranularity: openrtb_ext.PriceGranularity{ 1669 Precision: ptrutil.ToPtr(2), 1670 Ranges: []openrtb_ext.GranularityRange{{Min: 0.00, Max: 5.00, Increment: 1.00}}, 1671 }, 1672 includeWinners: true, 1673 includeBidderKeys: true, 1674 includeCacheBids: true, 1675 includeCacheVast: true, 1676 }, 1677 nilTargetData: false, 1678 }, 1679 }, 1680 } 1681 1682 for _, test := range testCases { 1683 actualTargetData := getExtTargetData(test.in.requestExtPrebid, test.in.cacheInstructions) 1684 1685 if test.out.nilTargetData { 1686 assert.Nil(t, actualTargetData, "%s. Targeting data should be nil. \n", test.desc) 1687 } else { 1688 assert.NotNil(t, actualTargetData, "%s. Targeting data should NOT be nil. \n", test.desc) 1689 assert.Equal(t, *test.out.targetData, *actualTargetData, "%s. Unexpected targeting data value. \n", test.desc) 1690 } 1691 } 1692 } 1693 1694 func TestParseRequestDebugValues(t *testing.T) { 1695 testCases := []struct { 1696 desc string 1697 givenTest int8 1698 givenExtPrebid *openrtb_ext.ExtRequestPrebid 1699 expected bool 1700 }{ 1701 { 1702 desc: "bid request test == 0, nil requestExt", 1703 givenTest: 0, 1704 givenExtPrebid: nil, 1705 expected: false, 1706 }, 1707 { 1708 desc: "bid request test == 0, requestExt debug flag false", 1709 givenTest: 0, 1710 givenExtPrebid: &openrtb_ext.ExtRequestPrebid{Debug: false}, 1711 expected: false, 1712 }, 1713 { 1714 desc: "bid request test == 1, requestExt debug flag false", 1715 givenTest: 1, 1716 givenExtPrebid: &openrtb_ext.ExtRequestPrebid{Debug: false}, 1717 expected: true, 1718 }, 1719 { 1720 desc: "bid request test == 0, requestExt debug flag true", 1721 givenTest: 0, 1722 givenExtPrebid: &openrtb_ext.ExtRequestPrebid{Debug: true}, 1723 expected: true, 1724 }, 1725 { 1726 desc: "bid request test == 1, requestExt debug flag true", 1727 givenTest: 1, 1728 givenExtPrebid: &openrtb_ext.ExtRequestPrebid{Debug: true}, 1729 expected: true, 1730 }, 1731 } 1732 for _, test := range testCases { 1733 actualDebugInfo := parseRequestDebugValues(test.givenTest, test.givenExtPrebid) 1734 1735 assert.Equal(t, test.expected, actualDebugInfo, "%s. Unexpected debug value. \n", test.desc) 1736 } 1737 } 1738 1739 func TestSetDebugLogValues(t *testing.T) { 1740 1741 type aTest struct { 1742 desc string 1743 inAccountDebugFlag bool 1744 inDebugLog *DebugLog 1745 expectedDebugLog *DebugLog 1746 } 1747 1748 testGroups := []struct { 1749 desc string 1750 testCases []aTest 1751 }{ 1752 1753 { 1754 "nil debug log", 1755 []aTest{ 1756 { 1757 desc: "accountDebugFlag false, expect all false flags in resulting debugLog", 1758 inAccountDebugFlag: false, 1759 inDebugLog: nil, 1760 expectedDebugLog: &DebugLog{}, 1761 }, 1762 { 1763 desc: "accountDebugFlag true, expect debugLog.Enabled to be true", 1764 inAccountDebugFlag: true, 1765 inDebugLog: nil, 1766 expectedDebugLog: &DebugLog{Enabled: true}, 1767 }, 1768 }, 1769 }, 1770 { 1771 "non-nil debug log", 1772 []aTest{ 1773 { 1774 desc: "both accountDebugFlag and DebugEnabledOrOverridden are false, expect debugLog.Enabled to be false", 1775 inAccountDebugFlag: false, 1776 inDebugLog: &DebugLog{}, 1777 expectedDebugLog: &DebugLog{}, 1778 }, 1779 { 1780 desc: "accountDebugFlag false but DebugEnabledOrOverridden is true, expect debugLog.Enabled to be true", 1781 inAccountDebugFlag: false, 1782 inDebugLog: &DebugLog{DebugEnabledOrOverridden: true}, 1783 expectedDebugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, 1784 }, 1785 { 1786 desc: "accountDebugFlag true but DebugEnabledOrOverridden is false, expect debugLog.Enabled to be true", 1787 inAccountDebugFlag: true, 1788 inDebugLog: &DebugLog{}, 1789 expectedDebugLog: &DebugLog{Enabled: true}, 1790 }, 1791 { 1792 desc: "Both accountDebugFlag and DebugEnabledOrOverridden are true, expect debugLog.Enabled to be true", 1793 inAccountDebugFlag: true, 1794 inDebugLog: &DebugLog{DebugEnabledOrOverridden: true}, 1795 expectedDebugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, 1796 }, 1797 }, 1798 }, 1799 } 1800 1801 for _, group := range testGroups { 1802 for _, tc := range group.testCases { 1803 // run 1804 actualDebugLog := setDebugLogValues(tc.inAccountDebugFlag, tc.inDebugLog) 1805 // assertions 1806 assert.Equal(t, tc.expectedDebugLog, actualDebugLog, "%s. %s", group.desc, tc.desc) 1807 } 1808 } 1809 } 1810 1811 func TestGetExtBidAdjustmentFactors(t *testing.T) { 1812 testCases := []struct { 1813 desc string 1814 requestExtPrebid *openrtb_ext.ExtRequestPrebid 1815 outBidAdjustmentFactors map[string]float64 1816 }{ 1817 { 1818 desc: "Nil request ext", 1819 requestExtPrebid: nil, 1820 outBidAdjustmentFactors: nil, 1821 }, 1822 { 1823 desc: "Non-nil request ext, nil BidAdjustmentFactors field", 1824 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: nil}, 1825 outBidAdjustmentFactors: nil, 1826 }, 1827 { 1828 desc: "Non-nil request ext, valid BidAdjustmentFactors field", 1829 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: map[string]float64{"bid-factor": 1.0}}, 1830 outBidAdjustmentFactors: map[string]float64{"bid-factor": 1.0}, 1831 }, 1832 { 1833 desc: "BidAdjustmentFactors contains uppercase bidders, expect case insensitve map returned", 1834 requestExtPrebid: &openrtb_ext.ExtRequestPrebid{BidAdjustmentFactors: map[string]float64{"Bidder": 1.0, "APPNEXUS": 2.0}}, 1835 outBidAdjustmentFactors: map[string]float64{"bidder": 1.0, "appnexus": 2.0}, 1836 }, 1837 } 1838 for _, test := range testCases { 1839 actualBidAdjustmentFactors := getExtBidAdjustmentFactors(test.requestExtPrebid) 1840 1841 assert.Equal(t, test.outBidAdjustmentFactors, actualBidAdjustmentFactors, "%s. Unexpected BidAdjustmentFactors value. \n", test.desc) 1842 } 1843 } 1844 1845 func TestCleanOpenRTBRequestsLMT(t *testing.T) { 1846 var ( 1847 enabled int8 = 1 1848 disabled int8 = 0 1849 ) 1850 testCases := []struct { 1851 description string 1852 lmt *int8 1853 enforceLMT bool 1854 expectDataScrub bool 1855 expectPrivacyLabels metrics.PrivacyLabels 1856 }{ 1857 { 1858 description: "Feature Flag Enabled - OpenTRB Enabled", 1859 lmt: &enabled, 1860 enforceLMT: true, 1861 expectDataScrub: true, 1862 expectPrivacyLabels: metrics.PrivacyLabels{ 1863 LMTEnforced: true, 1864 }, 1865 }, 1866 { 1867 description: "Feature Flag Disabled - OpenTRB Enabled", 1868 lmt: &enabled, 1869 enforceLMT: false, 1870 expectDataScrub: false, 1871 expectPrivacyLabels: metrics.PrivacyLabels{ 1872 LMTEnforced: false, 1873 }, 1874 }, 1875 { 1876 description: "Feature Flag Enabled - OpenTRB Disabled", 1877 lmt: &disabled, 1878 enforceLMT: true, 1879 expectDataScrub: false, 1880 expectPrivacyLabels: metrics.PrivacyLabels{ 1881 LMTEnforced: false, 1882 }, 1883 }, 1884 { 1885 description: "Feature Flag Disabled - OpenTRB Disabled", 1886 lmt: &disabled, 1887 enforceLMT: false, 1888 expectDataScrub: false, 1889 expectPrivacyLabels: metrics.PrivacyLabels{ 1890 LMTEnforced: false, 1891 }, 1892 }, 1893 } 1894 1895 for _, test := range testCases { 1896 req := newBidRequest(t) 1897 req.Device.Lmt = test.lmt 1898 1899 auctionReq := AuctionRequest{ 1900 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 1901 UserSyncs: &emptyUsersync{}, 1902 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 1903 } 1904 1905 gdprPermissionsBuilder := fakePermissionsBuilder{ 1906 permissions: &permissionsMock{ 1907 allowAllBidders: true, 1908 }, 1909 }.Builder 1910 1911 privacyConfig := config.Privacy{ 1912 LMT: config.LMT{ 1913 Enforce: test.enforceLMT, 1914 }, 1915 } 1916 1917 reqSplitter := &requestSplitter{ 1918 bidderToSyncerKey: map[string]string{}, 1919 me: &metrics.MetricsEngineMock{}, 1920 privacyConfig: privacyConfig, 1921 gdprPermsBuilder: gdprPermissionsBuilder, 1922 hostSChainNode: nil, 1923 bidderInfo: config.BidderInfos{}, 1924 } 1925 1926 results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) 1927 result := results[0] 1928 1929 assert.Nil(t, errs) 1930 if test.expectDataScrub { 1931 assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") 1932 assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") 1933 } else { 1934 assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") 1935 assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") 1936 } 1937 assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") 1938 } 1939 } 1940 1941 func TestCleanOpenRTBRequestsGDPR(t *testing.T) { 1942 tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" 1943 1944 testCases := []struct { 1945 description string 1946 gdprConsent string 1947 gdprScrub bool 1948 gdprSignal gdpr.Signal 1949 gdprEnforced bool 1950 permissionsError error 1951 expectPrivacyLabels metrics.PrivacyLabels 1952 expectError bool 1953 }{ 1954 { 1955 description: "enforce no scrub - TCF invalid", 1956 gdprConsent: "malformed", 1957 gdprScrub: false, 1958 gdprSignal: gdpr.SignalYes, 1959 gdprEnforced: true, 1960 expectPrivacyLabels: metrics.PrivacyLabels{ 1961 GDPREnforced: true, 1962 GDPRTCFVersion: "", 1963 }, 1964 }, 1965 { 1966 description: "enforce and scrub", 1967 gdprConsent: tcf2Consent, 1968 gdprScrub: true, 1969 gdprSignal: gdpr.SignalYes, 1970 gdprEnforced: true, 1971 expectPrivacyLabels: metrics.PrivacyLabels{ 1972 GDPREnforced: true, 1973 GDPRTCFVersion: metrics.TCFVersionV2, 1974 }, 1975 }, 1976 { 1977 description: "not enforce", 1978 gdprConsent: tcf2Consent, 1979 gdprScrub: false, 1980 gdprSignal: gdpr.SignalYes, 1981 gdprEnforced: false, 1982 expectPrivacyLabels: metrics.PrivacyLabels{ 1983 GDPREnforced: false, 1984 GDPRTCFVersion: "", 1985 }, 1986 }, 1987 { 1988 description: "enforce - error while checking if personal info is allowed", 1989 gdprConsent: tcf2Consent, 1990 gdprScrub: true, 1991 permissionsError: errors.New("Some error"), 1992 gdprSignal: gdpr.SignalYes, 1993 gdprEnforced: true, 1994 expectPrivacyLabels: metrics.PrivacyLabels{ 1995 GDPREnforced: true, 1996 GDPRTCFVersion: metrics.TCFVersionV2, 1997 }, 1998 }, 1999 } 2000 2001 for _, test := range testCases { 2002 req := newBidRequest(t) 2003 req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`) 2004 2005 privacyConfig := config.Privacy{} 2006 accountConfig := config.Account{} 2007 2008 auctionReq := AuctionRequest{ 2009 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 2010 UserSyncs: &emptyUsersync{}, 2011 Account: accountConfig, 2012 TCF2Config: gdpr.NewTCF2Config( 2013 privacyConfig.GDPR.TCF2, 2014 accountConfig.GDPR, 2015 ), 2016 } 2017 2018 gdprPermissionsBuilder := fakePermissionsBuilder{ 2019 permissions: &permissionsMock{ 2020 allowAllBidders: true, 2021 passGeo: !test.gdprScrub, 2022 passID: !test.gdprScrub, 2023 activitiesError: test.permissionsError, 2024 }, 2025 }.Builder 2026 2027 metricsMock := metrics.MetricsEngineMock{} 2028 metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return() 2029 2030 reqSplitter := &requestSplitter{ 2031 bidderToSyncerKey: map[string]string{}, 2032 me: &metricsMock, 2033 privacyConfig: privacyConfig, 2034 gdprPermsBuilder: gdprPermissionsBuilder, 2035 hostSChainNode: nil, 2036 bidderInfo: config.BidderInfos{}, 2037 } 2038 2039 results, privacyLabels, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, test.gdprSignal, test.gdprEnforced, map[string]float64{}) 2040 result := results[0] 2041 2042 if test.expectError { 2043 assert.NotNil(t, errs) 2044 } else { 2045 assert.Nil(t, errs) 2046 } 2047 2048 if test.gdprScrub { 2049 assert.Equal(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") 2050 assert.Equal(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") 2051 metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) 2052 } else { 2053 assert.NotEqual(t, result.BidRequest.User.BuyerUID, "", test.description+":User.BuyerUID") 2054 assert.NotEqual(t, result.BidRequest.Device.DIDMD5, "", test.description+":Device.DIDMD5") 2055 metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) 2056 } 2057 assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels") 2058 } 2059 } 2060 2061 func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { 2062 testCases := []struct { 2063 description string 2064 gdprEnforced bool 2065 gdprAllowedBidders []openrtb_ext.BidderName 2066 expectedBidders []openrtb_ext.BidderName 2067 expectedBlockedBidders []openrtb_ext.BidderName 2068 }{ 2069 { 2070 description: "gdpr enforced, one request allowed and one request blocked", 2071 gdprEnforced: true, 2072 gdprAllowedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, 2073 expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, 2074 expectedBlockedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderRubicon}, 2075 }, 2076 { 2077 description: "gdpr enforced, two requests allowed and no requests blocked", 2078 gdprEnforced: true, 2079 gdprAllowedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, 2080 expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, 2081 expectedBlockedBidders: []openrtb_ext.BidderName{}, 2082 }, 2083 { 2084 description: "gdpr not enforced, two requests allowed and no requests blocked", 2085 gdprEnforced: false, 2086 gdprAllowedBidders: []openrtb_ext.BidderName{}, 2087 expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, 2088 expectedBlockedBidders: []openrtb_ext.BidderName{}, 2089 }, 2090 } 2091 2092 for _, test := range testCases { 2093 req := newBidRequest(t) 2094 req.Regs = &openrtb2.Regs{ 2095 Ext: json.RawMessage(`{"gdpr":1}`), 2096 } 2097 req.Imp[0].Ext = json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}, "rubicon": {}}}}`) 2098 2099 privacyConfig := config.Privacy{} 2100 accountConfig := config.Account{ 2101 GDPR: config.AccountGDPR{ 2102 Enabled: nil, 2103 }, 2104 } 2105 2106 auctionReq := AuctionRequest{ 2107 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 2108 UserSyncs: &emptyUsersync{}, 2109 Account: accountConfig, 2110 TCF2Config: gdpr.NewTCF2Config(privacyConfig.GDPR.TCF2, accountConfig.GDPR), 2111 } 2112 2113 gdprPermissionsBuilder := fakePermissionsBuilder{ 2114 permissions: &permissionsMock{ 2115 allowedBidders: test.gdprAllowedBidders, 2116 passGeo: true, 2117 passID: true, 2118 activitiesError: nil, 2119 }, 2120 }.Builder 2121 2122 metricsMock := metrics.MetricsEngineMock{} 2123 metricsMock.Mock.On("RecordAdapterGDPRRequestBlocked", mock.Anything).Return() 2124 2125 reqSplitter := &requestSplitter{ 2126 bidderToSyncerKey: map[string]string{}, 2127 me: &metricsMock, 2128 privacyConfig: privacyConfig, 2129 gdprPermsBuilder: gdprPermissionsBuilder, 2130 hostSChainNode: nil, 2131 bidderInfo: config.BidderInfos{}, 2132 } 2133 2134 results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalYes, test.gdprEnforced, map[string]float64{}) 2135 2136 // extract bidder name from each request in the results 2137 bidders := []openrtb_ext.BidderName{} 2138 for _, req := range results { 2139 bidders = append(bidders, req.BidderName) 2140 } 2141 2142 assert.Empty(t, errs, test.description) 2143 assert.ElementsMatch(t, bidders, test.expectedBidders, test.description) 2144 2145 for _, blockedBidder := range test.expectedBlockedBidders { 2146 metricsMock.AssertCalled(t, "RecordAdapterGDPRRequestBlocked", blockedBidder) 2147 } 2148 for _, allowedBidder := range test.expectedBidders { 2149 metricsMock.AssertNotCalled(t, "RecordAdapterGDPRRequestBlocked", allowedBidder) 2150 } 2151 } 2152 } 2153 2154 func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) { 2155 emptyTCF2Config := gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}) 2156 2157 bidReq := newBidRequest(t) 2158 bidReq.Regs = &openrtb2.Regs{} 2159 bidReq.Regs.GPP = "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1NYN" 2160 bidReq.Regs.GPPSID = []int8{6} 2161 bidReq.User.ID = "" 2162 bidReq.User.BuyerUID = "" 2163 bidReq.User.Yob = 0 2164 bidReq.User.Gender = "" 2165 bidReq.User.Geo = &openrtb2.Geo{Lat: ptrutil.ToPtr(123.46)} 2166 2167 downgradedRegs := *bidReq.Regs 2168 downgradedUser := *bidReq.User 2169 downgradedRegs.GDPR = ptrutil.ToPtr[int8](0) 2170 downgradedRegs.USPrivacy = "1NYN" 2171 downgradedUser.Consent = "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA" 2172 2173 testCases := []struct { 2174 name string 2175 req AuctionRequest 2176 expectRegs *openrtb2.Regs 2177 expectUser *openrtb2.User 2178 bidderInfos config.BidderInfos 2179 }{ 2180 { 2181 name: "NotSupported", 2182 req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, 2183 expectRegs: &downgradedRegs, 2184 expectUser: &downgradedUser, 2185 bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: false}}}, 2186 }, 2187 { 2188 name: "Supported", 2189 req: AuctionRequest{BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: bidReq}, UserSyncs: &emptyUsersync{}, TCF2Config: emptyTCF2Config}, 2190 expectRegs: bidReq.Regs, 2191 expectUser: bidReq.User, 2192 bidderInfos: config.BidderInfos{"appnexus": config.BidderInfo{OpenRTB: &config.OpenRTBInfo{GPPSupported: true}}}, 2193 }, 2194 } 2195 2196 privacyConfig := config.Privacy{ 2197 CCPA: config.CCPA{ 2198 Enforce: true, 2199 }, 2200 LMT: config.LMT{ 2201 Enforce: true, 2202 }, 2203 } 2204 2205 for _, test := range testCases { 2206 t.Run(test.name, func(t *testing.T) { 2207 2208 gdprPermsBuilder := fakePermissionsBuilder{ 2209 permissions: &permissionsMock{ 2210 allowAllBidders: true, 2211 }, 2212 }.Builder 2213 2214 reqSplitter := &requestSplitter{ 2215 bidderToSyncerKey: map[string]string{}, 2216 me: &metrics.MetricsEngineMock{}, 2217 privacyConfig: privacyConfig, 2218 gdprPermsBuilder: gdprPermsBuilder, 2219 hostSChainNode: nil, 2220 bidderInfo: test.bidderInfos, 2221 } 2222 bidderRequests, _, err := reqSplitter.cleanOpenRTBRequests(context.Background(), test.req, nil, gdpr.SignalNo, false, map[string]float64{}) 2223 assert.Nil(t, err, "Err should be nil") 2224 bidRequest := bidderRequests[0] 2225 assert.Equal(t, test.expectRegs, bidRequest.BidRequest.Regs) 2226 assert.Equal(t, test.expectUser, bidRequest.BidRequest.User) 2227 2228 }) 2229 } 2230 } 2231 2232 func TestBuildRequestExtForBidder(t *testing.T) { 2233 var ( 2234 bidder = "foo" 2235 bidderParams = json.RawMessage(`"bar"`) 2236 ) 2237 2238 testCases := []struct { 2239 description string 2240 requestExt json.RawMessage 2241 bidderParams map[string]json.RawMessage 2242 alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes 2243 expectedJson json.RawMessage 2244 }{ 2245 { 2246 description: "Nil", 2247 bidderParams: nil, 2248 requestExt: nil, 2249 alternateBidderCodes: nil, 2250 expectedJson: nil, 2251 }, 2252 { 2253 description: "Empty", 2254 bidderParams: nil, 2255 alternateBidderCodes: nil, 2256 requestExt: json.RawMessage(`{}`), 2257 expectedJson: nil, 2258 }, 2259 { 2260 description: "Prebid - Allowed Fields Only", 2261 bidderParams: nil, 2262 requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), 2263 expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), 2264 }, 2265 { 2266 description: "Prebid - Allowed Fields + Bidder Params", 2267 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2268 requestExt: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), 2269 expectedJson: json.RawMessage(`{"prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`), 2270 }, 2271 { 2272 description: "Other", 2273 bidderParams: nil, 2274 requestExt: json.RawMessage(`{"other":"foo"}`), 2275 expectedJson: json.RawMessage(`{"other":"foo"}`), 2276 }, 2277 { 2278 description: "Prebid + Other + Bider Params", 2279 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2280 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}}}`), 2281 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true}, "server": {"externalurl": "url", "gvlid": 1, "datacenter": "2"}, "sdk": {"renderers": [{"name": "r1"}]}, "bidderparams":"bar"}}`), 2282 }, 2283 { 2284 description: "Prebid + AlternateBidderCodes in pbs config but current bidder not in AlternateBidderCodes config", 2285 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2286 alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"bar": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, 2287 requestExt: json.RawMessage(`{"other":"foo"}`), 2288 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`), 2289 }, 2290 { 2291 description: "Prebid + AlternateBidderCodes in request", 2292 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2293 alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, 2294 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), 2295 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`), 2296 }, 2297 { 2298 description: "Prebid + AlternateBidderCodes in request but current bidder not in AlternateBidderCodes config", 2299 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2300 alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{}, 2301 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), 2302 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":null},"bidderparams":"bar"}}`), 2303 }, 2304 { 2305 description: "Prebid + AlternateBidderCodes in both pbs config and in the request", 2306 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2307 alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true, Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{"foo": {Enabled: true, AllowedBidderCodes: []string{"*"}}}}, 2308 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]},"bar":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), 2309 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"bidderparams":"bar"}}`), 2310 }, 2311 { 2312 description: "Prebid + Other + Bider Params + MultiBid.Bidder", 2313 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2314 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), 2315 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo","maxbids":2,"targetbiddercodeprefix":"fmb"}],"bidderparams":"bar"}}`), 2316 }, 2317 { 2318 description: "Prebid + Other + Bider Params + MultiBid.Bidders", 2319 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2320 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"pubmatic","maxbids":3,"targetbiddercodeprefix":"pubM"},{"bidders":["foo","groupm"],"maxbids":4}]}}`), 2321 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidders":["foo"],"maxbids":4}],"bidderparams":"bar"}}`), 2322 }, 2323 { 2324 description: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", 2325 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2326 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo2","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), 2327 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`), 2328 }, 2329 { 2330 description: "Prebid + Other + Bider Params + MultiBid (foo not in MultiBid)", 2331 bidderParams: map[string]json.RawMessage{bidder: bidderParams}, 2332 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"multibid":[{"bidder":"foo2","maxbids":2,"targetbiddercodeprefix":"fmb"},{"bidders":["appnexus","groupm"],"maxbids":2}]}}`), 2333 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"bidderparams":"bar"}}`), 2334 }, 2335 { 2336 description: "Prebid + AlternateBidderCodes.MultiBid.Bidder", 2337 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), 2338 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), 2339 }, 2340 { 2341 description: "Prebid + AlternateBidderCodes.MultiBid.Bidders", 2342 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), 2343 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["pubmatic"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic"],"maxbids":4}]}}`), 2344 }, 2345 { 2346 description: "Prebid + AlternateBidderCodes.MultiBid.Bidder with *", 2347 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), 2348 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidder":"foo2","maxbids":4,"targetbiddercodeprefix":"fmb2"},{"bidder":"pubmatic","maxbids":5,"targetbiddercodeprefix":"pm"}]}}`), 2349 }, 2350 { 2351 description: "Prebid + AlternateBidderCodes.MultiBid.Bidders with *", 2352 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), 2353 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["*"]}}},"multibid":[{"bidder":"foo","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic"],"maxbids":4},{"bidders":["groupm"],"maxbids":4}]}}`), 2354 }, 2355 { 2356 description: "Prebid + AlternateBidderCodes + MultiBid", 2357 requestExt: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}},"multibid":[{"bidder":"foo3","maxbids":3,"targetbiddercodeprefix":"fmb"},{"bidders":["pubmatic","groupm"],"maxbids":4}]}}`), 2358 expectedJson: json.RawMessage(`{"other":"foo","prebid":{"integration":"a","channel":{"name":"b","version":"c"},"debug":true,"currency":{"rates":{"FOO":{"BAR":42}},"usepbsrates":true},"alternatebiddercodes":{"enabled":true,"bidders":{"foo":{"enabled":true,"allowedbiddercodes":["foo2"]}}}}}`), 2359 }, 2360 } 2361 2362 for _, test := range testCases { 2363 requestExtParsed := &openrtb_ext.ExtRequest{} 2364 if test.requestExt != nil { 2365 err := jsonutil.UnmarshalValid(test.requestExt, requestExtParsed) 2366 if !assert.NoError(t, err, test.description+":parse_ext") { 2367 continue 2368 } 2369 } 2370 2371 actualJson, actualErr := buildRequestExtForBidder(bidder, test.requestExt, requestExtParsed, test.bidderParams, test.alternateBidderCodes) 2372 if len(test.expectedJson) > 0 { 2373 assert.JSONEq(t, string(test.expectedJson), string(actualJson), test.description+":json") 2374 } else { 2375 assert.Equal(t, test.expectedJson, actualJson, test.description+":json") 2376 } 2377 assert.NoError(t, actualErr, test.description+":err") 2378 } 2379 } 2380 2381 func TestBuildRequestExtForBidder_RequestExtParsedNil(t *testing.T) { 2382 var ( 2383 bidder = "foo" 2384 requestExt = json.RawMessage(`{}`) 2385 requestExtParsed *openrtb_ext.ExtRequest 2386 bidderParams map[string]json.RawMessage 2387 alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes 2388 ) 2389 2390 actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) 2391 assert.Nil(t, actualJson) 2392 assert.NoError(t, actualErr) 2393 } 2394 2395 func TestBuildRequestExtForBidder_RequestExtMalformed(t *testing.T) { 2396 var ( 2397 bidder = "foo" 2398 requestExt = json.RawMessage(`malformed`) 2399 requestExtParsed = &openrtb_ext.ExtRequest{} 2400 bidderParams map[string]json.RawMessage 2401 alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes 2402 ) 2403 2404 actualJson, actualErr := buildRequestExtForBidder(bidder, requestExt, requestExtParsed, bidderParams, alternateBidderCodes) 2405 assert.Equal(t, json.RawMessage(nil), actualJson) 2406 assert.EqualError(t, actualErr, "expect { or n, but found m") 2407 } 2408 2409 // newAdapterAliasBidRequest builds a BidRequest with aliases 2410 func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { 2411 dnt := int8(1) 2412 return &openrtb2.BidRequest{ 2413 Site: &openrtb2.Site{ 2414 Page: "www.some.domain.com", 2415 Domain: "domain.com", 2416 Publisher: &openrtb2.Publisher{ 2417 ID: "some-publisher-id", 2418 }, 2419 }, 2420 Device: &openrtb2.Device{ 2421 DIDMD5: "some device ID hash", 2422 UA: deviceUA, 2423 IFA: "ifa", 2424 IP: "132.173.230.74", 2425 DNT: &dnt, 2426 Language: "EN", 2427 }, 2428 Source: &openrtb2.Source{ 2429 TID: "testTID", 2430 }, 2431 User: &openrtb2.User{ 2432 ID: "our-id", 2433 BuyerUID: "their-id", 2434 Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw"}`), 2435 }, 2436 Regs: &openrtb2.Regs{ 2437 Ext: json.RawMessage(`{"gdpr":1}`), 2438 }, 2439 Imp: []openrtb2.Imp{{ 2440 ID: "some-imp-id", 2441 Banner: &openrtb2.Banner{ 2442 Format: []openrtb2.Format{{ 2443 W: 300, 2444 H: 250, 2445 }, { 2446 W: 300, 2447 H: 600, 2448 }}, 2449 }, 2450 Ext: json.RawMessage(`{"appnexus": {"placementId": 1},"somealias": {"placementId": 105}}`), 2451 }}, 2452 Ext: json.RawMessage(`{"prebid":{"aliases":{"somealias":"appnexus"}}}`), 2453 } 2454 } 2455 2456 func newBidRequest(t *testing.T) *openrtb2.BidRequest { 2457 return &openrtb2.BidRequest{ 2458 Site: &openrtb2.Site{ 2459 Page: "www.some.domain.com", 2460 Domain: "domain.com", 2461 Publisher: &openrtb2.Publisher{ 2462 ID: "some-publisher-id", 2463 }, 2464 }, 2465 Device: &openrtb2.Device{ 2466 UA: deviceUA, 2467 IP: "132.173.230.74", 2468 Language: "EN", 2469 DIDMD5: "DIDMD5", 2470 IFA: "IFA", 2471 DIDSHA1: "DIDSHA1", 2472 DPIDMD5: "DPIDMD5", 2473 DPIDSHA1: "DPIDSHA1", 2474 MACMD5: "MACMD5", 2475 MACSHA1: "MACSHA1", 2476 Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, 2477 }, 2478 Source: &openrtb2.Source{ 2479 TID: "testTID", 2480 }, 2481 User: &openrtb2.User{ 2482 ID: "our-id", 2483 BuyerUID: "their-id", 2484 Yob: 1982, 2485 Gender: "test", 2486 Ext: json.RawMessage(`{"data": 1, "test": 2}`), 2487 Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, 2488 EIDs: []openrtb2.EID{ 2489 {Source: "eids-source"}, 2490 }, 2491 Data: []openrtb2.Data{{ID: "data-id"}}, 2492 }, 2493 Imp: []openrtb2.Imp{{ 2494 BidFloor: 100, 2495 ID: "some-imp-id", 2496 Banner: &openrtb2.Banner{ 2497 Format: []openrtb2.Format{{ 2498 W: 300, 2499 H: 250, 2500 }, { 2501 W: 300, 2502 H: 600, 2503 }}, 2504 }, 2505 Ext: json.RawMessage(`{"prebid":{"tid":"1234567", "bidder":{"appnexus": {"placementId": 1}}}}`), 2506 }}, 2507 } 2508 } 2509 2510 func newBidRequestWithBidderParams(t *testing.T) *openrtb2.BidRequest { 2511 return &openrtb2.BidRequest{ 2512 Site: &openrtb2.Site{ 2513 Page: "www.some.domain.com", 2514 Domain: "domain.com", 2515 Publisher: &openrtb2.Publisher{ 2516 ID: "some-publisher-id", 2517 }, 2518 }, 2519 Device: &openrtb2.Device{ 2520 DIDMD5: "some device ID hash", 2521 UA: deviceUA, 2522 IFA: "ifa", 2523 IP: "132.173.230.74", 2524 Language: "EN", 2525 }, 2526 Source: &openrtb2.Source{ 2527 TID: "testTID", 2528 }, 2529 User: &openrtb2.User{ 2530 ID: "our-id", 2531 BuyerUID: "their-id", 2532 Yob: 1982, 2533 Ext: json.RawMessage(`{}`), 2534 }, 2535 Imp: []openrtb2.Imp{{ 2536 ID: "some-imp-id", 2537 Banner: &openrtb2.Banner{ 2538 Format: []openrtb2.Format{{ 2539 W: 300, 2540 H: 250, 2541 }, { 2542 W: 300, 2543 H: 600, 2544 }}, 2545 }, 2546 Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}, "pubmatic":{"publisherId": "1234"}}}}`), 2547 }}, 2548 } 2549 } 2550 2551 func TestRandomizeList(t *testing.T) { 2552 var ( 2553 bidder1 = openrtb_ext.BidderName("bidder1") 2554 bidder2 = openrtb_ext.BidderName("bidder2") 2555 bidder3 = openrtb_ext.BidderName("bidder3") 2556 ) 2557 2558 testCases := []struct { 2559 description string 2560 bidders []openrtb_ext.BidderName 2561 }{ 2562 { 2563 description: "None", 2564 bidders: []openrtb_ext.BidderName{}, 2565 }, 2566 { 2567 description: "One", 2568 bidders: []openrtb_ext.BidderName{bidder1}, 2569 }, 2570 { 2571 description: "Many", 2572 bidders: []openrtb_ext.BidderName{bidder1, bidder2, bidder3}, 2573 }, 2574 } 2575 2576 for _, test := range testCases { 2577 biddersWorkingCopy := make([]openrtb_ext.BidderName, len(test.bidders)) 2578 copy(biddersWorkingCopy, test.bidders) 2579 2580 randomizeList(biddersWorkingCopy) 2581 2582 // test all bidders are still present, ignoring order. we are testing the algorithm doesn't loose 2583 // elements. we are not testing the random number generator itself. 2584 assert.ElementsMatch(t, test.bidders, biddersWorkingCopy) 2585 } 2586 } 2587 2588 func TestRemoveUnpermissionedEids(t *testing.T) { 2589 bidder := "bidderA" 2590 2591 testCases := []struct { 2592 description string 2593 userExt json.RawMessage 2594 eidPermissions []openrtb_ext.ExtRequestPrebidDataEidPermission 2595 expectedUserExt json.RawMessage 2596 }{ 2597 { 2598 description: "Extension Nil", 2599 userExt: nil, 2600 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2601 {Source: "source1", Bidders: []string{"bidderA"}}, 2602 }, 2603 expectedUserExt: nil, 2604 }, 2605 { 2606 description: "Extension Empty", 2607 userExt: json.RawMessage(`{}`), 2608 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2609 {Source: "source1", Bidders: []string{"bidderA"}}, 2610 }, 2611 expectedUserExt: json.RawMessage(`{}`), 2612 }, 2613 { 2614 description: "Extension Empty - Keep Other Data", 2615 userExt: json.RawMessage(`{"other":42}`), 2616 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2617 {Source: "source1", Bidders: []string{"bidderA"}}, 2618 }, 2619 expectedUserExt: json.RawMessage(`{"other":42}`), 2620 }, 2621 { 2622 description: "Eids Empty", 2623 userExt: json.RawMessage(`{"eids":[]}`), 2624 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2625 {Source: "source1", Bidders: []string{"bidderA"}}, 2626 }, 2627 expectedUserExt: json.RawMessage(`{"eids":[]}`), 2628 }, 2629 { 2630 description: "Eids Empty - Keep Other Data", 2631 userExt: json.RawMessage(`{"eids":[],"other":42}`), 2632 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2633 {Source: "source1", Bidders: []string{"bidderA"}}, 2634 }, 2635 expectedUserExt: json.RawMessage(`{"eids":[],"other":42}`), 2636 }, 2637 { 2638 description: "Allowed By Nil Permissions", 2639 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2640 eidPermissions: nil, 2641 expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2642 }, 2643 { 2644 description: "Allowed By Empty Permissions", 2645 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2646 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{}, 2647 expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2648 }, 2649 { 2650 description: "Allowed By Specific Bidder", 2651 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2652 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2653 {Source: "source1", Bidders: []string{"bidderA"}}, 2654 }, 2655 expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2656 }, 2657 { 2658 description: "Allowed By Specific Bidder - Case Insensitive", 2659 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2660 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2661 {Source: "source1", Bidders: []string{"BIDDERA"}}, 2662 }, 2663 expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2664 }, 2665 { 2666 description: "Allowed By All Bidders", 2667 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2668 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2669 {Source: "source1", Bidders: []string{"*"}}, 2670 }, 2671 expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2672 }, 2673 { 2674 description: "Allowed By Lack Of Matching Source", 2675 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2676 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2677 {Source: "source2", Bidders: []string{"otherBidder"}}, 2678 }, 2679 expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2680 }, 2681 { 2682 description: "Allowed - Keep Other Data", 2683 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`), 2684 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2685 {Source: "source1", Bidders: []string{"bidderA"}}, 2686 }, 2687 expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"other":42}`), 2688 }, 2689 { 2690 description: "Denied", 2691 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}]}`), 2692 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2693 {Source: "source1", Bidders: []string{"otherBidder"}}, 2694 }, 2695 expectedUserExt: nil, 2696 }, 2697 { 2698 description: "Denied - Keep Other Data", 2699 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID"}]}],"otherdata":42}`), 2700 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2701 {Source: "source1", Bidders: []string{"otherBidder"}}, 2702 }, 2703 expectedUserExt: json.RawMessage(`{"otherdata":42}`), 2704 }, 2705 { 2706 description: "Mix Of Allowed By Specific Bidder, Allowed By Lack Of Matching Source, Denied, Keep Other Data", 2707 userExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]},{"source":"source3","uids":[{"id":"anyID3"}]}],"other":42}`), 2708 eidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2709 {Source: "source1", Bidders: []string{"bidderA"}}, 2710 {Source: "source3", Bidders: []string{"otherBidder"}}, 2711 }, 2712 expectedUserExt: json.RawMessage(`{"eids":[{"source":"source1","uids":[{"id":"anyID1"}]},{"source":"source2","uids":[{"id":"anyID2"}]}],"other":42}`), 2713 }, 2714 } 2715 2716 for _, test := range testCases { 2717 request := &openrtb2.BidRequest{ 2718 User: &openrtb2.User{Ext: test.userExt}, 2719 } 2720 2721 requestExt := &openrtb_ext.ExtRequest{ 2722 Prebid: openrtb_ext.ExtRequestPrebid{ 2723 Data: &openrtb_ext.ExtRequestPrebidData{ 2724 EidPermissions: test.eidPermissions, 2725 }, 2726 }, 2727 } 2728 2729 expectedRequest := &openrtb2.BidRequest{ 2730 User: &openrtb2.User{Ext: test.expectedUserExt}, 2731 } 2732 2733 resultErr := removeUnpermissionedEids(request, bidder, requestExt) 2734 assert.NoError(t, resultErr, test.description) 2735 assert.Equal(t, expectedRequest, request, test.description) 2736 } 2737 } 2738 2739 func TestRemoveUnpermissionedEidsUnmarshalErrors(t *testing.T) { 2740 testCases := []struct { 2741 description string 2742 userExt json.RawMessage 2743 expectedErr string 2744 }{ 2745 { 2746 description: "Malformed Ext", 2747 userExt: json.RawMessage(`malformed`), 2748 expectedErr: "expect { or n, but found m", 2749 }, 2750 { 2751 description: "Malformed Eid Array Type", 2752 userExt: json.RawMessage(`{"eids":[42]}`), 2753 expectedErr: "cannot unmarshal []openrtb2.EID: expect { or n, but found 4", 2754 }, 2755 { 2756 description: "Malformed Eid Item Type", 2757 userExt: json.RawMessage(`{"eids":[{"source":42,"id":"anyID"}]}`), 2758 expectedErr: "cannot unmarshal openrtb2.EID.Source: expects \" or n, but found 4", 2759 }, 2760 } 2761 2762 for _, test := range testCases { 2763 request := &openrtb2.BidRequest{ 2764 User: &openrtb2.User{Ext: test.userExt}, 2765 } 2766 2767 requestExt := &openrtb_ext.ExtRequest{ 2768 Prebid: openrtb_ext.ExtRequestPrebid{ 2769 Data: &openrtb_ext.ExtRequestPrebidData{ 2770 EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2771 {Source: "source1", Bidders: []string{"*"}}, 2772 }, 2773 }, 2774 }, 2775 } 2776 2777 resultErr := removeUnpermissionedEids(request, "bidderA", requestExt) 2778 assert.EqualError(t, resultErr, test.expectedErr, test.description) 2779 } 2780 } 2781 2782 func TestGetDebugInfo(t *testing.T) { 2783 type testInput struct { 2784 debugEnabledOrOverridden bool 2785 accountDebugFlag bool 2786 } 2787 type testOut struct { 2788 responseDebugAllow bool 2789 accountDebugAllow bool 2790 debugLog *DebugLog 2791 } 2792 type testCase struct { 2793 in testInput 2794 expected testOut 2795 } 2796 2797 testGroups := []struct { 2798 description string 2799 isTestRequest int8 2800 testCases []testCase 2801 }{ 2802 { 2803 description: "Bid request doesn't call for debug info", 2804 isTestRequest: 0, 2805 testCases: []testCase{ 2806 { 2807 testInput{debugEnabledOrOverridden: false, accountDebugFlag: false}, 2808 testOut{ 2809 responseDebugAllow: false, 2810 accountDebugAllow: false, 2811 debugLog: &DebugLog{Enabled: false}, 2812 }, 2813 }, 2814 { 2815 testInput{debugEnabledOrOverridden: false, accountDebugFlag: true}, 2816 testOut{ 2817 responseDebugAllow: false, 2818 accountDebugAllow: false, 2819 debugLog: &DebugLog{Enabled: true}, 2820 }, 2821 }, 2822 { 2823 testInput{debugEnabledOrOverridden: true, accountDebugFlag: false}, 2824 testOut{ 2825 responseDebugAllow: true, 2826 accountDebugAllow: false, 2827 debugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, 2828 }, 2829 }, 2830 { 2831 testInput{debugEnabledOrOverridden: true, accountDebugFlag: true}, 2832 testOut{ 2833 responseDebugAllow: true, 2834 accountDebugAllow: true, 2835 debugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, 2836 }, 2837 }, 2838 }, 2839 }, 2840 { 2841 description: "Bid request requires debug info", 2842 isTestRequest: 1, 2843 testCases: []testCase{ 2844 { 2845 testInput{debugEnabledOrOverridden: false, accountDebugFlag: false}, 2846 testOut{ 2847 responseDebugAllow: false, 2848 accountDebugAllow: false, 2849 debugLog: &DebugLog{Enabled: false}, 2850 }, 2851 }, 2852 { 2853 testInput{debugEnabledOrOverridden: false, accountDebugFlag: true}, 2854 testOut{ 2855 responseDebugAllow: true, 2856 accountDebugAllow: true, 2857 debugLog: &DebugLog{Enabled: true}, 2858 }, 2859 }, 2860 { 2861 testInput{debugEnabledOrOverridden: true, accountDebugFlag: false}, 2862 testOut{ 2863 responseDebugAllow: true, 2864 accountDebugAllow: false, 2865 debugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, 2866 }, 2867 }, 2868 { 2869 testInput{debugEnabledOrOverridden: true, accountDebugFlag: true}, 2870 testOut{ 2871 responseDebugAllow: true, 2872 accountDebugAllow: true, 2873 debugLog: &DebugLog{DebugEnabledOrOverridden: true, Enabled: true}, 2874 }, 2875 }, 2876 }, 2877 }, 2878 } 2879 for _, group := range testGroups { 2880 for i, tc := range group.testCases { 2881 inDebugLog := &DebugLog{DebugEnabledOrOverridden: tc.in.debugEnabledOrOverridden} 2882 2883 // run 2884 responseDebugAllow, accountDebugAllow, debugLog := getDebugInfo(group.isTestRequest, nil, tc.in.accountDebugFlag, inDebugLog) 2885 2886 // assertions 2887 assert.Equal(t, tc.expected.responseDebugAllow, responseDebugAllow, "%s - %d", group.description, i) 2888 assert.Equal(t, tc.expected.accountDebugAllow, accountDebugAllow, "%s - %d", group.description, i) 2889 assert.Equal(t, tc.expected.debugLog, debugLog, "%s - %d", group.description, i) 2890 } 2891 } 2892 } 2893 2894 func TestRemoveUnpermissionedEidsEmptyValidations(t *testing.T) { 2895 testCases := []struct { 2896 description string 2897 request *openrtb2.BidRequest 2898 requestExt *openrtb_ext.ExtRequest 2899 }{ 2900 { 2901 description: "Nil User", 2902 request: &openrtb2.BidRequest{ 2903 User: nil, 2904 }, 2905 requestExt: &openrtb_ext.ExtRequest{ 2906 Prebid: openrtb_ext.ExtRequestPrebid{ 2907 Data: &openrtb_ext.ExtRequestPrebidData{ 2908 EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2909 {Source: "source1", Bidders: []string{"*"}}, 2910 }, 2911 }, 2912 }, 2913 }, 2914 }, 2915 { 2916 description: "Empty User", 2917 request: &openrtb2.BidRequest{ 2918 User: &openrtb2.User{}, 2919 }, 2920 requestExt: &openrtb_ext.ExtRequest{ 2921 Prebid: openrtb_ext.ExtRequestPrebid{ 2922 Data: &openrtb_ext.ExtRequestPrebidData{ 2923 EidPermissions: []openrtb_ext.ExtRequestPrebidDataEidPermission{ 2924 {Source: "source1", Bidders: []string{"*"}}, 2925 }, 2926 }, 2927 }, 2928 }, 2929 }, 2930 { 2931 description: "Nil Ext", 2932 request: &openrtb2.BidRequest{ 2933 User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, 2934 }, 2935 requestExt: nil, 2936 }, 2937 { 2938 description: "Nil Prebid Data", 2939 request: &openrtb2.BidRequest{ 2940 User: &openrtb2.User{Ext: json.RawMessage(`{"eids":[{"source":"source1","id":"anyID"}]}`)}, 2941 }, 2942 requestExt: &openrtb_ext.ExtRequest{ 2943 Prebid: openrtb_ext.ExtRequestPrebid{ 2944 Data: nil, 2945 }, 2946 }, 2947 }, 2948 } 2949 2950 for _, test := range testCases { 2951 requestExpected := *test.request 2952 2953 resultErr := removeUnpermissionedEids(test.request, "bidderA", test.requestExt) 2954 assert.NoError(t, resultErr, test.description+":err") 2955 assert.Equal(t, &requestExpected, test.request, test.description+":request") 2956 } 2957 } 2958 2959 func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) { 2960 req := &openrtb2.BidRequest{ 2961 Site: &openrtb2.Site{}, 2962 Source: &openrtb2.Source{ 2963 TID: "testTID", 2964 }, 2965 Imp: []openrtb2.Imp{{ 2966 Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}, "axonix": { "supplyId": "123"}}}}`), 2967 }}, 2968 Ext: json.RawMessage(`{"prebid":{"schains":[{ "bidders":["appnexus"],"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}, {"bidders":["axonix"],"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}]}}`), 2969 } 2970 2971 extRequest := &openrtb_ext.ExtRequest{} 2972 err := jsonutil.UnmarshalValid(req.Ext, extRequest) 2973 assert.NoErrorf(t, err, "Error unmarshaling inExt") 2974 2975 auctionReq := AuctionRequest{ 2976 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 2977 UserSyncs: &emptyUsersync{}, 2978 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 2979 } 2980 2981 gdprPermissionsBuilder := fakePermissionsBuilder{ 2982 permissions: &permissionsMock{ 2983 allowAllBidders: true, 2984 passGeo: true, 2985 passID: true, 2986 activitiesError: nil, 2987 }, 2988 }.Builder 2989 2990 reqSplitter := &requestSplitter{ 2991 bidderToSyncerKey: map[string]string{}, 2992 me: &metrics.MetricsEngineMock{}, 2993 privacyConfig: config.Privacy{}, 2994 gdprPermsBuilder: gdprPermissionsBuilder, 2995 hostSChainNode: nil, 2996 bidderInfo: config.BidderInfos{}, 2997 } 2998 bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) 2999 3000 assert.Nil(t, errs) 3001 assert.Len(t, bidderRequests, 2, "Bid request count is not 2") 3002 3003 bidRequestSourceExts := map[openrtb_ext.BidderName]json.RawMessage{} 3004 for _, bidderRequest := range bidderRequests { 3005 bidRequestSourceExts[bidderRequest.BidderName] = bidderRequest.BidRequest.Source.Ext 3006 } 3007 3008 appnexusPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller1.com","sid":"00001","rid":"BidRequest1","hp":1}],"ver":"1.0"}}`) 3009 axonixPrebidSchainsSchain := json.RawMessage(`{"schain":{"complete":1,"nodes":[{"asi":"directseller2.com","sid":"00002","rid":"BidRequest2","hp":1}],"ver":"1.0"}}`) 3010 assert.Equal(t, appnexusPrebidSchainsSchain, bidRequestSourceExts["appnexus"], "Incorrect appnexus bid request schain in source.ext") 3011 assert.Equal(t, axonixPrebidSchainsSchain, bidRequestSourceExts["axonix"], "Incorrect axonix bid request schain in source.ext") 3012 } 3013 3014 func TestCleanOpenRTBRequestsBidAdjustment(t *testing.T) { 3015 tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" 3016 falseValue := false 3017 testCases := []struct { 3018 description string 3019 gdprAccountEnabled *bool 3020 gdprHostEnabled bool 3021 gdpr string 3022 gdprConsent string 3023 gdprScrub bool 3024 permissionsError error 3025 gdprDefaultValue string 3026 expectPrivacyLabels metrics.PrivacyLabels 3027 expectError bool 3028 bidAdjustmentFactor map[string]float64 3029 expectedImp []openrtb2.Imp 3030 }{ 3031 { 3032 description: "BidFloor Adjustment Done for Appnexus", 3033 gdprAccountEnabled: &falseValue, 3034 gdprHostEnabled: true, 3035 gdpr: "1", 3036 gdprConsent: tcf2Consent, 3037 gdprScrub: false, 3038 gdprDefaultValue: "1", 3039 expectPrivacyLabels: metrics.PrivacyLabels{ 3040 GDPREnforced: false, 3041 GDPRTCFVersion: "", 3042 }, 3043 bidAdjustmentFactor: map[string]float64{"appnexus": 0.50}, 3044 expectedImp: []openrtb2.Imp{{ 3045 BidFloor: 200, 3046 ID: "some-imp-id", 3047 Banner: &openrtb2.Banner{ 3048 Format: []openrtb2.Format{{ 3049 W: 300, 3050 H: 250, 3051 }, { 3052 W: 300, 3053 H: 600, 3054 }}, 3055 }, 3056 Ext: json.RawMessage(`{"bidder":{"placementId": 1}}`), 3057 }}, 3058 }, 3059 { 3060 description: "bidAdjustment Not provided", 3061 gdprAccountEnabled: &falseValue, 3062 gdprHostEnabled: true, 3063 gdpr: "1", 3064 gdprConsent: tcf2Consent, 3065 gdprScrub: false, 3066 gdprDefaultValue: "1", 3067 expectPrivacyLabels: metrics.PrivacyLabels{ 3068 GDPREnforced: false, 3069 GDPRTCFVersion: "", 3070 }, 3071 bidAdjustmentFactor: map[string]float64{}, 3072 expectedImp: []openrtb2.Imp{{ 3073 BidFloor: 100, 3074 ID: "some-imp-id", 3075 Banner: &openrtb2.Banner{ 3076 Format: []openrtb2.Format{{ 3077 W: 300, 3078 H: 250, 3079 }, { 3080 W: 300, 3081 H: 600, 3082 }}, 3083 }, 3084 Ext: json.RawMessage(`{"bidder":{"placementId": 1}}`), 3085 }}, 3086 }, 3087 } 3088 for _, test := range testCases { 3089 req := newBidRequest(t) 3090 accountConfig := config.Account{ 3091 GDPR: config.AccountGDPR{ 3092 Enabled: &falseValue, 3093 }, 3094 PriceFloors: config.AccountPriceFloors{ 3095 AdjustForBidAdjustment: true, 3096 }, 3097 } 3098 auctionReq := AuctionRequest{ 3099 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 3100 UserSyncs: &emptyUsersync{}, 3101 Account: accountConfig, 3102 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 3103 } 3104 gdprPermissionsBuilder := fakePermissionsBuilder{ 3105 permissions: &permissionsMock{ 3106 allowAllBidders: true, 3107 passGeo: !test.gdprScrub, 3108 passID: !test.gdprScrub, 3109 activitiesError: test.permissionsError, 3110 }, 3111 }.Builder 3112 reqSplitter := &requestSplitter{ 3113 bidderToSyncerKey: map[string]string{}, 3114 me: &metrics.MetricsEngineMock{}, 3115 gdprPermsBuilder: gdprPermissionsBuilder, 3116 hostSChainNode: nil, 3117 bidderInfo: config.BidderInfos{}, 3118 } 3119 results, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, test.bidAdjustmentFactor) 3120 result := results[0] 3121 assert.Nil(t, errs) 3122 assert.Equal(t, test.expectedImp, result.BidRequest.Imp, test.description) 3123 } 3124 } 3125 3126 func TestApplyFPD(t *testing.T) { 3127 testCases := []struct { 3128 description string 3129 inputFpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData 3130 inputBidderName string 3131 inputBidderCoreName string 3132 inputBidderIsRequestAlias bool 3133 inputRequest openrtb2.BidRequest 3134 expectedRequest openrtb2.BidRequest 3135 }{ 3136 { 3137 description: "fpd-nil", 3138 inputFpd: nil, 3139 inputBidderName: "bidderFromRequest", 3140 inputBidderCoreName: "bidderNormalized", 3141 inputBidderIsRequestAlias: false, 3142 inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3143 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3144 }, 3145 { 3146 description: "fpd-bidderdata-nil", 3147 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3148 "bidderNormalized": nil, 3149 }, 3150 inputBidderName: "bidderFromRequest", 3151 inputBidderCoreName: "bidderNormalized", 3152 inputBidderIsRequestAlias: false, 3153 inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3154 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3155 }, 3156 { 3157 description: "fpd-bidderdata-notdefined", 3158 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3159 "differentBidder": {App: &openrtb2.App{ID: "AppId"}}, 3160 }, 3161 inputBidderName: "bidderFromRequest", 3162 inputBidderCoreName: "bidderNormalized", 3163 inputBidderIsRequestAlias: false, 3164 inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3165 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3166 }, 3167 { 3168 description: "fpd-bidderdata-alias", 3169 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3170 "alias": {App: &openrtb2.App{ID: "AppId"}}, 3171 }, 3172 inputBidderName: "alias", 3173 inputBidderCoreName: "bidder", 3174 inputBidderIsRequestAlias: true, 3175 inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3176 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, 3177 }, 3178 { 3179 description: "req.Site defined; bidderFPD.Site not defined; expect request.Site remains the same", 3180 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3181 "bidderNormalized": {Site: nil, App: nil, User: nil}, 3182 }, 3183 inputBidderName: "bidderFromRequest", 3184 inputBidderCoreName: "bidderNormalized", 3185 inputBidderIsRequestAlias: false, 3186 inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3187 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3188 }, 3189 { 3190 description: "req.Site, req.App, req.User are not defined; bidderFPD.App, bidderFPD.Site and bidderFPD.User defined; " + 3191 "expect req.Site, req.App, req.User to be overriden by bidderFPD.App, bidderFPD.Site and bidderFPD.User", 3192 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3193 "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, 3194 }, 3195 inputBidderName: "bidderFromRequest", 3196 inputBidderCoreName: "bidderNormalized", 3197 inputBidderIsRequestAlias: false, 3198 inputRequest: openrtb2.BidRequest{}, 3199 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, 3200 }, 3201 { 3202 description: "req.Site, defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App; expect req.Site remains the same", 3203 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3204 "bidderNormalized": {App: &openrtb2.App{ID: "AppId"}}, 3205 }, 3206 inputBidderName: "bidderFromRequest", 3207 inputBidderCoreName: "bidderNormalized", 3208 inputBidderIsRequestAlias: false, 3209 inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}}, 3210 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, 3211 }, 3212 { 3213 description: "req.Site, req.App defined; bidderFPD.App defined; expect request.App to be overriden by bidderFPD.App", 3214 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3215 "bidderNormalized": {App: &openrtb2.App{ID: "AppId"}}, 3216 }, 3217 inputBidderName: "bidderFromRequest", 3218 inputBidderCoreName: "bidderNormalized", 3219 inputBidderIsRequestAlias: false, 3220 inputRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "TestAppId"}}, 3221 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, 3222 }, 3223 { 3224 description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID. Expect to see user.BuyerUID in result request", 3225 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3226 "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, 3227 }, 3228 inputBidderName: "bidderFromRequest", 3229 inputBidderCoreName: "bidderNormalized", 3230 inputBidderIsRequestAlias: false, 3231 inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: "12345"}}, 3232 expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId", BuyerUID: "12345"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, 3233 }, 3234 { 3235 description: "req.User is defined; bidderFPD.User defined; req.User has BuyerUID with zero length. Expect to see empty user.BuyerUID in result request", 3236 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3237 "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId"}}, 3238 }, 3239 inputBidderName: "bidderFromRequest", 3240 inputBidderCoreName: "bidderNormalized", 3241 inputBidderIsRequestAlias: false, 3242 inputRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserIdIn", BuyerUID: ""}}, 3243 expectedRequest: openrtb2.BidRequest{User: &openrtb2.User{ID: "UserId"}, Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}}, 3244 }, 3245 { 3246 description: "req.User is not defined; bidderFPD.User defined and has BuyerUID. Expect to see user.BuyerUID in result request", 3247 inputFpd: map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData{ 3248 "bidderNormalized": {Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, 3249 }, 3250 inputBidderName: "bidderFromRequest", 3251 inputBidderCoreName: "bidderNormalized", 3252 inputBidderIsRequestAlias: false, 3253 inputRequest: openrtb2.BidRequest{}, 3254 expectedRequest: openrtb2.BidRequest{Site: &openrtb2.Site{ID: "SiteId"}, App: &openrtb2.App{ID: "AppId"}, User: &openrtb2.User{ID: "UserId", BuyerUID: "FPDBuyerUID"}}, 3255 }, 3256 } 3257 3258 for _, testCase := range testCases { 3259 bidderRequest := BidderRequest{ 3260 BidderName: openrtb_ext.BidderName(testCase.inputBidderName), 3261 BidderCoreName: openrtb_ext.BidderName(testCase.inputBidderCoreName), 3262 IsRequestAlias: testCase.inputBidderIsRequestAlias, 3263 BidRequest: &testCase.inputRequest, 3264 } 3265 applyFPD(testCase.inputFpd, bidderRequest) 3266 assert.Equal(t, testCase.expectedRequest, testCase.inputRequest, fmt.Sprintf("incorrect request after applying fpd, testcase %s", testCase.description)) 3267 } 3268 } 3269 3270 func TestGetRequestAliases(t *testing.T) { 3271 tests := []struct { 3272 name string 3273 givenRequest openrtb_ext.RequestWrapper 3274 wantAliases map[string]string 3275 wantGVLIDs map[string]uint16 3276 wantError string 3277 }{ 3278 { 3279 name: "nil", 3280 givenRequest: openrtb_ext.RequestWrapper{ 3281 BidRequest: &openrtb2.BidRequest{}, 3282 }, 3283 wantAliases: nil, 3284 wantGVLIDs: nil, 3285 wantError: "", 3286 }, 3287 { 3288 name: "empty", 3289 givenRequest: openrtb_ext.RequestWrapper{ 3290 BidRequest: &openrtb2.BidRequest{ 3291 Ext: json.RawMessage(`{}`), 3292 }, 3293 }, 3294 wantAliases: nil, 3295 wantGVLIDs: nil, 3296 wantError: "", 3297 }, 3298 { 3299 name: "empty-prebid", 3300 givenRequest: openrtb_ext.RequestWrapper{ 3301 BidRequest: &openrtb2.BidRequest{ 3302 Ext: json.RawMessage(`{"prebid":{}}`), 3303 }, 3304 }, 3305 wantAliases: nil, 3306 wantGVLIDs: nil, 3307 wantError: "", 3308 }, 3309 { 3310 name: "aliases-and-gvlids", 3311 givenRequest: openrtb_ext.RequestWrapper{ 3312 BidRequest: &openrtb2.BidRequest{ 3313 Ext: json.RawMessage(`{"prebid":{"aliases":{"alias1":"bidder1"}, "aliasgvlids":{"alias1":1}}}`), 3314 }, 3315 }, 3316 wantAliases: map[string]string{"alias1": "bidder1"}, 3317 wantGVLIDs: map[string]uint16{"alias1": 1}, 3318 wantError: "", 3319 }, 3320 { 3321 name: "malformed", 3322 givenRequest: openrtb_ext.RequestWrapper{ 3323 BidRequest: &openrtb2.BidRequest{ 3324 Ext: json.RawMessage(`malformed`), 3325 }, 3326 }, 3327 wantAliases: nil, 3328 wantGVLIDs: nil, 3329 wantError: "request.ext is invalid", 3330 }, 3331 } 3332 for _, test := range tests { 3333 t.Run(test.name, func(t *testing.T) { 3334 gotAliases, gotGVLIDs, err := getRequestAliases(&test.givenRequest) 3335 3336 assert.Equal(t, test.wantAliases, gotAliases, "aliases") 3337 assert.Equal(t, test.wantGVLIDs, gotGVLIDs, "gvlids") 3338 3339 if len(test.wantError) > 0 { 3340 require.Len(t, err, 1, "error-len") 3341 assert.EqualError(t, err[0], test.wantError, "error") 3342 } else { 3343 assert.Empty(t, err, "error") 3344 } 3345 }) 3346 } 3347 } 3348 3349 func TestBuildExtData(t *testing.T) { 3350 testCases := []struct { 3351 description string 3352 input []byte 3353 expectedRes string 3354 }{ 3355 { 3356 description: "Input object with int value", 3357 input: []byte(`{"someData": 123}`), 3358 expectedRes: `{"data": {"someData": 123}}`, 3359 }, 3360 { 3361 description: "Input object with bool value", 3362 input: []byte(`{"someData": true}`), 3363 expectedRes: `{"data": {"someData": true}}`, 3364 }, 3365 { 3366 description: "Input object with string value", 3367 input: []byte(`{"someData": "true"}`), 3368 expectedRes: `{"data": {"someData": "true"}}`, 3369 }, 3370 { 3371 description: "No input object", 3372 input: []byte(`{}`), 3373 expectedRes: `{"data": {}}`, 3374 }, 3375 { 3376 description: "Input object with object value", 3377 input: []byte(`{"someData": {"moreFpdData": "fpddata"}}`), 3378 expectedRes: `{"data": {"someData": {"moreFpdData": "fpddata"}}}`, 3379 }, 3380 } 3381 3382 for _, test := range testCases { 3383 actualRes := WrapJSONInData(test.input) 3384 assert.JSONEq(t, test.expectedRes, string(actualRes), "Incorrect result data") 3385 } 3386 } 3387 3388 func TestCleanOpenRTBRequestsFilterBidderRequestExt(t *testing.T) { 3389 testCases := []struct { 3390 desc string 3391 inExt json.RawMessage 3392 inCfgABC *openrtb_ext.ExtAlternateBidderCodes 3393 wantExt []json.RawMessage 3394 wantError bool 3395 }{ 3396 { 3397 desc: "Nil request ext, default account alternatebiddercodes config (nil)", 3398 inExt: nil, 3399 inCfgABC: nil, 3400 wantExt: nil, 3401 wantError: false, 3402 }, 3403 { 3404 desc: "Nil request ext, default account alternatebiddercodes config (explicity defined)", 3405 inExt: nil, 3406 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, 3407 wantExt: []json.RawMessage{ 3408 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3409 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3410 }, 3411 wantError: false, 3412 }, 3413 { 3414 desc: "request ext, default account alternatebiddercodes config (explicity defined)", 3415 inExt: json.RawMessage(`{"prebid":{}}`), 3416 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, 3417 wantExt: []json.RawMessage{ 3418 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3419 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3420 }, 3421 wantError: false, 3422 }, 3423 { 3424 desc: "Nil request ext, account alternatebiddercodes config disabled with biddercodes defined", 3425 inExt: nil, 3426 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{ 3427 Enabled: false, 3428 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 3429 "pubmatic": {Enabled: true}, 3430 }, 3431 }, 3432 wantExt: []json.RawMessage{ 3433 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3434 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":null}}}}}`), 3435 }, 3436 wantError: false, 3437 }, 3438 { 3439 desc: "Nil request ext, account alternatebiddercodes config disabled with biddercodes defined (not participant bidder)", 3440 inExt: nil, 3441 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{ 3442 Enabled: false, 3443 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 3444 "ix": {Enabled: true}, 3445 }, 3446 }, 3447 wantExt: []json.RawMessage{ 3448 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3449 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3450 }, 3451 wantError: false, 3452 }, 3453 { 3454 desc: "Nil request ext, alternatebiddercodes config enabled but bidder not present", 3455 inExt: nil, 3456 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true}, 3457 wantExt: []json.RawMessage{ 3458 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`), 3459 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`), 3460 }, 3461 wantError: false, 3462 }, 3463 { 3464 desc: "request ext with default alternatebiddercodes values (nil)", 3465 inExt: json.RawMessage(`{"prebid":{}}`), 3466 inCfgABC: nil, 3467 wantExt: nil, 3468 wantError: false, 3469 }, 3470 { 3471 desc: "request ext w/o alternatebiddercodes", 3472 inExt: json.RawMessage(`{"prebid":{}}`), 3473 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{}, 3474 wantExt: []json.RawMessage{ 3475 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3476 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":false,"bidders":null}}}`), 3477 }, 3478 wantError: false, 3479 }, 3480 { 3481 desc: "request ext having alternatebiddercodes for only one bidder", 3482 inExt: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), 3483 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, 3484 wantExt: []json.RawMessage{ 3485 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`), 3486 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), 3487 }, 3488 wantError: false, 3489 }, 3490 { 3491 desc: "request ext having alternatebiddercodes for multiple bidder", 3492 inExt: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]},"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), 3493 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, 3494 wantExt: []json.RawMessage{ 3495 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), 3496 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), 3497 }, 3498 wantError: false, 3499 }, 3500 { 3501 desc: "request ext having alternatebiddercodes for multiple bidder (config alternatebiddercodes not defined)", 3502 inExt: json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]},"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), 3503 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{Enabled: false}, 3504 wantExt: []json.RawMessage{ 3505 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"appnexus":{"enabled":true,"allowedbiddercodes":["ix"]}}}}}`), 3506 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), 3507 }, 3508 wantError: false, 3509 }, 3510 { 3511 desc: "Nil request ext, alternatebiddercodes config enabled with bidder code for only one bidder", 3512 inExt: nil, 3513 inCfgABC: &openrtb_ext.ExtAlternateBidderCodes{ 3514 Enabled: true, 3515 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 3516 "pubmatic": { 3517 Enabled: true, 3518 AllowedBidderCodes: []string{"groupm"}, 3519 }, 3520 }, 3521 }, 3522 wantExt: []json.RawMessage{ 3523 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":null}}}`), 3524 json.RawMessage(`{"prebid":{"alternatebiddercodes":{"enabled":true,"bidders":{"pubmatic":{"enabled":true,"allowedbiddercodes":["groupm"]}}}}}`), 3525 }, 3526 wantError: false, 3527 }, 3528 } 3529 3530 for _, test := range testCases { 3531 req := newBidRequestWithBidderParams(t) 3532 req.Ext = nil 3533 var extRequest *openrtb_ext.ExtRequest 3534 if test.inExt != nil { 3535 req.Ext = test.inExt 3536 extRequest = &openrtb_ext.ExtRequest{} 3537 err := jsonutil.UnmarshalValid(req.Ext, extRequest) 3538 assert.NoErrorf(t, err, test.desc+":Error unmarshaling inExt") 3539 } 3540 3541 auctionReq := AuctionRequest{ 3542 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: req}, 3543 UserSyncs: &emptyUsersync{}, 3544 Account: config.Account{AlternateBidderCodes: test.inCfgABC}, 3545 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 3546 } 3547 gdprPermissionsBuilder := fakePermissionsBuilder{ 3548 permissions: &permissionsMock{ 3549 allowAllBidders: true, 3550 }, 3551 }.Builder 3552 3553 reqSplitter := &requestSplitter{ 3554 bidderToSyncerKey: map[string]string{}, 3555 me: &metrics.MetricsEngineMock{}, 3556 privacyConfig: config.Privacy{}, 3557 gdprPermsBuilder: gdprPermissionsBuilder, 3558 hostSChainNode: nil, 3559 bidderInfo: config.BidderInfos{}, 3560 } 3561 3562 bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, gdpr.SignalNo, false, map[string]float64{}) 3563 assert.Equal(t, test.wantError, len(errs) != 0, test.desc) 3564 sort.Slice(bidderRequests, func(i, j int) bool { 3565 return bidderRequests[i].BidderCoreName < bidderRequests[j].BidderCoreName 3566 }) 3567 for i, wantBidderRequest := range test.wantExt { 3568 assert.Equal(t, wantBidderRequest, bidderRequests[i].BidRequest.Ext, test.desc+" : "+string(bidderRequests[i].BidderCoreName)+"\n\t\tGotRequestExt : "+string(bidderRequests[i].BidRequest.Ext)) 3569 } 3570 } 3571 } 3572 3573 type GPPMockSection struct { 3574 sectionID constants.SectionID 3575 value string 3576 } 3577 3578 func (gs GPPMockSection) GetID() constants.SectionID { 3579 return gs.sectionID 3580 } 3581 3582 func (gs GPPMockSection) GetValue() string { 3583 return gs.value 3584 } 3585 3586 func (gs GPPMockSection) Encode(bool) []byte { 3587 return nil 3588 } 3589 3590 func TestGdprFromGPP(t *testing.T) { 3591 testCases := []struct { 3592 name string 3593 initialRequest *openrtb2.BidRequest 3594 gpp gpplib.GppContainer 3595 expectedRequest *openrtb2.BidRequest 3596 }{ 3597 { 3598 name: "Empty", // Empty Request 3599 initialRequest: &openrtb2.BidRequest{}, 3600 gpp: gpplib.GppContainer{}, 3601 expectedRequest: &openrtb2.BidRequest{}, 3602 }, 3603 { 3604 name: "GDPR_Downgrade", // GDPR from GPP, into empty 3605 initialRequest: &openrtb2.BidRequest{ 3606 Regs: &openrtb2.Regs{ 3607 GPPSID: []int8{2}, 3608 }, 3609 }, 3610 gpp: gpplib.GppContainer{ 3611 SectionTypes: []constants.SectionID{2}, 3612 Sections: []gpplib.Section{ 3613 GPPMockSection{ 3614 sectionID: 2, 3615 value: "GDPRConsent", 3616 }, 3617 }, 3618 }, 3619 expectedRequest: &openrtb2.BidRequest{ 3620 Regs: &openrtb2.Regs{ 3621 GPPSID: []int8{2}, 3622 GDPR: ptrutil.ToPtr[int8](1), 3623 }, 3624 User: &openrtb2.User{ 3625 Consent: "GDPRConsent", 3626 }, 3627 }, 3628 }, 3629 { 3630 name: "GDPR_Downgrade", // GDPR from GPP, into empty legacy, existing objects 3631 initialRequest: &openrtb2.BidRequest{ 3632 Regs: &openrtb2.Regs{ 3633 GPPSID: []int8{2}, 3634 USPrivacy: "LegacyUSP", 3635 }, 3636 User: &openrtb2.User{ 3637 ID: "1234", 3638 }, 3639 }, 3640 gpp: gpplib.GppContainer{ 3641 SectionTypes: []constants.SectionID{2}, 3642 Sections: []gpplib.Section{ 3643 GPPMockSection{ 3644 sectionID: 2, 3645 value: "GDPRConsent", 3646 }, 3647 }, 3648 }, 3649 expectedRequest: &openrtb2.BidRequest{ 3650 Regs: &openrtb2.Regs{ 3651 GPPSID: []int8{2}, 3652 GDPR: ptrutil.ToPtr[int8](1), 3653 USPrivacy: "LegacyUSP", 3654 }, 3655 User: &openrtb2.User{ 3656 ID: "1234", 3657 Consent: "GDPRConsent", 3658 }, 3659 }, 3660 }, 3661 { 3662 name: "Downgrade_Blocked_By_Existing", // GDPR from GPP blocked by existing GDPR", 3663 initialRequest: &openrtb2.BidRequest{ 3664 Regs: &openrtb2.Regs{ 3665 GPPSID: []int8{2}, 3666 GDPR: ptrutil.ToPtr[int8](1), 3667 }, 3668 User: &openrtb2.User{ 3669 Consent: "LegacyConsent", 3670 }, 3671 }, 3672 gpp: gpplib.GppContainer{ 3673 SectionTypes: []constants.SectionID{2}, 3674 Sections: []gpplib.Section{ 3675 GPPMockSection{ 3676 sectionID: 2, 3677 value: "GDPRConsent", 3678 }, 3679 }, 3680 }, 3681 expectedRequest: &openrtb2.BidRequest{ 3682 Regs: &openrtb2.Regs{ 3683 GPPSID: []int8{2}, 3684 GDPR: ptrutil.ToPtr[int8](1), 3685 }, 3686 User: &openrtb2.User{ 3687 Consent: "LegacyConsent", 3688 }, 3689 }, 3690 }, 3691 { 3692 name: "Downgrade_Partial", // GDPR from GPP partially blocked by existing GDPR 3693 initialRequest: &openrtb2.BidRequest{ 3694 Regs: &openrtb2.Regs{ 3695 GPPSID: []int8{2}, 3696 GDPR: ptrutil.ToPtr[int8](0), 3697 }, 3698 }, 3699 gpp: gpplib.GppContainer{ 3700 SectionTypes: []constants.SectionID{2}, 3701 Sections: []gpplib.Section{ 3702 GPPMockSection{ 3703 sectionID: 2, 3704 value: "GDPRConsent", 3705 }, 3706 }, 3707 }, 3708 expectedRequest: &openrtb2.BidRequest{ 3709 Regs: &openrtb2.Regs{ 3710 GPPSID: []int8{2}, 3711 GDPR: ptrutil.ToPtr[int8](0), 3712 }, 3713 User: &openrtb2.User{ 3714 Consent: "GDPRConsent", 3715 }, 3716 }, 3717 }, 3718 { 3719 name: "No_GDPR", // Downgrade not possible due to missing GDPR 3720 initialRequest: &openrtb2.BidRequest{ 3721 Regs: &openrtb2.Regs{ 3722 GPPSID: []int8{6}, 3723 }, 3724 }, 3725 gpp: gpplib.GppContainer{ 3726 SectionTypes: []constants.SectionID{6}, 3727 Sections: []gpplib.Section{ 3728 GPPMockSection{ 3729 sectionID: 6, 3730 value: "USPrivacy", 3731 }, 3732 }, 3733 }, 3734 expectedRequest: &openrtb2.BidRequest{ 3735 Regs: &openrtb2.Regs{ 3736 GPPSID: []int8{6}, 3737 GDPR: ptrutil.ToPtr[int8](0), 3738 }, 3739 }, 3740 }, 3741 { 3742 name: "No_SID", // GDPR from GPP partially blocked by no SID 3743 initialRequest: &openrtb2.BidRequest{ 3744 Regs: &openrtb2.Regs{ 3745 GPPSID: []int8{6}, 3746 }, 3747 }, 3748 gpp: gpplib.GppContainer{ 3749 SectionTypes: []constants.SectionID{2, 6}, 3750 Sections: []gpplib.Section{ 3751 GPPMockSection{ 3752 sectionID: 2, 3753 value: "GDPRConsent", 3754 }, 3755 GPPMockSection{ 3756 sectionID: 6, 3757 value: "USPrivacy", 3758 }, 3759 }, 3760 }, 3761 expectedRequest: &openrtb2.BidRequest{ 3762 Regs: &openrtb2.Regs{ 3763 GPPSID: []int8{6}, 3764 GDPR: ptrutil.ToPtr[int8](0), 3765 }, 3766 User: &openrtb2.User{ 3767 Consent: "GDPRConsent", 3768 }, 3769 }, 3770 }, 3771 { 3772 name: "GDPR_Nil_SID", // GDPR from GPP, into empty, but with nil SID 3773 initialRequest: &openrtb2.BidRequest{}, 3774 gpp: gpplib.GppContainer{ 3775 SectionTypes: []constants.SectionID{2}, 3776 Sections: []gpplib.Section{ 3777 GPPMockSection{ 3778 sectionID: 2, 3779 value: "GDPRConsent", 3780 }, 3781 }, 3782 }, 3783 expectedRequest: &openrtb2.BidRequest{ 3784 User: &openrtb2.User{ 3785 Consent: "GDPRConsent", 3786 }, 3787 }, 3788 }, 3789 { 3790 name: "Downgrade_Nil_SID_Blocked_By_Existing", // GDPR from GPP blocked by existing GDPR, with nil SID", 3791 initialRequest: &openrtb2.BidRequest{ 3792 Regs: &openrtb2.Regs{ 3793 GDPR: ptrutil.ToPtr[int8](1), 3794 }, 3795 User: &openrtb2.User{ 3796 Consent: "LegacyConsent", 3797 }, 3798 }, 3799 gpp: gpplib.GppContainer{ 3800 SectionTypes: []constants.SectionID{2}, 3801 Sections: []gpplib.Section{ 3802 GPPMockSection{ 3803 sectionID: 2, 3804 value: "GDPRConsent", 3805 }, 3806 }, 3807 }, 3808 expectedRequest: &openrtb2.BidRequest{ 3809 Regs: &openrtb2.Regs{ 3810 GDPR: ptrutil.ToPtr[int8](1), 3811 }, 3812 User: &openrtb2.User{ 3813 Consent: "LegacyConsent", 3814 }, 3815 }, 3816 }, 3817 } 3818 3819 for _, test := range testCases { 3820 t.Run(test.name, func(t *testing.T) { 3821 setLegacyGDPRFromGPP(test.initialRequest, test.gpp) 3822 assert.Equal(t, test.expectedRequest, test.initialRequest) 3823 }) 3824 } 3825 } 3826 3827 func TestPrivacyFromGPP(t *testing.T) { 3828 testCases := []struct { 3829 name string 3830 initialRequest *openrtb2.BidRequest 3831 gpp gpplib.GppContainer 3832 expectedRequest *openrtb2.BidRequest 3833 }{ 3834 { 3835 name: "Empty", // Empty Request 3836 initialRequest: &openrtb2.BidRequest{}, 3837 gpp: gpplib.GppContainer{}, 3838 expectedRequest: &openrtb2.BidRequest{}, 3839 }, 3840 { 3841 name: "Privacy_Downgrade", // US Privacy from GPP, into empty 3842 initialRequest: &openrtb2.BidRequest{ 3843 Regs: &openrtb2.Regs{ 3844 GPPSID: []int8{6}, 3845 }, 3846 }, 3847 gpp: gpplib.GppContainer{ 3848 SectionTypes: []constants.SectionID{6}, 3849 Sections: []gpplib.Section{ 3850 GPPMockSection{ 3851 sectionID: 6, 3852 value: "USPrivacy", 3853 }, 3854 }, 3855 }, 3856 expectedRequest: &openrtb2.BidRequest{ 3857 Regs: &openrtb2.Regs{ 3858 GPPSID: []int8{6}, 3859 USPrivacy: "USPrivacy", 3860 }, 3861 }, 3862 }, 3863 { 3864 name: "Downgrade_Blocked_By_Existing", // US Privacy from GPP blocked by existing US Privacy 3865 initialRequest: &openrtb2.BidRequest{ 3866 Regs: &openrtb2.Regs{ 3867 GPPSID: []int8{6}, 3868 USPrivacy: "LegacyPrivacy", 3869 }, 3870 }, 3871 gpp: gpplib.GppContainer{ 3872 SectionTypes: []constants.SectionID{6}, 3873 Sections: []gpplib.Section{ 3874 GPPMockSection{ 3875 sectionID: 6, 3876 value: "USPrivacy", 3877 }, 3878 }, 3879 }, 3880 expectedRequest: &openrtb2.BidRequest{ 3881 Regs: &openrtb2.Regs{ 3882 GPPSID: []int8{6}, 3883 USPrivacy: "LegacyPrivacy", 3884 }, 3885 }, 3886 }, 3887 { 3888 name: "No_USPrivacy", // Downgrade not possible due to missing USPrivacy 3889 initialRequest: &openrtb2.BidRequest{ 3890 Regs: &openrtb2.Regs{ 3891 GPPSID: []int8{2}, 3892 }, 3893 }, 3894 gpp: gpplib.GppContainer{ 3895 SectionTypes: []constants.SectionID{2}, 3896 Sections: []gpplib.Section{ 3897 GPPMockSection{ 3898 sectionID: 2, 3899 value: "GDPRConsent", 3900 }, 3901 }, 3902 }, 3903 expectedRequest: &openrtb2.BidRequest{ 3904 Regs: &openrtb2.Regs{ 3905 GPPSID: []int8{2}, 3906 }, 3907 }, 3908 }, 3909 { 3910 name: "No_SID", // US Privacy from GPP partially blocked by no SID 3911 initialRequest: &openrtb2.BidRequest{ 3912 Regs: &openrtb2.Regs{ 3913 GPPSID: []int8{2}, 3914 }, 3915 }, 3916 gpp: gpplib.GppContainer{ 3917 SectionTypes: []constants.SectionID{2, 6}, 3918 Sections: []gpplib.Section{ 3919 GPPMockSection{ 3920 sectionID: 2, 3921 value: "GDPRConsent", 3922 }, 3923 GPPMockSection{ 3924 sectionID: 6, 3925 value: "USPrivacy", 3926 }, 3927 }, 3928 }, 3929 expectedRequest: &openrtb2.BidRequest{ 3930 Regs: &openrtb2.Regs{ 3931 GPPSID: []int8{2}, 3932 }, 3933 }, 3934 }, 3935 } 3936 3937 for _, test := range testCases { 3938 t.Run(test.name, func(t *testing.T) { 3939 setLegacyUSPFromGPP(test.initialRequest, test.gpp) 3940 assert.Equal(t, test.expectedRequest, test.initialRequest) 3941 }) 3942 } 3943 } 3944 3945 func Test_isBidderInExtAlternateBidderCodes(t *testing.T) { 3946 type args struct { 3947 adapter string 3948 currentMultiBidBidder string 3949 adapterABC *openrtb_ext.ExtAlternateBidderCodes 3950 } 3951 tests := []struct { 3952 name string 3953 args args 3954 want bool 3955 }{ 3956 { 3957 name: "alternatebiddercodes not defined", 3958 want: false, 3959 }, 3960 { 3961 name: "adapter not defined in alternatebiddercodes", 3962 args: args{ 3963 adapter: string(openrtb_ext.BidderPubmatic), 3964 adapterABC: &openrtb_ext.ExtAlternateBidderCodes{ 3965 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{string(openrtb_ext.BidderAppnexus): {}}, 3966 }, 3967 }, 3968 want: false, 3969 }, 3970 { 3971 name: "adapter defined in alternatebiddercodes but currentMultiBidBidder not in AllowedBidders list", 3972 args: args{ 3973 adapter: string(openrtb_ext.BidderPubmatic), 3974 currentMultiBidBidder: "groupm", 3975 adapterABC: &openrtb_ext.ExtAlternateBidderCodes{ 3976 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 3977 string(openrtb_ext.BidderPubmatic): { 3978 AllowedBidderCodes: []string{string(openrtb_ext.BidderAppnexus)}, 3979 }, 3980 }, 3981 }, 3982 }, 3983 want: false, 3984 }, 3985 { 3986 name: "adapter defined in alternatebiddercodes with currentMultiBidBidder mentioned in AllowedBidders list", 3987 args: args{ 3988 adapter: string(openrtb_ext.BidderPubmatic), 3989 currentMultiBidBidder: "groupm", 3990 adapterABC: &openrtb_ext.ExtAlternateBidderCodes{ 3991 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 3992 string(openrtb_ext.BidderPubmatic): { 3993 AllowedBidderCodes: []string{"groupm"}, 3994 }, 3995 }, 3996 }, 3997 }, 3998 want: true, 3999 }, 4000 { 4001 name: "adapter defined in alternatebiddercodes with AllowedBidders list as *", 4002 args: args{ 4003 adapter: string(openrtb_ext.BidderPubmatic), 4004 currentMultiBidBidder: "groupm", 4005 adapterABC: &openrtb_ext.ExtAlternateBidderCodes{ 4006 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4007 string(openrtb_ext.BidderPubmatic): { 4008 AllowedBidderCodes: []string{"*"}, 4009 }, 4010 }, 4011 }, 4012 }, 4013 want: true, 4014 }, 4015 } 4016 for _, tt := range tests { 4017 t.Run(tt.name, func(t *testing.T) { 4018 if got := isBidderInExtAlternateBidderCodes(tt.args.adapter, tt.args.currentMultiBidBidder, tt.args.adapterABC); got != tt.want { 4019 t.Errorf("isBidderInExtAlternateBidderCodes() = %v, want %v", got, tt.want) 4020 } 4021 }) 4022 } 4023 } 4024 4025 func Test_buildRequestExtMultiBid(t *testing.T) { 4026 type args struct { 4027 adapter string 4028 reqMultiBid []*openrtb_ext.ExtMultiBid 4029 adapterABC *openrtb_ext.ExtAlternateBidderCodes 4030 } 4031 tests := []struct { 4032 name string 4033 args args 4034 want []*openrtb_ext.ExtMultiBid 4035 }{ 4036 { 4037 name: "multi-bid config not defined", 4038 args: args{ 4039 adapter: string(openrtb_ext.BidderPubmatic), 4040 reqMultiBid: nil, 4041 }, 4042 want: nil, 4043 }, 4044 { 4045 name: "adapter not defined in multi-bid config", 4046 args: args{ 4047 adapter: string(openrtb_ext.BidderPubmatic), 4048 reqMultiBid: []*openrtb_ext.ExtMultiBid{ 4049 { 4050 Bidder: string(openrtb_ext.BidderAppnexus), 4051 MaxBids: ptrutil.ToPtr(2), 4052 }, 4053 }, 4054 }, 4055 want: nil, 4056 }, 4057 { 4058 name: "adapter defined in multi-bid config as Bidder object along with other bidders", 4059 args: args{ 4060 adapter: string(openrtb_ext.BidderPubmatic), 4061 reqMultiBid: []*openrtb_ext.ExtMultiBid{ 4062 { 4063 Bidder: string(openrtb_ext.BidderAppnexus), 4064 MaxBids: ptrutil.ToPtr(3), 4065 }, 4066 { 4067 Bidder: string(openrtb_ext.BidderPubmatic), 4068 MaxBids: ptrutil.ToPtr(2), 4069 }, 4070 { 4071 Bidders: []string{string(openrtb_ext.Bidder33Across), string(openrtb_ext.BidderRubicon)}, 4072 MaxBids: ptrutil.ToPtr(2), 4073 }, 4074 }, 4075 }, 4076 want: []*openrtb_ext.ExtMultiBid{ 4077 { 4078 Bidder: string(openrtb_ext.BidderPubmatic), 4079 MaxBids: ptrutil.ToPtr(2), 4080 }, 4081 }, 4082 }, 4083 { 4084 name: "adapter defined in multi-bid config as a entry of Bidders list along with other bidders", 4085 args: args{ 4086 adapter: string(openrtb_ext.BidderRubicon), 4087 reqMultiBid: []*openrtb_ext.ExtMultiBid{ 4088 { 4089 Bidder: string(openrtb_ext.BidderAppnexus), 4090 MaxBids: ptrutil.ToPtr(3), 4091 }, 4092 { 4093 Bidder: string(openrtb_ext.BidderPubmatic), 4094 MaxBids: ptrutil.ToPtr(2), 4095 }, 4096 { 4097 Bidders: []string{string(openrtb_ext.Bidder33Across), string(openrtb_ext.BidderRubicon)}, 4098 MaxBids: ptrutil.ToPtr(4), 4099 }, 4100 }, 4101 }, 4102 want: []*openrtb_ext.ExtMultiBid{ 4103 { 4104 Bidders: []string{string(openrtb_ext.BidderRubicon)}, 4105 MaxBids: ptrutil.ToPtr(4), 4106 }, 4107 }, 4108 }, 4109 { 4110 name: "adapter defined in multi-bid config as Bidder object along with other bidders with alternateBiddercode", 4111 args: args{ 4112 adapter: string(openrtb_ext.BidderPubmatic), 4113 reqMultiBid: []*openrtb_ext.ExtMultiBid{ 4114 { 4115 Bidder: "groupm", 4116 MaxBids: ptrutil.ToPtr(3), 4117 }, 4118 { 4119 Bidder: string(openrtb_ext.BidderPubmatic), 4120 MaxBids: ptrutil.ToPtr(2), 4121 }, 4122 { 4123 Bidders: []string{string(openrtb_ext.Bidder33Across), string(openrtb_ext.BidderRubicon)}, 4124 MaxBids: ptrutil.ToPtr(2), 4125 }, 4126 }, 4127 adapterABC: &openrtb_ext.ExtAlternateBidderCodes{ 4128 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4129 string(openrtb_ext.BidderPubmatic): { 4130 AllowedBidderCodes: []string{"groupm"}, 4131 }, 4132 }, 4133 }, 4134 }, 4135 want: []*openrtb_ext.ExtMultiBid{ 4136 { 4137 Bidder: "groupm", 4138 MaxBids: ptrutil.ToPtr(3), 4139 }, 4140 { 4141 Bidder: string(openrtb_ext.BidderPubmatic), 4142 MaxBids: ptrutil.ToPtr(2), 4143 }, 4144 }, 4145 }, 4146 { 4147 name: "adapter defined in multi-bid config as a entry of Bidders list along with other bidders with alternateBiddercode", 4148 args: args{ 4149 adapter: string(openrtb_ext.BidderAppnexus), 4150 reqMultiBid: []*openrtb_ext.ExtMultiBid{ 4151 { 4152 Bidder: "groupm", 4153 MaxBids: ptrutil.ToPtr(3), 4154 }, 4155 { 4156 Bidder: string(openrtb_ext.BidderPubmatic), 4157 MaxBids: ptrutil.ToPtr(2), 4158 }, 4159 { 4160 Bidders: []string{string(openrtb_ext.Bidder33Across), string(openrtb_ext.BidderAppnexus)}, 4161 MaxBids: ptrutil.ToPtr(4), 4162 }, 4163 }, 4164 adapterABC: &openrtb_ext.ExtAlternateBidderCodes{ 4165 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4166 string(openrtb_ext.BidderAppnexus): { 4167 AllowedBidderCodes: []string{"groupm"}, 4168 }, 4169 }, 4170 }, 4171 }, 4172 want: []*openrtb_ext.ExtMultiBid{ 4173 { 4174 Bidder: "groupm", 4175 MaxBids: ptrutil.ToPtr(3), 4176 }, 4177 { 4178 Bidders: []string{string(openrtb_ext.BidderAppnexus)}, 4179 MaxBids: ptrutil.ToPtr(4), 4180 }, 4181 }, 4182 }, 4183 { 4184 name: "adapter defined in multi-bid config as Bidder object along with other bidders with alternateBiddercode.AllowedBidders as *", 4185 args: args{ 4186 adapter: string(openrtb_ext.BidderPubmatic), 4187 reqMultiBid: []*openrtb_ext.ExtMultiBid{ 4188 { 4189 Bidder: "groupm", 4190 MaxBids: ptrutil.ToPtr(3), 4191 }, 4192 { 4193 Bidder: string(openrtb_ext.BidderPubmatic), 4194 MaxBids: ptrutil.ToPtr(2), 4195 }, 4196 { 4197 Bidders: []string{string(openrtb_ext.Bidder33Across), string(openrtb_ext.BidderRubicon)}, 4198 MaxBids: ptrutil.ToPtr(2), 4199 }, 4200 }, 4201 adapterABC: &openrtb_ext.ExtAlternateBidderCodes{ 4202 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4203 string(openrtb_ext.BidderPubmatic): { 4204 AllowedBidderCodes: []string{"*"}, 4205 }, 4206 }, 4207 }, 4208 }, 4209 want: []*openrtb_ext.ExtMultiBid{ 4210 { 4211 Bidder: "groupm", 4212 MaxBids: ptrutil.ToPtr(3), 4213 }, 4214 { 4215 Bidder: string(openrtb_ext.BidderPubmatic), 4216 MaxBids: ptrutil.ToPtr(2), 4217 }, 4218 { 4219 Bidders: []string{string(openrtb_ext.Bidder33Across)}, 4220 MaxBids: ptrutil.ToPtr(2), 4221 }, 4222 { 4223 Bidders: []string{string(openrtb_ext.BidderRubicon)}, 4224 MaxBids: ptrutil.ToPtr(2), 4225 }, 4226 }, 4227 }, 4228 { 4229 name: "adapter defined in multi-bid config as a entry of Bidders list along with other bidders with alternateBiddercode.AllowedBidders as *", 4230 args: args{ 4231 adapter: string(openrtb_ext.BidderAppnexus), 4232 reqMultiBid: []*openrtb_ext.ExtMultiBid{ 4233 { 4234 Bidder: "groupm", 4235 MaxBids: ptrutil.ToPtr(3), 4236 }, 4237 { 4238 Bidder: string(openrtb_ext.BidderPubmatic), 4239 MaxBids: ptrutil.ToPtr(2), 4240 }, 4241 { 4242 Bidders: []string{string(openrtb_ext.Bidder33Across), string(openrtb_ext.BidderAppnexus)}, 4243 MaxBids: ptrutil.ToPtr(4), 4244 }, 4245 }, 4246 adapterABC: &openrtb_ext.ExtAlternateBidderCodes{ 4247 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4248 string(openrtb_ext.BidderAppnexus): { 4249 AllowedBidderCodes: []string{"*"}, 4250 }, 4251 }, 4252 }, 4253 }, 4254 want: []*openrtb_ext.ExtMultiBid{ 4255 { 4256 Bidder: "groupm", 4257 MaxBids: ptrutil.ToPtr(3), 4258 }, 4259 { 4260 Bidder: string(openrtb_ext.BidderPubmatic), 4261 MaxBids: ptrutil.ToPtr(2), 4262 }, 4263 { 4264 Bidders: []string{string(openrtb_ext.Bidder33Across)}, 4265 MaxBids: ptrutil.ToPtr(4), 4266 }, 4267 { 4268 Bidders: []string{string(openrtb_ext.BidderAppnexus)}, 4269 MaxBids: ptrutil.ToPtr(4), 4270 }, 4271 }, 4272 }, 4273 } 4274 for _, tt := range tests { 4275 t.Run(tt.name, func(t *testing.T) { 4276 got := buildRequestExtMultiBid(tt.args.adapter, tt.args.reqMultiBid, tt.args.adapterABC) 4277 assert.Equal(t, tt.want, got) 4278 }) 4279 } 4280 } 4281 4282 func TestGetPrebidMediaTypeForBid(t *testing.T) { 4283 tests := []struct { 4284 description string 4285 inputBid openrtb2.Bid 4286 expectedBidType openrtb_ext.BidType 4287 expectedError string 4288 }{ 4289 { 4290 description: "Valid bid ext with bid type native", 4291 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`{"prebid": {"type": "native"}}`)}, 4292 expectedBidType: openrtb_ext.BidTypeNative, 4293 }, 4294 { 4295 description: "Valid bid ext with non-existing bid type", 4296 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`{"prebid": {"type": "unknown"}}`)}, 4297 expectedError: "Failed to parse bid mediatype for impression \"impId\", invalid BidType: unknown", 4298 }, 4299 { 4300 description: "Invalid bid ext", 4301 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`[true`)}, 4302 expectedError: "Failed to parse bid mediatype for impression \"impId\", expect { or n, but found [", 4303 }, 4304 { 4305 description: "Bid ext is nil", 4306 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: nil}, 4307 expectedError: "Failed to parse bid mediatype for impression \"impId\"", 4308 }, 4309 { 4310 description: "Empty bid ext", 4311 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`{}`)}, 4312 expectedError: "Failed to parse bid mediatype for impression \"impId\"", 4313 }, 4314 } 4315 for _, tt := range tests { 4316 t.Run(tt.description, func(t *testing.T) { 4317 bidType, err := getPrebidMediaTypeForBid(tt.inputBid) 4318 if len(tt.expectedError) == 0 { 4319 assert.Equal(t, tt.expectedBidType, bidType) 4320 } else { 4321 assert.Equal(t, tt.expectedError, err.Error()) 4322 } 4323 4324 }) 4325 } 4326 } 4327 4328 func TestGetMediaTypeForBid(t *testing.T) { 4329 tests := []struct { 4330 description string 4331 inputBid openrtb2.Bid 4332 expectedBidType openrtb_ext.BidType 4333 expectedError string 4334 }{ 4335 { 4336 description: "Valid bid ext with bid type native", 4337 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`{"prebid": {"type": "native"}}`)}, 4338 expectedBidType: openrtb_ext.BidTypeNative, 4339 }, 4340 { 4341 description: "invalid bid ext", 4342 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", Ext: json.RawMessage(`{"prebid"`)}, 4343 expectedError: "Failed to parse bid mediatype for impression \"impId\", expect :, but found \x00", 4344 }, 4345 { 4346 description: "Valid bid ext with mtype native", 4347 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", MType: openrtb2.MarkupNative}, 4348 expectedBidType: openrtb_ext.BidTypeNative, 4349 }, 4350 { 4351 description: "Valid bid ext with mtype banner", 4352 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", MType: openrtb2.MarkupBanner}, 4353 expectedBidType: openrtb_ext.BidTypeBanner, 4354 }, 4355 { 4356 description: "Valid bid ext with mtype video", 4357 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", MType: openrtb2.MarkupVideo}, 4358 expectedBidType: openrtb_ext.BidTypeVideo, 4359 }, 4360 { 4361 description: "Valid bid ext with mtype audio", 4362 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", MType: openrtb2.MarkupAudio}, 4363 expectedBidType: openrtb_ext.BidTypeAudio, 4364 }, 4365 { 4366 description: "Valid bid ext with mtype unknown", 4367 inputBid: openrtb2.Bid{ID: "bidId", ImpID: "impId", MType: 8}, 4368 expectedError: "Failed to parse bid mType for impression \"impId\"", 4369 }, 4370 } 4371 for _, tt := range tests { 4372 t.Run(tt.description, func(t *testing.T) { 4373 bidType, err := getMediaTypeForBid(tt.inputBid) 4374 if len(tt.expectedError) == 0 { 4375 assert.Equal(t, tt.expectedBidType, bidType) 4376 } else { 4377 assert.Equal(t, tt.expectedError, err.Error()) 4378 } 4379 4380 }) 4381 } 4382 } 4383 4384 func TestCleanOpenRTBRequestsActivities(t *testing.T) { 4385 expectedUserDefault := openrtb2.User{ 4386 ID: "our-id", 4387 BuyerUID: "their-id", 4388 Yob: 1982, 4389 Gender: "test", 4390 Ext: json.RawMessage(`{"data": 1, "test": 2}`), 4391 Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, 4392 EIDs: []openrtb2.EID{ 4393 {Source: "eids-source"}, 4394 }, 4395 Data: []openrtb2.Data{{ID: "data-id"}}, 4396 } 4397 expectedDeviceDefault := openrtb2.Device{ 4398 UA: deviceUA, 4399 IP: "132.173.230.74", 4400 Language: "EN", 4401 DIDMD5: "DIDMD5", 4402 IFA: "IFA", 4403 DIDSHA1: "DIDSHA1", 4404 DPIDMD5: "DPIDMD5", 4405 DPIDSHA1: "DPIDSHA1", 4406 MACMD5: "MACMD5", 4407 MACSHA1: "MACSHA1", 4408 Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, 4409 } 4410 4411 expectedSourceDefault := openrtb2.Source{ 4412 TID: "testTID", 4413 } 4414 4415 testCases := []struct { 4416 name string 4417 req *openrtb2.BidRequest 4418 privacyConfig config.AccountPrivacy 4419 componentName string 4420 allow bool 4421 expectedReqNumber int 4422 expectedUser openrtb2.User 4423 expectUserScrub bool 4424 expectedDevice openrtb2.Device 4425 expectedSource openrtb2.Source 4426 expectedImpExt json.RawMessage 4427 }{ 4428 { 4429 name: "fetch_bids_request_with_one_bidder_allowed", 4430 req: newBidRequest(t), 4431 privacyConfig: getFetchBidsActivityConfig("appnexus", true), 4432 expectedReqNumber: 1, 4433 expectedUser: expectedUserDefault, 4434 expectedDevice: expectedDeviceDefault, 4435 expectedSource: expectedSourceDefault, 4436 }, 4437 { 4438 name: "fetch_bids_request_with_one_bidder_not_allowed", 4439 req: newBidRequest(t), 4440 privacyConfig: getFetchBidsActivityConfig("appnexus", false), 4441 expectedReqNumber: 0, 4442 expectedUser: expectedUserDefault, 4443 expectedDevice: expectedDeviceDefault, 4444 expectedSource: expectedSourceDefault, 4445 }, 4446 { 4447 name: "transmit_ufpd_allowed", 4448 req: newBidRequest(t), 4449 privacyConfig: getTransmitUFPDActivityConfig("appnexus", true), 4450 expectedReqNumber: 1, 4451 expectedUser: expectedUserDefault, 4452 expectedDevice: expectedDeviceDefault, 4453 expectedSource: expectedSourceDefault, 4454 }, 4455 { 4456 //remove user.eids, user.ext.data.*, user.data.*, user.{id, buyeruid, yob, gender} 4457 //and device-specific IDs 4458 name: "transmit_ufpd_deny", 4459 req: newBidRequest(t), 4460 privacyConfig: getTransmitUFPDActivityConfig("appnexus", false), 4461 expectedReqNumber: 1, 4462 expectedUser: openrtb2.User{ 4463 ID: "", 4464 BuyerUID: "", 4465 Yob: 0, 4466 Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, 4467 EIDs: nil, 4468 Ext: json.RawMessage(`{"test":2}`), 4469 Data: nil, 4470 }, 4471 expectUserScrub: true, 4472 expectedDevice: openrtb2.Device{ 4473 UA: deviceUA, 4474 Language: "EN", 4475 IP: "132.173.230.74", 4476 DIDMD5: "", 4477 IFA: "", 4478 DIDSHA1: "", 4479 DPIDMD5: "", 4480 DPIDSHA1: "", 4481 MACMD5: "", 4482 MACSHA1: "", 4483 Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.456), Lon: ptrutil.ToPtr(11.278)}, 4484 }, 4485 expectedSource: expectedSourceDefault, 4486 }, 4487 { 4488 name: "transmit_precise_geo_allowed", 4489 req: newBidRequest(t), 4490 privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true), 4491 expectedReqNumber: 1, 4492 expectedUser: expectedUserDefault, 4493 expectedDevice: expectedDeviceDefault, 4494 expectedSource: expectedSourceDefault, 4495 }, 4496 { 4497 //round user's geographic location by rounding off IP address and lat/lng data. 4498 //this applies to both device.geo and user.geo 4499 name: "transmit_precise_geo_deny", 4500 req: newBidRequest(t), 4501 privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false), 4502 expectedReqNumber: 1, 4503 expectedUser: openrtb2.User{ 4504 ID: "our-id", 4505 BuyerUID: "their-id", 4506 Yob: 1982, 4507 Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.46), Lon: ptrutil.ToPtr(11.28)}, 4508 Gender: "test", 4509 Ext: json.RawMessage(`{"data": 1, "test": 2}`), 4510 EIDs: []openrtb2.EID{ 4511 {Source: "eids-source"}, 4512 }, 4513 Data: []openrtb2.Data{{ID: "data-id"}}, 4514 }, 4515 expectedDevice: openrtb2.Device{ 4516 UA: deviceUA, 4517 IP: "132.173.0.0", 4518 Language: "EN", 4519 DIDMD5: "DIDMD5", 4520 IFA: "IFA", 4521 DIDSHA1: "DIDSHA1", 4522 DPIDMD5: "DPIDMD5", 4523 DPIDSHA1: "DPIDSHA1", 4524 MACMD5: "MACMD5", 4525 MACSHA1: "MACSHA1", 4526 Geo: &openrtb2.Geo{Lat: ptrutil.ToPtr(123.46), Lon: ptrutil.ToPtr(11.28)}, 4527 }, 4528 expectedSource: expectedSourceDefault, 4529 }, 4530 { 4531 name: "transmit_tid_allowed", 4532 req: newBidRequest(t), 4533 privacyConfig: getTransmitTIDActivityConfig("appnexus", true), 4534 expectedReqNumber: 1, 4535 expectedUser: expectedUserDefault, 4536 expectedDevice: expectedDeviceDefault, 4537 expectedSource: expectedSourceDefault, 4538 }, 4539 { 4540 //remove source.tid and imp.ext.tid 4541 name: "transmit_tid_deny", 4542 req: newBidRequest(t), 4543 privacyConfig: getTransmitTIDActivityConfig("appnexus", false), 4544 expectedReqNumber: 1, 4545 expectedUser: expectedUserDefault, 4546 expectedDevice: expectedDeviceDefault, 4547 expectedSource: openrtb2.Source{ 4548 TID: "", 4549 }, 4550 expectedImpExt: json.RawMessage(`{"bidder": {"placementId": 1}}`), 4551 }, 4552 } 4553 4554 for _, test := range testCases { 4555 t.Run(test.name, func(t *testing.T) { 4556 activities := privacy.NewActivityControl(&test.privacyConfig) 4557 auctionReq := AuctionRequest{ 4558 BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, 4559 UserSyncs: &emptyUsersync{}, 4560 Activities: activities, 4561 Account: config.Account{Privacy: config.AccountPrivacy{ 4562 IPv6Config: config.IPv6{ 4563 AnonKeepBits: 32, 4564 }, 4565 IPv4Config: config.IPv4{ 4566 AnonKeepBits: 16, 4567 }, 4568 }}, 4569 TCF2Config: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 4570 } 4571 4572 metricsMock := metrics.MetricsEngineMock{} 4573 metricsMock.Mock.On("RecordAdapterBuyerUIDScrubbed", mock.Anything).Return() 4574 4575 bidderToSyncerKey := map[string]string{} 4576 reqSplitter := &requestSplitter{ 4577 bidderToSyncerKey: bidderToSyncerKey, 4578 me: &metricsMock, 4579 hostSChainNode: nil, 4580 bidderInfo: config.BidderInfos{}, 4581 } 4582 4583 bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo, false, map[string]float64{}) 4584 assert.Empty(t, errs) 4585 assert.Len(t, bidderRequests, test.expectedReqNumber) 4586 4587 if test.expectedReqNumber == 1 { 4588 assert.Equal(t, &test.expectedUser, bidderRequests[0].BidRequest.User) 4589 assert.Equal(t, &test.expectedDevice, bidderRequests[0].BidRequest.Device) 4590 assert.Equal(t, &test.expectedSource, bidderRequests[0].BidRequest.Source) 4591 4592 if len(test.expectedImpExt) > 0 { 4593 assert.JSONEq(t, string(test.expectedImpExt), string(bidderRequests[0].BidRequest.Imp[0].Ext)) 4594 } 4595 if test.expectUserScrub { 4596 metricsMock.AssertCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) 4597 } else { 4598 metricsMock.AssertNotCalled(t, "RecordAdapterBuyerUIDScrubbed", openrtb_ext.BidderAppnexus) 4599 } 4600 } 4601 }) 4602 } 4603 } 4604 4605 func buildDefaultActivityConfig(componentName string, allow bool) config.Activity { 4606 return config.Activity{ 4607 Default: ptrutil.ToPtr(true), 4608 Rules: []config.ActivityRule{ 4609 { 4610 Allow: allow, 4611 Condition: config.ActivityCondition{ 4612 ComponentName: []string{componentName}, 4613 ComponentType: []string{"bidder"}, 4614 }, 4615 }, 4616 }, 4617 } 4618 } 4619 4620 func getFetchBidsActivityConfig(componentName string, allow bool) config.AccountPrivacy { 4621 return config.AccountPrivacy{ 4622 AllowActivities: &config.AllowActivities{ 4623 FetchBids: buildDefaultActivityConfig(componentName, allow), 4624 }, 4625 } 4626 } 4627 4628 func getTransmitUFPDActivityConfig(componentName string, allow bool) config.AccountPrivacy { 4629 return config.AccountPrivacy{ 4630 AllowActivities: &config.AllowActivities{ 4631 TransmitUserFPD: buildDefaultActivityConfig(componentName, allow), 4632 }, 4633 } 4634 } 4635 4636 func getTransmitPreciseGeoActivityConfig(componentName string, allow bool) config.AccountPrivacy { 4637 return config.AccountPrivacy{ 4638 AllowActivities: &config.AllowActivities{ 4639 TransmitPreciseGeo: buildDefaultActivityConfig(componentName, allow), 4640 }, 4641 } 4642 } 4643 4644 func getTransmitTIDActivityConfig(componentName string, allow bool) config.AccountPrivacy { 4645 return config.AccountPrivacy{ 4646 AllowActivities: &config.AllowActivities{ 4647 TransmitTids: buildDefaultActivityConfig(componentName, allow), 4648 }, 4649 } 4650 } 4651 4652 func TestApplyBidAdjustmentToFloor(t *testing.T) { 4653 type args struct { 4654 allBidderRequests []BidderRequest 4655 bidAdjustmentFactors map[string]float64 4656 } 4657 tests := []struct { 4658 name string 4659 args args 4660 expectedAllBidderRequests []BidderRequest 4661 }{ 4662 { 4663 name: " bidAdjustmentFactor is empty", 4664 args: args{ 4665 allBidderRequests: []BidderRequest{ 4666 { 4667 BidRequest: &openrtb2.BidRequest{ 4668 Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, 4669 }, 4670 BidderName: openrtb_ext.BidderName("appnexus"), 4671 }, 4672 }, 4673 bidAdjustmentFactors: map[string]float64{}, 4674 }, 4675 expectedAllBidderRequests: []BidderRequest{ 4676 { 4677 BidRequest: &openrtb2.BidRequest{ 4678 Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, 4679 }, 4680 BidderName: openrtb_ext.BidderName("appnexus"), 4681 }, 4682 }, 4683 }, 4684 { 4685 name: "bidAdjustmentFactor not present for request bidder", 4686 args: args{ 4687 allBidderRequests: []BidderRequest{ 4688 { 4689 BidRequest: &openrtb2.BidRequest{ 4690 Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, 4691 }, 4692 BidderName: openrtb_ext.BidderName("appnexus"), 4693 }, 4694 }, 4695 bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0}, 4696 }, 4697 expectedAllBidderRequests: []BidderRequest{ 4698 { 4699 BidRequest: &openrtb2.BidRequest{ 4700 Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, 4701 }, 4702 BidderName: openrtb_ext.BidderName("appnexus"), 4703 }, 4704 }, 4705 }, 4706 { 4707 name: "bidAdjustmentFactor present for request bidder", 4708 args: args{ 4709 allBidderRequests: []BidderRequest{ 4710 { 4711 BidRequest: &openrtb2.BidRequest{ 4712 Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, 4713 }, 4714 BidderName: openrtb_ext.BidderName("appnexus"), 4715 }, 4716 }, 4717 bidAdjustmentFactors: map[string]float64{"pubmatic": 1.0, "appnexus": 0.75}, 4718 }, 4719 expectedAllBidderRequests: []BidderRequest{ 4720 { 4721 BidRequest: &openrtb2.BidRequest{ 4722 Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, 4723 }, 4724 BidderName: openrtb_ext.BidderName("appnexus"), 4725 }, 4726 }, 4727 }, 4728 { 4729 name: "bidAdjustmentFactor present only for appnexus request bidder", 4730 args: args{ 4731 allBidderRequests: []BidderRequest{ 4732 { 4733 BidRequest: &openrtb2.BidRequest{ 4734 Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, 4735 }, 4736 BidderName: openrtb_ext.BidderName("appnexus"), 4737 }, 4738 { 4739 BidRequest: &openrtb2.BidRequest{ 4740 Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, 4741 }, 4742 BidderName: openrtb_ext.BidderName("pubmatic"), 4743 }, 4744 }, 4745 bidAdjustmentFactors: map[string]float64{"appnexus": 0.75}, 4746 }, 4747 expectedAllBidderRequests: []BidderRequest{ 4748 { 4749 BidRequest: &openrtb2.BidRequest{ 4750 Imp: []openrtb2.Imp{{BidFloor: 133.33333333333334}, {BidFloor: 200}}, 4751 }, 4752 BidderName: openrtb_ext.BidderName("appnexus"), 4753 }, 4754 { 4755 BidRequest: &openrtb2.BidRequest{ 4756 Imp: []openrtb2.Imp{{BidFloor: 100}, {BidFloor: 150}}, 4757 }, 4758 BidderName: openrtb_ext.BidderName("pubmatic"), 4759 }, 4760 }, 4761 }, 4762 } 4763 for _, tt := range tests { 4764 t.Run(tt.name, func(t *testing.T) { 4765 applyBidAdjustmentToFloor(tt.args.allBidderRequests, tt.args.bidAdjustmentFactors) 4766 assert.Equal(t, tt.expectedAllBidderRequests, tt.args.allBidderRequests, tt.name) 4767 }) 4768 } 4769 } 4770 4771 func TestBuildRequestExtAlternateBidderCodes(t *testing.T) { 4772 type testInput struct { 4773 bidderNameRaw string 4774 accABC *openrtb_ext.ExtAlternateBidderCodes 4775 reqABC *openrtb_ext.ExtAlternateBidderCodes 4776 } 4777 testCases := []struct { 4778 desc string 4779 in testInput 4780 expected *openrtb_ext.ExtAlternateBidderCodes 4781 }{ 4782 { 4783 desc: "No biddername, nil reqABC and accABC", 4784 in: testInput{}, 4785 expected: nil, 4786 }, 4787 { 4788 desc: "No biddername, non-nil reqABC", 4789 in: testInput{ 4790 reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, 4791 }, 4792 expected: &openrtb_ext.ExtAlternateBidderCodes{}, 4793 }, 4794 { 4795 desc: "No biddername, non-nil accABC", 4796 in: testInput{ 4797 accABC: &openrtb_ext.ExtAlternateBidderCodes{}, 4798 }, 4799 expected: &openrtb_ext.ExtAlternateBidderCodes{}, 4800 }, 4801 { 4802 desc: "No biddername, non-nil reqABC nor accABC", 4803 in: testInput{ 4804 reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, 4805 accABC: &openrtb_ext.ExtAlternateBidderCodes{}, 4806 }, 4807 expected: &openrtb_ext.ExtAlternateBidderCodes{}, 4808 }, 4809 { 4810 desc: "non-nil reqABC", 4811 in: testInput{ 4812 bidderNameRaw: "pubmatic", 4813 reqABC: &openrtb_ext.ExtAlternateBidderCodes{}, 4814 }, 4815 expected: &openrtb_ext.ExtAlternateBidderCodes{}, 4816 }, 4817 { 4818 desc: "non-nil accABC", 4819 in: testInput{ 4820 bidderNameRaw: "pubmatic", 4821 accABC: &openrtb_ext.ExtAlternateBidderCodes{}, 4822 }, 4823 expected: &openrtb_ext.ExtAlternateBidderCodes{}, 4824 }, 4825 { 4826 desc: "both reqABC and accABC enabled and bidder matches elements in accABC but reqABC comes first", 4827 in: testInput{ 4828 bidderNameRaw: "PUBmatic", 4829 reqABC: &openrtb_ext.ExtAlternateBidderCodes{ 4830 Enabled: true, 4831 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4832 "appnexus": { 4833 AllowedBidderCodes: []string{"pubCode1"}, 4834 }, 4835 }, 4836 }, 4837 accABC: &openrtb_ext.ExtAlternateBidderCodes{ 4838 Enabled: true, 4839 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4840 "PubMatic": { 4841 AllowedBidderCodes: []string{"pubCode2"}, 4842 }, 4843 }, 4844 }, 4845 }, 4846 expected: &openrtb_ext.ExtAlternateBidderCodes{Enabled: true}, 4847 }, 4848 { 4849 desc: "both reqABC and accABC enabled and bidder matches elements in both but we prioritize reqABC", 4850 in: testInput{ 4851 bidderNameRaw: "pubmatic", 4852 reqABC: &openrtb_ext.ExtAlternateBidderCodes{ 4853 Enabled: true, 4854 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4855 "PubMatic": { 4856 AllowedBidderCodes: []string{"pubCode"}, 4857 }, 4858 }, 4859 }, 4860 accABC: &openrtb_ext.ExtAlternateBidderCodes{ 4861 Enabled: true, 4862 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4863 "appnexus": { 4864 AllowedBidderCodes: []string{"anxsCode"}, 4865 }, 4866 }, 4867 }, 4868 }, 4869 expected: &openrtb_ext.ExtAlternateBidderCodes{ 4870 Enabled: true, 4871 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4872 "pubmatic": { 4873 AllowedBidderCodes: []string{"pubCode"}, 4874 }, 4875 }, 4876 }, 4877 }, 4878 { 4879 desc: "nil reqABC non-nil accABC enabled and bidder matches elements in accABC", 4880 in: testInput{ 4881 bidderNameRaw: "APPnexus", 4882 accABC: &openrtb_ext.ExtAlternateBidderCodes{ 4883 Enabled: true, 4884 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4885 "appnexus": { 4886 AllowedBidderCodes: []string{"anxsCode"}, 4887 }, 4888 }, 4889 }, 4890 }, 4891 expected: &openrtb_ext.ExtAlternateBidderCodes{ 4892 Enabled: true, 4893 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4894 "APPnexus": { 4895 AllowedBidderCodes: []string{"anxsCode"}, 4896 }, 4897 }, 4898 }, 4899 }, 4900 } 4901 4902 for _, tc := range testCases { 4903 t.Run(tc.desc, func(t *testing.T) { 4904 alternateBidderCodes := buildRequestExtAlternateBidderCodes(tc.in.bidderNameRaw, tc.in.accABC, tc.in.reqABC) 4905 assert.Equal(t, tc.expected, alternateBidderCodes) 4906 }) 4907 } 4908 } 4909 4910 func TestCopyExtAlternateBidderCodes(t *testing.T) { 4911 type testInput struct { 4912 bidder string 4913 alternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes 4914 } 4915 testCases := []struct { 4916 desc string 4917 in testInput 4918 expected *openrtb_ext.ExtAlternateBidderCodes 4919 }{ 4920 { 4921 desc: "pass a nil alternateBidderCodes argument, expect nil output", 4922 in: testInput{}, 4923 expected: nil, 4924 }, 4925 { 4926 desc: "non-nil alternateBidderCodes argument but bidder doesn't match", 4927 in: testInput{ 4928 alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ 4929 Enabled: true, 4930 }, 4931 }, 4932 expected: &openrtb_ext.ExtAlternateBidderCodes{ 4933 Enabled: true, 4934 }, 4935 }, 4936 { 4937 desc: "non-nil alternateBidderCodes argument bidder is identical to one element in map", 4938 in: testInput{ 4939 bidder: "appnexus", 4940 alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ 4941 Enabled: true, 4942 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4943 "appnexus": { 4944 AllowedBidderCodes: []string{"adnxs"}, 4945 }, 4946 }, 4947 }, 4948 }, 4949 expected: &openrtb_ext.ExtAlternateBidderCodes{ 4950 Enabled: true, 4951 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4952 "appnexus": { 4953 AllowedBidderCodes: []string{"adnxs"}, 4954 }, 4955 }, 4956 }, 4957 }, 4958 { 4959 desc: "case insensitive match, keep bidder casing in output", 4960 in: testInput{ 4961 bidder: "AppNexus", 4962 alternateBidderCodes: &openrtb_ext.ExtAlternateBidderCodes{ 4963 Enabled: true, 4964 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4965 "appnexus": { 4966 AllowedBidderCodes: []string{"adnxs"}, 4967 }, 4968 }, 4969 }, 4970 }, 4971 expected: &openrtb_ext.ExtAlternateBidderCodes{ 4972 Enabled: true, 4973 Bidders: map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 4974 "AppNexus": { 4975 AllowedBidderCodes: []string{"adnxs"}, 4976 }, 4977 }, 4978 }, 4979 }, 4980 } 4981 for _, tc := range testCases { 4982 t.Run(tc.desc, func(t *testing.T) { 4983 alternateBidderCodes := copyExtAlternateBidderCodes(tc.in.bidder, tc.in.alternateBidderCodes) 4984 assert.Equal(t, tc.expected, alternateBidderCodes) 4985 }) 4986 } 4987 } 4988 4989 func TestBuildBidResponseRequestBidderName(t *testing.T) { 4990 bidderImpResponses := stored_responses.BidderImpsWithBidResponses{ 4991 openrtb_ext.BidderName("appnexus"): {"impId1": json.RawMessage(`{}`), "impId2": json.RawMessage(`{}`)}, 4992 openrtb_ext.BidderName("appneXUS"): {"impId3": json.RawMessage(`{}`), "impId4": json.RawMessage(`{}`)}, 4993 } 4994 4995 bidderImpReplaceImpID := stored_responses.BidderImpReplaceImpID{ 4996 "appnexus": {"impId1": true, "impId2": false}, 4997 "appneXUS": {"impId3": true, "impId4": false}, 4998 } 4999 result := buildBidResponseRequest(nil, bidderImpResponses, nil, bidderImpReplaceImpID) 5000 5001 resultAppnexus := result["appnexus"] 5002 assert.Equal(t, resultAppnexus.BidderName, openrtb_ext.BidderName("appnexus")) 5003 assert.Equal(t, resultAppnexus.ImpReplaceImpId, map[string]bool{"impId1": true, "impId2": false}) 5004 5005 resultAppneXUS := result["appneXUS"] 5006 assert.Equal(t, resultAppneXUS.BidderName, openrtb_ext.BidderName("appneXUS")) 5007 assert.Equal(t, resultAppneXUS.ImpReplaceImpId, map[string]bool{"impId3": true, "impId4": false}) 5008 5009 }