github.com/prebid/prebid-server@v0.275.0/endpoints/cookie_sync_test.go (about) 1 package endpoints 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "strconv" 11 "strings" 12 "testing" 13 "testing/iotest" 14 15 "github.com/prebid/prebid-server/analytics" 16 "github.com/prebid/prebid-server/config" 17 "github.com/prebid/prebid-server/errortypes" 18 "github.com/prebid/prebid-server/gdpr" 19 "github.com/prebid/prebid-server/macros" 20 "github.com/prebid/prebid-server/metrics" 21 "github.com/prebid/prebid-server/openrtb_ext" 22 "github.com/prebid/prebid-server/privacy" 23 "github.com/prebid/prebid-server/privacy/ccpa" 24 "github.com/prebid/prebid-server/usersync" 25 "github.com/prebid/prebid-server/util/ptrutil" 26 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/mock" 29 ) 30 31 func TestNewCookieSyncEndpoint(t *testing.T) { 32 var ( 33 syncersByBidder = map[string]usersync.Syncer{"a": &MockSyncer{}} 34 gdprPermsBuilder = fakePermissionsBuilder{ 35 permissions: &fakePermissions{}, 36 }.Builder 37 tcf2ConfigBuilder = fakeTCF2ConfigBuilder{ 38 cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 39 }.Builder 40 configUserSync = config.UserSync{Cooperative: config.UserSyncCooperative{EnabledByDefault: true}} 41 configHostCookie = config.HostCookie{Family: "foo"} 42 configGDPR = config.GDPR{HostVendorID: 42} 43 configCCPAEnforce = true 44 metrics = metrics.MetricsEngineMock{} 45 analytics = MockAnalytics{} 46 fetcher = FakeAccountsFetcher{} 47 bidders = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")} 48 ) 49 50 endpoint := NewCookieSyncEndpoint( 51 syncersByBidder, 52 &config.Configuration{ 53 UserSync: configUserSync, 54 HostCookie: configHostCookie, 55 GDPR: configGDPR, 56 CCPA: config.CCPA{Enforce: configCCPAEnforce}, 57 }, 58 gdprPermsBuilder, 59 tcf2ConfigBuilder, 60 &metrics, 61 &analytics, 62 &fetcher, 63 bidders, 64 ) 65 result := endpoint.(*cookieSyncEndpoint) 66 67 expected := &cookieSyncEndpoint{ 68 chooser: usersync.NewChooser(syncersByBidder), 69 config: &config.Configuration{ 70 UserSync: configUserSync, 71 HostCookie: configHostCookie, 72 GDPR: configGDPR, 73 CCPA: config.CCPA{Enforce: configCCPAEnforce}, 74 }, 75 privacyConfig: usersyncPrivacyConfig{ 76 gdprConfig: configGDPR, 77 gdprPermissionsBuilder: gdprPermsBuilder, 78 tcf2ConfigBuilder: tcf2ConfigBuilder, 79 ccpaEnforce: configCCPAEnforce, 80 bidderHashSet: map[string]struct{}{"bidderA": {}, "bidderB": {}}, 81 }, 82 metrics: &metrics, 83 pbsAnalytics: &analytics, 84 accountsFetcher: &fetcher, 85 } 86 87 assert.IsType(t, &cookieSyncEndpoint{}, endpoint) 88 89 assert.Equal(t, expected.config, result.config) 90 assert.Equal(t, expected.chooser, result.chooser) 91 assert.Equal(t, expected.metrics, result.metrics) 92 assert.Equal(t, expected.pbsAnalytics, result.pbsAnalytics) 93 assert.Equal(t, expected.accountsFetcher, result.accountsFetcher) 94 95 assert.Equal(t, expected.privacyConfig.gdprConfig, result.privacyConfig.gdprConfig) 96 assert.Equal(t, expected.privacyConfig.ccpaEnforce, result.privacyConfig.ccpaEnforce) 97 assert.Equal(t, expected.privacyConfig.bidderHashSet, result.privacyConfig.bidderHashSet) 98 } 99 100 func TestCookieSyncHandle(t *testing.T) { 101 syncTypeExpected := []usersync.SyncType{usersync.SyncTypeIFrame, usersync.SyncTypeRedirect} 102 sync := usersync.Sync{URL: "aURL", Type: usersync.SyncTypeRedirect, SupportCORS: true} 103 syncer := MockSyncer{} 104 syncer.On("GetSync", syncTypeExpected, macros.UserSyncPrivacy{}).Return(sync, nil).Maybe() 105 106 cookieWithSyncs := usersync.NewCookie() 107 cookieWithSyncs.Sync("foo", "anyID") 108 109 testCases := []struct { 110 description string 111 givenCookie *usersync.Cookie 112 givenBody io.Reader 113 givenChooserResult usersync.Result 114 expectedStatusCode int 115 expectedBody string 116 setMetricsExpectations func(*metrics.MetricsEngineMock) 117 setAnalyticsExpectations func(*MockAnalytics) 118 }{ 119 { 120 description: "Request With Cookie", 121 givenCookie: cookieWithSyncs, 122 givenBody: strings.NewReader(`{}`), 123 givenChooserResult: usersync.Result{ 124 Status: usersync.StatusOK, 125 BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, 126 SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, 127 }, 128 expectedStatusCode: 200, 129 expectedBody: `{"status":"ok","bidder_status":[` + 130 `{"bidder":"a","no_cookie":true,"usersync":{"url":"aURL","type":"redirect","supportCORS":true}}` + 131 `]}` + "\n", 132 setMetricsExpectations: func(m *metrics.MetricsEngineMock) { 133 m.On("RecordCookieSync", metrics.CookieSyncOK).Once() 134 m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() 135 }, 136 setAnalyticsExpectations: func(a *MockAnalytics) { 137 expected := analytics.CookieSyncObject{ 138 Status: 200, 139 Errors: nil, 140 BidderStatus: []*analytics.CookieSyncBidder{ 141 { 142 BidderCode: "a", 143 NoCookie: true, 144 UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "redirect", SupportCORS: true}, 145 }, 146 }, 147 } 148 a.On("LogCookieSyncObject", &expected).Once() 149 }, 150 }, 151 { 152 description: "Request Without Cookie", 153 givenCookie: nil, 154 givenBody: strings.NewReader(`{}`), 155 givenChooserResult: usersync.Result{ 156 Status: usersync.StatusOK, 157 BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, 158 SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, 159 }, 160 expectedStatusCode: 200, 161 expectedBody: `{"status":"no_cookie","bidder_status":[` + 162 `{"bidder":"a","no_cookie":true,"usersync":{"url":"aURL","type":"redirect","supportCORS":true}}` + 163 `]}` + "\n", 164 setMetricsExpectations: func(m *metrics.MetricsEngineMock) { 165 m.On("RecordCookieSync", metrics.CookieSyncOK).Once() 166 m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() 167 }, 168 setAnalyticsExpectations: func(a *MockAnalytics) { 169 expected := analytics.CookieSyncObject{ 170 Status: 200, 171 Errors: nil, 172 BidderStatus: []*analytics.CookieSyncBidder{ 173 { 174 BidderCode: "a", 175 NoCookie: true, 176 UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "redirect", SupportCORS: true}, 177 }, 178 }, 179 } 180 a.On("LogCookieSyncObject", &expected).Once() 181 }, 182 }, 183 { 184 description: "Malformed Request", 185 givenCookie: cookieWithSyncs, 186 givenBody: strings.NewReader(`malformed`), 187 givenChooserResult: usersync.Result{ 188 Status: usersync.StatusOK, 189 BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, 190 SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, 191 }, 192 expectedStatusCode: 400, 193 expectedBody: `JSON parsing failed: invalid character 'm' looking for beginning of value` + "\n", 194 setMetricsExpectations: func(m *metrics.MetricsEngineMock) { 195 m.On("RecordCookieSync", metrics.CookieSyncBadRequest).Once() 196 }, 197 setAnalyticsExpectations: func(a *MockAnalytics) { 198 expected := analytics.CookieSyncObject{ 199 Status: 400, 200 Errors: []error{errors.New("JSON parsing failed: invalid character 'm' looking for beginning of value")}, 201 BidderStatus: []*analytics.CookieSyncBidder{}, 202 } 203 a.On("LogCookieSyncObject", &expected).Once() 204 }, 205 }, 206 { 207 description: "Request Blocked By Opt Out", 208 givenCookie: cookieWithSyncs, 209 givenBody: strings.NewReader(`{}`), 210 givenChooserResult: usersync.Result{ 211 Status: usersync.StatusBlockedByUserOptOut, 212 BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, 213 SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, 214 }, 215 expectedStatusCode: 401, 216 expectedBody: `User has opted out` + "\n", 217 setMetricsExpectations: func(m *metrics.MetricsEngineMock) { 218 m.On("RecordCookieSync", metrics.CookieSyncOptOut).Once() 219 }, 220 setAnalyticsExpectations: func(a *MockAnalytics) { 221 expected := analytics.CookieSyncObject{ 222 Status: 401, 223 Errors: []error{errors.New("User has opted out")}, 224 BidderStatus: []*analytics.CookieSyncBidder{}, 225 } 226 a.On("LogCookieSyncObject", &expected).Once() 227 }, 228 }, 229 { 230 description: "Request Blocked By GDPR Host Cookie Restriction", 231 givenCookie: cookieWithSyncs, 232 givenBody: strings.NewReader(`{}`), 233 givenChooserResult: usersync.Result{ 234 Status: usersync.StatusBlockedByGDPR, 235 BiddersEvaluated: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, 236 SyncersChosen: []usersync.SyncerChoice{{Bidder: "a", Syncer: &syncer}}, 237 }, 238 expectedStatusCode: 200, 239 expectedBody: `{"status":"ok","bidder_status":[]}` + "\n", 240 setMetricsExpectations: func(m *metrics.MetricsEngineMock) { 241 m.On("RecordCookieSync", metrics.CookieSyncGDPRHostCookieBlocked).Once() 242 }, 243 setAnalyticsExpectations: func(a *MockAnalytics) { 244 expected := analytics.CookieSyncObject{ 245 Status: 200, 246 Errors: nil, 247 BidderStatus: []*analytics.CookieSyncBidder{}, 248 } 249 a.On("LogCookieSyncObject", &expected).Once() 250 }, 251 }, 252 } 253 254 for _, test := range testCases { 255 mockMetrics := metrics.MetricsEngineMock{} 256 test.setMetricsExpectations(&mockMetrics) 257 258 mockAnalytics := MockAnalytics{} 259 test.setAnalyticsExpectations(&mockAnalytics) 260 261 fakeAccountFetcher := FakeAccountsFetcher{} 262 263 gdprPermsBuilder := fakePermissionsBuilder{ 264 permissions: &fakePermissions{}, 265 }.Builder 266 tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ 267 cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 268 }.Builder 269 270 request := httptest.NewRequest("POST", "/cookiesync", test.givenBody) 271 if test.givenCookie != nil { 272 httpCookie, err := ToHTTPCookie(test.givenCookie) 273 assert.NoError(t, err) 274 request.AddCookie(httpCookie) 275 } 276 277 writer := httptest.NewRecorder() 278 279 endpoint := cookieSyncEndpoint{ 280 chooser: FakeChooser{Result: test.givenChooserResult}, 281 config: &config.Configuration{ 282 AccountDefaults: config.Account{Disabled: false}, 283 }, 284 privacyConfig: usersyncPrivacyConfig{ 285 gdprConfig: config.GDPR{ 286 Enabled: true, 287 DefaultValue: "0", 288 }, 289 gdprPermissionsBuilder: gdprPermsBuilder, 290 tcf2ConfigBuilder: tcf2ConfigBuilder, 291 ccpaEnforce: true, 292 }, 293 metrics: &mockMetrics, 294 pbsAnalytics: &mockAnalytics, 295 accountsFetcher: &fakeAccountFetcher, 296 } 297 assert.NoError(t, endpoint.config.MarshalAccountDefaults()) 298 299 endpoint.Handle(writer, request, nil) 300 301 assert.Equal(t, test.expectedStatusCode, writer.Code, test.description+":status_code") 302 assert.Equal(t, test.expectedBody, writer.Body.String(), test.description+":body") 303 mockMetrics.AssertExpectations(t) 304 mockAnalytics.AssertExpectations(t) 305 } 306 } 307 308 func TestExtractGDPRSignal(t *testing.T) { 309 type testInput struct { 310 requestGDPR *int 311 gppSID []int8 312 } 313 type testOutput struct { 314 gdprSignal gdpr.Signal 315 gdprString string 316 err error 317 } 318 testCases := []struct { 319 desc string 320 in testInput 321 expected testOutput 322 }{ 323 { 324 desc: "SectionTCFEU2 is listed in GPP_SID array, expect SignalYes and nil error", 325 in: testInput{ 326 requestGDPR: nil, 327 gppSID: []int8{2}, 328 }, 329 expected: testOutput{ 330 gdprSignal: gdpr.SignalYes, 331 gdprString: strconv.Itoa(int(gdpr.SignalYes)), 332 err: nil, 333 }, 334 }, 335 { 336 desc: "SectionTCFEU2 is not listed in GPP_SID array, expect SignalNo and nil error", 337 in: testInput{ 338 requestGDPR: nil, 339 gppSID: []int8{6}, 340 }, 341 expected: testOutput{ 342 gdprSignal: gdpr.SignalNo, 343 gdprString: strconv.Itoa(int(gdpr.SignalNo)), 344 err: nil, 345 }, 346 }, 347 { 348 desc: "Empty GPP_SID array and nil requestGDPR value, expect SignalAmbiguous and nil error", 349 in: testInput{ 350 requestGDPR: nil, 351 gppSID: []int8{}, 352 }, 353 expected: testOutput{ 354 gdprSignal: gdpr.SignalAmbiguous, 355 gdprString: "", 356 err: nil, 357 }, 358 }, 359 { 360 desc: "Empty GPP_SID array and non-nil requestGDPR value that could not be successfully parsed, expect SignalAmbiguous and parse error", 361 in: testInput{ 362 requestGDPR: ptrutil.ToPtr(2), 363 gppSID: nil, 364 }, 365 expected: testOutput{ 366 gdprSignal: gdpr.SignalAmbiguous, 367 gdprString: "2", 368 err: &errortypes.BadInput{Message: "GDPR signal should be integer 0 or 1"}, 369 }, 370 }, 371 { 372 desc: "Empty GPP_SID array and non-nil requestGDPR value that could be successfully parsed, expect SignalYes and nil error", 373 in: testInput{ 374 requestGDPR: ptrutil.ToPtr(1), 375 gppSID: nil, 376 }, 377 expected: testOutput{ 378 gdprSignal: gdpr.SignalYes, 379 gdprString: "1", 380 err: nil, 381 }, 382 }, 383 } 384 for _, tc := range testCases { 385 // run 386 outSignal, outGdprStr, outErr := extractGDPRSignal(tc.in.requestGDPR, tc.in.gppSID) 387 // assertions 388 assert.Equal(t, tc.expected.gdprSignal, outSignal, tc.desc) 389 assert.Equal(t, tc.expected.gdprString, outGdprStr, tc.desc) 390 assert.Equal(t, tc.expected.err, outErr, tc.desc) 391 } 392 } 393 394 func TestExtractPrivacyPolicies(t *testing.T) { 395 type testInput struct { 396 request cookieSyncRequest 397 usersyncDefaultGDPRValue string 398 } 399 type testOutput struct { 400 macros macros.UserSyncPrivacy 401 gdprSignal gdpr.Signal 402 policies privacy.Policies 403 err error 404 } 405 testCases := []struct { 406 desc string 407 in testInput 408 expected testOutput 409 }{ 410 { 411 desc: "request GPP string is malformed, expect empty policies, signal No and error", 412 in: testInput{ 413 request: cookieSyncRequest{GPP: "malformedGPPString"}, 414 }, 415 expected: testOutput{ 416 macros: macros.UserSyncPrivacy{}, 417 gdprSignal: gdpr.SignalNo, 418 policies: privacy.Policies{}, 419 err: errors.New("error parsing GPP header, header must have type=3"), 420 }, 421 }, 422 { 423 desc: "Malformed GPPSid string", 424 in: testInput{ 425 request: cookieSyncRequest{ 426 GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", 427 GPPSID: "malformed", 428 USPrivacy: "1YYY", 429 }, 430 }, 431 expected: testOutput{ 432 macros: macros.UserSyncPrivacy{}, 433 gdprSignal: gdpr.SignalNo, 434 policies: privacy.Policies{}, 435 err: &strconv.NumError{Func: "ParseInt", Num: "malformed", Err: strconv.ErrSyntax}, 436 }, 437 }, 438 { 439 desc: "request USPrivacy string is different from the one in the GPP string, expect empty policies, signalNo and error", 440 in: testInput{ 441 request: cookieSyncRequest{ 442 GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", 443 GPPSID: "6", 444 USPrivacy: "1YYY", 445 }, 446 }, 447 expected: testOutput{ 448 macros: macros.UserSyncPrivacy{}, 449 gdprSignal: gdpr.SignalNo, 450 policies: privacy.Policies{}, 451 err: errors.New("request.us_privacy consent does not match uspv1"), 452 }, 453 }, 454 { 455 desc: "no issues extracting privacy policies from request GPP and request GPPSid strings", 456 in: testInput{ 457 request: cookieSyncRequest{ 458 GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", 459 GPPSID: "6", 460 }, 461 }, 462 expected: testOutput{ 463 macros: macros.UserSyncPrivacy{ 464 GDPR: "0", 465 GDPRConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 466 USPrivacy: "1YNN", 467 GPP: "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", 468 GPPSID: "6", 469 }, 470 gdprSignal: gdpr.SignalNo, 471 policies: privacy.Policies{GPPSID: []int8{6}}, 472 err: nil, 473 }, 474 }, 475 } 476 for _, tc := range testCases { 477 outMacros, outSignal, outPolicies, outErr := extractPrivacyPolicies(tc.in.request, tc.in.usersyncDefaultGDPRValue) 478 479 assert.Equal(t, tc.expected.macros, outMacros, tc.desc) 480 assert.Equal(t, tc.expected.gdprSignal, outSignal, tc.desc) 481 assert.Equal(t, tc.expected.policies, outPolicies, tc.desc) 482 assert.Equal(t, tc.expected.err, outErr, tc.desc) 483 } 484 } 485 486 func TestCookieSyncParseRequest(t *testing.T) { 487 expectedCCPAParsedPolicy, _ := ccpa.Policy{Consent: "1NYN"}.Parse(map[string]struct{}{}) 488 emptyActivityPoliciesRequest := privacy.NewRequestFromPolicies(privacy.Policies{}) 489 490 testCases := []struct { 491 description string 492 givenConfig config.UserSync 493 givenBody io.Reader 494 givenGDPRConfig config.GDPR 495 givenCCPAEnabled bool 496 givenAccountRequired bool 497 expectedError string 498 expectedPrivacy macros.UserSyncPrivacy 499 expectedRequest usersync.Request 500 }{ 501 502 { 503 description: "Complete Request - includes GPP string with EU TCF V2", 504 givenBody: strings.NewReader(`{` + 505 `"bidders":["a", "b"],` + 506 `"gdpr":1,` + 507 `"gdpr_consent":"anyGDPRConsent",` + 508 `"us_privacy":"1NYN",` + 509 `"gpp":"DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",` + 510 `"gpp_sid":"2",` + 511 `"limit":42,` + 512 `"coopSync":true,` + 513 `"filterSettings":{"iframe":{"bidders":"*","filter":"include"}, "image":{"bidders":["b"],"filter":"exclude"}}` + 514 `}`), 515 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 516 givenCCPAEnabled: true, 517 givenConfig: config.UserSync{ 518 PriorityGroups: [][]string{{"a", "b", "c"}}, 519 Cooperative: config.UserSyncCooperative{ 520 EnabledByDefault: false, 521 }, 522 }, 523 expectedPrivacy: macros.UserSyncPrivacy{ 524 GDPR: "1", 525 GDPRConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 526 USPrivacy: "1NYN", 527 GPP: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 528 GPPSID: "2", 529 }, 530 expectedRequest: usersync.Request{ 531 Bidders: []string{"a", "b"}, 532 Cooperative: usersync.Cooperative{ 533 Enabled: true, 534 PriorityGroups: [][]string{{"a", "b", "c"}}, 535 }, 536 Limit: 42, 537 Privacy: usersyncPrivacy{ 538 gdprPermissions: &fakePermissions{}, 539 ccpaParsedPolicy: expectedCCPAParsedPolicy, 540 activityRequest: privacy.NewRequestFromPolicies(privacy.Policies{GPPSID: []int8{2}}), 541 }, 542 SyncTypeFilter: usersync.SyncTypeFilter{ 543 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 544 Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), 545 }, 546 }, 547 }, 548 { 549 description: "Complete Request - Legacy Fields Only", 550 givenBody: strings.NewReader(`{` + 551 `"bidders":["a", "b"],` + 552 `"gdpr":1,` + 553 `"gdpr_consent":"anyGDPRConsent",` + 554 `"us_privacy":"1NYN",` + 555 `"limit":42` + 556 `}`), 557 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 558 givenCCPAEnabled: true, 559 givenConfig: config.UserSync{ 560 PriorityGroups: [][]string{{"a", "b", "c"}}, 561 Cooperative: config.UserSyncCooperative{ 562 EnabledByDefault: false, 563 }, 564 }, 565 expectedPrivacy: macros.UserSyncPrivacy{ 566 GDPR: "1", 567 GDPRConsent: "anyGDPRConsent", 568 USPrivacy: "1NYN", 569 }, 570 expectedRequest: usersync.Request{ 571 Bidders: []string{"a", "b"}, 572 Cooperative: usersync.Cooperative{ 573 Enabled: false, 574 PriorityGroups: [][]string{{"a", "b", "c"}}, 575 }, 576 Limit: 42, 577 Privacy: usersyncPrivacy{ 578 gdprPermissions: &fakePermissions{}, 579 ccpaParsedPolicy: expectedCCPAParsedPolicy, 580 activityRequest: emptyActivityPoliciesRequest, 581 }, 582 SyncTypeFilter: usersync.SyncTypeFilter{ 583 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 584 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 585 }, 586 }, 587 }, 588 { 589 description: "Empty Request", 590 givenBody: strings.NewReader(`{}`), 591 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 592 givenCCPAEnabled: true, 593 expectedPrivacy: macros.UserSyncPrivacy{}, 594 expectedRequest: usersync.Request{ 595 Privacy: usersyncPrivacy{ 596 gdprPermissions: &fakePermissions{}, 597 activityRequest: emptyActivityPoliciesRequest, 598 }, 599 SyncTypeFilter: usersync.SyncTypeFilter{ 600 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 601 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 602 }, 603 }, 604 }, 605 { 606 description: "Cooperative Unspecified - Default True", 607 givenBody: strings.NewReader(`{}`), 608 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 609 givenCCPAEnabled: true, 610 givenConfig: config.UserSync{ 611 PriorityGroups: [][]string{{"a", "b", "c"}}, 612 Cooperative: config.UserSyncCooperative{ 613 EnabledByDefault: true, 614 }, 615 }, 616 expectedPrivacy: macros.UserSyncPrivacy{}, 617 expectedRequest: usersync.Request{ 618 Cooperative: usersync.Cooperative{ 619 Enabled: true, 620 PriorityGroups: [][]string{{"a", "b", "c"}}, 621 }, 622 Privacy: usersyncPrivacy{ 623 gdprPermissions: &fakePermissions{}, 624 activityRequest: emptyActivityPoliciesRequest, 625 }, 626 SyncTypeFilter: usersync.SyncTypeFilter{ 627 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 628 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 629 }, 630 }, 631 }, 632 { 633 description: "Cooperative Unspecified - Default False", 634 givenBody: strings.NewReader(`{}`), 635 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 636 givenCCPAEnabled: true, 637 givenConfig: config.UserSync{ 638 PriorityGroups: [][]string{{"a", "b", "c"}}, 639 Cooperative: config.UserSyncCooperative{ 640 EnabledByDefault: false, 641 }, 642 }, 643 expectedPrivacy: macros.UserSyncPrivacy{}, 644 expectedRequest: usersync.Request{ 645 Cooperative: usersync.Cooperative{ 646 Enabled: false, 647 PriorityGroups: [][]string{{"a", "b", "c"}}, 648 }, 649 Privacy: usersyncPrivacy{ 650 gdprPermissions: &fakePermissions{}, 651 activityRequest: emptyActivityPoliciesRequest, 652 }, 653 SyncTypeFilter: usersync.SyncTypeFilter{ 654 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 655 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 656 }, 657 }, 658 }, 659 { 660 description: "Cooperative False - Default True", 661 givenBody: strings.NewReader(`{"coopSync":false}`), 662 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 663 givenCCPAEnabled: true, 664 givenConfig: config.UserSync{ 665 PriorityGroups: [][]string{{"a", "b", "c"}}, 666 Cooperative: config.UserSyncCooperative{ 667 EnabledByDefault: true, 668 }, 669 }, 670 expectedPrivacy: macros.UserSyncPrivacy{}, 671 expectedRequest: usersync.Request{ 672 Cooperative: usersync.Cooperative{ 673 Enabled: false, 674 PriorityGroups: [][]string{{"a", "b", "c"}}, 675 }, 676 Privacy: usersyncPrivacy{ 677 gdprPermissions: &fakePermissions{}, 678 activityRequest: emptyActivityPoliciesRequest, 679 }, 680 SyncTypeFilter: usersync.SyncTypeFilter{ 681 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 682 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 683 }, 684 }, 685 }, 686 { 687 description: "Cooperative False - Default False", 688 givenBody: strings.NewReader(`{"coopSync":false}`), 689 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 690 givenCCPAEnabled: true, 691 givenConfig: config.UserSync{ 692 PriorityGroups: [][]string{{"a", "b", "c"}}, 693 Cooperative: config.UserSyncCooperative{ 694 EnabledByDefault: false, 695 }, 696 }, 697 expectedPrivacy: macros.UserSyncPrivacy{}, 698 expectedRequest: usersync.Request{ 699 Cooperative: usersync.Cooperative{ 700 Enabled: false, 701 PriorityGroups: [][]string{{"a", "b", "c"}}, 702 }, 703 Privacy: usersyncPrivacy{ 704 gdprPermissions: &fakePermissions{}, 705 activityRequest: emptyActivityPoliciesRequest, 706 }, 707 SyncTypeFilter: usersync.SyncTypeFilter{ 708 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 709 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 710 }, 711 }, 712 }, 713 { 714 description: "Cooperative True - Default True", 715 givenBody: strings.NewReader(`{"coopSync":true}`), 716 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 717 givenCCPAEnabled: true, 718 givenConfig: config.UserSync{ 719 PriorityGroups: [][]string{{"a", "b", "c"}}, 720 Cooperative: config.UserSyncCooperative{ 721 EnabledByDefault: true, 722 }, 723 }, 724 expectedPrivacy: macros.UserSyncPrivacy{}, 725 expectedRequest: usersync.Request{ 726 Cooperative: usersync.Cooperative{ 727 Enabled: true, 728 PriorityGroups: [][]string{{"a", "b", "c"}}, 729 }, 730 Privacy: usersyncPrivacy{ 731 gdprPermissions: &fakePermissions{}, 732 activityRequest: emptyActivityPoliciesRequest, 733 }, 734 SyncTypeFilter: usersync.SyncTypeFilter{ 735 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 736 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 737 }, 738 }, 739 }, 740 { 741 description: "Cooperative True - Default False", 742 givenBody: strings.NewReader(`{"coopSync":true}`), 743 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 744 givenCCPAEnabled: true, 745 givenConfig: config.UserSync{ 746 PriorityGroups: [][]string{{"a", "b", "c"}}, 747 Cooperative: config.UserSyncCooperative{ 748 EnabledByDefault: false, 749 }, 750 }, 751 expectedPrivacy: macros.UserSyncPrivacy{}, 752 expectedRequest: usersync.Request{ 753 Cooperative: usersync.Cooperative{ 754 Enabled: true, 755 PriorityGroups: [][]string{{"a", "b", "c"}}, 756 }, 757 Privacy: usersyncPrivacy{ 758 gdprPermissions: &fakePermissions{}, 759 activityRequest: emptyActivityPoliciesRequest, 760 }, 761 SyncTypeFilter: usersync.SyncTypeFilter{ 762 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 763 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 764 }, 765 }, 766 }, 767 { 768 description: "CCPA Consent Invalid", 769 givenBody: strings.NewReader(`{"us_privacy":"invalid"}`), 770 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 771 givenCCPAEnabled: true, 772 expectedPrivacy: macros.UserSyncPrivacy{}, 773 expectedRequest: usersync.Request{ 774 Privacy: usersyncPrivacy{ 775 gdprPermissions: &fakePermissions{}, 776 activityRequest: emptyActivityPoliciesRequest, 777 }, 778 SyncTypeFilter: usersync.SyncTypeFilter{ 779 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 780 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 781 }, 782 }, 783 }, 784 { 785 description: "CCPA Disabled", 786 givenBody: strings.NewReader(`{"us_privacy":"1NYN"}`), 787 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 788 givenCCPAEnabled: false, 789 expectedPrivacy: macros.UserSyncPrivacy{ 790 USPrivacy: "1NYN", 791 }, 792 expectedRequest: usersync.Request{ 793 Privacy: usersyncPrivacy{ 794 gdprPermissions: &fakePermissions{}, 795 activityRequest: emptyActivityPoliciesRequest, 796 }, 797 SyncTypeFilter: usersync.SyncTypeFilter{ 798 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 799 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 800 }, 801 }, 802 }, 803 { 804 description: "Invalid JSON", 805 givenBody: strings.NewReader(`malformed`), 806 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 807 givenCCPAEnabled: true, 808 expectedError: "JSON parsing failed: invalid character 'm' looking for beginning of value", 809 }, 810 { 811 description: "Invalid Type Filter", 812 givenBody: strings.NewReader(`{"filterSettings":{"iframe":{"bidders":"invalid","filter":"exclude"}}}`), 813 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 814 givenCCPAEnabled: true, 815 expectedError: "error parsing filtersettings.iframe: invalid bidders value `invalid`. must either be '*' or a string array", 816 }, 817 { 818 description: "Invalid GDPR Signal", 819 givenBody: strings.NewReader(`{"gdpr":5}`), 820 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 821 givenCCPAEnabled: true, 822 expectedError: "GDPR signal should be integer 0 or 1", 823 }, 824 { 825 description: "Missing GDPR Consent - Explicit Signal 0", 826 givenBody: strings.NewReader(`{"gdpr":0}`), 827 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 828 givenCCPAEnabled: true, 829 expectedPrivacy: macros.UserSyncPrivacy{ 830 GDPR: "0", 831 }, 832 expectedRequest: usersync.Request{ 833 Privacy: usersyncPrivacy{ 834 gdprPermissions: &fakePermissions{}, 835 activityRequest: emptyActivityPoliciesRequest, 836 }, 837 SyncTypeFilter: usersync.SyncTypeFilter{ 838 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 839 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 840 }, 841 }, 842 }, 843 { 844 description: "Missing GDPR Consent - Explicit Signal 1", 845 givenBody: strings.NewReader(`{"gdpr":1}`), 846 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 847 givenCCPAEnabled: true, 848 expectedError: "gdpr_consent is required if gdpr=1", 849 }, 850 { 851 description: "Missing GDPR Consent - Ambiguous Signal - Default Value 0", 852 givenBody: strings.NewReader(`{}`), 853 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 854 givenCCPAEnabled: true, 855 expectedPrivacy: macros.UserSyncPrivacy{ 856 GDPR: "", 857 }, 858 expectedRequest: usersync.Request{ 859 Privacy: usersyncPrivacy{ 860 gdprPermissions: &fakePermissions{}, 861 activityRequest: emptyActivityPoliciesRequest, 862 }, 863 SyncTypeFilter: usersync.SyncTypeFilter{ 864 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 865 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 866 }, 867 }, 868 }, 869 { 870 description: "Missing GDPR Consent - Ambiguous Signal - Default Value 1", 871 givenBody: strings.NewReader(`{}`), 872 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "1"}, 873 givenCCPAEnabled: true, 874 expectedError: "gdpr_consent is required. gdpr is not specified and is assumed to be 1 by the server. set gdpr=0 to exempt this request", 875 }, 876 { 877 description: "HTTP Read Error", 878 givenBody: iotest.ErrReader(errors.New("anyError")), 879 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 880 givenCCPAEnabled: true, 881 expectedError: "Failed to read request body", 882 }, 883 { 884 description: "Account Defaults - Max Limit + Default Coop", 885 givenBody: strings.NewReader(`{` + 886 `"bidders":["a", "b"],` + 887 `"limit":42,` + 888 `"account":"TestAccount"` + 889 `}`), 890 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 891 givenCCPAEnabled: true, 892 givenConfig: config.UserSync{ 893 PriorityGroups: [][]string{{"a", "b", "c"}}, 894 Cooperative: config.UserSyncCooperative{}, 895 }, 896 expectedPrivacy: macros.UserSyncPrivacy{}, 897 expectedRequest: usersync.Request{ 898 Bidders: []string{"a", "b"}, 899 Cooperative: usersync.Cooperative{ 900 Enabled: true, 901 PriorityGroups: [][]string{{"a", "b", "c"}}, 902 }, 903 Limit: 30, 904 Privacy: usersyncPrivacy{ 905 gdprPermissions: &fakePermissions{}, 906 activityRequest: emptyActivityPoliciesRequest, 907 }, 908 SyncTypeFilter: usersync.SyncTypeFilter{ 909 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 910 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 911 }, 912 }, 913 }, 914 { 915 description: "Account Defaults - DefaultLimit", 916 givenBody: strings.NewReader(`{` + 917 `"bidders":["a", "b"],` + 918 `"account":"TestAccount"` + 919 `}`), 920 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 921 givenCCPAEnabled: true, 922 givenConfig: config.UserSync{ 923 PriorityGroups: [][]string{{"a", "b", "c"}}, 924 Cooperative: config.UserSyncCooperative{ 925 EnabledByDefault: false, 926 }, 927 }, 928 expectedPrivacy: macros.UserSyncPrivacy{}, 929 expectedRequest: usersync.Request{ 930 Bidders: []string{"a", "b"}, 931 Cooperative: usersync.Cooperative{ 932 Enabled: true, 933 PriorityGroups: [][]string{{"a", "b", "c"}}, 934 }, 935 Limit: 20, 936 Privacy: usersyncPrivacy{ 937 gdprPermissions: &fakePermissions{}, 938 activityRequest: emptyActivityPoliciesRequest, 939 }, 940 SyncTypeFilter: usersync.SyncTypeFilter{ 941 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 942 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 943 }, 944 }, 945 }, 946 { 947 description: "Account Defaults - Error", 948 givenBody: strings.NewReader(`{` + 949 `"bidders":["a", "b"],` + 950 `"account":"DisabledAccount"` + 951 `}`), 952 givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, 953 givenCCPAEnabled: true, 954 givenConfig: config.UserSync{ 955 PriorityGroups: [][]string{{"a", "b", "c"}}, 956 Cooperative: config.UserSyncCooperative{ 957 EnabledByDefault: false, 958 }, 959 }, 960 expectedPrivacy: macros.UserSyncPrivacy{}, 961 expectedRequest: usersync.Request{ 962 Bidders: []string{"a", "b"}, 963 Cooperative: usersync.Cooperative{ 964 Enabled: true, 965 PriorityGroups: [][]string{{"a", "b", "c"}}, 966 }, 967 Limit: 20, 968 Privacy: usersyncPrivacy{ 969 gdprPermissions: &fakePermissions{}, 970 activityRequest: emptyActivityPoliciesRequest, 971 }, 972 SyncTypeFilter: usersync.SyncTypeFilter{ 973 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 974 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 975 }, 976 }, 977 expectedError: errCookieSyncAccountBlocked.Error(), 978 givenAccountRequired: true, 979 }, 980 } 981 982 for _, test := range testCases { 983 httpRequest := httptest.NewRequest("POST", "/cookiesync", test.givenBody) 984 985 gdprPermsBuilder := fakePermissionsBuilder{ 986 permissions: &fakePermissions{}, 987 }.Builder 988 tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ 989 cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 990 }.Builder 991 992 endpoint := cookieSyncEndpoint{ 993 config: &config.Configuration{ 994 UserSync: test.givenConfig, 995 AccountRequired: test.givenAccountRequired, 996 }, 997 privacyConfig: usersyncPrivacyConfig{ 998 gdprConfig: test.givenGDPRConfig, 999 gdprPermissionsBuilder: gdprPermsBuilder, 1000 tcf2ConfigBuilder: tcf2ConfigBuilder, 1001 ccpaEnforce: test.givenCCPAEnabled, 1002 }, 1003 accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{ 1004 "TestAccount": json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true}}`), 1005 "DisabledAccount": json.RawMessage(`{"disabled":true}`), 1006 "ValidAccountInvalidActivities": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"rules":[{"condition":{"componentName": ["bidderA.bidderB.bidderC"]}}]}}}}`), 1007 }}, 1008 } 1009 assert.NoError(t, endpoint.config.MarshalAccountDefaults()) 1010 request, privacyPolicies, err := endpoint.parseRequest(httpRequest) 1011 1012 if test.expectedError == "" { 1013 assert.NoError(t, err, test.description+":err") 1014 assert.Equal(t, test.expectedRequest, request, test.description+":request") 1015 assert.Equal(t, test.expectedPrivacy, privacyPolicies, test.description+":privacy") 1016 } else { 1017 assert.EqualError(t, err, test.expectedError, test.description+":err") 1018 assert.Empty(t, request, test.description+":request") 1019 assert.Empty(t, privacyPolicies, test.description+":privacy") 1020 } 1021 } 1022 } 1023 1024 func TestSetLimit(t *testing.T) { 1025 intNegative1 := -1 1026 int20 := 20 1027 int30 := 30 1028 int40 := 40 1029 1030 testCases := []struct { 1031 description string 1032 givenRequest cookieSyncRequest 1033 givenAccount *config.Account 1034 expectedRequest cookieSyncRequest 1035 }{ 1036 { 1037 description: "Default Limit is Applied (request limit = 0)", 1038 givenRequest: cookieSyncRequest{ 1039 Limit: 0, 1040 }, 1041 givenAccount: &config.Account{ 1042 CookieSync: config.CookieSync{ 1043 DefaultLimit: &int20, 1044 }, 1045 }, 1046 expectedRequest: cookieSyncRequest{ 1047 Limit: 20, 1048 }, 1049 }, 1050 { 1051 description: "Default Limit is Not Applied (default limit not set)", 1052 givenRequest: cookieSyncRequest{ 1053 Limit: 0, 1054 }, 1055 givenAccount: &config.Account{ 1056 CookieSync: config.CookieSync{ 1057 DefaultLimit: nil, 1058 }, 1059 }, 1060 expectedRequest: cookieSyncRequest{ 1061 Limit: 0, 1062 }, 1063 }, 1064 { 1065 description: "Default Limit is Not Applied (request limit > 0)", 1066 givenRequest: cookieSyncRequest{ 1067 Limit: 10, 1068 }, 1069 givenAccount: &config.Account{ 1070 CookieSync: config.CookieSync{ 1071 DefaultLimit: &int20, 1072 }, 1073 }, 1074 expectedRequest: cookieSyncRequest{ 1075 Limit: 10, 1076 }, 1077 }, 1078 { 1079 description: "Max Limit is Applied (request limit <= 0)", 1080 givenRequest: cookieSyncRequest{ 1081 Limit: 0, 1082 }, 1083 givenAccount: &config.Account{ 1084 CookieSync: config.CookieSync{ 1085 MaxLimit: &int30, 1086 }, 1087 }, 1088 expectedRequest: cookieSyncRequest{ 1089 Limit: 30, 1090 }, 1091 }, 1092 { 1093 description: "Max Limit is Applied (0 < max < limit)", 1094 givenRequest: cookieSyncRequest{ 1095 Limit: 40, 1096 }, 1097 givenAccount: &config.Account{ 1098 CookieSync: config.CookieSync{ 1099 MaxLimit: &int30, 1100 }, 1101 }, 1102 expectedRequest: cookieSyncRequest{ 1103 Limit: 30, 1104 }, 1105 }, 1106 { 1107 description: "Max Limit is Not Applied (max not set)", 1108 givenRequest: cookieSyncRequest{ 1109 Limit: 10, 1110 }, 1111 givenAccount: &config.Account{ 1112 CookieSync: config.CookieSync{ 1113 MaxLimit: nil, 1114 }, 1115 }, 1116 expectedRequest: cookieSyncRequest{ 1117 Limit: 10, 1118 }, 1119 }, 1120 { 1121 description: "Max Limit is Not Applied (0 < limit < max)", 1122 givenRequest: cookieSyncRequest{ 1123 Limit: 10, 1124 }, 1125 givenAccount: &config.Account{ 1126 CookieSync: config.CookieSync{ 1127 MaxLimit: &int30, 1128 }, 1129 }, 1130 expectedRequest: cookieSyncRequest{ 1131 Limit: 10, 1132 }, 1133 }, 1134 { 1135 description: "Max Limit is Applied After applying the default", 1136 givenRequest: cookieSyncRequest{ 1137 Limit: 0, 1138 }, 1139 givenAccount: &config.Account{ 1140 CookieSync: config.CookieSync{ 1141 DefaultLimit: &int40, 1142 MaxLimit: &int30, 1143 }, 1144 }, 1145 expectedRequest: cookieSyncRequest{ 1146 Limit: 30, 1147 }, 1148 }, 1149 { 1150 description: "Negative Value Check", 1151 givenRequest: cookieSyncRequest{ 1152 Limit: 0, 1153 }, 1154 givenAccount: &config.Account{ 1155 CookieSync: config.CookieSync{ 1156 DefaultLimit: &intNegative1, 1157 MaxLimit: &intNegative1, 1158 }, 1159 }, 1160 expectedRequest: cookieSyncRequest{ 1161 Limit: 0, 1162 }, 1163 }, 1164 } 1165 1166 for _, test := range testCases { 1167 endpoint := cookieSyncEndpoint{} 1168 request := endpoint.setLimit(test.givenRequest, test.givenAccount.CookieSync) 1169 assert.Equal(t, test.expectedRequest, request, test.description) 1170 } 1171 } 1172 1173 func TestSetCooperativeSync(t *testing.T) { 1174 coopSyncFalse := false 1175 coopSyncTrue := true 1176 1177 testCases := []struct { 1178 description string 1179 givenRequest cookieSyncRequest 1180 givenAccount *config.Account 1181 expectedRequest cookieSyncRequest 1182 }{ 1183 { 1184 description: "Request coop sync unmodified - request sync nil & default sync nil", 1185 givenRequest: cookieSyncRequest{ 1186 CooperativeSync: nil, 1187 }, 1188 givenAccount: &config.Account{ 1189 CookieSync: config.CookieSync{ 1190 DefaultCoopSync: nil, 1191 }, 1192 }, 1193 expectedRequest: cookieSyncRequest{ 1194 CooperativeSync: nil, 1195 }, 1196 }, 1197 { 1198 description: "Request coop sync set to default - request sync nil & default sync not nil", 1199 givenRequest: cookieSyncRequest{ 1200 CooperativeSync: nil, 1201 }, 1202 givenAccount: &config.Account{ 1203 CookieSync: config.CookieSync{ 1204 DefaultCoopSync: &coopSyncTrue, 1205 }, 1206 }, 1207 expectedRequest: cookieSyncRequest{ 1208 CooperativeSync: &coopSyncTrue, 1209 }, 1210 }, 1211 { 1212 description: "Request coop sync unmodified - request sync not nil & default sync nil", 1213 givenRequest: cookieSyncRequest{ 1214 CooperativeSync: &coopSyncTrue, 1215 }, 1216 givenAccount: &config.Account{ 1217 CookieSync: config.CookieSync{ 1218 DefaultCoopSync: nil, 1219 }, 1220 }, 1221 expectedRequest: cookieSyncRequest{ 1222 CooperativeSync: &coopSyncTrue, 1223 }, 1224 }, 1225 { 1226 description: "Request coop sync unmodified - request sync not nil & default sync not nil", 1227 givenRequest: cookieSyncRequest{ 1228 CooperativeSync: &coopSyncFalse, 1229 }, 1230 givenAccount: &config.Account{ 1231 CookieSync: config.CookieSync{ 1232 DefaultCoopSync: &coopSyncTrue, 1233 }, 1234 }, 1235 expectedRequest: cookieSyncRequest{ 1236 CooperativeSync: &coopSyncFalse, 1237 }, 1238 }, 1239 } 1240 1241 for _, test := range testCases { 1242 endpoint := cookieSyncEndpoint{} 1243 request := endpoint.setCooperativeSync(test.givenRequest, test.givenAccount.CookieSync) 1244 assert.Equal(t, test.expectedRequest, request, test.description) 1245 } 1246 } 1247 1248 func TestWriteParseRequestErrorMetrics(t *testing.T) { 1249 err := errors.New("anyError") 1250 1251 mockAnalytics := MockAnalytics{} 1252 mockAnalytics.On("LogCookieSyncObject", mock.Anything) 1253 writer := httptest.NewRecorder() 1254 1255 endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics} 1256 endpoint.handleError(writer, err, 418) 1257 1258 assert.Equal(t, writer.Code, 418) 1259 assert.Equal(t, writer.Body.String(), "anyError\n") 1260 mockAnalytics.AssertCalled(t, "LogCookieSyncObject", &analytics.CookieSyncObject{ 1261 Status: 418, 1262 Errors: []error{err}, 1263 BidderStatus: []*analytics.CookieSyncBidder{}, 1264 }) 1265 } 1266 1267 func TestCookieSyncWriteParseRequestErrorMetrics(t *testing.T) { 1268 testCases := []struct { 1269 description string 1270 err error 1271 setExpectations func(*metrics.MetricsEngineMock) 1272 }{ 1273 { 1274 description: "Account Blocked", 1275 err: errCookieSyncAccountBlocked, 1276 setExpectations: func(m *metrics.MetricsEngineMock) { 1277 m.On("RecordCookieSync", metrics.CookieSyncAccountBlocked).Once() 1278 }, 1279 }, 1280 { 1281 description: "Account Invalid", 1282 err: errCookieSyncAccountInvalid, 1283 setExpectations: func(m *metrics.MetricsEngineMock) { 1284 m.On("RecordCookieSync", metrics.CookieSyncAccountInvalid).Once() 1285 }, 1286 }, 1287 { 1288 description: "Account Malformed", 1289 err: errCookieSyncAccountConfigMalformed, 1290 setExpectations: func(m *metrics.MetricsEngineMock) { 1291 m.On("RecordCookieSync", metrics.CookieSyncAccountConfigMalformed).Once() 1292 }, 1293 }, 1294 { 1295 description: "No Special Case", 1296 err: errors.New("any error"), 1297 setExpectations: func(m *metrics.MetricsEngineMock) { 1298 m.On("RecordCookieSync", metrics.CookieSyncBadRequest).Once() 1299 }, 1300 }, 1301 } 1302 1303 for _, test := range testCases { 1304 mockMetrics := metrics.MetricsEngineMock{} 1305 test.setExpectations(&mockMetrics) 1306 1307 endpoint := &cookieSyncEndpoint{metrics: &mockMetrics} 1308 endpoint.writeParseRequestErrorMetrics(test.err) 1309 1310 mockMetrics.AssertExpectations(t) 1311 } 1312 } 1313 1314 func TestParseTypeFilter(t *testing.T) { 1315 testCases := []struct { 1316 description string 1317 given *cookieSyncRequestFilterSettings 1318 expectedError string 1319 expectedFilter usersync.SyncTypeFilter 1320 }{ 1321 { 1322 description: "Nil", 1323 given: nil, 1324 expectedFilter: usersync.SyncTypeFilter{ 1325 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1326 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1327 }, 1328 }, 1329 { 1330 description: "Nil Object", 1331 given: &cookieSyncRequestFilterSettings{}, 1332 expectedFilter: usersync.SyncTypeFilter{ 1333 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1334 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1335 }, 1336 }, 1337 { 1338 description: "Given IFrame Only", 1339 given: &cookieSyncRequestFilterSettings{ 1340 IFrame: &cookieSyncRequestFilter{Bidders: []interface{}{"a"}, Mode: "exclude"}, 1341 }, 1342 expectedFilter: usersync.SyncTypeFilter{ 1343 IFrame: usersync.NewSpecificBidderFilter([]string{"a"}, usersync.BidderFilterModeExclude), 1344 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1345 }, 1346 }, 1347 { 1348 description: "Given Redirect Only", 1349 given: &cookieSyncRequestFilterSettings{ 1350 Redirect: &cookieSyncRequestFilter{Bidders: []interface{}{"b"}, Mode: "exclude"}, 1351 }, 1352 expectedFilter: usersync.SyncTypeFilter{ 1353 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1354 Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), 1355 }, 1356 }, 1357 { 1358 description: "Given Both", 1359 given: &cookieSyncRequestFilterSettings{ 1360 IFrame: &cookieSyncRequestFilter{Bidders: []interface{}{"a"}, Mode: "exclude"}, 1361 Redirect: &cookieSyncRequestFilter{Bidders: []interface{}{"b"}, Mode: "exclude"}, 1362 }, 1363 expectedFilter: usersync.SyncTypeFilter{ 1364 IFrame: usersync.NewSpecificBidderFilter([]string{"a"}, usersync.BidderFilterModeExclude), 1365 Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), 1366 }, 1367 }, 1368 { 1369 description: "IFrame Error", 1370 given: &cookieSyncRequestFilterSettings{ 1371 IFrame: &cookieSyncRequestFilter{Bidders: 42, Mode: "exclude"}, 1372 Redirect: &cookieSyncRequestFilter{Bidders: []interface{}{"b"}, Mode: "exclude"}, 1373 }, 1374 expectedError: "error parsing filtersettings.iframe: invalid bidders type. must either be a string '*' or a string array of bidders", 1375 }, 1376 { 1377 description: "Redirect Error", 1378 given: &cookieSyncRequestFilterSettings{ 1379 IFrame: &cookieSyncRequestFilter{Bidders: []interface{}{"a"}, Mode: "exclude"}, 1380 Redirect: &cookieSyncRequestFilter{Bidders: 42, Mode: "exclude"}, 1381 }, 1382 expectedError: "error parsing filtersettings.image: invalid bidders type. must either be a string '*' or a string array of bidders", 1383 }, 1384 } 1385 1386 for _, test := range testCases { 1387 result, err := parseTypeFilter(test.given) 1388 1389 if test.expectedError == "" { 1390 assert.NoError(t, err, test.description+":err") 1391 assert.Equal(t, test.expectedFilter, result, test.description+":result") 1392 } else { 1393 assert.EqualError(t, err, test.expectedError, test.description+":err") 1394 assert.Empty(t, result, test.description+":result") 1395 } 1396 } 1397 } 1398 1399 func TestParseBidderFilter(t *testing.T) { 1400 testCases := []struct { 1401 description string 1402 given *cookieSyncRequestFilter 1403 expectedError string 1404 expectedFilter usersync.BidderFilter 1405 }{ 1406 { 1407 description: "Nil", 1408 given: nil, 1409 expectedFilter: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1410 }, 1411 { 1412 description: "All Bidders - Include", 1413 given: &cookieSyncRequestFilter{Bidders: "*", Mode: "include"}, 1414 expectedFilter: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1415 }, 1416 { 1417 description: "All Bidders - Exclude", 1418 given: &cookieSyncRequestFilter{Bidders: "*", Mode: "exclude"}, 1419 expectedFilter: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), 1420 }, 1421 { 1422 description: "All Bidders - Invalid Mode", 1423 given: &cookieSyncRequestFilter{Bidders: "*", Mode: "invalid"}, 1424 expectedError: "invalid filter value 'invalid'. must be either 'include' or 'exclude'", 1425 }, 1426 { 1427 description: "All Bidders - Unexpected Bidders Value", 1428 given: &cookieSyncRequestFilter{Bidders: "invalid", Mode: "include"}, 1429 expectedError: "invalid bidders value `invalid`. must either be '*' or a string array", 1430 }, 1431 { 1432 description: "Specific Bidders - Include", 1433 given: &cookieSyncRequestFilter{Bidders: []interface{}{"a", "b"}, Mode: "include"}, 1434 expectedFilter: usersync.NewSpecificBidderFilter([]string{"a", "b"}, usersync.BidderFilterModeInclude), 1435 }, 1436 { 1437 description: "Specific Bidders - Exclude", 1438 given: &cookieSyncRequestFilter{Bidders: []interface{}{"a", "b"}, Mode: "exclude"}, 1439 expectedFilter: usersync.NewSpecificBidderFilter([]string{"a", "b"}, usersync.BidderFilterModeExclude), 1440 }, 1441 { 1442 description: "Specific Bidders - Invalid Mode", 1443 given: &cookieSyncRequestFilter{Bidders: []interface{}{"a", "b"}, Mode: "invalid"}, 1444 expectedError: "invalid filter value 'invalid'. must be either 'include' or 'exclude'", 1445 }, 1446 { 1447 description: "Invalid Bidders Type", 1448 given: &cookieSyncRequestFilter{Bidders: 42, Mode: "include"}, 1449 expectedError: "invalid bidders type. must either be a string '*' or a string array of bidders", 1450 }, 1451 { 1452 description: "Invalid Bidders Type Of Array Element", 1453 given: &cookieSyncRequestFilter{Bidders: []interface{}{"a", 42}, Mode: "include"}, 1454 expectedError: "invalid bidders type. must either be a string '*' or a string array of bidders", 1455 }, 1456 } 1457 1458 for _, test := range testCases { 1459 result, err := parseBidderFilter(test.given) 1460 1461 if test.expectedError == "" { 1462 assert.NoError(t, err, test.description+":err") 1463 assert.Equal(t, test.expectedFilter, result, test.description+":result") 1464 } else { 1465 assert.EqualError(t, err, test.expectedError, test.description+":err") 1466 assert.Nil(t, result, test.description+":result") 1467 } 1468 } 1469 } 1470 1471 func TestCookieSyncHandleError(t *testing.T) { 1472 err := errors.New("anyError") 1473 1474 mockAnalytics := MockAnalytics{} 1475 mockAnalytics.On("LogCookieSyncObject", mock.Anything) 1476 writer := httptest.NewRecorder() 1477 1478 endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics} 1479 endpoint.handleError(writer, err, 418) 1480 1481 assert.Equal(t, writer.Code, 418) 1482 assert.Equal(t, writer.Body.String(), "anyError\n") 1483 mockAnalytics.AssertCalled(t, "LogCookieSyncObject", &analytics.CookieSyncObject{ 1484 Status: 418, 1485 Errors: []error{err}, 1486 BidderStatus: []*analytics.CookieSyncBidder{}, 1487 }) 1488 } 1489 1490 func TestCookieSyncWriteBidderMetrics(t *testing.T) { 1491 testCases := []struct { 1492 description string 1493 given []usersync.BidderEvaluation 1494 setExpectations func(*metrics.MetricsEngineMock) 1495 }{ 1496 { 1497 description: "None", 1498 given: []usersync.BidderEvaluation{}, 1499 setExpectations: func(m *metrics.MetricsEngineMock) { 1500 }, 1501 }, 1502 { 1503 description: "One - OK", 1504 given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}}, 1505 setExpectations: func(m *metrics.MetricsEngineMock) { 1506 m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() 1507 }, 1508 }, 1509 { 1510 description: "One - Blocked By GDPR", 1511 given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByGDPR}}, 1512 setExpectations: func(m *metrics.MetricsEngineMock) { 1513 m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() 1514 }, 1515 }, 1516 { 1517 description: "One - Blocked By CCPA", 1518 given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusBlockedByCCPA}}, 1519 setExpectations: func(m *metrics.MetricsEngineMock) { 1520 m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncPrivacyBlocked).Once() 1521 }, 1522 }, 1523 { 1524 description: "One - Already Synced", 1525 given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusAlreadySynced}}, 1526 setExpectations: func(m *metrics.MetricsEngineMock) { 1527 m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncAlreadySynced).Once() 1528 }, 1529 }, 1530 { 1531 description: "One - Type Not Supported", 1532 given: []usersync.BidderEvaluation{{Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusTypeNotSupported}}, 1533 setExpectations: func(m *metrics.MetricsEngineMock) { 1534 m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncTypeNotSupported).Once() 1535 }, 1536 }, 1537 { 1538 description: "Many", 1539 given: []usersync.BidderEvaluation{ 1540 {Bidder: "a", SyncerKey: "aSyncer", Status: usersync.StatusOK}, 1541 {Bidder: "b", SyncerKey: "bSyncer", Status: usersync.StatusAlreadySynced}, 1542 }, 1543 setExpectations: func(m *metrics.MetricsEngineMock) { 1544 m.On("RecordSyncerRequest", "aSyncer", metrics.SyncerCookieSyncOK).Once() 1545 m.On("RecordSyncerRequest", "bSyncer", metrics.SyncerCookieSyncAlreadySynced).Once() 1546 }, 1547 }, 1548 } 1549 1550 for _, test := range testCases { 1551 mockMetrics := metrics.MetricsEngineMock{} 1552 test.setExpectations(&mockMetrics) 1553 1554 endpoint := &cookieSyncEndpoint{metrics: &mockMetrics} 1555 endpoint.writeSyncerMetrics(test.given) 1556 1557 mockMetrics.AssertExpectations(t) 1558 } 1559 } 1560 1561 func TestCookieSyncHandleResponse(t *testing.T) { 1562 syncTypeFilter := usersync.SyncTypeFilter{ 1563 IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), 1564 Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), 1565 } 1566 syncTypeExpected := []usersync.SyncType{usersync.SyncTypeRedirect} 1567 privacyMacros := macros.UserSyncPrivacy{USPrivacy: "anyConsent"} 1568 1569 // The & in the URL is necessary to test proper JSON encoding. 1570 syncA := usersync.Sync{URL: "https://syncA.com/sync?a=1&b=2", Type: usersync.SyncTypeRedirect, SupportCORS: true} 1571 syncerA := MockSyncer{} 1572 syncerA.On("GetSync", syncTypeExpected, privacyMacros).Return(syncA, nil).Maybe() 1573 1574 // The & in the URL is necessary to test proper JSON encoding. 1575 syncB := usersync.Sync{URL: "https://syncB.com/sync?a=1&b=2", Type: usersync.SyncTypeRedirect, SupportCORS: false} 1576 syncerB := MockSyncer{} 1577 syncerB.On("GetSync", syncTypeExpected, privacyMacros).Return(syncB, nil).Maybe() 1578 1579 syncWithError := usersync.Sync{} 1580 syncerWithError := MockSyncer{} 1581 syncerWithError.On("GetSync", syncTypeExpected, privacyMacros).Return(syncWithError, errors.New("anyError")).Maybe() 1582 1583 testCases := []struct { 1584 description string 1585 givenCookieHasSyncs bool 1586 givenSyncersChosen []usersync.SyncerChoice 1587 expectedJSON string 1588 expectedAnalytics analytics.CookieSyncObject 1589 }{ 1590 { 1591 description: "None", 1592 givenCookieHasSyncs: true, 1593 givenSyncersChosen: []usersync.SyncerChoice{}, 1594 expectedJSON: `{"status":"ok","bidder_status":[]}` + "\n", 1595 expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, 1596 }, 1597 { 1598 description: "One", 1599 givenCookieHasSyncs: true, 1600 givenSyncersChosen: []usersync.SyncerChoice{{Bidder: "foo", Syncer: &syncerA}}, 1601 expectedJSON: `{"status":"ok","bidder_status":[` + 1602 `{"bidder":"foo","no_cookie":true,"usersync":{"url":"https://syncA.com/sync?a=1&b=2","type":"redirect","supportCORS":true}}` + 1603 `]}` + "\n", 1604 expectedAnalytics: analytics.CookieSyncObject{ 1605 Status: 200, 1606 BidderStatus: []*analytics.CookieSyncBidder{ 1607 { 1608 BidderCode: "foo", 1609 NoCookie: true, 1610 UsersyncInfo: &analytics.UsersyncInfo{URL: "https://syncA.com/sync?a=1&b=2", Type: "redirect", SupportCORS: true}, 1611 }, 1612 }, 1613 }, 1614 }, 1615 { 1616 description: "Many", 1617 givenCookieHasSyncs: true, 1618 givenSyncersChosen: []usersync.SyncerChoice{{Bidder: "foo", Syncer: &syncerA}, {Bidder: "bar", Syncer: &syncerB}}, 1619 expectedJSON: `{"status":"ok","bidder_status":[` + 1620 `{"bidder":"foo","no_cookie":true,"usersync":{"url":"https://syncA.com/sync?a=1&b=2","type":"redirect","supportCORS":true}},` + 1621 `{"bidder":"bar","no_cookie":true,"usersync":{"url":"https://syncB.com/sync?a=1&b=2","type":"redirect"}}` + 1622 `]}` + "\n", 1623 expectedAnalytics: analytics.CookieSyncObject{ 1624 Status: 200, 1625 BidderStatus: []*analytics.CookieSyncBidder{ 1626 { 1627 BidderCode: "foo", 1628 NoCookie: true, 1629 UsersyncInfo: &analytics.UsersyncInfo{URL: "https://syncA.com/sync?a=1&b=2", Type: "redirect", SupportCORS: true}, 1630 }, 1631 { 1632 BidderCode: "bar", 1633 NoCookie: true, 1634 UsersyncInfo: &analytics.UsersyncInfo{URL: "https://syncB.com/sync?a=1&b=2", Type: "redirect", SupportCORS: false}, 1635 }, 1636 }, 1637 }, 1638 }, 1639 { 1640 description: "Many With One GetSync Error", 1641 givenCookieHasSyncs: true, 1642 givenSyncersChosen: []usersync.SyncerChoice{{Bidder: "foo", Syncer: &syncerWithError}, {Bidder: "bar", Syncer: &syncerB}}, 1643 expectedJSON: `{"status":"ok","bidder_status":[` + 1644 `{"bidder":"bar","no_cookie":true,"usersync":{"url":"https://syncB.com/sync?a=1&b=2","type":"redirect"}}` + 1645 `]}` + "\n", 1646 expectedAnalytics: analytics.CookieSyncObject{ 1647 Status: 200, 1648 BidderStatus: []*analytics.CookieSyncBidder{ 1649 { 1650 BidderCode: "bar", 1651 NoCookie: true, 1652 UsersyncInfo: &analytics.UsersyncInfo{URL: "https://syncB.com/sync?a=1&b=2", Type: "redirect", SupportCORS: false}, 1653 }, 1654 }, 1655 }, 1656 }, 1657 { 1658 description: "No Existing Syncs", 1659 givenCookieHasSyncs: false, 1660 givenSyncersChosen: []usersync.SyncerChoice{}, 1661 expectedJSON: `{"status":"no_cookie","bidder_status":[]}` + "\n", 1662 expectedAnalytics: analytics.CookieSyncObject{Status: 200, BidderStatus: []*analytics.CookieSyncBidder{}}, 1663 }, 1664 } 1665 1666 for _, test := range testCases { 1667 mockAnalytics := MockAnalytics{} 1668 mockAnalytics.On("LogCookieSyncObject", &test.expectedAnalytics).Once() 1669 1670 cookie := usersync.NewCookie() 1671 if test.givenCookieHasSyncs { 1672 if err := cookie.Sync("foo", "anyID"); err != nil { 1673 assert.FailNow(t, test.description+":set_cookie") 1674 } 1675 } 1676 1677 writer := httptest.NewRecorder() 1678 endpoint := cookieSyncEndpoint{pbsAnalytics: &mockAnalytics} 1679 endpoint.handleResponse(writer, syncTypeFilter, cookie, privacyMacros, test.givenSyncersChosen) 1680 1681 if assert.Equal(t, writer.Code, http.StatusOK, test.description+":http_status") { 1682 assert.Equal(t, writer.Header().Get("Content-Type"), "application/json; charset=utf-8", test.description+":http_header") 1683 assert.Equal(t, test.expectedJSON, writer.Body.String(), test.description+":http_response") 1684 } 1685 mockAnalytics.AssertExpectations(t) 1686 } 1687 } 1688 1689 func TestMapBidderStatusToAnalytics(t *testing.T) { 1690 testCases := []struct { 1691 description string 1692 given []cookieSyncResponseBidder 1693 expected []*analytics.CookieSyncBidder 1694 }{ 1695 { 1696 description: "None", 1697 given: []cookieSyncResponseBidder{}, 1698 expected: []*analytics.CookieSyncBidder{}, 1699 }, 1700 { 1701 description: "One", 1702 given: []cookieSyncResponseBidder{ 1703 { 1704 BidderCode: "a", 1705 NoCookie: true, 1706 UsersyncInfo: cookieSyncResponseSync{URL: "aURL", Type: "aType", SupportCORS: false}, 1707 }, 1708 }, 1709 expected: []*analytics.CookieSyncBidder{ 1710 { 1711 BidderCode: "a", 1712 NoCookie: true, 1713 UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "aType", SupportCORS: false}, 1714 }, 1715 }, 1716 }, 1717 { 1718 description: "Many", 1719 given: []cookieSyncResponseBidder{ 1720 { 1721 BidderCode: "a", 1722 NoCookie: true, 1723 UsersyncInfo: cookieSyncResponseSync{URL: "aURL", Type: "aType", SupportCORS: false}, 1724 }, 1725 { 1726 BidderCode: "b", 1727 NoCookie: false, 1728 UsersyncInfo: cookieSyncResponseSync{URL: "bURL", Type: "bType", SupportCORS: true}, 1729 }, 1730 }, 1731 expected: []*analytics.CookieSyncBidder{ 1732 { 1733 BidderCode: "a", 1734 NoCookie: true, 1735 UsersyncInfo: &analytics.UsersyncInfo{URL: "aURL", Type: "aType", SupportCORS: false}, 1736 }, 1737 { 1738 BidderCode: "b", 1739 NoCookie: false, 1740 UsersyncInfo: &analytics.UsersyncInfo{URL: "bURL", Type: "bType", SupportCORS: true}, 1741 }, 1742 }, 1743 }, 1744 } 1745 1746 for _, test := range testCases { 1747 result := mapBidderStatusToAnalytics(test.given) 1748 assert.ElementsMatch(t, test.expected, result, test.description) 1749 } 1750 } 1751 1752 func TestUsersyncPrivacyGDPRAllowsHostCookie(t *testing.T) { 1753 testCases := []struct { 1754 description string 1755 givenResponse bool 1756 givenError error 1757 expected bool 1758 }{ 1759 { 1760 description: "Allowed - No Error", 1761 givenResponse: true, 1762 givenError: nil, 1763 expected: true, 1764 }, 1765 { 1766 description: "Allowed - Error", 1767 givenResponse: true, 1768 givenError: errors.New("anyError"), 1769 expected: false, 1770 }, 1771 { 1772 description: "Not Allowed - No Error", 1773 givenResponse: false, 1774 givenError: nil, 1775 expected: false, 1776 }, 1777 { 1778 description: "Not Allowed - Error", 1779 givenResponse: false, 1780 givenError: errors.New("anyError"), 1781 expected: false, 1782 }, 1783 } 1784 1785 for _, test := range testCases { 1786 mockPerms := MockGDPRPerms{} 1787 mockPerms.On("HostCookiesAllowed", mock.Anything).Return(test.givenResponse, test.givenError) 1788 1789 privacy := usersyncPrivacy{ 1790 gdprPermissions: &mockPerms, 1791 } 1792 1793 result := privacy.GDPRAllowsHostCookie() 1794 assert.Equal(t, test.expected, result, test.description) 1795 } 1796 } 1797 1798 func TestUsersyncPrivacyGDPRAllowsBidderSync(t *testing.T) { 1799 testCases := []struct { 1800 description string 1801 givenResponse bool 1802 givenError error 1803 expected bool 1804 }{ 1805 { 1806 description: "Allowed - No Error", 1807 givenResponse: true, 1808 givenError: nil, 1809 expected: true, 1810 }, 1811 { 1812 description: "Allowed - Error", 1813 givenResponse: true, 1814 givenError: errors.New("anyError"), 1815 expected: false, 1816 }, 1817 { 1818 description: "Not Allowed - No Error", 1819 givenResponse: false, 1820 givenError: nil, 1821 expected: false, 1822 }, 1823 { 1824 description: "Not Allowed - Error", 1825 givenResponse: false, 1826 givenError: errors.New("anyError"), 1827 expected: false, 1828 }, 1829 } 1830 1831 for _, test := range testCases { 1832 mockPerms := MockGDPRPerms{} 1833 mockPerms.On("BidderSyncAllowed", mock.Anything, openrtb_ext.BidderName("foo")).Return(test.givenResponse, test.givenError) 1834 1835 privacy := usersyncPrivacy{ 1836 gdprPermissions: &mockPerms, 1837 } 1838 1839 result := privacy.GDPRAllowsBidderSync("foo") 1840 assert.Equal(t, test.expected, result, test.description) 1841 } 1842 } 1843 1844 func TestUsersyncPrivacyCCPAAllowsBidderSync(t *testing.T) { 1845 testCases := []struct { 1846 description string 1847 givenConsent string 1848 expected bool 1849 }{ 1850 { 1851 description: "Allowed - No Opt-Out", 1852 givenConsent: "1NNN", 1853 expected: true, 1854 }, 1855 { 1856 description: "Not Allowed - Opt-Out", 1857 givenConsent: "1NYN", 1858 expected: false, 1859 }, 1860 { 1861 description: "Not Specified", 1862 givenConsent: "", 1863 expected: true, 1864 }, 1865 } 1866 1867 for _, test := range testCases { 1868 validBidders := map[string]struct{}{"foo": {}} 1869 parsedPolicy, err := ccpa.Policy{Consent: test.givenConsent}.Parse(validBidders) 1870 1871 if assert.NoError(t, err) { 1872 privacy := usersyncPrivacy{ccpaParsedPolicy: parsedPolicy} 1873 result := privacy.CCPAAllowsBidderSync("foo") 1874 assert.Equal(t, test.expected, result, test.description) 1875 } 1876 } 1877 } 1878 1879 func TestCookieSyncActivityControlIntegration(t *testing.T) { 1880 testCases := []struct { 1881 name string 1882 bidderName string 1883 accountPrivacy *config.AccountPrivacy 1884 expectedResult bool 1885 }{ 1886 { 1887 name: "activity_is_allowed", 1888 bidderName: "bidderA", 1889 accountPrivacy: getDefaultActivityConfig("bidderA", true), 1890 expectedResult: true, 1891 }, 1892 { 1893 name: "activity_is_denied", 1894 bidderName: "bidderA", 1895 accountPrivacy: getDefaultActivityConfig("bidderA", false), 1896 expectedResult: false, 1897 }, 1898 { 1899 name: "activity_is_abstain", 1900 bidderName: "bidderA", 1901 accountPrivacy: nil, 1902 expectedResult: true, 1903 }, 1904 } 1905 1906 for _, test := range testCases { 1907 t.Run(test.name, func(t *testing.T) { 1908 activities := privacy.NewActivityControl(test.accountPrivacy) 1909 up := usersyncPrivacy{ 1910 activityControl: activities, 1911 } 1912 actualResult := up.ActivityAllowsUserSync(test.bidderName) 1913 assert.Equal(t, test.expectedResult, actualResult) 1914 }) 1915 } 1916 } 1917 1918 func TestCombineErrors(t *testing.T) { 1919 testCases := []struct { 1920 description string 1921 givenErrorList []error 1922 expectedError error 1923 }{ 1924 { 1925 description: "No errors given", 1926 givenErrorList: []error{}, 1927 expectedError: errors.New(""), 1928 }, 1929 { 1930 description: "One error given", 1931 givenErrorList: []error{errors.New("Error #1")}, 1932 expectedError: errors.New("Error #1"), 1933 }, 1934 { 1935 description: "Multiple errors given", 1936 givenErrorList: []error{errors.New("Error #1"), errors.New("Error #2")}, 1937 expectedError: errors.New("Error #1 Error #2"), 1938 }, 1939 { 1940 description: "Special Case: blocked (rejected via block list)", 1941 givenErrorList: []error{&errortypes.BlacklistedAcct{}}, 1942 expectedError: errCookieSyncAccountBlocked, 1943 }, 1944 { 1945 description: "Special Case: invalid (rejected via allow list)", 1946 givenErrorList: []error{&errortypes.AcctRequired{}}, 1947 expectedError: errCookieSyncAccountInvalid, 1948 }, 1949 { 1950 description: "Special Case: malformed account config", 1951 givenErrorList: []error{&errortypes.MalformedAcct{}}, 1952 expectedError: errCookieSyncAccountConfigMalformed, 1953 }, 1954 { 1955 description: "Special Case: multiple special cases, first one wins", 1956 givenErrorList: []error{&errortypes.BlacklistedAcct{}, &errortypes.AcctRequired{}, &errortypes.MalformedAcct{}}, 1957 expectedError: errCookieSyncAccountBlocked, 1958 }, 1959 } 1960 1961 for _, test := range testCases { 1962 combinedErrors := combineErrors(test.givenErrorList) 1963 assert.Equal(t, test.expectedError, combinedErrors, test.description) 1964 } 1965 } 1966 1967 type FakeChooser struct { 1968 Result usersync.Result 1969 } 1970 1971 func (c FakeChooser) Choose(request usersync.Request, cookie *usersync.Cookie) usersync.Result { 1972 return c.Result 1973 } 1974 1975 type MockSyncer struct { 1976 mock.Mock 1977 } 1978 1979 func (m *MockSyncer) Key() string { 1980 args := m.Called() 1981 return args.String(0) 1982 } 1983 1984 func (m *MockSyncer) DefaultSyncType() usersync.SyncType { 1985 args := m.Called() 1986 return args.Get(0).(usersync.SyncType) 1987 } 1988 1989 func (m *MockSyncer) SupportsType(syncTypes []usersync.SyncType) bool { 1990 args := m.Called(syncTypes) 1991 return args.Bool(0) 1992 } 1993 1994 func (m *MockSyncer) GetSync(syncTypes []usersync.SyncType, privacyMacros macros.UserSyncPrivacy) (usersync.Sync, error) { 1995 args := m.Called(syncTypes, privacyMacros) 1996 return args.Get(0).(usersync.Sync), args.Error(1) 1997 } 1998 1999 type MockAnalytics struct { 2000 mock.Mock 2001 } 2002 2003 func (m *MockAnalytics) LogAuctionObject(obj *analytics.AuctionObject) { 2004 m.Called(obj) 2005 } 2006 2007 func (m *MockAnalytics) LogVideoObject(obj *analytics.VideoObject) { 2008 m.Called(obj) 2009 } 2010 2011 func (m *MockAnalytics) LogCookieSyncObject(obj *analytics.CookieSyncObject) { 2012 m.Called(obj) 2013 } 2014 2015 func (m *MockAnalytics) LogSetUIDObject(obj *analytics.SetUIDObject) { 2016 m.Called(obj) 2017 } 2018 2019 func (m *MockAnalytics) LogAmpObject(obj *analytics.AmpObject) { 2020 m.Called(obj) 2021 } 2022 2023 func (m *MockAnalytics) LogNotificationEventObject(obj *analytics.NotificationEvent) { 2024 m.Called(obj) 2025 } 2026 2027 type MockGDPRPerms struct { 2028 mock.Mock 2029 } 2030 2031 func (m *MockGDPRPerms) HostCookiesAllowed(ctx context.Context) (bool, error) { 2032 args := m.Called(ctx) 2033 return args.Bool(0), args.Error(1) 2034 } 2035 2036 func (m *MockGDPRPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { 2037 args := m.Called(ctx, bidder) 2038 return args.Bool(0), args.Error(1) 2039 } 2040 2041 func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { 2042 args := m.Called(ctx, bidderCoreName, bidder) 2043 return args.Get(0).(gdpr.AuctionPermissions), args.Error(1) 2044 } 2045 2046 type FakeAccountsFetcher struct { 2047 AccountData map[string]json.RawMessage 2048 } 2049 2050 func (f FakeAccountsFetcher) FetchAccount(ctx context.Context, defaultAccountJSON json.RawMessage, accountID string) (json.RawMessage, []error) { 2051 defaultAccountJSON = json.RawMessage(`{"disabled":false}`) 2052 2053 if accountID == metrics.PublisherUnknown { 2054 return defaultAccountJSON, nil 2055 } 2056 if account, ok := f.AccountData[accountID]; ok { 2057 return account, nil 2058 } 2059 return nil, []error{errors.New("Account not found")} 2060 } 2061 2062 type fakePermissions struct { 2063 } 2064 2065 func (p *fakePermissions) HostCookiesAllowed(ctx context.Context) (bool, error) { 2066 return true, nil 2067 } 2068 2069 func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { 2070 return true, nil 2071 } 2072 2073 func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { 2074 return gdpr.AuctionPermissions{ 2075 AllowBidRequest: true, 2076 }, nil 2077 } 2078 2079 func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy { 2080 return &config.AccountPrivacy{ 2081 AllowActivities: &config.AllowActivities{ 2082 SyncUser: config.Activity{ 2083 Default: ptrutil.ToPtr(true), 2084 Rules: []config.ActivityRule{ 2085 { 2086 Allow: allow, 2087 Condition: config.ActivityCondition{ 2088 ComponentName: []string{componentName}, 2089 ComponentType: []string{"bidder"}, 2090 }, 2091 }, 2092 }, 2093 }, 2094 }, 2095 } 2096 }