github.com/prebid/prebid-server@v0.275.0/usersync/syncer_test.go (about) 1 package usersync 2 3 import ( 4 "testing" 5 "text/template" 6 7 "github.com/prebid/prebid-server/config" 8 "github.com/prebid/prebid-server/macros" 9 "github.com/stretchr/testify/assert" 10 ) 11 12 func TestNewSyncer(t *testing.T) { 13 var ( 14 supportCORS = true 15 hostConfig = config.UserSync{ExternalURL: "http://host.com", RedirectURL: "{{.ExternalURL}}/host"} 16 macroValues = macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"} 17 iframeConfig = &config.SyncerEndpoint{URL: "https://bidder.com/iframe?redirect={{.RedirectURL}}"} 18 redirectConfig = &config.SyncerEndpoint{URL: "https://bidder.com/redirect?redirect={{.RedirectURL}}"} 19 errParseConfig = &config.SyncerEndpoint{URL: "{{malformed}}"} 20 errInvalidConfig = &config.SyncerEndpoint{URL: "notAURL:{{.RedirectURL}}"} 21 ) 22 23 testCases := []struct { 24 description string 25 givenKey string 26 givenBidderName string 27 givenIFrameConfig *config.SyncerEndpoint 28 givenRedirectConfig *config.SyncerEndpoint 29 givenExternalURL string 30 expectedError string 31 expectedDefault SyncType 32 expectedIFrame string 33 expectedRedirect string 34 }{ 35 { 36 description: "Missing Key", 37 givenKey: "", 38 givenBidderName: "", 39 givenIFrameConfig: iframeConfig, 40 givenRedirectConfig: nil, 41 expectedError: "key is required", 42 }, 43 { 44 description: "Missing Endpoints", 45 givenKey: "a", 46 givenBidderName: "bidderA", 47 givenIFrameConfig: nil, 48 givenRedirectConfig: nil, 49 expectedError: "at least one endpoint (iframe and/or redirect) is required", 50 }, 51 { 52 description: "IFrame & Redirect Endpoints", 53 givenKey: "a", 54 givenBidderName: "bidderA", 55 givenIFrameConfig: iframeConfig, 56 givenRedirectConfig: redirectConfig, 57 expectedDefault: SyncTypeIFrame, 58 expectedIFrame: "https://bidder.com/iframe?redirect=http%3A%2F%2Fhost.com%2Fhost", 59 expectedRedirect: "https://bidder.com/redirect?redirect=http%3A%2F%2Fhost.com%2Fhost", 60 }, 61 { 62 description: "IFrame - Parse Error", 63 givenKey: "a", 64 givenBidderName: "bidderA", 65 givenIFrameConfig: errParseConfig, 66 givenRedirectConfig: nil, 67 expectedError: "iframe template: biddera_usersync_url:1: function \"malformed\" not defined", 68 }, 69 { 70 description: "IFrame - Validation Error", 71 givenKey: "a", 72 givenBidderName: "bidderA", 73 givenIFrameConfig: errInvalidConfig, 74 givenRedirectConfig: nil, 75 expectedError: "iframe composed url: \"notAURL:http%3A%2F%2Fhost.com%2Fhost\" is invalid", 76 }, 77 { 78 description: "Redirect - Parse Error", 79 givenKey: "a", 80 givenBidderName: "bidderA", 81 givenIFrameConfig: nil, 82 givenRedirectConfig: errParseConfig, 83 expectedError: "redirect template: biddera_usersync_url:1: function \"malformed\" not defined", 84 }, 85 { 86 description: "Redirect - Validation Error", 87 givenKey: "a", 88 givenBidderName: "bidderA", 89 givenIFrameConfig: nil, 90 givenRedirectConfig: errInvalidConfig, 91 expectedError: "redirect composed url: \"notAURL:http%3A%2F%2Fhost.com%2Fhost\" is invalid", 92 }, 93 { 94 description: "Syncer Level External URL", 95 givenKey: "a", 96 givenBidderName: "bidderA", 97 givenExternalURL: "http://syncer.com", 98 givenIFrameConfig: iframeConfig, 99 givenRedirectConfig: redirectConfig, 100 expectedDefault: SyncTypeIFrame, 101 expectedIFrame: "https://bidder.com/iframe?redirect=http%3A%2F%2Fsyncer.com%2Fhost", 102 expectedRedirect: "https://bidder.com/redirect?redirect=http%3A%2F%2Fsyncer.com%2Fhost", 103 }, 104 } 105 106 for _, test := range testCases { 107 syncerConfig := config.Syncer{ 108 Key: test.givenKey, 109 SupportCORS: &supportCORS, 110 IFrame: test.givenIFrameConfig, 111 Redirect: test.givenRedirectConfig, 112 ExternalURL: test.givenExternalURL, 113 } 114 115 result, err := NewSyncer(hostConfig, syncerConfig, test.givenBidderName) 116 117 if test.expectedError == "" { 118 assert.NoError(t, err, test.description+":err") 119 if assert.IsType(t, standardSyncer{}, result, test.description+":result_type") { 120 result := result.(standardSyncer) 121 assert.Equal(t, test.givenKey, result.key, test.description+":key") 122 assert.Equal(t, supportCORS, result.supportCORS, test.description+":cors") 123 assert.Equal(t, test.expectedDefault, result.defaultSyncType, test.description+":default_sync") 124 125 if test.expectedIFrame == "" { 126 assert.Nil(t, result.iframe, test.description+":iframe") 127 } else { 128 iframeRendered, err := macros.ResolveMacros(result.iframe, macroValues) 129 if assert.NoError(t, err, test.description+":iframe_render") { 130 assert.Equal(t, test.expectedIFrame, iframeRendered, test.description+":iframe") 131 } 132 } 133 134 if test.expectedRedirect == "" { 135 assert.Nil(t, result.redirect, test.description+":redirect") 136 } else { 137 redirectRendered, err := macros.ResolveMacros(result.redirect, macroValues) 138 if assert.NoError(t, err, test.description+":redirect_render") { 139 assert.Equal(t, test.expectedRedirect, redirectRendered, test.description+":redirect") 140 } 141 } 142 } 143 } else { 144 assert.EqualError(t, err, test.expectedError, test.description+":err") 145 assert.Empty(t, result) 146 } 147 } 148 } 149 150 func TestResolveDefaultSyncType(t *testing.T) { 151 anyEndpoint := &config.SyncerEndpoint{} 152 153 testCases := []struct { 154 description string 155 givenConfig config.Syncer 156 expectedSyncType SyncType 157 }{ 158 { 159 description: "IFrame & Redirect", 160 givenConfig: config.Syncer{IFrame: anyEndpoint, Redirect: anyEndpoint}, 161 expectedSyncType: SyncTypeIFrame, 162 }, 163 { 164 description: "IFrame Only", 165 givenConfig: config.Syncer{IFrame: anyEndpoint, Redirect: nil}, 166 expectedSyncType: SyncTypeIFrame, 167 }, 168 { 169 description: "Redirect Only - Redirect Default", 170 givenConfig: config.Syncer{IFrame: nil, Redirect: anyEndpoint}, 171 expectedSyncType: SyncTypeRedirect, 172 }, 173 } 174 175 for _, test := range testCases { 176 result := resolveDefaultSyncType(test.givenConfig) 177 assert.Equal(t, test.expectedSyncType, result, test.description+":result") 178 } 179 } 180 181 func TestBuildTemplate(t *testing.T) { 182 var ( 183 key = "anyKey" 184 syncTypeValue = "x" 185 hostConfig = config.UserSync{ExternalURL: "http://host.com", RedirectURL: "{{.ExternalURL}}/host"} 186 macroValues = macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C", GPP: "D", GPPSID: "1"} 187 ) 188 189 testCases := []struct { 190 description string 191 givenHostConfig config.UserSync 192 givenSyncerExternalURL string 193 givenSyncerEndpoint config.SyncerEndpoint 194 expectedError string 195 expectedRendered string 196 }{ 197 { 198 description: "No Composed Macros", 199 givenSyncerEndpoint: config.SyncerEndpoint{ 200 URL: "hasNoComposedMacros,gdpr={{.GDPR}}", 201 }, 202 expectedRendered: "hasNoComposedMacros,gdpr=A", 203 }, 204 { 205 description: "All Composed Macros - SyncerKey", 206 givenSyncerEndpoint: config.SyncerEndpoint{ 207 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 208 RedirectURL: "{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&f={{.SyncType}}&gdpr={{.GDPR}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&uid={{.UserMacro}}", 209 ExternalURL: "http://syncer.com", 210 UserMacro: "$UID$", 211 }, 212 expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fsetuid%3Fbidder%3DanyKey%26f%3Dx%26gdpr%3DA%26gpp%3DD%26gpp_sid%3D1%26uid%3D%24UID%24", 213 }, 214 { 215 description: "All Composed Macros - BidderName", 216 givenSyncerEndpoint: config.SyncerEndpoint{ 217 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 218 RedirectURL: "{{.ExternalURL}}/setuid?bidder={{.BidderName}}&f={{.SyncType}}&gdpr={{.GDPR}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&uid={{.UserMacro}}", 219 ExternalURL: "http://syncer.com", 220 UserMacro: "$UID$", 221 }, 222 expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fsetuid%3Fbidder%3DanyKey%26f%3Dx%26gdpr%3DA%26gpp%3DD%26gpp_sid%3D1%26uid%3D%24UID%24", 223 }, 224 { 225 description: "Redirect URL + External URL From Host", 226 givenSyncerEndpoint: config.SyncerEndpoint{ 227 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 228 }, 229 expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fhost.com%2Fhost", 230 }, 231 { 232 description: "Redirect URL From Syncer", 233 givenSyncerEndpoint: config.SyncerEndpoint{ 234 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 235 RedirectURL: "{{.ExternalURL}}/syncer", 236 }, 237 expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fhost.com%2Fsyncer", 238 }, 239 { 240 description: "External URL From Host", 241 givenSyncerEndpoint: config.SyncerEndpoint{ 242 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 243 ExternalURL: "http://syncer.com", 244 }, 245 expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fhost", 246 }, 247 { 248 description: "External URL From Syncer Config", 249 givenSyncerExternalURL: "http://syncershared.com", 250 givenSyncerEndpoint: config.SyncerEndpoint{ 251 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 252 }, 253 expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncershared.com%2Fhost", 254 }, 255 { 256 description: "External URL From Syncer Config (Most Specific Wins)", 257 givenSyncerExternalURL: "http://syncershared.com", 258 givenSyncerEndpoint: config.SyncerEndpoint{ 259 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 260 ExternalURL: "http://syncer.com", 261 }, 262 expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fsyncer.com%2Fhost", 263 }, 264 { 265 description: "Template Parse Error", 266 givenSyncerEndpoint: config.SyncerEndpoint{ 267 URL: "{{malformed}}", 268 }, 269 expectedError: "template: anykey_usersync_url:1: function \"malformed\" not defined", 270 }, 271 { 272 description: "User Macro Is Go Template Macro-Like", 273 givenSyncerEndpoint: config.SyncerEndpoint{ 274 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 275 RedirectURL: "{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&f={{.SyncType}}&gdpr={{.GDPR}}&uid={{.UserMacro}}", 276 UserMacro: "{{UID}}", 277 }, 278 expectedRendered: "https://bidder.com/sync?redirect=http%3A%2F%2Fhost.com%2Fsetuid%3Fbidder%3DanyKey%26f%3Dx%26gdpr%3DA%26uid%3D%7B%7BUID%7D%7D", 279 }, 280 281 // The following tests protect against the "\"." literal character vs the "." character class in regex. Literal 282 // value which use {{ }} but do not match Go's naming pattern of {{ .Name }} are escaped. 283 { 284 description: "Invalid Macro - Redirect URL", 285 givenSyncerEndpoint: config.SyncerEndpoint{ 286 URL: "https://bidder.com/sync?redirect={{xRedirectURL}}", 287 }, 288 expectedError: "template: anykey_usersync_url:1: function \"xRedirectURL\" not defined", 289 }, 290 { 291 description: "Macro-Like Literal Value - External URL", 292 givenSyncerEndpoint: config.SyncerEndpoint{ 293 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 294 RedirectURL: "{{xExternalURL}}", 295 }, 296 expectedRendered: "https://bidder.com/sync?redirect=%7B%7BxExternalURL%7D%7D", 297 }, 298 { 299 description: "Macro-Like Literal Value - Syncer Key", 300 givenSyncerEndpoint: config.SyncerEndpoint{ 301 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 302 RedirectURL: "{{xSyncerKey}}", 303 }, 304 expectedRendered: "https://bidder.com/sync?redirect=%7B%7BxSyncerKey%7D%7D", 305 }, 306 { 307 description: "Macro-Like Literal Value - Sync Type", 308 givenSyncerEndpoint: config.SyncerEndpoint{ 309 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 310 RedirectURL: "{{xSyncType}}", 311 }, 312 expectedRendered: "https://bidder.com/sync?redirect=%7B%7BxSyncType%7D%7D", 313 }, 314 { 315 description: "Macro-Like Literal Value - User Macro", 316 givenSyncerEndpoint: config.SyncerEndpoint{ 317 URL: "https://bidder.com/sync?redirect={{.RedirectURL}}", 318 RedirectURL: "{{xUserMacro}}", 319 }, 320 expectedRendered: "https://bidder.com/sync?redirect=%7B%7BxUserMacro%7D%7D", 321 }, 322 } 323 324 for _, test := range testCases { 325 result, err := buildTemplate(key, syncTypeValue, hostConfig, test.givenSyncerExternalURL, test.givenSyncerEndpoint) 326 327 if test.expectedError == "" { 328 assert.NoError(t, err, test.description+":err") 329 resultRendered, err := macros.ResolveMacros(result, macroValues) 330 if assert.NoError(t, err, test.description+":template_render") { 331 assert.Equal(t, test.expectedRendered, resultRendered, test.description+":template") 332 } 333 } else { 334 assert.EqualError(t, err, test.expectedError, test.description+":err") 335 } 336 } 337 } 338 339 func TestChooseExternalURL(t *testing.T) { 340 testCases := []struct { 341 description string 342 givenSyncerEndpointURL string 343 givenSyncerURL string 344 givenHostConfigURL string 345 expected string 346 }{ 347 { 348 description: "Syncer Endpoint Chosen", 349 givenSyncerEndpointURL: "a", 350 givenSyncerURL: "b", 351 givenHostConfigURL: "c", 352 expected: "a", 353 }, 354 { 355 description: "Syncer Chosen", 356 givenSyncerEndpointURL: "", 357 givenSyncerURL: "b", 358 givenHostConfigURL: "c", 359 expected: "b", 360 }, 361 { 362 description: "Host Config Chosen", 363 givenSyncerEndpointURL: "", 364 givenSyncerURL: "", 365 givenHostConfigURL: "c", 366 expected: "c", 367 }, 368 { 369 description: "All Empty", 370 givenSyncerEndpointURL: "", 371 givenSyncerURL: "", 372 givenHostConfigURL: "", 373 expected: "", 374 }, 375 } 376 377 for _, test := range testCases { 378 result := chooseExternalURL(test.givenSyncerEndpointURL, test.givenSyncerURL, test.givenHostConfigURL) 379 assert.Equal(t, test.expected, result, test.description) 380 } 381 } 382 383 func TestEscapeTemplate(t *testing.T) { 384 testCases := []struct { 385 description string 386 given string 387 expected string 388 }{ 389 { 390 description: "Macro Only", 391 given: "{{.Macro}}", 392 expected: "{{.Macro}}", 393 }, 394 { 395 description: "Text Only", 396 given: "/a", 397 expected: "%2Fa", 398 }, 399 { 400 description: "Start Only", 401 given: "&a{{.Macro1}}", 402 expected: "%26a{{.Macro1}}", 403 }, 404 { 405 description: "Middle Only", 406 given: "{{.Macro1}}&a{{.Macro2}}", 407 expected: "{{.Macro1}}%26a{{.Macro2}}", 408 }, 409 { 410 description: "End Only", 411 given: "{{.Macro1}}&a", 412 expected: "{{.Macro1}}%26a", 413 }, 414 { 415 description: "Start / Middle / End", 416 given: "&a{{.Macro1}}/b{{.Macro2}}&c", 417 expected: "%26a{{.Macro1}}%2Fb{{.Macro2}}%26c", 418 }, 419 { 420 description: "Characters In Macro Not Escaped", 421 given: "{{.Macro&}}", 422 expected: "{{.Macro&}}", 423 }, 424 { 425 description: "Macro Whitespace Insensitive", 426 given: " &a {{ .Macro1 }} /b ", 427 expected: "+%26a+{{ .Macro1 }}+%2Fb+", 428 }, 429 { 430 description: "Double Curly Braces, But Not Macro", 431 given: "{{Macro}}", 432 expected: "%7B%7BMacro%7D%7D", 433 }, 434 } 435 436 for _, test := range testCases { 437 result := escapeTemplate(test.given) 438 assert.Equal(t, test.expected, result, test.description) 439 } 440 } 441 442 func TestValidateTemplate(t *testing.T) { 443 testCases := []struct { 444 description string 445 given *template.Template 446 expectedError string 447 }{ 448 { 449 description: "Contains Unrecognized Macro", 450 given: template.Must(template.New("test").Parse("invalid:{{.DoesNotExist}}")), 451 expectedError: "template: test:1:10: executing \"test\" at <.DoesNotExist>: can't evaluate field DoesNotExist in type macros.UserSyncPrivacy", 452 }, 453 { 454 description: "Not A Url", 455 given: template.Must(template.New("test").Parse("not-a-url,gdpr:{{.GDPR}},gdprconsent:{{.GDPRConsent}},ccpa:{{.USPrivacy}}")), 456 expectedError: "composed url: \"not-a-url,gdpr:anyGDPR,gdprconsent:anyGDPRConsent,ccpa:anyCCPAConsent\" is invalid", 457 }, 458 { 459 description: "Valid", 460 given: template.Must(template.New("test").Parse("http://server.com/sync?gdpr={{.GDPR}}&gdprconsent={{.GDPRConsent}}&ccpa={{.USPrivacy}}")), 461 expectedError: "", 462 }, 463 } 464 465 for _, test := range testCases { 466 err := validateTemplate(test.given) 467 468 if test.expectedError == "" { 469 assert.NoError(t, err, test.description) 470 } else { 471 assert.EqualError(t, err, test.expectedError, test.description) 472 } 473 } 474 } 475 476 func TestSyncerKey(t *testing.T) { 477 syncer := standardSyncer{key: "a"} 478 assert.Equal(t, "a", syncer.Key()) 479 } 480 481 func TestSyncerDefaultSyncType(t *testing.T) { 482 syncer := standardSyncer{defaultSyncType: SyncTypeRedirect} 483 assert.Equal(t, SyncTypeRedirect, syncer.DefaultSyncType()) 484 } 485 486 func TestSyncerSupportsType(t *testing.T) { 487 endpointTemplate := template.Must(template.New("test").Parse("iframe")) 488 489 testCases := []struct { 490 description string 491 givenSyncTypes []SyncType 492 givenIFrameTemplate *template.Template 493 givenRedirectTemplate *template.Template 494 expectedResult bool 495 }{ 496 { 497 description: "All Available - None", 498 givenSyncTypes: []SyncType{}, 499 givenIFrameTemplate: endpointTemplate, 500 givenRedirectTemplate: endpointTemplate, 501 expectedResult: false, 502 }, 503 { 504 description: "All Available - One", 505 givenSyncTypes: []SyncType{SyncTypeIFrame}, 506 givenIFrameTemplate: endpointTemplate, 507 givenRedirectTemplate: endpointTemplate, 508 expectedResult: true, 509 }, 510 { 511 description: "All Available - Many", 512 givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, 513 givenIFrameTemplate: endpointTemplate, 514 givenRedirectTemplate: endpointTemplate, 515 expectedResult: true, 516 }, 517 { 518 description: "One Available - None", 519 givenSyncTypes: []SyncType{}, 520 givenIFrameTemplate: endpointTemplate, 521 givenRedirectTemplate: nil, 522 expectedResult: false, 523 }, 524 { 525 description: "One Available - One - Supported", 526 givenSyncTypes: []SyncType{SyncTypeIFrame}, 527 givenIFrameTemplate: endpointTemplate, 528 givenRedirectTemplate: nil, 529 expectedResult: true, 530 }, 531 { 532 description: "One Available - One - Not Supported", 533 givenSyncTypes: []SyncType{SyncTypeRedirect}, 534 givenIFrameTemplate: endpointTemplate, 535 givenRedirectTemplate: nil, 536 expectedResult: false, 537 }, 538 { 539 description: "One Available - Many", 540 givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, 541 givenIFrameTemplate: endpointTemplate, 542 givenRedirectTemplate: nil, 543 expectedResult: true, 544 }, 545 } 546 547 for _, test := range testCases { 548 syncer := standardSyncer{ 549 iframe: test.givenIFrameTemplate, 550 redirect: test.givenRedirectTemplate, 551 } 552 result := syncer.SupportsType(test.givenSyncTypes) 553 assert.Equal(t, test.expectedResult, result, test.description) 554 } 555 } 556 557 func TestSyncerFilterSupportedSyncTypes(t *testing.T) { 558 endpointTemplate := template.Must(template.New("test").Parse("iframe")) 559 560 testCases := []struct { 561 description string 562 givenSyncTypes []SyncType 563 givenIFrameTemplate *template.Template 564 givenRedirectTemplate *template.Template 565 expectedSyncTypes []SyncType 566 }{ 567 { 568 description: "All Available - None", 569 givenSyncTypes: []SyncType{}, 570 givenIFrameTemplate: endpointTemplate, 571 givenRedirectTemplate: endpointTemplate, 572 expectedSyncTypes: []SyncType{}, 573 }, 574 { 575 description: "All Available - One", 576 givenSyncTypes: []SyncType{SyncTypeIFrame}, 577 givenIFrameTemplate: endpointTemplate, 578 givenRedirectTemplate: endpointTemplate, 579 expectedSyncTypes: []SyncType{SyncTypeIFrame}, 580 }, 581 { 582 description: "All Available - Many", 583 givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, 584 givenIFrameTemplate: endpointTemplate, 585 givenRedirectTemplate: endpointTemplate, 586 expectedSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, 587 }, 588 { 589 description: "One Available - None", 590 givenSyncTypes: []SyncType{}, 591 givenIFrameTemplate: nil, 592 givenRedirectTemplate: endpointTemplate, 593 expectedSyncTypes: []SyncType{}, 594 }, 595 { 596 description: "One Available - One - Not Supported", 597 givenSyncTypes: []SyncType{SyncTypeIFrame}, 598 givenIFrameTemplate: nil, 599 givenRedirectTemplate: endpointTemplate, 600 expectedSyncTypes: []SyncType{}, 601 }, 602 { 603 description: "One Available - One - Supported", 604 givenSyncTypes: []SyncType{SyncTypeRedirect}, 605 givenIFrameTemplate: nil, 606 givenRedirectTemplate: endpointTemplate, 607 expectedSyncTypes: []SyncType{SyncTypeRedirect}, 608 }, 609 { 610 description: "One Available - Many", 611 givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, 612 givenIFrameTemplate: nil, 613 givenRedirectTemplate: endpointTemplate, 614 expectedSyncTypes: []SyncType{SyncTypeRedirect}, 615 }, 616 } 617 618 for _, test := range testCases { 619 syncer := standardSyncer{ 620 iframe: test.givenIFrameTemplate, 621 redirect: test.givenRedirectTemplate, 622 } 623 result := syncer.filterSupportedSyncTypes(test.givenSyncTypes) 624 assert.ElementsMatch(t, test.expectedSyncTypes, result, test.description) 625 } 626 } 627 628 func TestSyncerGetSync(t *testing.T) { 629 var ( 630 iframeTemplate = template.Must(template.New("test").Parse("iframe,gdpr:{{.GDPR}},gdprconsent:{{.GDPRConsent}},ccpa:{{.USPrivacy}}")) 631 redirectTemplate = template.Must(template.New("test").Parse("redirect,gdpr:{{.GDPR}},gdprconsent:{{.GDPRConsent}},ccpa:{{.USPrivacy}}")) 632 malformedTemplate = template.Must(template.New("test").Parse("malformed,invalid:{{.DoesNotExist}}")) 633 ) 634 635 testCases := []struct { 636 description string 637 givenSyncer standardSyncer 638 givenSyncTypes []SyncType 639 givenMacros macros.UserSyncPrivacy 640 expectedError string 641 expectedSync Sync 642 }{ 643 { 644 description: "No Sync Types", 645 givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, 646 givenSyncTypes: []SyncType{}, 647 givenMacros: macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"}, 648 expectedError: "no sync types provided", 649 }, 650 { 651 description: "IFrame", 652 givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, 653 givenSyncTypes: []SyncType{SyncTypeIFrame}, 654 givenMacros: macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"}, 655 expectedSync: Sync{URL: "iframe,gdpr:A,gdprconsent:B,ccpa:C", Type: SyncTypeIFrame, SupportCORS: false}, 656 }, 657 { 658 description: "Redirect", 659 givenSyncer: standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate}, 660 givenSyncTypes: []SyncType{SyncTypeRedirect}, 661 givenMacros: macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"}, 662 expectedSync: Sync{URL: "redirect,gdpr:A,gdprconsent:B,ccpa:C", Type: SyncTypeRedirect, SupportCORS: false}, 663 }, 664 { 665 description: "Macro Error", 666 givenSyncer: standardSyncer{iframe: malformedTemplate}, 667 givenSyncTypes: []SyncType{SyncTypeIFrame}, 668 givenMacros: macros.UserSyncPrivacy{GDPR: "A", GDPRConsent: "B", USPrivacy: "C"}, 669 expectedError: "template: test:1:20: executing \"test\" at <.DoesNotExist>: can't evaluate field DoesNotExist in type macros.UserSyncPrivacy", 670 }, 671 } 672 673 for _, test := range testCases { 674 result, err := test.givenSyncer.GetSync(test.givenSyncTypes, test.givenMacros) 675 676 if test.expectedError == "" { 677 assert.NoError(t, err, test.description+":err") 678 assert.Equal(t, test.expectedSync, result, test.description+":sync") 679 } else { 680 assert.EqualError(t, err, test.expectedError, test.description+":err") 681 } 682 } 683 } 684 685 func TestSyncerChooseSyncType(t *testing.T) { 686 endpointTemplate := template.Must(template.New("test").Parse("iframe")) 687 688 testCases := []struct { 689 description string 690 givenSyncTypes []SyncType 691 givenDefaultSyncType SyncType 692 givenIFrameTemplate *template.Template 693 givenRedirectTemplate *template.Template 694 expectedError string 695 expectedSyncType SyncType 696 }{ 697 { 698 description: "None Available - Error", 699 givenSyncTypes: []SyncType{}, 700 givenDefaultSyncType: SyncTypeRedirect, 701 givenIFrameTemplate: endpointTemplate, 702 givenRedirectTemplate: endpointTemplate, 703 expectedError: "no sync types provided", 704 }, 705 { 706 description: "All Available - Choose Default", 707 givenSyncTypes: []SyncType{SyncTypeIFrame, SyncTypeRedirect}, 708 givenDefaultSyncType: SyncTypeRedirect, 709 givenIFrameTemplate: endpointTemplate, 710 givenRedirectTemplate: endpointTemplate, 711 expectedSyncType: SyncTypeRedirect, 712 }, 713 { 714 description: "Default Not Available - Choose Other One", 715 givenSyncTypes: []SyncType{SyncTypeIFrame}, 716 givenDefaultSyncType: SyncTypeRedirect, 717 givenIFrameTemplate: endpointTemplate, 718 givenRedirectTemplate: endpointTemplate, 719 expectedSyncType: SyncTypeIFrame, 720 }, 721 { 722 description: "None Supported - Error", 723 givenSyncTypes: []SyncType{SyncTypeIFrame}, 724 givenDefaultSyncType: SyncTypeRedirect, 725 givenIFrameTemplate: nil, 726 givenRedirectTemplate: endpointTemplate, 727 expectedError: "no sync types supported", 728 }, 729 } 730 731 for _, test := range testCases { 732 syncer := standardSyncer{ 733 defaultSyncType: test.givenDefaultSyncType, 734 iframe: test.givenIFrameTemplate, 735 redirect: test.givenRedirectTemplate, 736 } 737 result, err := syncer.chooseSyncType(test.givenSyncTypes) 738 739 if test.expectedError == "" { 740 assert.NoError(t, err, test.description+":err") 741 assert.Equal(t, test.expectedSyncType, result, test.description+":sync_type") 742 } else { 743 assert.EqualError(t, err, test.expectedError, test.description+":err") 744 } 745 } 746 } 747 748 func TestSyncerChooseTemplate(t *testing.T) { 749 var ( 750 iframeTemplate = template.Must(template.New("test").Parse("iframe")) 751 redirectTemplate = template.Must(template.New("test").Parse("redirect")) 752 ) 753 754 testCases := []struct { 755 description string 756 givenSyncType SyncType 757 expectedTemplate *template.Template 758 }{ 759 { 760 description: "IFrame", 761 givenSyncType: SyncTypeIFrame, 762 expectedTemplate: iframeTemplate, 763 }, 764 { 765 description: "Redirect", 766 givenSyncType: SyncTypeRedirect, 767 expectedTemplate: redirectTemplate, 768 }, 769 { 770 description: "Invalid", 771 givenSyncType: SyncType("invalid"), 772 expectedTemplate: nil, 773 }, 774 } 775 776 for _, test := range testCases { 777 syncer := standardSyncer{iframe: iframeTemplate, redirect: redirectTemplate} 778 result := syncer.chooseTemplate(test.givenSyncType) 779 assert.Equal(t, test.expectedTemplate, result, test.description) 780 } 781 }