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