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  }