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