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  }