github.com/prebid/prebid-server/v2@v2.18.0/endpoints/setuid_test.go (about)

     1  package endpoints
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"net/url"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/prebid/prebid-server/v2/analytics"
    16  	analyticsBuild "github.com/prebid/prebid-server/v2/analytics/build"
    17  	"github.com/prebid/prebid-server/v2/config"
    18  	"github.com/prebid/prebid-server/v2/errortypes"
    19  	"github.com/prebid/prebid-server/v2/gdpr"
    20  	"github.com/prebid/prebid-server/v2/macros"
    21  	"github.com/prebid/prebid-server/v2/metrics"
    22  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    23  	"github.com/prebid/prebid-server/v2/usersync"
    24  	"github.com/stretchr/testify/assert"
    25  
    26  	metricsConf "github.com/prebid/prebid-server/v2/metrics/config"
    27  )
    28  
    29  func TestSetUIDEndpoint(t *testing.T) {
    30  	testCases := []struct {
    31  		uri                    string
    32  		syncersBidderNameToKey map[string]string
    33  		existingSyncs          map[string]string
    34  		gdprAllowsHostCookies  bool
    35  		gdprReturnsError       bool
    36  		gdprMalformed          bool
    37  		formatOverride         string
    38  		expectedSyncs          map[string]string
    39  		expectedBody           string
    40  		expectedStatusCode     int
    41  		expectedHeaders        map[string]string
    42  		description            string
    43  	}{
    44  		{
    45  			uri:                    "/setuid?bidder=pubmatic&uid=123",
    46  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
    47  			existingSyncs:          nil,
    48  			gdprAllowsHostCookies:  true,
    49  			expectedSyncs:          map[string]string{"pubmatic": "123"},
    50  			expectedStatusCode:     http.StatusOK,
    51  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
    52  			description:            "Set uid for valid bidder",
    53  		},
    54  		{
    55  			uri:                    "/setuid?bidder=PUBMATIC&uid=123",
    56  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
    57  			existingSyncs:          nil,
    58  			gdprAllowsHostCookies:  true,
    59  			expectedSyncs:          map[string]string{"pubmatic": "123"},
    60  			expectedStatusCode:     http.StatusOK,
    61  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
    62  			description:            "Set uid for valid bidder case insensitive",
    63  		},
    64  		{
    65  			uri:                    "/setuid?bidder=appnexus&uid=123",
    66  			syncersBidderNameToKey: map[string]string{"appnexus": "adnxs"},
    67  			existingSyncs:          nil,
    68  			gdprAllowsHostCookies:  true,
    69  			expectedSyncs:          map[string]string{"adnxs": "123"},
    70  			expectedStatusCode:     http.StatusOK,
    71  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
    72  			description:            "Set uid for valid bidder with different key",
    73  		},
    74  		{
    75  			uri:                    "/setuid?bidder=unsupported-bidder&uid=123",
    76  			syncersBidderNameToKey: map[string]string{},
    77  			existingSyncs:          nil,
    78  			gdprAllowsHostCookies:  true,
    79  			expectedSyncs:          nil,
    80  			expectedStatusCode:     http.StatusBadRequest,
    81  			expectedBody:           "The bidder name provided is not supported by Prebid Server",
    82  			description:            "Don't set uid for an unsupported bidder",
    83  		},
    84  		{
    85  			uri:                    "/setuid?bidder=&uid=123",
    86  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
    87  			existingSyncs:          nil,
    88  			gdprAllowsHostCookies:  true,
    89  			expectedSyncs:          nil,
    90  			expectedStatusCode:     http.StatusBadRequest,
    91  			expectedBody:           `"bidder" query param is required`,
    92  			description:            "Don't set uid for an empty bidder",
    93  		},
    94  		{
    95  			uri:                    "/setuid?bidder=unsupported-bidder&uid=123",
    96  			syncersBidderNameToKey: map[string]string{},
    97  			existingSyncs:          map[string]string{"pubmatic": "1234"},
    98  			gdprAllowsHostCookies:  true,
    99  			expectedSyncs:          nil,
   100  			expectedStatusCode:     http.StatusBadRequest,
   101  			expectedBody:           "The bidder name provided is not supported by Prebid Server",
   102  			description: "No need to set existing syncs back in response for a request " +
   103  				"to set uid for an unsupported bidder",
   104  		},
   105  		{
   106  			uri:                    "/setuid?bidder=&uid=123",
   107  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   108  			existingSyncs:          map[string]string{"pubmatic": "1234"},
   109  			gdprAllowsHostCookies:  true,
   110  			expectedSyncs:          nil,
   111  			expectedStatusCode:     http.StatusBadRequest,
   112  			expectedBody:           `"bidder" query param is required`,
   113  			description: "No need to set existing syncs back in response for a request " +
   114  				"to set uid for an empty bidder",
   115  		},
   116  		{
   117  			uri:                    "/setuid?bidder=pubmatic",
   118  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   119  			existingSyncs:          map[string]string{"pubmatic": "1234"},
   120  			gdprAllowsHostCookies:  true,
   121  			expectedSyncs:          map[string]string{},
   122  			expectedStatusCode:     http.StatusOK,
   123  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   124  			description:            "Unset uid for a bidder if the request contains an empty uid for that bidder",
   125  		},
   126  		{
   127  			uri:                    "/setuid?bidder=pubmatic&uid=123",
   128  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   129  			existingSyncs:          map[string]string{"rubicon": "def"},
   130  			gdprAllowsHostCookies:  true,
   131  			expectedSyncs:          map[string]string{"pubmatic": "123", "rubicon": "def"},
   132  			expectedStatusCode:     http.StatusOK,
   133  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   134  			description:            "Add the uid for the requested bidder to the list of existing syncs",
   135  		},
   136  		{
   137  			uri:                    "/setuid?bidder=pubmatic&uid=123&gdpr=0",
   138  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   139  			existingSyncs:          nil,
   140  			gdprAllowsHostCookies:  true,
   141  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   142  			expectedStatusCode:     http.StatusOK,
   143  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   144  			description:            "Don't care about GDPR consent if GDPR is set to 0",
   145  		},
   146  		{
   147  			uri:                    "/setuid?uid=123",
   148  			syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"},
   149  			existingSyncs:          nil,
   150  			expectedSyncs:          nil,
   151  			gdprAllowsHostCookies:  true,
   152  			expectedStatusCode:     http.StatusBadRequest,
   153  			expectedBody:           `"bidder" query param is required`,
   154  			description:            "Return an error if the bidder param is missing from the request",
   155  		},
   156  		{
   157  			uri:                    "/setuid?bidder=appnexus&uid=123&gdpr=2",
   158  			syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"},
   159  			existingSyncs:          nil,
   160  			expectedSyncs:          nil,
   161  			gdprAllowsHostCookies:  true,
   162  			expectedStatusCode:     http.StatusBadRequest,
   163  			expectedBody:           "the gdpr query param must be either 0 or 1. You gave 2",
   164  			description:            "Return an error if GDPR is set to anything else other that 0 or 1",
   165  		},
   166  		{
   167  			uri:                    "/setuid?bidder=appnexus&uid=123&gdpr=1",
   168  			syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"},
   169  			existingSyncs:          nil,
   170  			expectedSyncs:          nil,
   171  			gdprAllowsHostCookies:  true,
   172  			expectedStatusCode:     http.StatusBadRequest,
   173  			expectedBody:           "GDPR consent is required when gdpr signal equals 1",
   174  			description:            "Return an error if GDPR is set to 1 but GDPR consent string is missing",
   175  		},
   176  		{
   177  			uri: "/setuid?bidder=pubmatic&uid=123&gdpr_consent=" +
   178  				"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw",
   179  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   180  			existingSyncs:          nil,
   181  			expectedSyncs:          nil,
   182  			gdprReturnsError:       true,
   183  			expectedStatusCode:     http.StatusBadRequest,
   184  			expectedBody: "No global vendor list was available to interpret this consent string. " +
   185  				"If this is a new, valid version, it should become available soon.",
   186  			description: "Return an error if the GDPR string is either malformed or using a newer version that isn't yet supported",
   187  		},
   188  		{
   189  			uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=" +
   190  				"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw",
   191  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   192  			existingSyncs:          nil,
   193  			expectedSyncs:          nil,
   194  			expectedStatusCode:     http.StatusUnavailableForLegalReasons,
   195  			expectedBody:           "The gdpr_consent string prevents cookies from being saved",
   196  			description:            "Shouldn't set uid for a bidder if it is not allowed by the GDPR consent string",
   197  		},
   198  		{
   199  			uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=" +
   200  				"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw",
   201  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   202  			gdprAllowsHostCookies:  true,
   203  			existingSyncs:          nil,
   204  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   205  			expectedStatusCode:     http.StatusOK,
   206  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   207  			description:            "Should set uid for a bidder that is allowed by the GDPR consent string",
   208  		},
   209  		{
   210  			uri:                    "/setuid?bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   211  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   212  			gdprAllowsHostCookies:  true,
   213  			existingSyncs:          nil,
   214  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   215  			expectedStatusCode:     http.StatusOK,
   216  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   217  			description:            "Sets uid for a bidder allowed by GDPR consent string in the GPP query field",
   218  		},
   219  		{
   220  			uri: "/setuid?bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA" +
   221  				"&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw",
   222  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   223  			gdprAllowsHostCookies:  true,
   224  			existingSyncs:          nil,
   225  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   226  			expectedStatusCode:     http.StatusOK,
   227  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   228  			description:            "GPP value will be used over the one found in the deprecated GDPR consent field for iframe format",
   229  		},
   230  		{
   231  			uri: "/setuid?f=i&bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA" +
   232  				"&gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw",
   233  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   234  			gdprAllowsHostCookies:  true,
   235  			existingSyncs:          nil,
   236  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   237  			expectedStatusCode:     http.StatusOK,
   238  			expectedHeaders:        map[string]string{"Content-Type": "image/png", "Content-Length": "86"},
   239  			description:            "GPP value will be used over the one found in the deprecated GDPR consent field for redirect format",
   240  		},
   241  		{
   242  			uri:                    "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=malformed",
   243  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   244  			gdprAllowsHostCookies:  true,
   245  			gdprMalformed:          true,
   246  			existingSyncs:          nil,
   247  			expectedStatusCode:     http.StatusBadRequest,
   248  			expectedBody:           "gdpr_consent was invalid. malformed consent string malformed: some error",
   249  			description:            "Should return an error if GDPR consent string is malformed",
   250  		},
   251  		{
   252  			uri:                    "/setuid?bidder=pubmatic&uid=123&f=b",
   253  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   254  			existingSyncs:          nil,
   255  			gdprAllowsHostCookies:  true,
   256  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   257  			expectedStatusCode:     http.StatusOK,
   258  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   259  			description:            "Set uid for valid bidder with iframe format",
   260  		},
   261  		{
   262  			uri:                    "/setuid?bidder=pubmatic&uid=123&f=i",
   263  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   264  			existingSyncs:          nil,
   265  			gdprAllowsHostCookies:  true,
   266  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   267  			expectedStatusCode:     http.StatusOK,
   268  			expectedHeaders:        map[string]string{"Content-Type": "image/png", "Content-Length": "86"},
   269  			description:            "Set uid for valid bidder with redirect format",
   270  		},
   271  		{
   272  			uri:                    "/setuid?bidder=pubmatic&uid=123&f=x",
   273  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   274  			existingSyncs:          nil,
   275  			gdprAllowsHostCookies:  true,
   276  			expectedSyncs:          nil,
   277  			expectedStatusCode:     http.StatusBadRequest,
   278  			expectedBody:           `"f" query param is invalid. must be "b" or "i"`,
   279  			description:            "Set uid for valid bidder with invalid format",
   280  		},
   281  		{
   282  			uri:                    "/setuid?bidder=pubmatic&uid=123&account=valid_acct",
   283  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   284  			existingSyncs:          nil,
   285  			gdprAllowsHostCookies:  true,
   286  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   287  			expectedStatusCode:     http.StatusOK,
   288  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   289  			description:            "Set uid for valid bidder with valid account provided",
   290  		},
   291  		{
   292  			uri:                    "/setuid?bidder=pubmatic&uid=123&account=disabled_acct",
   293  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   294  			existingSyncs:          nil,
   295  			gdprAllowsHostCookies:  true,
   296  			expectedSyncs:          nil,
   297  			expectedStatusCode:     http.StatusBadRequest,
   298  			expectedBody:           "account is disabled, please reach out to the prebid server host",
   299  			description:            "Set uid for valid bidder with valid disabled account provided",
   300  		},
   301  		{
   302  			uri:                    "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_valid_activities_usersync_enabled",
   303  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   304  			existingSyncs:          nil,
   305  			gdprAllowsHostCookies:  true,
   306  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   307  			expectedStatusCode:     http.StatusOK,
   308  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   309  			description:            "Set uid for valid bidder with valid account provided with user sync allowed activity",
   310  		},
   311  		{
   312  			uri:                    "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_valid_activities_usersync_disabled",
   313  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   314  			existingSyncs:          nil,
   315  			gdprAllowsHostCookies:  true,
   316  			expectedSyncs:          nil,
   317  			expectedStatusCode:     http.StatusUnavailableForLegalReasons,
   318  			description:            "Set uid for valid bidder with valid account provided with user sync disallowed activity",
   319  		},
   320  		{
   321  			uri:                    "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_invalid_activities",
   322  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   323  			existingSyncs:          nil,
   324  			gdprAllowsHostCookies:  true,
   325  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   326  			expectedStatusCode:     http.StatusOK,
   327  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   328  			description:            "Set uid for valid bidder with valid account provided with invalid user sync activity",
   329  		},
   330  		{
   331  			description:            "gppsid-valid",
   332  			uri:                    "/setuid?bidder=appnexus&uid=123&gpp_sid=100,101", // fake sids to avoid GDPR logic in this test
   333  			syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"},
   334  			existingSyncs:          nil,
   335  			gdprAllowsHostCookies:  true,
   336  			expectedSyncs:          map[string]string{"appnexus": "123"},
   337  			expectedStatusCode:     http.StatusOK,
   338  			expectedHeaders:        map[string]string{"Content-Type": "text/html", "Content-Length": "0"},
   339  		},
   340  		{
   341  			description:            "gppsid-malformed",
   342  			uri:                    "/setuid?bidder=appnexus&uid=123&gpp_sid=malformed",
   343  			syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"},
   344  			existingSyncs:          nil,
   345  			gdprAllowsHostCookies:  true,
   346  			expectedSyncs:          nil,
   347  			expectedStatusCode:     http.StatusBadRequest,
   348  			expectedBody:           "invalid gpp_sid encoding, must be a csv list of integers",
   349  		},
   350  		{
   351  			uri:                    "/setuid?bidder=pubmatic&uid=123",
   352  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
   353  			existingSyncs:          nil,
   354  			gdprAllowsHostCookies:  true,
   355  			formatOverride:         "i",
   356  			expectedSyncs:          map[string]string{"pubmatic": "123"},
   357  			expectedStatusCode:     http.StatusOK,
   358  			expectedHeaders:        map[string]string{"Content-Length": "86", "Content-Type": "image/png"},
   359  			description:            "Format not provided in URL, but formatOverride is defined",
   360  		},
   361  	}
   362  
   363  	analytics := analyticsBuild.New(&config.Analytics{})
   364  	metrics := &metricsConf.NilMetricsEngine{}
   365  
   366  	for _, test := range testCases {
   367  		response := doRequest(makeRequest(test.uri, test.existingSyncs), analytics, metrics,
   368  			test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed, false, 0, nil, test.formatOverride)
   369  		assert.Equal(t, test.expectedStatusCode, response.Code, "Test Case: %s. /setuid returned unexpected error code", test.description)
   370  
   371  		if test.expectedSyncs != nil {
   372  			assertHasSyncs(t, test.description, response, test.expectedSyncs)
   373  		} else {
   374  			assert.Equal(t, "", response.Header().Get("Set-Cookie"), "Test Case: %s. /setuid returned unexpected cookie", test.description)
   375  		}
   376  
   377  		if test.expectedBody != "" {
   378  			assert.Equal(t, test.expectedBody, response.Body.String(), "Test Case: %s. /setuid returned unexpected message", test.description)
   379  		}
   380  
   381  		// compare header values, except for the cookies
   382  		responseHeaders := map[string]string{}
   383  		for k, v := range response.Result().Header {
   384  			if k != "Set-Cookie" {
   385  				responseHeaders[k] = v[0]
   386  			}
   387  		}
   388  		if test.expectedHeaders == nil {
   389  			test.expectedHeaders = map[string]string{}
   390  		}
   391  		assert.Equal(t, test.expectedHeaders, responseHeaders, test.description+":headers")
   392  	}
   393  }
   394  
   395  func TestSetUIDPriorityEjection(t *testing.T) {
   396  	decoder := usersync.Base64Decoder{}
   397  	analytics := analyticsBuild.New(&config.Analytics{})
   398  	syncersByBidder := map[string]string{
   399  		"pubmatic":             "pubmatic",
   400  		"syncer1":              "syncer1",
   401  		"syncer2":              "syncer2",
   402  		"syncer3":              "syncer3",
   403  		"syncer4":              "syncer4",
   404  		"mismatchedBidderName": "syncer5",
   405  		"syncerToEject":        "syncerToEject",
   406  	}
   407  
   408  	testCases := []struct {
   409  		description           string
   410  		uri                   string
   411  		givenExistingSyncs    []string
   412  		givenPriorityGroups   [][]string
   413  		givenMaxCookieSize    int
   414  		expectedStatusCode    int
   415  		expectedSyncer        string
   416  		expectedUID           string
   417  		expectedNumOfElements int
   418  		expectedWarning       string
   419  	}{
   420  		{
   421  			description:           "Cookie empty, expect bidder to be synced, no ejection",
   422  			uri:                   "/setuid?bidder=pubmatic&uid=123",
   423  			givenPriorityGroups:   [][]string{},
   424  			givenMaxCookieSize:    500,
   425  			expectedSyncer:        "pubmatic",
   426  			expectedUID:           "123",
   427  			expectedNumOfElements: 1,
   428  			expectedStatusCode:    http.StatusOK,
   429  		},
   430  		{
   431  			description:           "Cookie full, no priority groups, one ejection",
   432  			uri:                   "/setuid?bidder=pubmatic&uid=123",
   433  			givenExistingSyncs:    []string{"syncer1", "syncer2", "syncer3", "syncer4"},
   434  			givenPriorityGroups:   [][]string{},
   435  			givenMaxCookieSize:    500,
   436  			expectedUID:           "123",
   437  			expectedSyncer:        "pubmatic",
   438  			expectedNumOfElements: 4,
   439  			expectedStatusCode:    http.StatusOK,
   440  		},
   441  		{
   442  			description:           "Cookie full, eject lowest priority element",
   443  			uri:                   "/setuid?bidder=pubmatic&uid=123",
   444  			givenExistingSyncs:    []string{"syncer2", "syncer3", "syncer4", "syncerToEject"},
   445  			givenPriorityGroups:   [][]string{{"pubmatic", "syncer2", "syncer3", "syncer4"}, {"syncerToEject"}},
   446  			givenMaxCookieSize:    500,
   447  			expectedUID:           "123",
   448  			expectedSyncer:        "pubmatic",
   449  			expectedNumOfElements: 4,
   450  			expectedStatusCode:    http.StatusOK,
   451  		},
   452  		{
   453  			description:           "Cookie full, all elements same priority, one ejection",
   454  			uri:                   "/setuid?bidder=pubmatic&uid=123",
   455  			givenExistingSyncs:    []string{"syncer1", "syncer2", "syncer3", "syncer5"},
   456  			givenPriorityGroups:   [][]string{{"pubmatic", "syncer1", "syncer2", "syncer3", "mismatchedBidderName"}},
   457  			givenMaxCookieSize:    500,
   458  			expectedUID:           "123",
   459  			expectedSyncer:        "pubmatic",
   460  			expectedNumOfElements: 4,
   461  			expectedStatusCode:    http.StatusOK,
   462  		},
   463  		{
   464  			description:         "There are only priority elements left, but the bidder being synced isn't one",
   465  			uri:                 "/setuid?bidder=pubmatic&uid=123",
   466  			givenExistingSyncs:  []string{"syncer1", "syncer2", "syncer3", "syncer4"},
   467  			givenPriorityGroups: [][]string{{"syncer1", "syncer2", "syncer3", "syncer4"}},
   468  			givenMaxCookieSize:  500,
   469  			expectedStatusCode:  http.StatusOK,
   470  			expectedWarning:     "Warning: syncer key is not a priority, and there are only priority elements left, cookie not updated",
   471  		},
   472  		{
   473  			description:        "Uid that's trying to be synced is bigger than MaxCookieSize",
   474  			uri:                "/setuid?bidder=pubmatic&uid=123",
   475  			givenMaxCookieSize: 1,
   476  			expectedStatusCode: http.StatusBadRequest,
   477  		},
   478  	}
   479  	for _, test := range testCases {
   480  		request := httptest.NewRequest("GET", test.uri, nil)
   481  
   482  		// Cookie Set Up
   483  		cookie := usersync.NewCookie()
   484  		for _, key := range test.givenExistingSyncs {
   485  			cookie.Sync(key, "111")
   486  		}
   487  		httpCookie, err := ToHTTPCookie(cookie)
   488  		assert.NoError(t, err)
   489  		request.AddCookie(httpCookie)
   490  
   491  		// Make Request to /setuid
   492  		response := doRequest(request, analytics, &metricsConf.NilMetricsEngine{}, syncersByBidder, true, false, false, false, test.givenMaxCookieSize, test.givenPriorityGroups, "")
   493  
   494  		if test.expectedWarning != "" {
   495  			assert.Equal(t, test.expectedWarning, response.Body.String(), test.description)
   496  		} else if test.expectedSyncer != "" {
   497  			// Get Cookie From Header
   498  			var cookieHeader string
   499  			for k, v := range response.Result().Header {
   500  				if k == "Set-Cookie" {
   501  					cookieHeader = v[0]
   502  				}
   503  			}
   504  			encodedCookieValue := getUIDFromHeader(cookieHeader)
   505  
   506  			// Check That Bidder On Request was Synced, it's UID matches, and that the right number of elements are present after ejection
   507  			decodedCookie := decoder.Decode(encodedCookieValue)
   508  			decodedCookieUIDs := decodedCookie.GetUIDs()
   509  
   510  			assert.Equal(t, test.expectedUID, decodedCookieUIDs[test.expectedSyncer], test.description)
   511  			assert.Equal(t, test.expectedNumOfElements, len(decodedCookieUIDs), test.description)
   512  
   513  			// Specific test case handling where we eject the lowest priority element
   514  			if len(test.givenPriorityGroups) == 2 {
   515  				syncer := test.givenPriorityGroups[len(test.givenPriorityGroups)-1][0]
   516  				_, syncerExists := decodedCookieUIDs[syncer]
   517  				assert.False(t, syncerExists, test.description)
   518  			}
   519  		}
   520  		assert.Equal(t, test.expectedStatusCode, response.Result().StatusCode, test.description)
   521  	}
   522  }
   523  
   524  func TestParseSignalFromGPPSID(t *testing.T) {
   525  	type testOutput struct {
   526  		signal gdpr.Signal
   527  		err    error
   528  	}
   529  	testCases := []struct {
   530  		desc     string
   531  		strSID   string
   532  		expected testOutput
   533  	}{
   534  		{
   535  			desc:   "Empty gpp_sid, expect gdpr.SignalAmbiguous",
   536  			strSID: "",
   537  			expected: testOutput{
   538  				signal: gdpr.SignalAmbiguous,
   539  				err:    nil,
   540  			},
   541  		},
   542  		{
   543  			desc:   "Malformed gpp_sid, expect gdpr.SignalAmbiguous",
   544  			strSID: "malformed",
   545  			expected: testOutput{
   546  				signal: gdpr.SignalAmbiguous,
   547  				err:    errors.New(`Error parsing gpp_sid strconv.ParseInt: parsing "malformed": invalid syntax`),
   548  			},
   549  		},
   550  		{
   551  			desc:   "Valid gpp_sid doesn't come with TCF2, expect gdpr.SignalNo",
   552  			strSID: "6",
   553  			expected: testOutput{
   554  				signal: gdpr.SignalNo,
   555  				err:    nil,
   556  			},
   557  		},
   558  		{
   559  			desc:   "Valid gpp_sid comes with TCF2, expect gdpr.SignalYes",
   560  			strSID: "2",
   561  			expected: testOutput{
   562  				signal: gdpr.SignalYes,
   563  				err:    nil,
   564  			},
   565  		},
   566  	}
   567  	for _, tc := range testCases {
   568  		outSignal, outErr := parseSignalFromGppSidStr(tc.strSID)
   569  
   570  		assert.Equal(t, tc.expected.signal, outSignal, tc.desc)
   571  		assert.Equal(t, tc.expected.err, outErr, tc.desc)
   572  	}
   573  }
   574  
   575  func TestParseConsentFromGppStr(t *testing.T) {
   576  	type testOutput struct {
   577  		gdprConsent string
   578  		err         []error
   579  	}
   580  	testCases := []struct {
   581  		desc       string
   582  		inGppQuery string
   583  		expected   testOutput
   584  	}{
   585  		{
   586  			desc:       "Empty gpp field, expect empty GDPR consent",
   587  			inGppQuery: "",
   588  			expected: testOutput{
   589  				gdprConsent: "",
   590  				err:         nil,
   591  			},
   592  		},
   593  		{
   594  			desc:       "Malformed gpp field value, expect empty GDPR consent and error",
   595  			inGppQuery: "malformed",
   596  			expected: testOutput{
   597  				gdprConsent: "",
   598  				err:         []error{errors.New(`error parsing GPP header, header must have type=3`)},
   599  			},
   600  		},
   601  		{
   602  			desc:       "Valid gpp string comes with TCF2 in its gppConstants.SectionID's, expect non-empty GDPR consent",
   603  			inGppQuery: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   604  			expected: testOutput{
   605  				gdprConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   606  				err:         nil,
   607  			},
   608  		},
   609  		{
   610  			desc:       "Valid gpp string doesn't come with TCF2 in its gppConstants.SectionID's, expect blank GDPR consent",
   611  			inGppQuery: "DBABjw~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN",
   612  			expected: testOutput{
   613  				gdprConsent: "",
   614  				err:         nil,
   615  			},
   616  		},
   617  	}
   618  	for _, tc := range testCases {
   619  		outConsent, outErr := parseConsentFromGppStr(tc.inGppQuery)
   620  
   621  		assert.Equal(t, tc.expected.gdprConsent, outConsent, tc.desc)
   622  		assert.ElementsMatch(t, tc.expected.err, outErr, tc.desc)
   623  	}
   624  }
   625  
   626  func TestParseGDPRFromGPP(t *testing.T) {
   627  	type testOutput struct {
   628  		reqInfo gdpr.RequestInfo
   629  		err     error
   630  	}
   631  	type aTest struct {
   632  		desc     string
   633  		inUri    string
   634  		expected testOutput
   635  	}
   636  	testGroups := []struct {
   637  		groupDesc string
   638  		testCases []aTest
   639  	}{
   640  		{
   641  			groupDesc: "No gpp_sid nor gpp",
   642  			testCases: []aTest{
   643  				{
   644  					desc:  "Input URL is mising gpp_sid and gpp, expect signal ambiguous and no error",
   645  					inUri: "/setuid?bidder=pubmatic&uid=123",
   646  					expected: testOutput{
   647  						reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   648  						err:     nil,
   649  					},
   650  				},
   651  			},
   652  		},
   653  		{
   654  			groupDesc: "gpp only",
   655  			testCases: []aTest{
   656  				{
   657  					desc:  "gpp is malformed, expect error",
   658  					inUri: "/setuid?gpp=malformed",
   659  					expected: testOutput{
   660  						reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   661  						err:     errors.New("error parsing GPP header, header must have type=3"),
   662  					},
   663  				},
   664  				{
   665  					desc:  "gpp with a valid TCF2 value. Expect valid consent string and no error",
   666  					inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   667  					expected: testOutput{
   668  						reqInfo: gdpr.RequestInfo{
   669  							GDPRSignal: gdpr.SignalAmbiguous,
   670  							Consent:    "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   671  						},
   672  						err: nil,
   673  					},
   674  				},
   675  				{
   676  					desc:  "gpp does not include TCF2 string. Expect empty consent string and no error",
   677  					inUri: "/setuid?gpp=DBABjw~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN",
   678  					expected: testOutput{
   679  						reqInfo: gdpr.RequestInfo{
   680  							GDPRSignal: gdpr.SignalAmbiguous,
   681  							Consent:    "",
   682  						},
   683  						err: nil,
   684  					},
   685  				},
   686  			},
   687  		},
   688  		{
   689  			groupDesc: "gpp_sid only",
   690  			testCases: []aTest{
   691  				{
   692  					desc:  "gpp_sid is malformed, expect error",
   693  					inUri: "/setuid?gpp_sid=malformed",
   694  					expected: testOutput{
   695  						reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   696  						err:     errors.New("Error parsing gpp_sid strconv.ParseInt: parsing \"malformed\": invalid syntax"),
   697  					},
   698  				},
   699  				{
   700  					desc:  "TCF2 found in gpp_sid list. Given that the consent string will be empty, expect an error",
   701  					inUri: "/setuid?gpp_sid=2,6",
   702  					expected: testOutput{
   703  						reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalYes},
   704  						err:     nil,
   705  					},
   706  				},
   707  				{
   708  					desc:  "TCF2 not found in gpp_sid list. Expect SignalNo and no error",
   709  					inUri: "/setuid?gpp_sid=6,8",
   710  					expected: testOutput{
   711  						reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalNo},
   712  						err:     nil,
   713  					},
   714  				},
   715  			},
   716  		},
   717  		{
   718  			groupDesc: "both gpp_sid and gpp",
   719  			testCases: []aTest{
   720  				{
   721  					desc:  "TCF2 found in gpp_sid list and gpp has a valid GDPR string. Expect no error",
   722  					inUri: "/setuid?gpp_sid=2,6&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   723  					expected: testOutput{
   724  						reqInfo: gdpr.RequestInfo{
   725  							GDPRSignal: gdpr.SignalYes,
   726  							Consent:    "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   727  						},
   728  						err: nil,
   729  					},
   730  				},
   731  			},
   732  		},
   733  	}
   734  	for _, tgroup := range testGroups {
   735  		for _, tc := range tgroup.testCases {
   736  			// set test
   737  			testURL, err := url.Parse(tc.inUri)
   738  			assert.NoError(t, err, "%s - %s", tgroup.groupDesc, tc.desc)
   739  
   740  			query := testURL.Query()
   741  
   742  			// run
   743  			outReqInfo, outErr := parseGDPRFromGPP(query)
   744  
   745  			// assertions
   746  			assert.Equal(t, tc.expected.reqInfo, outReqInfo, "%s - %s", tgroup.groupDesc, tc.desc)
   747  			assert.Equal(t, tc.expected.err, outErr, "%s - %s", tgroup.groupDesc, tc.desc)
   748  		}
   749  	}
   750  }
   751  
   752  func TestParseLegacyGDPRFields(t *testing.T) {
   753  	type testInput struct {
   754  		uri            string
   755  		gppGDPRSignal  gdpr.Signal
   756  		gppGDPRConsent string
   757  	}
   758  	type testOutput struct {
   759  		signal  gdpr.Signal
   760  		consent string
   761  		err     error
   762  	}
   763  	testCases := []struct {
   764  		desc     string
   765  		in       testInput
   766  		expected testOutput
   767  	}{
   768  		{
   769  			desc: `both "gdpr" and "gdpr_consent" missing from URI, expect SignalAmbiguous, blank consent and no error`,
   770  			in: testInput{
   771  				uri: "/setuid?bidder=pubmatic&uid=123",
   772  			},
   773  			expected: testOutput{
   774  				signal:  gdpr.SignalAmbiguous,
   775  				consent: "",
   776  				err:     nil,
   777  			},
   778  		},
   779  		{
   780  			desc: `invalid "gdpr" value, expect SignalAmbiguous, blank consent and error`,
   781  			in: testInput{
   782  				uri:           "/setuid?gdpr=2",
   783  				gppGDPRSignal: gdpr.SignalAmbiguous,
   784  			},
   785  			expected: testOutput{
   786  				signal:  gdpr.SignalAmbiguous,
   787  				consent: "",
   788  				err:     errors.New("the gdpr query param must be either 0 or 1. You gave 2"),
   789  			},
   790  		},
   791  		{
   792  			desc: `valid "gdpr" value but valid GDPRSignal was previously parsed before, expect SignalAmbiguous, blank consent and a warning`,
   793  			in: testInput{
   794  				uri:           "/setuid?gdpr=1",
   795  				gppGDPRSignal: gdpr.SignalYes,
   796  			},
   797  			expected: testOutput{
   798  				signal:  gdpr.SignalAmbiguous,
   799  				consent: "",
   800  				err: &errortypes.Warning{
   801  					Message:     "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.",
   802  					WarningCode: errortypes.UnknownWarningCode,
   803  				},
   804  			},
   805  		},
   806  		{
   807  			desc: `valid "gdpr_consent" value but valid GDPRSignal was previously parsed before, expect SignalAmbiguous, blank consent and a warning`,
   808  			in: testInput{
   809  				uri:            "/setuid?gdpr_consent=someConsent",
   810  				gppGDPRConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   811  			},
   812  			expected: testOutput{
   813  				signal:  gdpr.SignalAmbiguous,
   814  				consent: "",
   815  				err: &errortypes.Warning{
   816  					Message:     "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.",
   817  					WarningCode: errortypes.UnknownWarningCode,
   818  				},
   819  			},
   820  		},
   821  	}
   822  	for _, tc := range testCases {
   823  		// set test
   824  		testURL, err := url.Parse(tc.in.uri)
   825  		assert.NoError(t, err, tc.desc)
   826  
   827  		query := testURL.Query()
   828  
   829  		// run
   830  		outSignal, outConsent, outErr := parseLegacyGDPRFields(query, tc.in.gppGDPRSignal, tc.in.gppGDPRConsent)
   831  
   832  		// assertions
   833  		assert.Equal(t, tc.expected.signal, outSignal, tc.desc)
   834  		assert.Equal(t, tc.expected.consent, outConsent, tc.desc)
   835  		assert.Equal(t, tc.expected.err, outErr, tc.desc)
   836  	}
   837  }
   838  
   839  func TestExtractGDPRInfo(t *testing.T) {
   840  	type testOutput struct {
   841  		requestInfo gdpr.RequestInfo
   842  		err         error
   843  	}
   844  	type testCase struct {
   845  		desc     string
   846  		inUri    string
   847  		expected testOutput
   848  	}
   849  	testSuite := []struct {
   850  		sDesc string
   851  		tests []testCase
   852  	}{
   853  		{
   854  			sDesc: "no gdpr nor gpp values in query",
   855  			tests: []testCase{
   856  				{
   857  					desc:  "expect blank consent, signalNo and nil error",
   858  					inUri: "/setuid?bidder=pubmatic&uid=123",
   859  					expected: testOutput{
   860  						requestInfo: gdpr.RequestInfo{
   861  							Consent:    "",
   862  							GDPRSignal: gdpr.SignalAmbiguous,
   863  						},
   864  						err: nil,
   865  					},
   866  				},
   867  			},
   868  		},
   869  		{
   870  			sDesc: "missing gpp, gdpr only",
   871  			tests: []testCase{
   872  				{
   873  					desc:  "Invalid gdpr signal value in query, expect blank request info and error",
   874  					inUri: "/setuid?gdpr=2",
   875  					expected: testOutput{
   876  						requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   877  						err:         errors.New("the gdpr query param must be either 0 or 1. You gave 2"),
   878  					},
   879  				},
   880  				{
   881  					desc:  "GDPR equals 0, blank consent, expect blank consent, signalNo and nil error",
   882  					inUri: "/setuid?gdpr=0",
   883  					expected: testOutput{
   884  						requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalNo},
   885  						err:         nil,
   886  					},
   887  				},
   888  				{
   889  					desc:  "GDPR equals 1, blank consent, expect blank request info and error",
   890  					inUri: "/setuid?gdpr=1",
   891  					expected: testOutput{
   892  						requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   893  						err:         errors.New("GDPR consent is required when gdpr signal equals 1"),
   894  					},
   895  				},
   896  				{
   897  					desc:  "GDPR equals 0, non-blank consent, expect non-blank request info and nil error",
   898  					inUri: "/setuid?gdpr=0&gdpr_consent=someConsent",
   899  					expected: testOutput{
   900  						requestInfo: gdpr.RequestInfo{
   901  							Consent:    "someConsent",
   902  							GDPRSignal: gdpr.SignalNo,
   903  						},
   904  						err: nil,
   905  					},
   906  				},
   907  				{
   908  					desc:  "GDPR equals 1, non-blank consent, expect non-blank request info and nil error",
   909  					inUri: "/setuid?gdpr=1&gdpr_consent=someConsent",
   910  					expected: testOutput{
   911  						requestInfo: gdpr.RequestInfo{
   912  							Consent:    "someConsent",
   913  							GDPRSignal: gdpr.SignalYes,
   914  						},
   915  						err: nil,
   916  					},
   917  				},
   918  			},
   919  		},
   920  		{
   921  			sDesc: "missing gdpr, gpp only",
   922  			tests: []testCase{
   923  				{
   924  					desc:  "Malformed GPP_SID string, expect blank request info and error",
   925  					inUri: "/setuid?gpp_sid=malformed",
   926  					expected: testOutput{
   927  						requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   928  						err:         errors.New("Error parsing gpp_sid strconv.ParseInt: parsing \"malformed\": invalid syntax"),
   929  					},
   930  				},
   931  				{
   932  					desc:  "Valid GPP_SID string but invalid GPP string in query, expect blank request info and error",
   933  					inUri: "/setuid?gpp=malformed&gpp_sid=2",
   934  					expected: testOutput{
   935  						requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   936  						err:         errors.New("error parsing GPP header, header must have type=3"),
   937  					},
   938  				},
   939  				{
   940  					desc:  "SectionTCFEU2 not found in GPP string, expect blank consent and signalAmbiguous",
   941  					inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA",
   942  					expected: testOutput{
   943  						requestInfo: gdpr.RequestInfo{
   944  							Consent:    "",
   945  							GDPRSignal: gdpr.SignalAmbiguous,
   946  						},
   947  						err: nil,
   948  					},
   949  				},
   950  				{
   951  					desc:  "No GPP string, nor SectionTCFEU2 found in SID list in query, expect blank consent and signalAmbiguous",
   952  					inUri: "/setuid?gpp_sid=3,6",
   953  					expected: testOutput{
   954  						requestInfo: gdpr.RequestInfo{
   955  							Consent:    "",
   956  							GDPRSignal: gdpr.SignalNo,
   957  						},
   958  						err: nil,
   959  					},
   960  				},
   961  				{
   962  					desc:  "No GPP string, SectionTCFEU2 found in SID list in query, expect blank request info and error",
   963  					inUri: "/setuid?gpp_sid=2",
   964  					expected: testOutput{
   965  						requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   966  						err:         errors.New("GDPR consent is required when gdpr signal equals 1"),
   967  					},
   968  				},
   969  				{
   970  					desc:  "SectionTCFEU2 only found in SID list, expect blank request info and error",
   971  					inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=2",
   972  					expected: testOutput{
   973  						requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous},
   974  						err:         errors.New("GDPR consent is required when gdpr signal equals 1"),
   975  					},
   976  				},
   977  				{
   978  					desc:  "SectionTCFEU2 found in GPP string but SID list is nil, expect valid consent and SignalAmbiguous",
   979  					inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   980  					expected: testOutput{
   981  						requestInfo: gdpr.RequestInfo{
   982  							Consent:    "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   983  							GDPRSignal: gdpr.SignalAmbiguous,
   984  						},
   985  						err: nil,
   986  					},
   987  				},
   988  				{
   989  					desc:  "SectionTCFEU2 found in GPP string but not in the non-nil SID list, expect valid consent and signalNo",
   990  					inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=6",
   991  					expected: testOutput{
   992  						requestInfo: gdpr.RequestInfo{
   993  							Consent:    "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
   994  							GDPRSignal: gdpr.SignalNo,
   995  						},
   996  						err: nil,
   997  					},
   998  				},
   999  				{
  1000  					desc:  "SectionTCFEU2 found both in GPP string and SID list, expect valid consent and signalYes",
  1001  					inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4",
  1002  					expected: testOutput{
  1003  						requestInfo: gdpr.RequestInfo{
  1004  							Consent:    "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
  1005  							GDPRSignal: gdpr.SignalYes,
  1006  						},
  1007  						err: nil,
  1008  					},
  1009  				},
  1010  			},
  1011  		},
  1012  		{
  1013  			sDesc: "GPP values take priority over GDPR",
  1014  			tests: []testCase{
  1015  				{
  1016  					desc:  "SignalNo in gdpr field but SignalYes in SID list, CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA consent in gpp but legacyConsent in gdpr_consent, expect GPP values to prevail",
  1017  					inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4&gdpr=0&gdpr_consent=legacyConsent",
  1018  					expected: testOutput{
  1019  						requestInfo: gdpr.RequestInfo{
  1020  							Consent:    "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
  1021  							GDPRSignal: gdpr.SignalYes,
  1022  						},
  1023  						err: &errortypes.Warning{
  1024  							Message:     "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.",
  1025  							WarningCode: errortypes.UnknownWarningCode,
  1026  						},
  1027  					},
  1028  				},
  1029  				{
  1030  					desc:  "SignalNo in gdpr field but SignalYes in SID list because SectionTCFEU2 is listed, expect GPP to prevail",
  1031  					inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4&gdpr=0",
  1032  					expected: testOutput{
  1033  						requestInfo: gdpr.RequestInfo{
  1034  							Consent:    "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
  1035  							GDPRSignal: gdpr.SignalYes,
  1036  						},
  1037  						err: &errortypes.Warning{
  1038  							Message:     "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.",
  1039  							WarningCode: errortypes.UnknownWarningCode,
  1040  						},
  1041  					},
  1042  				},
  1043  				{
  1044  					desc:  "No gpp string in URL query, use gdpr_consent and SignalYes found in SID list because SectionTCFEU2 is listed",
  1045  					inUri: "/setuid?gpp_sid=2,4&gdpr_consent=legacyConsent",
  1046  					expected: testOutput{
  1047  						requestInfo: gdpr.RequestInfo{
  1048  							Consent:    "",
  1049  							GDPRSignal: gdpr.SignalAmbiguous,
  1050  						},
  1051  						err: errors.New("GDPR consent is required when gdpr signal equals 1"),
  1052  					},
  1053  				},
  1054  				{
  1055  					desc:  "SectionTCFEU2 not found in GPP string but found in SID list, choose the GDPR_CONSENT and GPP_SID signal",
  1056  					inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=2&gdpr=0&gdpr_consent=legacyConsent",
  1057  					expected: testOutput{
  1058  						requestInfo: gdpr.RequestInfo{
  1059  							Consent:    "",
  1060  							GDPRSignal: gdpr.SignalAmbiguous,
  1061  						},
  1062  						err: errors.New("GDPR consent is required when gdpr signal equals 1"),
  1063  					},
  1064  				},
  1065  				{
  1066  					desc:  "SectionTCFEU2 found in GPP string but not in SID list, choose GDPR signal GPP consent value",
  1067  					inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=6&gdpr=1&gdpr_consent=legacyConsent",
  1068  					expected: testOutput{
  1069  						requestInfo: gdpr.RequestInfo{
  1070  							Consent:    "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA",
  1071  							GDPRSignal: gdpr.SignalNo,
  1072  						},
  1073  						err: &errortypes.Warning{
  1074  							Message:     "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.",
  1075  							WarningCode: errortypes.UnknownWarningCode,
  1076  						},
  1077  					},
  1078  				},
  1079  				{
  1080  					desc:  "SectionTCFEU2 not found in GPP, use GDPR_CONSENT value. SignalYes found in gdpr field, but not in the valid SID list, expect SignalNo",
  1081  					inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=6&gdpr=1&gdpr_consent=legacyConsent",
  1082  					expected: testOutput{
  1083  						requestInfo: gdpr.RequestInfo{
  1084  							Consent:    "",
  1085  							GDPRSignal: gdpr.SignalNo,
  1086  						},
  1087  						err: &errortypes.Warning{
  1088  							Message:     "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.",
  1089  							WarningCode: errortypes.UnknownWarningCode,
  1090  						},
  1091  					},
  1092  				},
  1093  			},
  1094  		},
  1095  	}
  1096  
  1097  	for _, ts := range testSuite {
  1098  		for _, tc := range ts.tests {
  1099  			// set test
  1100  			testURL, err := url.Parse(tc.inUri)
  1101  			assert.NoError(t, err, tc.desc)
  1102  
  1103  			query := testURL.Query()
  1104  
  1105  			// run
  1106  			outReqInfo, outErr := extractGDPRInfo(query)
  1107  
  1108  			// assertions
  1109  			assert.Equal(t, tc.expected.requestInfo, outReqInfo, tc.desc)
  1110  			assert.Equal(t, tc.expected.err, outErr, tc.desc)
  1111  		}
  1112  	}
  1113  }
  1114  
  1115  func TestSetUIDEndpointMetrics(t *testing.T) {
  1116  	cookieWithOptOut := usersync.NewCookie()
  1117  	cookieWithOptOut.SetOptOut(true)
  1118  
  1119  	testCases := []struct {
  1120  		description            string
  1121  		uri                    string
  1122  		cookies                []*usersync.Cookie
  1123  		syncersBidderNameToKey map[string]string
  1124  		gdprAllowsHostCookies  bool
  1125  		cfgAccountRequired     bool
  1126  		expectedResponseCode   int
  1127  		expectedMetrics        func(*metrics.MetricsEngineMock)
  1128  		expectedAnalytics      func(*MockAnalyticsRunner)
  1129  	}{
  1130  		{
  1131  			description:            "Success - Sync",
  1132  			uri:                    "/setuid?bidder=pubmatic&uid=123",
  1133  			cookies:                []*usersync.Cookie{},
  1134  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1135  			gdprAllowsHostCookies:  true,
  1136  			expectedResponseCode:   200,
  1137  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1138  				m.On("RecordSetUid", metrics.SetUidOK).Once()
  1139  				m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidOK).Once()
  1140  			},
  1141  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1142  				expected := analytics.SetUIDObject{
  1143  					Status:  200,
  1144  					Bidder:  "pubmatic",
  1145  					UID:     "123",
  1146  					Errors:  []error{},
  1147  					Success: true,
  1148  				}
  1149  				a.On("LogSetUIDObject", &expected).Once()
  1150  			},
  1151  		},
  1152  		{
  1153  			description:            "Success - Unsync",
  1154  			uri:                    "/setuid?bidder=pubmatic&uid=",
  1155  			cookies:                []*usersync.Cookie{},
  1156  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1157  			gdprAllowsHostCookies:  true,
  1158  			expectedResponseCode:   200,
  1159  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1160  				m.On("RecordSetUid", metrics.SetUidOK).Once()
  1161  				m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidCleared).Once()
  1162  			},
  1163  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1164  				expected := analytics.SetUIDObject{
  1165  					Status:  200,
  1166  					Bidder:  "pubmatic",
  1167  					UID:     "",
  1168  					Errors:  []error{},
  1169  					Success: true,
  1170  				}
  1171  				a.On("LogSetUIDObject", &expected).Once()
  1172  			},
  1173  		},
  1174  		{
  1175  			description:            "Cookie Opted Out",
  1176  			uri:                    "/setuid?bidder=pubmatic&uid=123",
  1177  			cookies:                []*usersync.Cookie{cookieWithOptOut},
  1178  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1179  			gdprAllowsHostCookies:  true,
  1180  			expectedResponseCode:   401,
  1181  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1182  				m.On("RecordSetUid", metrics.SetUidOptOut).Once()
  1183  			},
  1184  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1185  				expected := analytics.SetUIDObject{
  1186  					Status:  401,
  1187  					Bidder:  "",
  1188  					UID:     "",
  1189  					Errors:  []error{},
  1190  					Success: false,
  1191  				}
  1192  				a.On("LogSetUIDObject", &expected).Once()
  1193  			},
  1194  		},
  1195  		{
  1196  			description:            "Unknown Syncer Key",
  1197  			uri:                    "/setuid?bidder=pubmatic&uid=123",
  1198  			cookies:                []*usersync.Cookie{},
  1199  			syncersBidderNameToKey: map[string]string{},
  1200  			gdprAllowsHostCookies:  true,
  1201  			expectedResponseCode:   400,
  1202  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1203  				m.On("RecordSetUid", metrics.SetUidSyncerUnknown).Once()
  1204  			},
  1205  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1206  				expected := analytics.SetUIDObject{
  1207  					Status:  400,
  1208  					Bidder:  "",
  1209  					UID:     "",
  1210  					Errors:  []error{errors.New("The bidder name provided is not supported by Prebid Server")},
  1211  					Success: false,
  1212  				}
  1213  				a.On("LogSetUIDObject", &expected).Once()
  1214  			},
  1215  		},
  1216  		{
  1217  			description:            "Unknown Format",
  1218  			uri:                    "/setuid?bidder=pubmatic&uid=123&f=z",
  1219  			cookies:                []*usersync.Cookie{},
  1220  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1221  			gdprAllowsHostCookies:  true,
  1222  			expectedResponseCode:   400,
  1223  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1224  				m.On("RecordSetUid", metrics.SetUidBadRequest).Once()
  1225  			},
  1226  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1227  				expected := analytics.SetUIDObject{
  1228  					Status:  400,
  1229  					Bidder:  "pubmatic",
  1230  					UID:     "",
  1231  					Errors:  []error{errors.New(`"f" query param is invalid. must be "b" or "i"`)},
  1232  					Success: false,
  1233  				}
  1234  				a.On("LogSetUIDObject", &expected).Once()
  1235  			},
  1236  		},
  1237  		{
  1238  			description:            "Prevented By GDPR - Invalid Consent String",
  1239  			uri:                    "/setuid?bidder=pubmatic&uid=123&gdpr=1",
  1240  			cookies:                []*usersync.Cookie{},
  1241  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1242  			gdprAllowsHostCookies:  true,
  1243  			expectedResponseCode:   400,
  1244  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1245  				m.On("RecordSetUid", metrics.SetUidBadRequest).Once()
  1246  			},
  1247  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1248  				expected := analytics.SetUIDObject{
  1249  					Status:  400,
  1250  					Bidder:  "pubmatic",
  1251  					UID:     "",
  1252  					Errors:  []error{errors.New("GDPR consent is required when gdpr signal equals 1")},
  1253  					Success: false,
  1254  				}
  1255  				a.On("LogSetUIDObject", &expected).Once()
  1256  			},
  1257  		},
  1258  		{
  1259  			description:            "Prevented By GDPR - Permission Denied By Consent String",
  1260  			uri:                    "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=any",
  1261  			cookies:                []*usersync.Cookie{},
  1262  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1263  			gdprAllowsHostCookies:  false,
  1264  			expectedResponseCode:   451,
  1265  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1266  				m.On("RecordSetUid", metrics.SetUidGDPRHostCookieBlocked).Once()
  1267  			},
  1268  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1269  				expected := analytics.SetUIDObject{
  1270  					Status:  451,
  1271  					Bidder:  "pubmatic",
  1272  					UID:     "",
  1273  					Errors:  []error{errors.New("The gdpr_consent string prevents cookies from being saved")},
  1274  					Success: false,
  1275  				}
  1276  				a.On("LogSetUIDObject", &expected).Once()
  1277  			},
  1278  		},
  1279  		{
  1280  			description:            "Invalid account",
  1281  			uri:                    "/setuid?bidder=pubmatic&uid=123&account=unknown",
  1282  			cookies:                []*usersync.Cookie{},
  1283  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1284  			gdprAllowsHostCookies:  true,
  1285  			cfgAccountRequired:     true,
  1286  			expectedResponseCode:   400,
  1287  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1288  				m.On("RecordSetUid", metrics.SetUidAccountInvalid).Once()
  1289  			},
  1290  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1291  				expected := analytics.SetUIDObject{
  1292  					Status:  400,
  1293  					Bidder:  "pubmatic",
  1294  					UID:     "",
  1295  					Errors:  []error{errCookieSyncAccountInvalid},
  1296  					Success: false,
  1297  				}
  1298  				a.On("LogSetUIDObject", &expected).Once()
  1299  			},
  1300  		},
  1301  		{
  1302  			description:            "Malformed account",
  1303  			uri:                    "/setuid?bidder=pubmatic&uid=123&account=malformed_acct",
  1304  			cookies:                []*usersync.Cookie{},
  1305  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1306  			gdprAllowsHostCookies:  true,
  1307  			cfgAccountRequired:     true,
  1308  			expectedResponseCode:   400,
  1309  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1310  				m.On("RecordSetUid", metrics.SetUidAccountConfigMalformed).Once()
  1311  			},
  1312  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1313  				expected := analytics.SetUIDObject{
  1314  					Status:  400,
  1315  					Bidder:  "pubmatic",
  1316  					UID:     "",
  1317  					Errors:  []error{errCookieSyncAccountConfigMalformed},
  1318  					Success: false,
  1319  				}
  1320  				a.On("LogSetUIDObject", &expected).Once()
  1321  			},
  1322  		},
  1323  		{
  1324  			description:            "Invalid JSON account",
  1325  			uri:                    "/setuid?bidder=pubmatic&uid=123&account=invalid_json_acct",
  1326  			cookies:                []*usersync.Cookie{},
  1327  			syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"},
  1328  			gdprAllowsHostCookies:  true,
  1329  			cfgAccountRequired:     true,
  1330  			expectedResponseCode:   400,
  1331  			expectedMetrics: func(m *metrics.MetricsEngineMock) {
  1332  				m.On("RecordSetUid", metrics.SetUidAccountConfigMalformed).Once()
  1333  			},
  1334  			expectedAnalytics: func(a *MockAnalyticsRunner) {
  1335  				expected := analytics.SetUIDObject{
  1336  					Status:  400,
  1337  					Bidder:  "pubmatic",
  1338  					UID:     "",
  1339  					Errors:  []error{errCookieSyncAccountConfigMalformed},
  1340  					Success: false,
  1341  				}
  1342  				a.On("LogSetUIDObject", &expected).Once()
  1343  			},
  1344  		},
  1345  	}
  1346  
  1347  	for _, test := range testCases {
  1348  		analyticsEngine := &MockAnalyticsRunner{}
  1349  		test.expectedAnalytics(analyticsEngine)
  1350  
  1351  		metricsEngine := &metrics.MetricsEngineMock{}
  1352  		test.expectedMetrics(metricsEngine)
  1353  
  1354  		req := httptest.NewRequest("GET", test.uri, nil)
  1355  		for _, v := range test.cookies {
  1356  			addCookie(req, v)
  1357  		}
  1358  		response := doRequest(req, analyticsEngine, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false, test.cfgAccountRequired, 0, nil, "")
  1359  
  1360  		assert.Equal(t, test.expectedResponseCode, response.Code, test.description)
  1361  		analyticsEngine.AssertExpectations(t)
  1362  		metricsEngine.AssertExpectations(t)
  1363  	}
  1364  }
  1365  
  1366  func TestOptedOut(t *testing.T) {
  1367  	request := httptest.NewRequest("GET", "/setuid?bidder=pubmatic&uid=123", nil)
  1368  	cookie := usersync.NewCookie()
  1369  	cookie.SetOptOut(true)
  1370  	addCookie(request, cookie)
  1371  	syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"}
  1372  	analytics := analyticsBuild.New(&config.Analytics{})
  1373  	metrics := &metricsConf.NilMetricsEngine{}
  1374  	response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false, 0, nil, "")
  1375  
  1376  	assert.Equal(t, http.StatusUnauthorized, response.Code)
  1377  }
  1378  
  1379  func TestSiteCookieCheck(t *testing.T) {
  1380  	testCases := []struct {
  1381  		ua             string
  1382  		expectedResult bool
  1383  		description    string
  1384  	}{
  1385  		{
  1386  			ua:             "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",
  1387  			expectedResult: true,
  1388  			description:    "Should return true for a valid chrome version",
  1389  		},
  1390  		{
  1391  			ua:             "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",
  1392  			expectedResult: false,
  1393  			description:    "Should return false for chrome version below than the supported min version",
  1394  		},
  1395  	}
  1396  
  1397  	for _, test := range testCases {
  1398  		assert.Equal(t, test.expectedResult, siteCookieCheck(test.ua), test.description)
  1399  	}
  1400  }
  1401  
  1402  func TestGetResponseFormat(t *testing.T) {
  1403  	testCases := []struct {
  1404  		urlValues      url.Values
  1405  		syncer         usersync.Syncer
  1406  		expectedFormat string
  1407  		expectedError  string
  1408  		description    string
  1409  	}{
  1410  		{
  1411  			urlValues:      url.Values{},
  1412  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame},
  1413  			expectedFormat: "b",
  1414  			description:    "parameter not provided, use default sync type iframe",
  1415  		},
  1416  		{
  1417  			urlValues:      url.Values{},
  1418  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect},
  1419  			expectedFormat: "i",
  1420  			description:    "parameter not provided, use default sync type redirect",
  1421  		},
  1422  		{
  1423  			urlValues:      url.Values{},
  1424  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncType("invalid")},
  1425  			expectedFormat: "",
  1426  			description:    "parameter not provided,  default sync type is invalid",
  1427  		},
  1428  		{
  1429  			urlValues:      url.Values{"f": []string{"b"}},
  1430  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect},
  1431  			expectedFormat: "b",
  1432  			description:    "parameter given as `b`, default sync type is opposite",
  1433  		},
  1434  		{
  1435  			urlValues:      url.Values{"f": []string{"B"}},
  1436  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect},
  1437  			expectedFormat: "b",
  1438  			description:    "parameter given as `b`, default sync type is opposite - case insensitive",
  1439  		},
  1440  		{
  1441  			urlValues:      url.Values{"f": []string{"i"}},
  1442  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame},
  1443  			expectedFormat: "i",
  1444  			description:    "parameter given as `b`, default sync type is opposite",
  1445  		},
  1446  		{
  1447  			urlValues:      url.Values{"f": []string{"I"}},
  1448  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame},
  1449  			expectedFormat: "i",
  1450  			description:    "parameter given as `b`, default sync type is opposite - case insensitive",
  1451  		},
  1452  		{
  1453  			urlValues:     url.Values{"f": []string{"x"}},
  1454  			syncer:        fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame},
  1455  			expectedError: `"f" query param is invalid. must be "b" or "i"`,
  1456  			description:   "parameter given invalid",
  1457  		},
  1458  		{
  1459  			urlValues:      url.Values{"f": []string{}},
  1460  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect},
  1461  			expectedFormat: "i",
  1462  			description:    "parameter given is empty (by slice), use default sync type redirect",
  1463  		},
  1464  		{
  1465  			urlValues:      url.Values{"f": []string{""}},
  1466  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect},
  1467  			expectedFormat: "i",
  1468  			description:    "parameter given is empty (by empty item), use default sync type redirect",
  1469  		},
  1470  		{
  1471  			urlValues:      url.Values{"f": []string{""}},
  1472  			syncer:         fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect},
  1473  			expectedFormat: "i",
  1474  			description:    "parameter given is empty (by empty item), use default sync type redirect",
  1475  		},
  1476  		{
  1477  			urlValues:      url.Values{"f": []string{}},
  1478  			syncer:         fakeSyncer{key: "a", formatOverride: "i"},
  1479  			expectedFormat: "i",
  1480  			description:    "format not provided, but formatOverride is defined, expect i",
  1481  		},
  1482  		{
  1483  			urlValues:      url.Values{"f": []string{}},
  1484  			syncer:         fakeSyncer{key: "a", formatOverride: "b"},
  1485  			expectedFormat: "b",
  1486  			description:    "format not provided, but formatOverride is defined, expect b",
  1487  		},
  1488  		{
  1489  			urlValues:      url.Values{"f": []string{}},
  1490  			syncer:         fakeSyncer{key: "a", formatOverride: "b", defaultSyncType: usersync.SyncTypeRedirect},
  1491  			expectedFormat: "b",
  1492  			description:    "format not provided, default is defined but formatOverride is defined as well, expect b",
  1493  		},
  1494  	}
  1495  
  1496  	for _, test := range testCases {
  1497  		result, err := getResponseFormat(test.urlValues, test.syncer)
  1498  
  1499  		if test.expectedError == "" {
  1500  			assert.NoError(t, err, test.description+":err")
  1501  			assert.Equal(t, test.expectedFormat, result, test.description+":result")
  1502  		} else {
  1503  			assert.EqualError(t, err, test.expectedError, test.description+":err")
  1504  			assert.Empty(t, result, test.description+":result")
  1505  		}
  1506  	}
  1507  }
  1508  
  1509  func TestIsSyncerPriority(t *testing.T) {
  1510  	testCases := []struct {
  1511  		name                           string
  1512  		givenBidderNameFromSyncerQuery string
  1513  		givenPriorityGroups            [][]string
  1514  		expected                       bool
  1515  	}{
  1516  		{
  1517  			name:                           "priority-tier-1",
  1518  			givenBidderNameFromSyncerQuery: "a",
  1519  			givenPriorityGroups:            [][]string{{"a"}},
  1520  			expected:                       true,
  1521  		},
  1522  		{
  1523  			name:                           "priority-tier-other",
  1524  			givenBidderNameFromSyncerQuery: "c",
  1525  			givenPriorityGroups:            [][]string{{"a"}, {"b", "c"}},
  1526  			expected:                       true,
  1527  		},
  1528  		{
  1529  			name:                           "priority-case-insensitive",
  1530  			givenBidderNameFromSyncerQuery: "A",
  1531  			givenPriorityGroups:            [][]string{{"a"}},
  1532  			expected:                       true,
  1533  		},
  1534  		{
  1535  			name:                           "not-priority-empty",
  1536  			givenBidderNameFromSyncerQuery: "a",
  1537  			givenPriorityGroups:            [][]string{},
  1538  			expected:                       false,
  1539  		},
  1540  		{
  1541  			name:                           "not-priority-not-defined",
  1542  			givenBidderNameFromSyncerQuery: "a",
  1543  			givenPriorityGroups:            [][]string{{"b"}},
  1544  			expected:                       false,
  1545  		},
  1546  		{
  1547  			name:                           "no-bidder",
  1548  			givenBidderNameFromSyncerQuery: "",
  1549  			givenPriorityGroups:            [][]string{{"b"}},
  1550  			expected:                       false,
  1551  		},
  1552  		{
  1553  			name:                           "no-priority-groups",
  1554  			givenBidderNameFromSyncerQuery: "a",
  1555  			givenPriorityGroups:            [][]string{},
  1556  			expected:                       false,
  1557  		},
  1558  	}
  1559  
  1560  	for _, test := range testCases {
  1561  		t.Run(test.name, func(t *testing.T) {
  1562  			isPriority := isSyncerPriority(test.givenBidderNameFromSyncerQuery, test.givenPriorityGroups)
  1563  			assert.Equal(t, test.expected, isPriority)
  1564  		})
  1565  	}
  1566  }
  1567  
  1568  func assertHasSyncs(t *testing.T, testCase string, resp *httptest.ResponseRecorder, syncs map[string]string) {
  1569  	t.Helper()
  1570  	cookie := parseCookieString(t, resp)
  1571  
  1572  	assert.Equal(t, len(syncs), len(cookie.GetUIDs()), "Test Case: %s. /setuid response doesn't contain expected number of syncs", testCase)
  1573  
  1574  	for bidder, uid := range syncs {
  1575  		assert.True(t, cookie.HasLiveSync(bidder), "Test Case: %s. /setuid response cookie doesn't contain uid for bidder: %s", testCase, bidder)
  1576  		actualUID, _, _ := cookie.GetUID(bidder)
  1577  		assert.Equal(t, uid, actualUID, "Test Case: %s. /setuid response cookie doesn't contain correct uid for bidder: %s", testCase, bidder)
  1578  	}
  1579  }
  1580  
  1581  func makeRequest(uri string, existingSyncs map[string]string) *http.Request {
  1582  	request := httptest.NewRequest("GET", uri, nil)
  1583  	if len(existingSyncs) > 0 {
  1584  		pbsCookie := usersync.NewCookie()
  1585  		for key, value := range existingSyncs {
  1586  			pbsCookie.Sync(key, value)
  1587  		}
  1588  		addCookie(request, pbsCookie)
  1589  	}
  1590  	return request
  1591  }
  1592  
  1593  func doRequest(req *http.Request, analytics analytics.Runner, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool, maxCookieSize int, priorityGroups [][]string, formatOverride string) *httptest.ResponseRecorder {
  1594  	cfg := config.Configuration{
  1595  		AccountRequired: cfgAccountRequired,
  1596  		AccountDefaults: config.Account{},
  1597  		UserSync: config.UserSync{
  1598  			PriorityGroups: priorityGroups,
  1599  		},
  1600  		HostCookie: config.HostCookie{
  1601  			MaxCookieSizeBytes: maxCookieSize,
  1602  		},
  1603  	}
  1604  	cfg.MarshalAccountDefaults()
  1605  
  1606  	query := req.URL.Query()
  1607  
  1608  	perms := &fakePermsSetUID{
  1609  		allowHost:           gdprAllowsHostCookies,
  1610  		consent:             query.Get("gdpr_consent"),
  1611  		errorHost:           gdprReturnsError,
  1612  		errorMalformed:      gdprReturnsMalformedError,
  1613  		personalInfoAllowed: true,
  1614  	}
  1615  	gdprPermsBuilder := fakePermissionsBuilder{
  1616  		permissions: perms,
  1617  	}.Builder
  1618  	tcf2ConfigBuilder := fakeTCF2ConfigBuilder{
  1619  		cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}),
  1620  	}.Builder
  1621  
  1622  	syncersByBidder := make(map[string]usersync.Syncer)
  1623  	for bidderName, syncerKey := range syncersBidderNameToKey {
  1624  		syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame, formatOverride: formatOverride}
  1625  		if priorityGroups == nil {
  1626  			cfg.UserSync.PriorityGroups = [][]string{{}}
  1627  			cfg.UserSync.PriorityGroups[0] = append(cfg.UserSync.PriorityGroups[0], bidderName)
  1628  		}
  1629  	}
  1630  
  1631  	fakeAccountsFetcher := FakeAccountsFetcher{AccountData: map[string]json.RawMessage{
  1632  		"valid_acct":        json.RawMessage(`{"disabled":false}`),
  1633  		"disabled_acct":     json.RawMessage(`{"disabled":true}`),
  1634  		"malformed_acct":    json.RawMessage(`{"disabled":"malformed"}`),
  1635  		"invalid_json_acct": json.RawMessage(`{"}`),
  1636  
  1637  		"valid_acct_with_valid_activities_usersync_enabled":  json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"default": true}}}}`),
  1638  		"valid_acct_with_valid_activities_usersync_disabled": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"default": false}}}}`),
  1639  		"valid_acct_with_invalid_activities":                 json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"rules":[{"condition":{"componentName": ["bidderA.bidderB.bidderC"]}}]}}}}`),
  1640  	}}
  1641  
  1642  	endpoint := NewSetUIDEndpoint(&cfg, syncersByBidder, gdprPermsBuilder, tcf2ConfigBuilder, analytics, fakeAccountsFetcher, metrics)
  1643  	response := httptest.NewRecorder()
  1644  	endpoint(response, req, nil)
  1645  	return response
  1646  }
  1647  
  1648  func addCookie(req *http.Request, cookie *usersync.Cookie) {
  1649  	httpCookie, _ := ToHTTPCookie(cookie)
  1650  	req.AddCookie(httpCookie)
  1651  }
  1652  
  1653  func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *usersync.Cookie {
  1654  	decoder := usersync.Base64Decoder{}
  1655  	cookieString := response.Header().Get("Set-Cookie")
  1656  	parser := regexp.MustCompile("uids=(.*?);")
  1657  	res := parser.FindStringSubmatch(cookieString)
  1658  	assert.Equal(t, 2, len(res))
  1659  	httpCookie := http.Cookie{
  1660  		Name:  "uids",
  1661  		Value: res[1],
  1662  	}
  1663  	return decoder.Decode(httpCookie.Value)
  1664  }
  1665  
  1666  type fakePermissionsBuilder struct {
  1667  	permissions gdpr.Permissions
  1668  }
  1669  
  1670  func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions {
  1671  	return fpb.permissions
  1672  }
  1673  
  1674  type fakeTCF2ConfigBuilder struct {
  1675  	cfg gdpr.TCF2ConfigReader
  1676  }
  1677  
  1678  func (fcr fakeTCF2ConfigBuilder) Builder(hostConfig config.TCF2, accountConfig config.AccountGDPR) gdpr.TCF2ConfigReader {
  1679  	return fcr.cfg
  1680  }
  1681  
  1682  type fakePermsSetUID struct {
  1683  	allowHost           bool
  1684  	consent             string
  1685  	errorHost           bool
  1686  	errorMalformed      bool
  1687  	personalInfoAllowed bool
  1688  }
  1689  
  1690  func (g *fakePermsSetUID) HostCookiesAllowed(ctx context.Context) (bool, error) {
  1691  	if g.errorMalformed {
  1692  		return g.allowHost, &gdpr.ErrorMalformedConsent{Consent: g.consent, Cause: errors.New("some error")}
  1693  	}
  1694  	if g.errorHost {
  1695  		return g.allowHost, errors.New("something went wrong")
  1696  	}
  1697  	return g.allowHost, nil
  1698  }
  1699  
  1700  func (g *fakePermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) {
  1701  	return false, nil
  1702  }
  1703  
  1704  func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
  1705  	return gdpr.AuctionPermissions{
  1706  		AllowBidRequest: g.personalInfoAllowed,
  1707  		PassGeo:         g.personalInfoAllowed,
  1708  		PassID:          g.personalInfoAllowed,
  1709  	}, nil
  1710  }
  1711  
  1712  type fakeSyncer struct {
  1713  	key             string
  1714  	defaultSyncType usersync.SyncType
  1715  	formatOverride  string
  1716  }
  1717  
  1718  func (s fakeSyncer) Key() string {
  1719  	return s.key
  1720  }
  1721  
  1722  func (s fakeSyncer) DefaultResponseFormat() usersync.SyncType {
  1723  	switch s.formatOverride {
  1724  	case "b":
  1725  		return usersync.SyncTypeIFrame
  1726  	case "i":
  1727  		return usersync.SyncTypeRedirect
  1728  	default:
  1729  		return s.defaultSyncType
  1730  	}
  1731  }
  1732  
  1733  func (s fakeSyncer) SupportsType(syncTypes []usersync.SyncType) bool {
  1734  	return true
  1735  }
  1736  
  1737  func (s fakeSyncer) GetSync(syncTypes []usersync.SyncType, privacyMacros macros.UserSyncPrivacy) (usersync.Sync, error) {
  1738  	return usersync.Sync{}, nil
  1739  }
  1740  
  1741  func ToHTTPCookie(cookie *usersync.Cookie) (*http.Cookie, error) {
  1742  	encoder := usersync.Base64Encoder{}
  1743  	encodedCookie, err := encoder.Encode(cookie)
  1744  	if err != nil {
  1745  		return nil, nil
  1746  	}
  1747  
  1748  	return &http.Cookie{
  1749  		Name:    uidCookieName,
  1750  		Value:   encodedCookie,
  1751  		Expires: time.Now().Add((90 * 24 * time.Hour)),
  1752  		Path:    "/",
  1753  	}, nil
  1754  }
  1755  
  1756  func getUIDFromHeader(setCookieHeader string) string {
  1757  	cookies := strings.Split(setCookieHeader, ";")
  1758  	for _, cookie := range cookies {
  1759  		trimmedCookie := strings.TrimSpace(cookie)
  1760  		if strings.HasPrefix(trimmedCookie, "uids=") {
  1761  			parts := strings.SplitN(trimmedCookie, "=", 2)
  1762  			if len(parts) == 2 {
  1763  				return parts[1]
  1764  			}
  1765  		}
  1766  	}
  1767  	return ""
  1768  }