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 }