github.com/prebid/prebid-server/v2@v2.18.0/usersync/chooser_test.go (about) 1 package usersync 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/prebid/prebid-server/v2/config" 8 "github.com/prebid/prebid-server/v2/openrtb_ext" 9 "github.com/prebid/prebid-server/v2/util/ptrutil" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/mock" 12 13 "github.com/prebid/prebid-server/v2/macros" 14 ) 15 16 func TestNewChooser(t *testing.T) { 17 testCases := []struct { 18 description string 19 bidderSyncerLookup map[string]Syncer 20 bidderInfo map[string]config.BidderInfo 21 expectedBiddersAvailable []string 22 }{ 23 { 24 description: "Nil", 25 bidderSyncerLookup: nil, 26 expectedBiddersAvailable: []string{}, 27 }, 28 { 29 description: "Empty", 30 bidderSyncerLookup: map[string]Syncer{}, 31 expectedBiddersAvailable: []string{}, 32 }, 33 { 34 description: "One", 35 bidderSyncerLookup: map[string]Syncer{"a": fakeSyncer{}}, 36 expectedBiddersAvailable: []string{"a"}, 37 }, 38 { 39 description: "Many", 40 bidderSyncerLookup: map[string]Syncer{"a": fakeSyncer{}, "b": fakeSyncer{}}, 41 expectedBiddersAvailable: []string{"a", "b"}, 42 }, 43 } 44 45 for _, test := range testCases { 46 chooser, _ := NewChooser(test.bidderSyncerLookup, make(map[string]struct{}), test.bidderInfo).(standardChooser) 47 assert.ElementsMatch(t, test.expectedBiddersAvailable, chooser.biddersAvailable, test.description) 48 } 49 } 50 51 func TestChooserChoose(t *testing.T) { 52 fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} 53 fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: true} 54 fakeSyncerC := fakeSyncer{key: "keyC", supportsIFrame: false} 55 56 duplicateSyncer := fakeSyncer{key: "syncerForDuplicateTest", supportsIFrame: true} 57 bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC, "appnexus": fakeSyncerA, "d": duplicateSyncer, "e": duplicateSyncer} 58 biddersKnown := map[string]struct{}{"a": {}, "b": {}, "c": {}} 59 60 normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { 61 return openrtb_ext.BidderName(name), true 62 } 63 64 syncerChoiceA := SyncerChoice{Bidder: "a", Syncer: fakeSyncerA} 65 syncerChoiceB := SyncerChoice{Bidder: "b", Syncer: fakeSyncerB} 66 67 syncTypeFilter := SyncTypeFilter{ 68 IFrame: NewUniformBidderFilter(BidderFilterModeInclude), 69 Redirect: NewUniformBidderFilter(BidderFilterModeExclude), 70 } 71 72 cooperativeConfig := Cooperative{Enabled: true} 73 74 usersyncDisabled := ptrutil.ToPtr(false) 75 76 testCases := []struct { 77 description string 78 givenRequest Request 79 givenChosenBidders []string 80 givenCookie Cookie 81 givenBidderInfo map[string]config.BidderInfo 82 bidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) 83 expected Result 84 }{ 85 { 86 description: "Cookie Opt Out", 87 givenRequest: Request{ 88 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 89 Limit: 0, 90 }, 91 givenChosenBidders: []string{"a"}, 92 givenCookie: Cookie{optOut: true}, 93 bidderNamesLookup: normalizedBidderNamesLookup, 94 expected: Result{ 95 Status: StatusBlockedByUserOptOut, 96 BiddersEvaluated: nil, 97 SyncersChosen: nil, 98 }, 99 }, 100 { 101 description: "GDPR Host Cookie Not Allowed", 102 givenRequest: Request{ 103 Privacy: &fakePrivacy{gdprAllowsHostCookie: false, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 104 Limit: 0, 105 }, 106 givenChosenBidders: []string{"a"}, 107 givenCookie: Cookie{}, 108 bidderNamesLookup: normalizedBidderNamesLookup, 109 expected: Result{ 110 Status: StatusBlockedByPrivacy, 111 BiddersEvaluated: nil, 112 SyncersChosen: nil, 113 }, 114 }, 115 { 116 description: "No Bidders", 117 givenRequest: Request{ 118 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 119 Limit: 0, 120 }, 121 givenChosenBidders: []string{}, 122 givenCookie: Cookie{}, 123 bidderNamesLookup: normalizedBidderNamesLookup, 124 expected: Result{ 125 Status: StatusOK, 126 BiddersEvaluated: []BidderEvaluation{}, 127 SyncersChosen: []SyncerChoice{}, 128 }, 129 }, 130 { 131 description: "One Bidder - Sync", 132 givenRequest: Request{ 133 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 134 Limit: 0, 135 }, 136 givenChosenBidders: []string{"a"}, 137 givenCookie: Cookie{}, 138 bidderNamesLookup: normalizedBidderNamesLookup, 139 expected: Result{ 140 Status: StatusOK, 141 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, 142 SyncersChosen: []SyncerChoice{syncerChoiceA}, 143 }, 144 }, 145 { 146 description: "One Bidder - No Sync", 147 givenRequest: Request{ 148 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 149 Limit: 0, 150 }, 151 givenChosenBidders: []string{"c"}, 152 givenCookie: Cookie{}, 153 bidderNamesLookup: normalizedBidderNamesLookup, 154 expected: Result{ 155 Status: StatusOK, 156 BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, 157 SyncersChosen: []SyncerChoice{}, 158 }, 159 }, 160 { 161 description: "One Bidder - No Sync - Unknown", 162 givenRequest: Request{ 163 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 164 Limit: 0, 165 }, 166 givenChosenBidders: []string{"unknown"}, 167 givenCookie: Cookie{}, 168 bidderNamesLookup: normalizedBidderNamesLookup, 169 expected: Result{ 170 Status: StatusOK, 171 BiddersEvaluated: []BidderEvaluation{{Bidder: "unknown", Status: StatusUnknownBidder}}, 172 SyncersChosen: []SyncerChoice{}, 173 }, 174 }, 175 { 176 description: "Many Bidders - All Sync - Limit Disabled With 0", 177 givenRequest: Request{ 178 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 179 Limit: 0, 180 }, 181 givenChosenBidders: []string{"a", "b"}, 182 givenCookie: Cookie{}, 183 bidderNamesLookup: normalizedBidderNamesLookup, 184 expected: Result{ 185 Status: StatusOK, 186 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "b", SyncerKey: "keyB", Status: StatusOK}}, 187 SyncersChosen: []SyncerChoice{syncerChoiceA, syncerChoiceB}, 188 }, 189 }, 190 { 191 description: "Many Bidders - All Sync - Limit Disabled With Negative Value", 192 givenRequest: Request{ 193 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 194 Limit: -1, 195 }, 196 givenChosenBidders: []string{"a", "b"}, 197 givenCookie: Cookie{}, 198 bidderNamesLookup: normalizedBidderNamesLookup, 199 expected: Result{ 200 Status: StatusOK, 201 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "b", SyncerKey: "keyB", Status: StatusOK}}, 202 SyncersChosen: []SyncerChoice{syncerChoiceA, syncerChoiceB}, 203 }, 204 }, 205 { 206 description: "Many Bidders - Limited Sync", 207 givenRequest: Request{ 208 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 209 Limit: 1, 210 }, 211 givenChosenBidders: []string{"a", "b"}, 212 givenCookie: Cookie{}, 213 bidderNamesLookup: normalizedBidderNamesLookup, 214 expected: Result{ 215 Status: StatusOK, 216 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, 217 SyncersChosen: []SyncerChoice{syncerChoiceA}, 218 }, 219 }, 220 { 221 description: "Many Bidders - Limited Sync - Disqualified Syncers Don't Count Towards Limit", 222 givenRequest: Request{ 223 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 224 Limit: 1, 225 }, 226 givenChosenBidders: []string{"c", "a", "b"}, 227 givenCookie: Cookie{}, 228 bidderNamesLookup: normalizedBidderNamesLookup, 229 expected: Result{ 230 Status: StatusOK, 231 BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}, {Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, 232 SyncersChosen: []SyncerChoice{syncerChoiceA}, 233 }, 234 }, 235 { 236 description: "Many Bidders - Some Sync, Some Don't", 237 givenRequest: Request{ 238 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 239 Limit: 0, 240 }, 241 givenChosenBidders: []string{"a", "c"}, 242 givenCookie: Cookie{}, 243 bidderNamesLookup: normalizedBidderNamesLookup, 244 expected: Result{ 245 Status: StatusOK, 246 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, 247 SyncersChosen: []SyncerChoice{syncerChoiceA}, 248 }, 249 }, 250 { 251 description: "Chosen bidders have duplicate syncer keys, the one that comes first should be labled OK", 252 givenRequest: Request{ 253 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 254 Limit: 0, 255 }, 256 givenChosenBidders: []string{"d", "e"}, 257 givenCookie: Cookie{}, 258 bidderNamesLookup: normalizedBidderNamesLookup, 259 expected: Result{ 260 Status: StatusOK, 261 BiddersEvaluated: []BidderEvaluation{ 262 {Bidder: "d", SyncerKey: "syncerForDuplicateTest", Status: StatusOK}, 263 {Bidder: "e", SyncerKey: "syncerForDuplicateTest", Status: StatusDuplicate}, 264 }, 265 SyncersChosen: []SyncerChoice{{Bidder: "d", Syncer: duplicateSyncer}}, 266 }, 267 }, 268 { 269 description: "Chosen bidders have duplicate syncer keys, the one that comes first should be labled OK (reverse)", 270 givenRequest: Request{ 271 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 272 Limit: 0, 273 }, 274 givenChosenBidders: []string{"e", "d"}, 275 givenCookie: Cookie{}, 276 bidderNamesLookup: normalizedBidderNamesLookup, 277 expected: Result{ 278 Status: StatusOK, 279 BiddersEvaluated: []BidderEvaluation{ 280 {Bidder: "e", SyncerKey: "syncerForDuplicateTest", Status: StatusOK}, 281 {Bidder: "d", SyncerKey: "syncerForDuplicateTest", Status: StatusDuplicate}, 282 }, 283 SyncersChosen: []SyncerChoice{{Bidder: "e", Syncer: duplicateSyncer}}, 284 }, 285 }, 286 { 287 description: "Same bidder name, no duplicate warning", 288 givenRequest: Request{ 289 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 290 Limit: 0, 291 }, 292 givenChosenBidders: []string{"a", "a"}, 293 givenCookie: Cookie{}, 294 bidderNamesLookup: normalizedBidderNamesLookup, 295 expected: Result{ 296 Status: StatusOK, 297 BiddersEvaluated: []BidderEvaluation{ 298 {Bidder: "a", SyncerKey: fakeSyncerA.key, Status: StatusOK}, 299 }, 300 SyncersChosen: []SyncerChoice{{Bidder: "a", Syncer: fakeSyncerA}}, 301 }, 302 }, 303 { 304 description: "Unknown Bidder", 305 givenRequest: Request{ 306 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 307 Limit: 0, 308 }, 309 givenChosenBidders: []string{"a"}, 310 givenCookie: Cookie{}, 311 bidderNamesLookup: func(name string) (openrtb_ext.BidderName, bool) { 312 return openrtb_ext.BidderName(name), false 313 }, 314 expected: Result{ 315 Status: StatusOK, 316 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", Status: StatusUnknownBidder}}, 317 SyncersChosen: []SyncerChoice{}, 318 }, 319 }, 320 { 321 description: "Case insensitive bidder name", 322 givenRequest: Request{ 323 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 324 Limit: 0, 325 }, 326 givenChosenBidders: []string{"AppNexus"}, 327 givenCookie: Cookie{}, 328 bidderNamesLookup: openrtb_ext.NormalizeBidderName, 329 expected: Result{ 330 Status: StatusOK, 331 BiddersEvaluated: []BidderEvaluation{{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}}, 332 SyncersChosen: []SyncerChoice{{Bidder: "AppNexus", Syncer: fakeSyncerA}}, 333 }, 334 }, 335 { 336 description: "Duplicate bidder name", 337 givenRequest: Request{ 338 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 339 Limit: 0, 340 }, 341 givenChosenBidders: []string{"AppNexus", "appNexus"}, 342 givenCookie: Cookie{}, 343 bidderNamesLookup: openrtb_ext.NormalizeBidderName, 344 expected: Result{ 345 Status: StatusOK, 346 BiddersEvaluated: []BidderEvaluation{{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "appNexus", SyncerKey: "keyA", Status: StatusDuplicate}}, 347 SyncersChosen: []SyncerChoice{{Bidder: "AppNexus", Syncer: fakeSyncerA}}, 348 }, 349 }, 350 { 351 description: "Disabled Usersync", 352 givenRequest: Request{ 353 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 354 Limit: 0, 355 }, 356 givenChosenBidders: []string{"a"}, 357 givenCookie: Cookie{}, 358 givenBidderInfo: map[string]config.BidderInfo{ 359 "a": { 360 Syncer: &config.Syncer{ 361 Enabled: usersyncDisabled, 362 }, 363 }, 364 }, 365 bidderNamesLookup: normalizedBidderNamesLookup, 366 expected: Result{ 367 Status: StatusOK, 368 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByDisabledUsersync}}, 369 SyncersChosen: []SyncerChoice{}, 370 }, 371 }, 372 { 373 description: "Regulation Scope GDPR", 374 givenRequest: Request{ 375 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true, gdprInScope: true}, 376 Limit: 0, 377 }, 378 givenChosenBidders: []string{"a"}, 379 givenCookie: Cookie{}, 380 givenBidderInfo: map[string]config.BidderInfo{ 381 "a": { 382 Syncer: &config.Syncer{ 383 SkipWhen: &config.SkipWhen{ 384 GDPR: true, 385 }, 386 }, 387 }, 388 }, 389 bidderNamesLookup: normalizedBidderNamesLookup, 390 expected: Result{ 391 Status: StatusOK, 392 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}}, 393 SyncersChosen: []SyncerChoice{}, 394 }, 395 }, 396 { 397 description: "Regulation Scope GPP", 398 givenRequest: Request{ 399 Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 400 Limit: 0, 401 GPPSID: "2", 402 }, 403 givenChosenBidders: []string{"a"}, 404 givenCookie: Cookie{}, 405 givenBidderInfo: map[string]config.BidderInfo{ 406 "a": { 407 Syncer: &config.Syncer{ 408 SkipWhen: &config.SkipWhen{ 409 GPPSID: []string{"2", "3"}, 410 }, 411 }, 412 }, 413 }, 414 bidderNamesLookup: normalizedBidderNamesLookup, 415 expected: Result{ 416 Status: StatusOK, 417 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}}, 418 SyncersChosen: []SyncerChoice{}, 419 }, 420 }, 421 } 422 423 bidders := []string{"anyRequested"} 424 biddersAvailable := []string{"anyAvailable"} 425 for _, test := range testCases { 426 // set request values which don't need to be specified for each test case 427 test.givenRequest.Bidders = bidders 428 test.givenRequest.SyncTypeFilter = syncTypeFilter 429 test.givenRequest.Cooperative = cooperativeConfig 430 431 mockBidderChooser := &mockBidderChooser{} 432 mockBidderChooser. 433 On("choose", test.givenRequest.Bidders, biddersAvailable, cooperativeConfig). 434 Return(test.givenChosenBidders) 435 436 if test.givenBidderInfo == nil { 437 test.givenBidderInfo = map[string]config.BidderInfo{} 438 } 439 440 chooser := standardChooser{ 441 bidderSyncerLookup: bidderSyncerLookup, 442 biddersAvailable: biddersAvailable, 443 bidderChooser: mockBidderChooser, 444 normalizeValidBidderName: test.bidderNamesLookup, 445 biddersKnown: biddersKnown, 446 bidderInfo: test.givenBidderInfo, 447 } 448 449 result := chooser.Choose(test.givenRequest, &test.givenCookie) 450 assert.Equal(t, test.expected, result, test.description) 451 } 452 } 453 454 func TestChooserEvaluate(t *testing.T) { 455 fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} 456 fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: false} 457 458 biddersKnown := map[string]struct{}{"a": {}, "b": {}, "unconfigured": {}} 459 bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "appnexus": fakeSyncerA, "seedingAlliance": fakeSyncerA} 460 461 syncTypeFilter := SyncTypeFilter{ 462 IFrame: NewUniformBidderFilter(BidderFilterModeInclude), 463 Redirect: NewUniformBidderFilter(BidderFilterModeExclude), 464 } 465 normalizedBidderNamesLookup := func(name string) (openrtb_ext.BidderName, bool) { 466 return openrtb_ext.BidderName(name), true 467 } 468 cookieNeedsSync := Cookie{} 469 cookieAlreadyHasSyncForA := Cookie{uids: map[string]UIDEntry{"keyA": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} 470 cookieAlreadyHasSyncForB := Cookie{uids: map[string]UIDEntry{"keyB": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} 471 472 usersyncDisabled := ptrutil.ToPtr(false) 473 474 testCases := []struct { 475 description string 476 givenBidder string 477 normalisedBidderName string 478 givenSyncersSeen map[string]struct{} 479 givenPrivacy fakePrivacy 480 givenCookie Cookie 481 givenGPPSID string 482 givenBidderInfo map[string]config.BidderInfo 483 givenSyncTypeFilter SyncTypeFilter 484 normalizedBidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) 485 expectedSyncer Syncer 486 expectedEvaluation BidderEvaluation 487 }{ 488 { 489 description: "Valid", 490 givenBidder: "a", 491 normalisedBidderName: "a", 492 givenSyncersSeen: map[string]struct{}{}, 493 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 494 givenCookie: cookieNeedsSync, 495 givenSyncTypeFilter: syncTypeFilter, 496 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 497 expectedSyncer: fakeSyncerA, 498 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, 499 }, 500 { 501 description: "Unknown Bidder", 502 givenBidder: "unknown", 503 normalisedBidderName: "", 504 givenSyncersSeen: map[string]struct{}{}, 505 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 506 givenCookie: cookieNeedsSync, 507 givenSyncTypeFilter: syncTypeFilter, 508 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 509 expectedSyncer: nil, 510 expectedEvaluation: BidderEvaluation{Bidder: "unknown", Status: StatusUnknownBidder}, 511 }, 512 { 513 description: "Duplicate Syncer", 514 givenBidder: "a", 515 normalisedBidderName: "", 516 givenSyncersSeen: map[string]struct{}{"keyA": {}}, 517 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 518 givenCookie: cookieNeedsSync, 519 givenSyncTypeFilter: syncTypeFilter, 520 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 521 expectedSyncer: nil, 522 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusDuplicate}, 523 }, 524 { 525 description: "Incompatible Kind", 526 givenBidder: "b", 527 normalisedBidderName: "", 528 givenSyncersSeen: map[string]struct{}{}, 529 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 530 givenCookie: cookieNeedsSync, 531 givenSyncTypeFilter: syncTypeFilter, 532 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 533 expectedSyncer: nil, 534 expectedEvaluation: BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusTypeNotSupported}, 535 }, 536 { 537 description: "Already Synced", 538 givenBidder: "a", 539 normalisedBidderName: "", 540 givenSyncersSeen: map[string]struct{}{}, 541 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 542 givenCookie: cookieAlreadyHasSyncForA, 543 givenSyncTypeFilter: syncTypeFilter, 544 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 545 expectedSyncer: nil, 546 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusAlreadySynced}, 547 }, 548 { 549 description: "Different Bidder Already Synced", 550 givenBidder: "a", 551 normalisedBidderName: "a", 552 givenSyncersSeen: map[string]struct{}{}, 553 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 554 givenCookie: cookieAlreadyHasSyncForB, 555 givenSyncTypeFilter: syncTypeFilter, 556 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 557 expectedSyncer: fakeSyncerA, 558 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, 559 }, 560 { 561 description: "Blocked By GDPR", 562 givenBidder: "a", 563 normalisedBidderName: "a", 564 givenSyncersSeen: map[string]struct{}{}, 565 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 566 givenCookie: cookieNeedsSync, 567 givenSyncTypeFilter: syncTypeFilter, 568 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 569 expectedSyncer: nil, 570 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, 571 }, 572 { 573 description: "Blocked By activity control", 574 givenBidder: "a", 575 normalisedBidderName: "", 576 givenSyncersSeen: map[string]struct{}{}, 577 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: false}, 578 givenCookie: cookieNeedsSync, 579 givenSyncTypeFilter: syncTypeFilter, 580 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 581 expectedSyncer: nil, 582 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, 583 }, 584 { 585 description: "Case insensitive bidder name", 586 givenBidder: "AppNexus", 587 normalisedBidderName: "appnexus", 588 givenSyncersSeen: map[string]struct{}{}, 589 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 590 givenCookie: cookieNeedsSync, 591 givenSyncTypeFilter: syncTypeFilter, 592 normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, 593 expectedSyncer: fakeSyncerA, 594 expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusOK}, 595 }, 596 { 597 description: "Case insensitivity check for sync type filter", 598 givenBidder: "SeedingAlliance", 599 normalisedBidderName: "seedingAlliance", 600 givenSyncersSeen: map[string]struct{}{}, 601 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 602 givenCookie: cookieNeedsSync, 603 givenSyncTypeFilter: SyncTypeFilter{ 604 IFrame: NewSpecificBidderFilter([]string{"SeedingAlliance"}, BidderFilterModeInclude), 605 Redirect: NewSpecificBidderFilter([]string{"SeedingAlliance"}, BidderFilterModeExclude), 606 }, 607 normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, 608 expectedSyncer: fakeSyncerA, 609 expectedEvaluation: BidderEvaluation{Bidder: "SeedingAlliance", SyncerKey: "keyA", Status: StatusOK}, 610 }, 611 { 612 description: "Case Insensitivity Check For Blocked By GDPR", 613 givenBidder: "AppNexus", 614 normalisedBidderName: "appnexus", 615 givenSyncersSeen: map[string]struct{}{}, 616 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 617 givenCookie: cookieNeedsSync, 618 givenSyncTypeFilter: syncTypeFilter, 619 normalizedBidderNamesLookup: openrtb_ext.NormalizeBidderName, 620 expectedSyncer: nil, 621 expectedEvaluation: BidderEvaluation{Bidder: "AppNexus", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, 622 }, 623 { 624 description: "Unconfigured Bidder", 625 givenBidder: "unconfigured", 626 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 627 givenSyncersSeen: map[string]struct{}{}, 628 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 629 givenCookie: cookieNeedsSync, 630 expectedSyncer: nil, 631 expectedEvaluation: BidderEvaluation{Bidder: "unconfigured", Status: StatusUnconfiguredBidder}, 632 }, 633 { 634 description: "Disabled Usersync", 635 givenBidder: "a", 636 normalisedBidderName: "a", 637 givenSyncersSeen: map[string]struct{}{}, 638 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 639 givenCookie: cookieNeedsSync, 640 givenBidderInfo: map[string]config.BidderInfo{ 641 "a": { 642 Syncer: &config.Syncer{ 643 Enabled: usersyncDisabled, 644 }, 645 }, 646 }, 647 givenSyncTypeFilter: syncTypeFilter, 648 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 649 expectedSyncer: nil, 650 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByDisabledUsersync}, 651 }, 652 { 653 description: "Blocked By Regulation Scope - GDPR", 654 givenBidder: "a", 655 givenSyncersSeen: map[string]struct{}{}, 656 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true, gdprInScope: true}, 657 givenCookie: cookieNeedsSync, 658 givenBidderInfo: map[string]config.BidderInfo{ 659 "a": { 660 Syncer: &config.Syncer{ 661 SkipWhen: &config.SkipWhen{ 662 GDPR: true, 663 }, 664 }, 665 }, 666 }, 667 givenSyncTypeFilter: syncTypeFilter, 668 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 669 normalisedBidderName: "a", 670 expectedSyncer: nil, 671 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}, 672 }, 673 { 674 description: "Blocked By Regulation Scope - GPP", 675 givenBidder: "a", 676 givenSyncersSeen: map[string]struct{}{}, 677 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 678 givenCookie: cookieNeedsSync, 679 givenBidderInfo: map[string]config.BidderInfo{ 680 "a": { 681 Syncer: &config.Syncer{ 682 SkipWhen: &config.SkipWhen{ 683 GPPSID: []string{"2", "3"}, 684 }, 685 }, 686 }, 687 }, 688 givenGPPSID: "2", 689 givenSyncTypeFilter: syncTypeFilter, 690 normalizedBidderNamesLookup: normalizedBidderNamesLookup, 691 normalisedBidderName: "a", 692 expectedSyncer: nil, 693 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}, 694 }, 695 } 696 697 for _, test := range testCases { 698 t.Run(test.description, func(t *testing.T) { 699 chooser, _ := NewChooser(bidderSyncerLookup, biddersKnown, test.givenBidderInfo).(standardChooser) 700 chooser.normalizeValidBidderName = test.normalizedBidderNamesLookup 701 sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, test.givenSyncTypeFilter, &test.givenPrivacy, &test.givenCookie, test.givenGPPSID) 702 703 assert.Equal(t, test.normalisedBidderName, test.givenPrivacy.inputBidderName) 704 assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") 705 assert.Equal(t, test.expectedEvaluation, evaluation, test.description+":evaluation") 706 }) 707 } 708 } 709 710 type mockBidderChooser struct { 711 mock.Mock 712 } 713 714 func (m *mockBidderChooser) choose(requested, available []string, cooperative Cooperative) []string { 715 args := m.Called(requested, available, cooperative) 716 return args.Get(0).([]string) 717 } 718 719 type fakeSyncer struct { 720 key string 721 supportsIFrame bool 722 supportsRedirect bool 723 formatOverride string 724 } 725 726 func (s fakeSyncer) Key() string { 727 return s.key 728 } 729 730 func (s fakeSyncer) DefaultResponseFormat() SyncType { 731 return SyncTypeIFrame 732 } 733 734 func (s fakeSyncer) SupportsType(syncTypes []SyncType) bool { 735 for _, syncType := range syncTypes { 736 if syncType == SyncTypeIFrame && s.supportsIFrame { 737 return true 738 } 739 if syncType == SyncTypeRedirect && s.supportsRedirect { 740 return true 741 } 742 } 743 return false 744 } 745 746 func (fakeSyncer) GetSync([]SyncType, macros.UserSyncPrivacy) (Sync, error) { 747 return Sync{}, nil 748 } 749 750 type fakePrivacy struct { 751 gdprAllowsHostCookie bool 752 gdprAllowsBidderSync bool 753 ccpaAllowsBidderSync bool 754 activityAllowUserSync bool 755 gdprInScope bool 756 inputBidderName string 757 } 758 759 func (p *fakePrivacy) GDPRAllowsHostCookie() bool { 760 return p.gdprAllowsHostCookie 761 } 762 763 func (p *fakePrivacy) GDPRAllowsBidderSync(bidder string) bool { 764 p.inputBidderName = bidder 765 return p.gdprAllowsBidderSync 766 } 767 768 func (p *fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { 769 p.inputBidderName = bidder 770 return p.ccpaAllowsBidderSync 771 } 772 773 func (p *fakePrivacy) ActivityAllowsUserSync(bidder string) bool { 774 return p.activityAllowUserSync 775 } 776 777 func (p *fakePrivacy) GDPRInScope() bool { 778 return p.gdprInScope 779 }