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