github.com/prebid/prebid-server@v0.275.0/usersync/chooser_test.go (about) 1 package usersync 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/prebid/prebid-server/macros" 8 "github.com/stretchr/testify/assert" 9 "github.com/stretchr/testify/mock" 10 ) 11 12 func TestNewChooser(t *testing.T) { 13 testCases := []struct { 14 description string 15 bidderSyncerLookup map[string]Syncer 16 expectedBiddersAvailable []string 17 }{ 18 { 19 description: "Nil", 20 bidderSyncerLookup: nil, 21 expectedBiddersAvailable: []string{}, 22 }, 23 { 24 description: "Empty", 25 bidderSyncerLookup: map[string]Syncer{}, 26 expectedBiddersAvailable: []string{}, 27 }, 28 { 29 description: "One", 30 bidderSyncerLookup: map[string]Syncer{"a": fakeSyncer{}}, 31 expectedBiddersAvailable: []string{"a"}, 32 }, 33 { 34 description: "Many", 35 bidderSyncerLookup: map[string]Syncer{"a": fakeSyncer{}, "b": fakeSyncer{}}, 36 expectedBiddersAvailable: []string{"a", "b"}, 37 }, 38 } 39 40 for _, test := range testCases { 41 chooser, _ := NewChooser(test.bidderSyncerLookup).(standardChooser) 42 assert.ElementsMatch(t, test.expectedBiddersAvailable, chooser.biddersAvailable, test.description) 43 } 44 } 45 46 func TestChooserChoose(t *testing.T) { 47 fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} 48 fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: true} 49 fakeSyncerC := fakeSyncer{key: "keyC", supportsIFrame: false} 50 bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB, "c": fakeSyncerC} 51 syncerChoiceA := SyncerChoice{Bidder: "a", Syncer: fakeSyncerA} 52 syncerChoiceB := SyncerChoice{Bidder: "b", Syncer: fakeSyncerB} 53 syncTypeFilter := SyncTypeFilter{ 54 IFrame: NewUniformBidderFilter(BidderFilterModeInclude), 55 Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} 56 57 cooperativeConfig := Cooperative{Enabled: true} 58 59 testCases := []struct { 60 description string 61 givenRequest Request 62 givenChosenBidders []string 63 givenCookie Cookie 64 expected Result 65 }{ 66 { 67 description: "Cookie Opt Out", 68 givenRequest: Request{ 69 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 70 Limit: 0, 71 }, 72 givenChosenBidders: []string{"a"}, 73 givenCookie: Cookie{optOut: true}, 74 expected: Result{ 75 Status: StatusBlockedByUserOptOut, 76 BiddersEvaluated: nil, 77 SyncersChosen: nil, 78 }, 79 }, 80 { 81 description: "GDPR Host Cookie Not Allowed", 82 givenRequest: Request{ 83 Privacy: fakePrivacy{gdprAllowsHostCookie: false, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 84 Limit: 0, 85 }, 86 givenChosenBidders: []string{"a"}, 87 givenCookie: Cookie{}, 88 expected: Result{ 89 Status: StatusBlockedByGDPR, 90 BiddersEvaluated: nil, 91 SyncersChosen: nil, 92 }, 93 }, 94 { 95 description: "No Bidders", 96 givenRequest: Request{ 97 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 98 Limit: 0, 99 }, 100 givenChosenBidders: []string{}, 101 givenCookie: Cookie{}, 102 expected: Result{ 103 Status: StatusOK, 104 BiddersEvaluated: []BidderEvaluation{}, 105 SyncersChosen: []SyncerChoice{}, 106 }, 107 }, 108 { 109 description: "One Bidder - Sync", 110 givenRequest: Request{ 111 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 112 Limit: 0, 113 }, 114 givenChosenBidders: []string{"a"}, 115 givenCookie: Cookie{}, 116 expected: Result{ 117 Status: StatusOK, 118 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, 119 SyncersChosen: []SyncerChoice{syncerChoiceA}, 120 }, 121 }, 122 { 123 description: "One Bidder - No Sync", 124 givenRequest: Request{ 125 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 126 Limit: 0, 127 }, 128 givenChosenBidders: []string{"c"}, 129 givenCookie: Cookie{}, 130 expected: Result{ 131 Status: StatusOK, 132 BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, 133 SyncersChosen: []SyncerChoice{}, 134 }, 135 }, 136 { 137 description: "Many Bidders - All Sync - Limit Disabled With 0", 138 givenRequest: Request{ 139 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 140 Limit: 0, 141 }, 142 givenChosenBidders: []string{"a", "b"}, 143 givenCookie: Cookie{}, 144 expected: Result{ 145 Status: StatusOK, 146 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "b", SyncerKey: "keyB", Status: StatusOK}}, 147 SyncersChosen: []SyncerChoice{syncerChoiceA, syncerChoiceB}, 148 }, 149 }, 150 { 151 description: "Many Bidders - All Sync - Limit Disabled With Negative Value", 152 givenRequest: Request{ 153 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 154 Limit: -1, 155 }, 156 givenChosenBidders: []string{"a", "b"}, 157 givenCookie: Cookie{}, 158 expected: Result{ 159 Status: StatusOK, 160 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "b", SyncerKey: "keyB", Status: StatusOK}}, 161 SyncersChosen: []SyncerChoice{syncerChoiceA, syncerChoiceB}, 162 }, 163 }, 164 { 165 description: "Many Bidders - Limited Sync", 166 givenRequest: Request{ 167 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 168 Limit: 1, 169 }, 170 givenChosenBidders: []string{"a", "b"}, 171 givenCookie: Cookie{}, 172 expected: Result{ 173 Status: StatusOK, 174 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, 175 SyncersChosen: []SyncerChoice{syncerChoiceA}, 176 }, 177 }, 178 { 179 description: "Many Bidders - Limited Sync - Disqualified Syncers Don't Count Towards Limit", 180 givenRequest: Request{ 181 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 182 Limit: 1, 183 }, 184 givenChosenBidders: []string{"c", "a", "b"}, 185 givenCookie: Cookie{}, 186 expected: Result{ 187 Status: StatusOK, 188 BiddersEvaluated: []BidderEvaluation{{Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}, {Bidder: "a", SyncerKey: "keyA", Status: StatusOK}}, 189 SyncersChosen: []SyncerChoice{syncerChoiceA}, 190 }, 191 }, 192 { 193 description: "Many Bidders - Some Sync, Some Don't", 194 givenRequest: Request{ 195 Privacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 196 Limit: 0, 197 }, 198 givenChosenBidders: []string{"a", "c"}, 199 givenCookie: Cookie{}, 200 expected: Result{ 201 Status: StatusOK, 202 BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, {Bidder: "c", SyncerKey: "keyC", Status: StatusTypeNotSupported}}, 203 SyncersChosen: []SyncerChoice{syncerChoiceA}, 204 }, 205 }, 206 } 207 208 bidders := []string{"anyRequested"} 209 biddersAvailable := []string{"anyAvailable"} 210 for _, test := range testCases { 211 // set request values which don't need to be specified for each test case 212 test.givenRequest.Bidders = bidders 213 test.givenRequest.SyncTypeFilter = syncTypeFilter 214 test.givenRequest.Cooperative = cooperativeConfig 215 216 mockBidderChooser := &mockBidderChooser{} 217 mockBidderChooser. 218 On("choose", test.givenRequest.Bidders, biddersAvailable, cooperativeConfig). 219 Return(test.givenChosenBidders) 220 221 chooser := standardChooser{ 222 bidderSyncerLookup: bidderSyncerLookup, 223 biddersAvailable: biddersAvailable, 224 bidderChooser: mockBidderChooser, 225 } 226 227 result := chooser.Choose(test.givenRequest, &test.givenCookie) 228 assert.Equal(t, test.expected, result, test.description) 229 } 230 } 231 232 func TestChooserEvaluate(t *testing.T) { 233 fakeSyncerA := fakeSyncer{key: "keyA", supportsIFrame: true} 234 fakeSyncerB := fakeSyncer{key: "keyB", supportsIFrame: false} 235 bidderSyncerLookup := map[string]Syncer{"a": fakeSyncerA, "b": fakeSyncerB} 236 syncTypeFilter := SyncTypeFilter{ 237 IFrame: NewUniformBidderFilter(BidderFilterModeInclude), 238 Redirect: NewUniformBidderFilter(BidderFilterModeExclude)} 239 240 cookieNeedsSync := Cookie{} 241 cookieAlreadyHasSyncForA := Cookie{uids: map[string]UIDEntry{"keyA": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} 242 cookieAlreadyHasSyncForB := Cookie{uids: map[string]UIDEntry{"keyB": {Expires: time.Now().Add(time.Duration(24) * time.Hour)}}} 243 244 testCases := []struct { 245 description string 246 givenBidder string 247 givenSyncersSeen map[string]struct{} 248 givenPrivacy Privacy 249 givenCookie Cookie 250 expectedSyncer Syncer 251 expectedEvaluation BidderEvaluation 252 }{ 253 { 254 description: "Valid", 255 givenBidder: "a", 256 givenSyncersSeen: map[string]struct{}{}, 257 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 258 givenCookie: cookieNeedsSync, 259 expectedSyncer: fakeSyncerA, 260 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, 261 }, 262 { 263 description: "Unknown Bidder", 264 givenBidder: "unknown", 265 givenSyncersSeen: map[string]struct{}{}, 266 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 267 givenCookie: cookieNeedsSync, 268 expectedSyncer: nil, 269 expectedEvaluation: BidderEvaluation{Bidder: "unknown", Status: StatusUnknownBidder}, 270 }, 271 { 272 description: "Duplicate Syncer", 273 givenBidder: "a", 274 givenSyncersSeen: map[string]struct{}{"keyA": {}}, 275 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 276 givenCookie: cookieNeedsSync, 277 expectedSyncer: nil, 278 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusDuplicate}, 279 }, 280 { 281 description: "Incompatible Kind", 282 givenBidder: "b", 283 givenSyncersSeen: map[string]struct{}{}, 284 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 285 givenCookie: cookieNeedsSync, 286 expectedSyncer: nil, 287 expectedEvaluation: BidderEvaluation{Bidder: "b", SyncerKey: "keyB", Status: StatusTypeNotSupported}, 288 }, 289 { 290 description: "Already Synced", 291 givenBidder: "a", 292 givenSyncersSeen: map[string]struct{}{}, 293 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 294 givenCookie: cookieAlreadyHasSyncForA, 295 expectedSyncer: nil, 296 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusAlreadySynced}, 297 }, 298 { 299 description: "Different Bidder Already Synced", 300 givenBidder: "a", 301 givenSyncersSeen: map[string]struct{}{}, 302 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 303 givenCookie: cookieAlreadyHasSyncForB, 304 expectedSyncer: fakeSyncerA, 305 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusOK}, 306 }, 307 { 308 description: "Blocked By GDPR", 309 givenBidder: "a", 310 givenSyncersSeen: map[string]struct{}{}, 311 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: false, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, 312 givenCookie: cookieNeedsSync, 313 expectedSyncer: nil, 314 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByGDPR}, 315 }, 316 { 317 description: "Blocked By CCPA", 318 givenBidder: "a", 319 givenSyncersSeen: map[string]struct{}{}, 320 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: false, activityAllowUserSync: true}, 321 givenCookie: cookieNeedsSync, 322 expectedSyncer: nil, 323 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByCCPA}, 324 }, 325 { 326 description: "Blocked By activity control", 327 givenBidder: "a", 328 givenSyncersSeen: map[string]struct{}{}, 329 givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: false}, 330 givenCookie: cookieNeedsSync, 331 expectedSyncer: nil, 332 expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByPrivacy}, 333 }, 334 } 335 336 for _, test := range testCases { 337 chooser, _ := NewChooser(bidderSyncerLookup).(standardChooser) 338 sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, syncTypeFilter, test.givenPrivacy, &test.givenCookie) 339 340 assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") 341 assert.Equal(t, test.expectedEvaluation, evaluation, test.description+":evaluation") 342 } 343 } 344 345 type mockBidderChooser struct { 346 mock.Mock 347 } 348 349 func (m *mockBidderChooser) choose(requested, available []string, cooperative Cooperative) []string { 350 args := m.Called(requested, available, cooperative) 351 return args.Get(0).([]string) 352 } 353 354 type fakeSyncer struct { 355 key string 356 supportsIFrame bool 357 supportsRedirect bool 358 } 359 360 func (s fakeSyncer) Key() string { 361 return s.key 362 } 363 364 func (s fakeSyncer) DefaultSyncType() SyncType { 365 return SyncTypeIFrame 366 } 367 368 func (s fakeSyncer) SupportsType(syncTypes []SyncType) bool { 369 for _, syncType := range syncTypes { 370 if syncType == SyncTypeIFrame && s.supportsIFrame { 371 return true 372 } 373 if syncType == SyncTypeRedirect && s.supportsRedirect { 374 return true 375 } 376 } 377 return false 378 } 379 380 func (fakeSyncer) GetSync([]SyncType, macros.UserSyncPrivacy) (Sync, error) { 381 return Sync{}, nil 382 } 383 384 type fakePrivacy struct { 385 gdprAllowsHostCookie bool 386 gdprAllowsBidderSync bool 387 ccpaAllowsBidderSync bool 388 activityAllowUserSync bool 389 } 390 391 func (p fakePrivacy) GDPRAllowsHostCookie() bool { 392 return p.gdprAllowsHostCookie 393 } 394 395 func (p fakePrivacy) GDPRAllowsBidderSync(bidder string) bool { 396 return p.gdprAllowsBidderSync 397 } 398 399 func (p fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { 400 return p.ccpaAllowsBidderSync 401 } 402 403 func (p fakePrivacy) ActivityAllowsUserSync(bidder string) bool { 404 return p.activityAllowUserSync 405 }