github.com/prebid/prebid-server/v2@v2.18.0/usersync/cookie_test.go (about)

     1  package usersync
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/prebid/prebid-server/v2/config"
    11  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    12  	"github.com/stretchr/testify/assert"
    13  )
    14  
    15  func TestReadCookie(t *testing.T) {
    16  	testCases := []struct {
    17  		name            string
    18  		givenRequest    *http.Request
    19  		givenHttpCookie *http.Cookie
    20  		givenCookie     *Cookie
    21  		givenDecoder    Decoder
    22  		expectedCookie  *Cookie
    23  	}{
    24  		{
    25  			name:         "simple-cookie",
    26  			givenRequest: httptest.NewRequest("POST", "http://www.prebid.com", nil),
    27  			givenCookie: &Cookie{
    28  				uids: map[string]UIDEntry{
    29  					"adnxs": {
    30  						UID:     "UID",
    31  						Expires: time.Time{},
    32  					},
    33  				},
    34  				optOut: false,
    35  			},
    36  			expectedCookie: &Cookie{
    37  				uids: map[string]UIDEntry{
    38  					"adnxs": {
    39  						UID: "UID",
    40  					},
    41  				},
    42  				optOut: false,
    43  			},
    44  		},
    45  		{
    46  			name:         "empty-cookie",
    47  			givenRequest: httptest.NewRequest("POST", "http://www.prebid.com", nil),
    48  			givenCookie:  &Cookie{},
    49  			expectedCookie: &Cookie{
    50  				uids:   map[string]UIDEntry{},
    51  				optOut: false,
    52  			},
    53  		},
    54  		{
    55  			name:         "nil-cookie",
    56  			givenRequest: httptest.NewRequest("POST", "http://www.prebid.com", nil),
    57  			givenCookie:  nil,
    58  			expectedCookie: &Cookie{
    59  				uids:   map[string]UIDEntry{},
    60  				optOut: false,
    61  			},
    62  		},
    63  		{
    64  			name:         "corruptted-http-cookie",
    65  			givenRequest: httptest.NewRequest("POST", "http://www.prebid.com", nil),
    66  			givenHttpCookie: &http.Cookie{
    67  				Name:  "uids",
    68  				Value: "bad base64 encoding",
    69  			},
    70  			givenCookie: nil,
    71  			expectedCookie: &Cookie{
    72  				uids:   map[string]UIDEntry{},
    73  				optOut: false,
    74  			},
    75  		},
    76  	}
    77  
    78  	for _, test := range testCases {
    79  		t.Run(test.name, func(t *testing.T) {
    80  			if test.givenCookie != nil {
    81  				httpCookie, err := ToHTTPCookie(test.givenCookie)
    82  				assert.NoError(t, err)
    83  				test.givenRequest.AddCookie(httpCookie)
    84  			} else if test.givenCookie == nil && test.givenHttpCookie != nil {
    85  				test.givenRequest.AddCookie(test.givenHttpCookie)
    86  			}
    87  			actualCookie := ReadCookie(test.givenRequest, Base64Decoder{}, &config.HostCookie{})
    88  			assert.Equal(t, test.expectedCookie.uids, actualCookie.uids)
    89  			assert.Equal(t, test.expectedCookie.optOut, actualCookie.optOut)
    90  		})
    91  	}
    92  }
    93  
    94  func TestWriteCookie(t *testing.T) {
    95  	encoder := Base64Encoder{}
    96  	decoder := Base64Decoder{}
    97  
    98  	testCases := []struct {
    99  		name               string
   100  		givenCookie        *Cookie
   101  		givenSetSiteCookie bool
   102  		expectedCookie     *Cookie
   103  	}{
   104  		{
   105  			name: "simple-cookie",
   106  			givenCookie: &Cookie{
   107  				uids: map[string]UIDEntry{
   108  					"adnxs": {
   109  						UID:     "UID",
   110  						Expires: time.Time{},
   111  					},
   112  				},
   113  				optOut: false,
   114  			},
   115  			givenSetSiteCookie: false,
   116  			expectedCookie: &Cookie{
   117  				uids: map[string]UIDEntry{
   118  					"adnxs": {
   119  						UID:     "UID",
   120  						Expires: time.Time{},
   121  					},
   122  				},
   123  				optOut: false,
   124  			},
   125  		},
   126  		{
   127  			name: "simple-cookie-opt-out",
   128  			givenCookie: &Cookie{
   129  				uids: map[string]UIDEntry{
   130  					"adnxs": {
   131  						UID:     "UID",
   132  						Expires: time.Time{},
   133  					},
   134  				},
   135  				optOut: true,
   136  			},
   137  			givenSetSiteCookie: true,
   138  			expectedCookie: &Cookie{
   139  				uids:   map[string]UIDEntry{},
   140  				optOut: true,
   141  			},
   142  		},
   143  		{
   144  			name: "cookie-multiple-uids",
   145  			givenCookie: &Cookie{
   146  				uids: map[string]UIDEntry{
   147  					"adnxs": {
   148  						UID:     "UID",
   149  						Expires: time.Time{},
   150  					},
   151  					"rubicon": {
   152  						UID:     "UID2",
   153  						Expires: time.Time{},
   154  					},
   155  				},
   156  				optOut: false,
   157  			},
   158  			givenSetSiteCookie: true,
   159  			expectedCookie: &Cookie{
   160  				uids: map[string]UIDEntry{
   161  					"adnxs": {
   162  						UID:     "UID",
   163  						Expires: time.Time{},
   164  					},
   165  					"rubicon": {
   166  						UID:     "UID2",
   167  						Expires: time.Time{},
   168  					},
   169  				},
   170  				optOut: false,
   171  			},
   172  		},
   173  	}
   174  
   175  	for _, test := range testCases {
   176  		t.Run(test.name, func(t *testing.T) {
   177  			// Write Cookie
   178  			w := httptest.NewRecorder()
   179  			encodedCookie, err := encoder.Encode(test.givenCookie)
   180  			assert.NoError(t, err)
   181  			WriteCookie(w, encodedCookie, &config.HostCookie{}, test.givenSetSiteCookie)
   182  			writtenCookie := w.Header().Get("Set-Cookie")
   183  
   184  			// Read Cookie
   185  			header := http.Header{}
   186  			header.Add("Cookie", writtenCookie)
   187  			r := &http.Request{Header: header}
   188  			actualCookie := ReadCookie(r, decoder, &config.HostCookie{})
   189  
   190  			assert.Equal(t, test.expectedCookie, actualCookie)
   191  		})
   192  	}
   193  }
   194  
   195  func TestSync(t *testing.T) {
   196  	testCases := []struct {
   197  		name           string
   198  		givenCookie    *Cookie
   199  		givenSyncerKey string
   200  		givenUID       string
   201  		expectedCookie *Cookie
   202  		expectedError  error
   203  	}{
   204  		{
   205  			name: "simple-sync",
   206  			givenCookie: &Cookie{
   207  				uids: map[string]UIDEntry{},
   208  			},
   209  			givenSyncerKey: "adnxs",
   210  			givenUID:       "123",
   211  			expectedCookie: &Cookie{
   212  				uids: map[string]UIDEntry{
   213  					"adnxs": {
   214  						UID: "123",
   215  					},
   216  				},
   217  			},
   218  		},
   219  		{
   220  			name: "dont-allow-syncs",
   221  			givenCookie: &Cookie{
   222  				uids:   map[string]UIDEntry{},
   223  				optOut: true,
   224  			},
   225  			givenSyncerKey: "adnxs",
   226  			givenUID:       "123",
   227  			expectedCookie: &Cookie{
   228  				uids: map[string]UIDEntry{},
   229  			},
   230  			expectedError: errors.New("the user has opted out of prebid server cookie syncs"),
   231  		},
   232  		{
   233  			name: "audienceNetwork",
   234  			givenCookie: &Cookie{
   235  				uids: map[string]UIDEntry{},
   236  			},
   237  			givenSyncerKey: string(openrtb_ext.BidderAudienceNetwork),
   238  			givenUID:       "0",
   239  			expectedError:  errors.New("audienceNetwork uses a UID of 0 as \"not yet recognized\""),
   240  		},
   241  	}
   242  
   243  	for _, test := range testCases {
   244  		t.Run(test.name, func(t *testing.T) {
   245  			err := test.givenCookie.Sync(test.givenSyncerKey, test.givenUID)
   246  			if test.expectedError != nil {
   247  				assert.Equal(t, test.expectedError, err)
   248  			} else {
   249  				assert.NoError(t, err)
   250  				assert.Equal(t, test.expectedCookie.uids[test.givenSyncerKey].UID, test.givenCookie.uids[test.givenSyncerKey].UID)
   251  			}
   252  		})
   253  	}
   254  }
   255  
   256  func TestGetUIDs(t *testing.T) {
   257  	testCases := []struct {
   258  		name           string
   259  		givenCookie    *Cookie
   260  		expectedCookie *Cookie
   261  		expectedLen    int
   262  	}{
   263  		{
   264  			name: "two-uids",
   265  			givenCookie: &Cookie{
   266  				uids: map[string]UIDEntry{
   267  					"adnxs": {
   268  						UID: "123",
   269  					},
   270  					"rubicon": {
   271  						UID: "456",
   272  					},
   273  				},
   274  			},
   275  			expectedLen: 2,
   276  		},
   277  		{
   278  			name: "one-uid",
   279  			givenCookie: &Cookie{
   280  				uids: map[string]UIDEntry{
   281  					"adnxs": {
   282  						UID: "123",
   283  					},
   284  				},
   285  			},
   286  			expectedLen: 1,
   287  		},
   288  		{
   289  			name:        "empty",
   290  			givenCookie: &Cookie{},
   291  			expectedLen: 0,
   292  		},
   293  		{
   294  			name:        "nil",
   295  			givenCookie: nil,
   296  			expectedLen: 0,
   297  		},
   298  	}
   299  
   300  	for _, test := range testCases {
   301  		t.Run(test.name, func(t *testing.T) {
   302  			uids := test.givenCookie.GetUIDs()
   303  			assert.Len(t, uids, test.expectedLen)
   304  			for key, value := range uids {
   305  				assert.Equal(t, test.givenCookie.uids[key].UID, value)
   306  			}
   307  
   308  		})
   309  	}
   310  }
   311  
   312  func TestWriteCookieUserAgent(t *testing.T) {
   313  	encoder := Base64Encoder{}
   314  
   315  	testCases := []struct {
   316  		name                string
   317  		givenUserAgent      string
   318  		givenCookie         *Cookie
   319  		givenHostCookie     config.HostCookie
   320  		givenSetSiteCookie  bool
   321  		expectedContains    string
   322  		expectedNotContains string
   323  	}{
   324  		{
   325  			name:           "same-site-none",
   326  			givenUserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36",
   327  			givenCookie: &Cookie{
   328  				uids: map[string]UIDEntry{
   329  					"adnxs": {
   330  						UID:     "UID",
   331  						Expires: time.Time{},
   332  					},
   333  				},
   334  				optOut: false,
   335  			},
   336  			givenHostCookie:    config.HostCookie{},
   337  			givenSetSiteCookie: true,
   338  			expectedContains:   "; Secure;",
   339  		},
   340  		{
   341  			name:           "older-chrome-version",
   342  			givenUserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3770.142 Safari/537.36",
   343  			givenCookie: &Cookie{
   344  				uids: map[string]UIDEntry{
   345  					"adnxs": {
   346  						UID:     "UID",
   347  						Expires: time.Time{},
   348  					},
   349  				},
   350  				optOut: false,
   351  			},
   352  			givenHostCookie:     config.HostCookie{},
   353  			givenSetSiteCookie:  true,
   354  			expectedNotContains: "SameSite=none",
   355  		},
   356  	}
   357  
   358  	for _, test := range testCases {
   359  		t.Run(test.name, func(t *testing.T) {
   360  			// Set Up
   361  			req := httptest.NewRequest("GET", "http://www.prebid.com", nil)
   362  			req.Header.Set("User-Agent", test.givenUserAgent)
   363  
   364  			// Write Cookie
   365  			w := httptest.NewRecorder()
   366  			encodedCookie, err := encoder.Encode(test.givenCookie)
   367  			assert.NoError(t, err)
   368  			WriteCookie(w, encodedCookie, &test.givenHostCookie, test.givenSetSiteCookie)
   369  			writtenCookie := w.Header().Get("Set-Cookie")
   370  
   371  			if test.expectedContains == "" {
   372  				assert.NotContains(t, writtenCookie, test.expectedNotContains)
   373  			} else {
   374  				assert.Contains(t, writtenCookie, test.expectedContains)
   375  			}
   376  		})
   377  	}
   378  }
   379  
   380  func TestPrepareCookieForWrite(t *testing.T) {
   381  	encoder := Base64Encoder{}
   382  	decoder := Base64Decoder{}
   383  
   384  	mainCookie := &Cookie{
   385  		uids: map[string]UIDEntry{
   386  			"mainUID": newTempId("1234567890123456789012345678901234567890123456", 7),
   387  			"2":       newTempId("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6),
   388  			"3":       newTempId("123456789012345678901234567896123456789012345678", 5),
   389  			"4":       newTempId("aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ", 4),
   390  			"5":       newTempId("12345678901234567890123456789012345678901234567890", 3),
   391  			"6":       newTempId("abcdefghij", 2),
   392  			"7":       newTempId("abcdefghijklmnopqrstuvwxy", 1),
   393  		},
   394  		optOut: false,
   395  	}
   396  
   397  	errorCookie := &Cookie{
   398  		uids: map[string]UIDEntry{
   399  			"syncerNotPriority": newTempId("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 7),
   400  			"2":                 newTempId("1234567890123456789012345678901234567890123456", 7), // Priority Element
   401  		},
   402  		optOut: false,
   403  	}
   404  
   405  	ejector := &PriorityBidderEjector{
   406  		PriorityGroups: [][]string{
   407  			{"mainUID"},
   408  			{"2", "3"},
   409  			{"4", "5", "6"},
   410  			{"7"},
   411  		},
   412  		SyncersByBidder: map[string]Syncer{
   413  			"mainUID": fakeSyncer{
   414  				key: "mainUID",
   415  			},
   416  			"2": fakeSyncer{
   417  				key: "2",
   418  			},
   419  			"3": fakeSyncer{
   420  				key: "3",
   421  			},
   422  			"4": fakeSyncer{
   423  				key: "4",
   424  			},
   425  			"5": fakeSyncer{
   426  				key: "5",
   427  			},
   428  			"6": fakeSyncer{
   429  				key: "6",
   430  			},
   431  			"mistmatchedBidder": fakeSyncer{
   432  				key: "7",
   433  			},
   434  		},
   435  		TieEjector: &OldestEjector{},
   436  	}
   437  
   438  	testCases := []struct {
   439  		name                     string
   440  		givenMaxCookieSize       int
   441  		givenCookieToSend        *Cookie
   442  		givenIsSyncerPriority    bool
   443  		expectedRemainingUidKeys []string
   444  		expectedError            error
   445  	}{
   446  		{
   447  			name:                  "no-uids-ejected",
   448  			givenMaxCookieSize:    2000,
   449  			givenCookieToSend:     mainCookie,
   450  			givenIsSyncerPriority: true,
   451  			expectedRemainingUidKeys: []string{
   452  				"mainUID", "2", "3", "4", "5", "6", "7",
   453  			},
   454  		},
   455  		{
   456  			name:               "invalid-max-size",
   457  			givenMaxCookieSize: -100,
   458  			givenCookieToSend:  mainCookie,
   459  			expectedRemainingUidKeys: []string{
   460  				"mainUID", "2", "3", "4", "5", "6", "7",
   461  			},
   462  		},
   463  		{
   464  			name:                  "syncer-is-not-priority",
   465  			givenMaxCookieSize:    100,
   466  			givenCookieToSend:     errorCookie,
   467  			givenIsSyncerPriority: false,
   468  			expectedError:         errors.New("syncer key is not a priority, and there are only priority elements left"),
   469  		},
   470  		{
   471  			name:                  "no-uids-ejected-2",
   472  			givenMaxCookieSize:    0,
   473  			givenCookieToSend:     mainCookie,
   474  			givenIsSyncerPriority: true,
   475  			expectedRemainingUidKeys: []string{
   476  				"mainUID", "2", "3", "4", "5", "6", "7",
   477  			},
   478  		},
   479  		{
   480  			name:                  "one-uid-ejected",
   481  			givenMaxCookieSize:    900,
   482  			givenCookieToSend:     mainCookie,
   483  			givenIsSyncerPriority: true,
   484  			expectedRemainingUidKeys: []string{
   485  				"mainUID", "2", "3", "4", "5", "6",
   486  			},
   487  		},
   488  		{
   489  			name:                  "four-uids-ejected",
   490  			givenMaxCookieSize:    500,
   491  			givenCookieToSend:     mainCookie,
   492  			givenIsSyncerPriority: true,
   493  			expectedRemainingUidKeys: []string{
   494  				"mainUID", "2", "3",
   495  			},
   496  		},
   497  		{
   498  			name:                  "all-but-one-uids-ejected",
   499  			givenMaxCookieSize:    300,
   500  			givenCookieToSend:     mainCookie,
   501  			givenIsSyncerPriority: true,
   502  			expectedRemainingUidKeys: []string{
   503  				"mainUID",
   504  			},
   505  		},
   506  		{
   507  			name:                     "only-main-uid-left",
   508  			givenMaxCookieSize:       100,
   509  			givenCookieToSend:        mainCookie,
   510  			expectedError:            errors.New("uid that's trying to be synced is bigger than MaxCookieSize"),
   511  			expectedRemainingUidKeys: []string{},
   512  		},
   513  	}
   514  
   515  	for _, test := range testCases {
   516  		t.Run(test.name, func(t *testing.T) {
   517  			ejector.IsSyncerPriority = test.givenIsSyncerPriority
   518  			encodedCookie, err := test.givenCookieToSend.PrepareCookieForWrite(&config.HostCookie{MaxCookieSizeBytes: test.givenMaxCookieSize}, encoder, ejector)
   519  
   520  			if test.expectedError != nil {
   521  				assert.Equal(t, test.expectedError, err)
   522  			} else {
   523  				assert.NoError(t, err)
   524  				decodedCookie := decoder.Decode(encodedCookie)
   525  
   526  				for _, key := range test.expectedRemainingUidKeys {
   527  					_, ok := decodedCookie.uids[key]
   528  					assert.Equal(t, true, ok)
   529  				}
   530  				assert.Equal(t, len(decodedCookie.uids), len(test.expectedRemainingUidKeys))
   531  			}
   532  		})
   533  	}
   534  }
   535  
   536  func TestSyncHostCookie(t *testing.T) {
   537  	testCases := []struct {
   538  		name            string
   539  		givenCookie     *Cookie
   540  		givenUID        string
   541  		givenHostCookie *config.HostCookie
   542  		expectedCookie  *Cookie
   543  		expectedError   error
   544  	}{
   545  		{
   546  			name: "simple-sync",
   547  			givenCookie: &Cookie{
   548  				uids: map[string]UIDEntry{},
   549  			},
   550  			givenHostCookie: &config.HostCookie{
   551  				Family:     "syncer",
   552  				CookieName: "adnxs",
   553  			},
   554  			expectedCookie: &Cookie{
   555  				uids: map[string]UIDEntry{
   556  					"syncer": {
   557  						UID: "some-user-id",
   558  					},
   559  				},
   560  			},
   561  		},
   562  		{
   563  			name: "uids-already-present",
   564  			givenCookie: &Cookie{
   565  				uids: map[string]UIDEntry{
   566  					"some-syncer": {
   567  						UID: "some-other-user-id",
   568  					},
   569  				},
   570  			},
   571  			givenHostCookie: &config.HostCookie{
   572  				Family:     "syncer",
   573  				CookieName: "adnxs",
   574  			},
   575  			expectedCookie: &Cookie{
   576  				uids: map[string]UIDEntry{
   577  					"syncer": {
   578  						UID: "some-user-id",
   579  					},
   580  					"some-syncer": {
   581  						UID: "some-other-user-id",
   582  					},
   583  				},
   584  			},
   585  		},
   586  		{
   587  			name: "host-already-synced",
   588  			givenCookie: &Cookie{
   589  				uids: map[string]UIDEntry{
   590  					"syncer": {
   591  						UID: "some-user-id",
   592  					},
   593  				},
   594  			},
   595  			givenHostCookie: &config.HostCookie{
   596  				Family:     "syncer",
   597  				CookieName: "adnxs",
   598  			},
   599  			expectedCookie: &Cookie{
   600  				uids: map[string]UIDEntry{
   601  					"syncer": {
   602  						UID: "some-user-id",
   603  					},
   604  				},
   605  			},
   606  		},
   607  	}
   608  
   609  	for _, test := range testCases {
   610  		t.Run(test.name, func(t *testing.T) {
   611  			r := httptest.NewRequest("POST", "http://www.prebid.com", nil)
   612  			r.AddCookie(&http.Cookie{
   613  				Name:  test.givenHostCookie.CookieName,
   614  				Value: "some-user-id",
   615  			})
   616  
   617  			SyncHostCookie(r, test.givenCookie, test.givenHostCookie)
   618  			for key, value := range test.expectedCookie.uids {
   619  				assert.Equal(t, value.UID, test.givenCookie.uids[key].UID)
   620  			}
   621  		})
   622  	}
   623  }
   624  
   625  func TestBidderNameGets(t *testing.T) {
   626  	cookie := newSampleCookie()
   627  	id, exists, _ := cookie.GetUID("adnxs")
   628  	if !exists {
   629  		t.Errorf("Cookie missing expected Appnexus ID")
   630  	}
   631  	if id != "123" {
   632  		t.Errorf("Bad appnexus id. Expected %s, got %s", "123", id)
   633  	}
   634  
   635  	id, exists, _ = cookie.GetUID("rubicon")
   636  	if !exists {
   637  		t.Errorf("Cookie missing expected Rubicon ID")
   638  	}
   639  	if id != "456" {
   640  		t.Errorf("Bad rubicon id. Expected %s, got %s", "456", id)
   641  	}
   642  }
   643  
   644  func TestReadCookieOptOut(t *testing.T) {
   645  	optOutCookieName := "optOutCookieName"
   646  	optOutCookieValue := "optOutCookieValue"
   647  	decoder := Base64Decoder{}
   648  
   649  	cookie := *(&Cookie{
   650  		uids: map[string]UIDEntry{
   651  			"foo": newTempId("fooID", 1),
   652  			"bar": newTempId("barID", 2),
   653  		},
   654  		optOut: false,
   655  	})
   656  
   657  	existingCookie, _ := ToHTTPCookie(&cookie)
   658  
   659  	testCases := []struct {
   660  		description          string
   661  		givenExistingCookies []*http.Cookie
   662  		expectedEmpty        bool
   663  		expectedSetOptOut    bool
   664  	}{
   665  		{
   666  			description: "Opt Out Cookie",
   667  			givenExistingCookies: []*http.Cookie{
   668  				existingCookie,
   669  				{Name: optOutCookieName, Value: optOutCookieValue}},
   670  			expectedEmpty:     true,
   671  			expectedSetOptOut: true,
   672  		},
   673  		{
   674  			description: "No Opt Out Cookie",
   675  			givenExistingCookies: []*http.Cookie{
   676  				existingCookie},
   677  			expectedEmpty:     false,
   678  			expectedSetOptOut: false,
   679  		},
   680  		{
   681  			description: "Opt Out Cookie - Wrong Value",
   682  			givenExistingCookies: []*http.Cookie{
   683  				existingCookie,
   684  				{Name: optOutCookieName, Value: "wrong"}},
   685  			expectedEmpty:     false,
   686  			expectedSetOptOut: false,
   687  		},
   688  		{
   689  			description: "Opt Out Cookie - Wrong Name",
   690  			givenExistingCookies: []*http.Cookie{
   691  				existingCookie,
   692  				{Name: "wrong", Value: optOutCookieValue}},
   693  			expectedEmpty:     false,
   694  			expectedSetOptOut: false,
   695  		},
   696  		{
   697  			description: "Opt Out Cookie - No Host Cookies",
   698  			givenExistingCookies: []*http.Cookie{
   699  				{Name: optOutCookieName, Value: optOutCookieValue}},
   700  			expectedEmpty:     true,
   701  			expectedSetOptOut: true,
   702  		},
   703  	}
   704  
   705  	for _, test := range testCases {
   706  		req := httptest.NewRequest("POST", "http://www.prebid.com", nil)
   707  
   708  		for _, c := range test.givenExistingCookies {
   709  			req.AddCookie(c)
   710  		}
   711  
   712  		parsed := ReadCookie(req, decoder, &config.HostCookie{
   713  			Family: "foo",
   714  			OptOutCookie: config.Cookie{
   715  				Name:  optOutCookieName,
   716  				Value: optOutCookieValue,
   717  			},
   718  		})
   719  
   720  		if test.expectedEmpty {
   721  			assert.Empty(t, parsed.uids, test.description+":empty")
   722  		} else {
   723  			assert.NotEmpty(t, parsed.uids, test.description+":not-empty")
   724  		}
   725  		assert.Equal(t, parsed.optOut, test.expectedSetOptOut, test.description+":opt-out")
   726  	}
   727  }
   728  
   729  func TestOptIn(t *testing.T) {
   730  	cookie := &Cookie{
   731  		uids:   make(map[string]UIDEntry),
   732  		optOut: true,
   733  	}
   734  
   735  	cookie.SetOptOut(false)
   736  	if !cookie.AllowSyncs() {
   737  		t.Error("After SetOptOut(false), a cookie should allow more user syncs.")
   738  	}
   739  	ensureConsistency(t, cookie)
   740  }
   741  
   742  func TestOptOutReset(t *testing.T) {
   743  	cookie := newSampleCookie()
   744  
   745  	cookie.SetOptOut(true)
   746  	if cookie.AllowSyncs() {
   747  		t.Error("After SetOptOut(true), a cookie should not allow more user syncs.")
   748  	}
   749  	ensureConsistency(t, cookie)
   750  }
   751  
   752  func TestOptOutCookie(t *testing.T) {
   753  	cookie := &Cookie{
   754  		uids:   make(map[string]UIDEntry),
   755  		optOut: true,
   756  	}
   757  	ensureConsistency(t, cookie)
   758  }
   759  
   760  func newTempId(uid string, offset int) UIDEntry {
   761  	return UIDEntry{
   762  		UID:     uid,
   763  		Expires: time.Now().Add(time.Duration(offset) * time.Minute).UTC(),
   764  	}
   765  }
   766  
   767  func newSampleCookie() *Cookie {
   768  	return &Cookie{
   769  		uids: map[string]UIDEntry{
   770  			"adnxs":   newTempId("123", 10),
   771  			"rubicon": newTempId("456", 10),
   772  		},
   773  		optOut: false,
   774  	}
   775  }
   776  
   777  func ensureConsistency(t *testing.T, cookie *Cookie) {
   778  	decoder := Base64Decoder{}
   779  
   780  	if cookie.AllowSyncs() {
   781  		err := cookie.Sync("pulsepoint", "1")
   782  		if err != nil {
   783  			t.Errorf("Cookie sync should succeed if the user has opted in.")
   784  		}
   785  		if !cookie.HasLiveSync("pulsepoint") {
   786  			t.Errorf("The PBSCookie should have a usersync after a successful call to TrySync")
   787  		}
   788  		savedUID, hadSync, isLive := cookie.GetUID("pulsepoint")
   789  		if !hadSync {
   790  			t.Error("The GetUID function should properly report that it has a sync.")
   791  		}
   792  		if !isLive {
   793  			t.Error("The GetUID function should properly report live syncs.")
   794  		}
   795  		if savedUID != "1" {
   796  			t.Errorf("The PBSCookie isn't saving syncs correctly. Expected %s, got %s", "1", savedUID)
   797  		}
   798  		cookie.Unsync("pulsepoint")
   799  		if cookie.HasLiveSync("pulsepoint") {
   800  			t.Errorf("The PBSCookie should not have have a usersync after a call to Unsync")
   801  		}
   802  		if value, hadValue, isLive := cookie.GetUID("pulsepoint"); value != "" || hadValue || isLive {
   803  			t.Error("PBSCookie.GetUID() should return empty strings if it doesn't have a sync")
   804  		}
   805  	} else {
   806  		if cookie.HasAnyLiveSyncs() {
   807  			t.Error("If the user opted out, the PBSCookie should have no user syncs.")
   808  		}
   809  
   810  		err := cookie.Sync("adnxs", "123")
   811  		if err == nil {
   812  			t.Error("TrySync should fail if the user has opted out of PBSCookie syncs, but it succeeded.")
   813  		}
   814  	}
   815  	httpCookie, err := ToHTTPCookie(cookie)
   816  	assert.NoError(t, err)
   817  	copiedCookie := decoder.Decode(httpCookie.Value)
   818  	if copiedCookie.AllowSyncs() != cookie.AllowSyncs() {
   819  		t.Error("The PBSCookie interface shouldn't let modifications happen if the user has opted out")
   820  	}
   821  
   822  	if cookie.optOut {
   823  		assert.Equal(t, 0, len(copiedCookie.uids), "Incorrect sync count on reparsed cookie.")
   824  	} else {
   825  		assert.Equal(t, len(cookie.uids), len(copiedCookie.uids), "Incorrect sync count on reparsed cookie.")
   826  	}
   827  
   828  	for family, uid := range copiedCookie.uids {
   829  		if !cookie.HasLiveSync(family) {
   830  			t.Errorf("Cookie is missing sync for family %s", family)
   831  		}
   832  		savedUID, hadSync, isLive := cookie.GetUID(family)
   833  		if !hadSync {
   834  			t.Error("The GetUID function should properly report that it has a sync.")
   835  		}
   836  		if !isLive {
   837  			t.Error("The GetUID function should properly report live syncs.")
   838  		}
   839  		if savedUID != uid.UID {
   840  			t.Errorf("Wrong UID saved for family %s. Expected %s, got %s", family, uid, savedUID)
   841  		}
   842  	}
   843  }
   844  
   845  func ToHTTPCookie(cookie *Cookie) (*http.Cookie, error) {
   846  	encoder := Base64Encoder{}
   847  	encodedCookie, err := encoder.Encode(cookie)
   848  	if err != nil {
   849  		return nil, nil
   850  	}
   851  
   852  	return &http.Cookie{
   853  		Name:    uidCookieName,
   854  		Value:   encodedCookie,
   855  		Expires: time.Now().Add((90 * 24 * time.Hour)),
   856  		Path:    "/",
   857  	}, nil
   858  }