github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/utils/utils_test.go (about)

     1  package utils
     2  
     3  import (
     4  	"crypto/x509"
     5  	"encoding/json"
     6  	"os"
     7  	"path"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/sirupsen/logrus"
    12  	logtest "github.com/sirupsen/logrus/hooks/test"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/apimachinery/pkg/types"
    19  
    20  	argoappsv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    21  )
    22  
    23  func TestRenderTemplateParams(t *testing.T) {
    24  	// Believe it or not, this is actually less complex than the equivalent solution using reflection
    25  	fieldMap := map[string]func(app *argoappsv1.Application) *string{}
    26  	fieldMap["Path"] = func(app *argoappsv1.Application) *string { return &app.Spec.Source.Path }
    27  	fieldMap["RepoURL"] = func(app *argoappsv1.Application) *string { return &app.Spec.Source.RepoURL }
    28  	fieldMap["TargetRevision"] = func(app *argoappsv1.Application) *string { return &app.Spec.Source.TargetRevision }
    29  	fieldMap["Chart"] = func(app *argoappsv1.Application) *string { return &app.Spec.Source.Chart }
    30  
    31  	fieldMap["Server"] = func(app *argoappsv1.Application) *string { return &app.Spec.Destination.Server }
    32  	fieldMap["Namespace"] = func(app *argoappsv1.Application) *string { return &app.Spec.Destination.Namespace }
    33  	fieldMap["Name"] = func(app *argoappsv1.Application) *string { return &app.Spec.Destination.Name }
    34  
    35  	fieldMap["Project"] = func(app *argoappsv1.Application) *string { return &app.Spec.Project }
    36  
    37  	emptyApplication := &argoappsv1.Application{
    38  		ObjectMeta: metav1.ObjectMeta{
    39  			Annotations:       map[string]string{"annotation-key": "annotation-value", "annotation-key2": "annotation-value2"},
    40  			Labels:            map[string]string{"label-key": "label-value", "label-key2": "label-value2"},
    41  			CreationTimestamp: metav1.NewTime(time.Now()),
    42  			UID:               types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"),
    43  			Name:              "application-one",
    44  			Namespace:         "default",
    45  		},
    46  		Spec: argoappsv1.ApplicationSpec{
    47  			Source: &argoappsv1.ApplicationSource{
    48  				Path:           "",
    49  				RepoURL:        "",
    50  				TargetRevision: "",
    51  				Chart:          "",
    52  			},
    53  			Destination: argoappsv1.ApplicationDestination{
    54  				Server:    "",
    55  				Namespace: "",
    56  				Name:      "",
    57  			},
    58  			Project: "",
    59  		},
    60  	}
    61  
    62  	tests := []struct {
    63  		name        string
    64  		fieldVal    string
    65  		params      map[string]any
    66  		expectedVal string
    67  	}{
    68  		{
    69  			name:        "simple substitution",
    70  			fieldVal:    "{{one}}",
    71  			expectedVal: "two",
    72  			params: map[string]any{
    73  				"one": "two",
    74  			},
    75  		},
    76  		{
    77  			name:        "simple substitution with whitespace",
    78  			fieldVal:    "{{ one }}",
    79  			expectedVal: "two",
    80  			params: map[string]any{
    81  				"one": "two",
    82  			},
    83  		},
    84  
    85  		{
    86  			name:        "template characters but not in a template",
    87  			fieldVal:    "}} {{",
    88  			expectedVal: "}} {{",
    89  			params: map[string]any{
    90  				"one": "two",
    91  			},
    92  		},
    93  
    94  		{
    95  			name:        "nested template",
    96  			fieldVal:    "{{ }}",
    97  			expectedVal: "{{ }}",
    98  			params: map[string]any{
    99  				"one": "{{ }}",
   100  			},
   101  		},
   102  		{
   103  			name:        "field with whitespace",
   104  			fieldVal:    "{{ }}",
   105  			expectedVal: "{{ }}",
   106  			params: map[string]any{
   107  				" ": "two",
   108  				"":  "three",
   109  			},
   110  		},
   111  
   112  		{
   113  			name:        "template contains itself, containing itself",
   114  			fieldVal:    "{{one}}",
   115  			expectedVal: "{{one}}",
   116  			params: map[string]any{
   117  				"{{one}}": "{{one}}",
   118  			},
   119  		},
   120  
   121  		{
   122  			name:        "template contains itself, containing something else",
   123  			fieldVal:    "{{one}}",
   124  			expectedVal: "{{one}}",
   125  			params: map[string]any{
   126  				"{{one}}": "{{two}}",
   127  			},
   128  		},
   129  
   130  		{
   131  			name:        "templates are case sensitive",
   132  			fieldVal:    "{{ONE}}",
   133  			expectedVal: "{{ONE}}",
   134  			params: map[string]any{
   135  				"{{one}}": "two",
   136  			},
   137  		},
   138  		{
   139  			name:        "multiple on a line",
   140  			fieldVal:    "{{one}}{{one}}",
   141  			expectedVal: "twotwo",
   142  			params: map[string]any{
   143  				"one": "two",
   144  			},
   145  		},
   146  		{
   147  			name:        "multiple different on a line",
   148  			fieldVal:    "{{one}}{{three}}",
   149  			expectedVal: "twofour",
   150  			params: map[string]any{
   151  				"one":   "two",
   152  				"three": "four",
   153  			},
   154  		},
   155  		{
   156  			name:        "multiple different on a line with quote",
   157  			fieldVal:    "{{one}} {{three}}",
   158  			expectedVal: "\"hello\" world four",
   159  			params: map[string]any{
   160  				"one":   "\"hello\" world",
   161  				"three": "four",
   162  			},
   163  		},
   164  	}
   165  
   166  	for _, test := range tests {
   167  		t.Run(test.name, func(t *testing.T) {
   168  			for fieldName, getPtrFunc := range fieldMap {
   169  				// Clone the template application
   170  				application := emptyApplication.DeepCopy()
   171  
   172  				// Set the value of the target field, to the test value
   173  				*getPtrFunc(application) = test.fieldVal
   174  
   175  				// Render the cloned application, into a new application
   176  				render := Render{}
   177  				newApplication, err := render.RenderTemplateParams(application, nil, test.params, false, nil)
   178  
   179  				// Retrieve the value of the target field from the newApplication, then verify that
   180  				// the target field has been templated into the expected value
   181  				actualValue := *getPtrFunc(newApplication)
   182  				assert.Equal(t, test.expectedVal, actualValue, "Field '%s' had an unexpected value. expected: '%s' value: '%s'", fieldName, test.expectedVal, actualValue)
   183  				assert.Equal(t, "annotation-value", newApplication.Annotations["annotation-key"])
   184  				assert.Equal(t, "annotation-value2", newApplication.Annotations["annotation-key2"])
   185  				assert.Equal(t, "label-value", newApplication.Labels["label-key"])
   186  				assert.Equal(t, "label-value2", newApplication.Labels["label-key2"])
   187  				assert.Equal(t, "application-one", newApplication.Name)
   188  				assert.Equal(t, "default", newApplication.Namespace)
   189  				assert.Equal(t, newApplication.UID, types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"))
   190  				assert.Equal(t, newApplication.CreationTimestamp, application.CreationTimestamp)
   191  				require.NoError(t, err)
   192  			}
   193  		})
   194  	}
   195  }
   196  
   197  func TestRenderHelmValuesObjectJson(t *testing.T) {
   198  	params := map[string]any{
   199  		"test": "Hello world",
   200  	}
   201  
   202  	application := &argoappsv1.Application{
   203  		ObjectMeta: metav1.ObjectMeta{
   204  			Annotations:       map[string]string{"annotation-key": "annotation-value", "annotation-key2": "annotation-value2"},
   205  			Labels:            map[string]string{"label-key": "label-value", "label-key2": "label-value2"},
   206  			CreationTimestamp: metav1.NewTime(time.Now()),
   207  			UID:               types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"),
   208  			Name:              "application-one",
   209  			Namespace:         "default",
   210  		},
   211  		Spec: argoappsv1.ApplicationSpec{
   212  			Source: &argoappsv1.ApplicationSource{
   213  				Path:           "",
   214  				RepoURL:        "",
   215  				TargetRevision: "",
   216  				Chart:          "",
   217  				Helm: &argoappsv1.ApplicationSourceHelm{
   218  					ValuesObject: &runtime.RawExtension{
   219  						Raw: []byte(`{
   220  								"some": {
   221  									"string": "{{.test}}"
   222  								}
   223  							  }`),
   224  					},
   225  				},
   226  			},
   227  			Destination: argoappsv1.ApplicationDestination{
   228  				Server:    "",
   229  				Namespace: "",
   230  				Name:      "",
   231  			},
   232  			Project: "",
   233  		},
   234  	}
   235  
   236  	// Render the cloned application, into a new application
   237  	render := Render{}
   238  	newApplication, err := render.RenderTemplateParams(application, nil, params, true, []string{})
   239  
   240  	require.NoError(t, err)
   241  	assert.NotNil(t, newApplication)
   242  
   243  	var unmarshaled any
   244  	err = json.Unmarshal(newApplication.Spec.Source.Helm.ValuesObject.Raw, &unmarshaled)
   245  
   246  	require.NoError(t, err)
   247  	assert.Equal(t, "Hello world", unmarshaled.(map[string]any)["some"].(map[string]any)["string"])
   248  }
   249  
   250  func TestRenderHelmValuesObjectYaml(t *testing.T) {
   251  	params := map[string]any{
   252  		"test": "Hello world",
   253  	}
   254  
   255  	application := &argoappsv1.Application{
   256  		ObjectMeta: metav1.ObjectMeta{
   257  			Annotations:       map[string]string{"annotation-key": "annotation-value", "annotation-key2": "annotation-value2"},
   258  			Labels:            map[string]string{"label-key": "label-value", "label-key2": "label-value2"},
   259  			CreationTimestamp: metav1.NewTime(time.Now()),
   260  			UID:               types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"),
   261  			Name:              "application-one",
   262  			Namespace:         "default",
   263  		},
   264  		Spec: argoappsv1.ApplicationSpec{
   265  			Source: &argoappsv1.ApplicationSource{
   266  				Path:           "",
   267  				RepoURL:        "",
   268  				TargetRevision: "",
   269  				Chart:          "",
   270  				Helm: &argoappsv1.ApplicationSourceHelm{
   271  					ValuesObject: &runtime.RawExtension{
   272  						Raw: []byte(`some:
   273    string: "{{.test}}"`),
   274  					},
   275  				},
   276  			},
   277  			Destination: argoappsv1.ApplicationDestination{
   278  				Server:    "",
   279  				Namespace: "",
   280  				Name:      "",
   281  			},
   282  			Project: "",
   283  		},
   284  	}
   285  
   286  	// Render the cloned application, into a new application
   287  	render := Render{}
   288  	newApplication, err := render.RenderTemplateParams(application, nil, params, true, []string{})
   289  
   290  	require.NoError(t, err)
   291  	assert.NotNil(t, newApplication)
   292  
   293  	var unmarshaled any
   294  	err = json.Unmarshal(newApplication.Spec.Source.Helm.ValuesObject.Raw, &unmarshaled)
   295  
   296  	require.NoError(t, err)
   297  	assert.Equal(t, "Hello world", unmarshaled.(map[string]any)["some"].(map[string]any)["string"])
   298  }
   299  
   300  func TestRenderTemplateParamsGoTemplate(t *testing.T) {
   301  	// Believe it or not, this is actually less complex than the equivalent solution using reflection
   302  	fieldMap := map[string]func(app *argoappsv1.Application) *string{}
   303  	fieldMap["Path"] = func(app *argoappsv1.Application) *string { return &app.Spec.Source.Path }
   304  	fieldMap["RepoURL"] = func(app *argoappsv1.Application) *string { return &app.Spec.Source.RepoURL }
   305  	fieldMap["TargetRevision"] = func(app *argoappsv1.Application) *string { return &app.Spec.Source.TargetRevision }
   306  	fieldMap["Chart"] = func(app *argoappsv1.Application) *string { return &app.Spec.Source.Chart }
   307  
   308  	fieldMap["Server"] = func(app *argoappsv1.Application) *string { return &app.Spec.Destination.Server }
   309  	fieldMap["Namespace"] = func(app *argoappsv1.Application) *string { return &app.Spec.Destination.Namespace }
   310  	fieldMap["Name"] = func(app *argoappsv1.Application) *string { return &app.Spec.Destination.Name }
   311  
   312  	fieldMap["Project"] = func(app *argoappsv1.Application) *string { return &app.Spec.Project }
   313  
   314  	emptyApplication := &argoappsv1.Application{
   315  		ObjectMeta: metav1.ObjectMeta{
   316  			Annotations:       map[string]string{"annotation-key": "annotation-value", "annotation-key2": "annotation-value2"},
   317  			Labels:            map[string]string{"label-key": "label-value", "label-key2": "label-value2"},
   318  			CreationTimestamp: metav1.NewTime(time.Now()),
   319  			UID:               types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"),
   320  			Name:              "application-one",
   321  			Namespace:         "default",
   322  		},
   323  		Spec: argoappsv1.ApplicationSpec{
   324  			Source: &argoappsv1.ApplicationSource{
   325  				Path:           "",
   326  				RepoURL:        "",
   327  				TargetRevision: "",
   328  				Chart:          "",
   329  			},
   330  			Destination: argoappsv1.ApplicationDestination{
   331  				Server:    "",
   332  				Namespace: "",
   333  				Name:      "",
   334  			},
   335  			Project: "",
   336  		},
   337  	}
   338  
   339  	tests := []struct {
   340  		name            string
   341  		fieldVal        string
   342  		params          map[string]any
   343  		expectedVal     string
   344  		errorMessage    string
   345  		templateOptions []string
   346  	}{
   347  		{
   348  			name:        "simple substitution",
   349  			fieldVal:    "{{ .one }}",
   350  			expectedVal: "two",
   351  			params: map[string]any{
   352  				"one": "two",
   353  			},
   354  		},
   355  		{
   356  			name:        "simple substitution with whitespace",
   357  			fieldVal:    "{{ .one }}",
   358  			expectedVal: "two",
   359  			params: map[string]any{
   360  				"one": "two",
   361  			},
   362  		},
   363  		{
   364  			name:        "template contains itself, containing itself",
   365  			fieldVal:    "{{ .one }}",
   366  			expectedVal: "{{one}}",
   367  			params: map[string]any{
   368  				"one": "{{one}}",
   369  			},
   370  		},
   371  
   372  		{
   373  			name:        "template contains itself, containing something else",
   374  			fieldVal:    "{{ .one }}",
   375  			expectedVal: "{{two}}",
   376  			params: map[string]any{
   377  				"one": "{{two}}",
   378  			},
   379  		},
   380  		{
   381  			name:        "multiple on a line",
   382  			fieldVal:    "{{.one}}{{.one}}",
   383  			expectedVal: "twotwo",
   384  			params: map[string]any{
   385  				"one": "two",
   386  			},
   387  		},
   388  		{
   389  			name:        "multiple different on a line",
   390  			fieldVal:    "{{.one}}{{.three}}",
   391  			expectedVal: "twofour",
   392  			params: map[string]any{
   393  				"one":   "two",
   394  				"three": "four",
   395  			},
   396  		},
   397  		{
   398  			name:        "multiple different on a line with quote",
   399  			fieldVal:    "{{.one}} {{.three}}",
   400  			expectedVal: "\"hello\" world four",
   401  			params: map[string]any{
   402  				"one":   "\"hello\" world",
   403  				"three": "four",
   404  			},
   405  		},
   406  		{
   407  			name:        "depth",
   408  			fieldVal:    "{{ .image.version }}",
   409  			expectedVal: "latest",
   410  			params: map[string]any{
   411  				"replicas": 3,
   412  				"image": map[string]any{
   413  					"name":    "busybox",
   414  					"version": "latest",
   415  				},
   416  			},
   417  		},
   418  		{
   419  			name:        "multiple depth",
   420  			fieldVal:    "{{ .image.name }}:{{ .image.version }}",
   421  			expectedVal: "busybox:latest",
   422  			params: map[string]any{
   423  				"replicas": 3,
   424  				"image": map[string]any{
   425  					"name":    "busybox",
   426  					"version": "latest",
   427  				},
   428  			},
   429  		},
   430  		{
   431  			name:        "if ok",
   432  			fieldVal:    "{{ if .hpa.enabled }}{{ .hpa.maxReplicas }}{{ else }}{{ .replicas }}{{ end }}",
   433  			expectedVal: "5",
   434  			params: map[string]any{
   435  				"replicas": 3,
   436  				"hpa": map[string]any{
   437  					"enabled":     true,
   438  					"minReplicas": 1,
   439  					"maxReplicas": 5,
   440  				},
   441  			},
   442  		},
   443  		{
   444  			name:        "if not ok",
   445  			fieldVal:    "{{ if .hpa.enabled }}{{ .hpa.maxReplicas }}{{ else }}{{ .replicas }}{{ end }}",
   446  			expectedVal: "3",
   447  			params: map[string]any{
   448  				"replicas": 3,
   449  				"hpa": map[string]any{
   450  					"enabled":     false,
   451  					"minReplicas": 1,
   452  					"maxReplicas": 5,
   453  				},
   454  			},
   455  		},
   456  		{
   457  			name:        "loop",
   458  			fieldVal:    "{{ range .volumes }}[{{ .name }}]{{ end }}",
   459  			expectedVal: "[volume-one][volume-two]",
   460  			params: map[string]any{
   461  				"replicas": 3,
   462  				"volumes": []map[string]any{
   463  					{
   464  						"name":     "volume-one",
   465  						"emptyDir": map[string]any{},
   466  					},
   467  					{
   468  						"name":     "volume-two",
   469  						"emptyDir": map[string]any{},
   470  					},
   471  				},
   472  			},
   473  		},
   474  		{
   475  			name:        "Index",
   476  			fieldVal:    `{{ index .admin "admin-ca" }}, {{ index .admin "admin-jks" }}`,
   477  			expectedVal: "value admin ca, value admin jks",
   478  			params: map[string]any{
   479  				"admin": map[string]any{
   480  					"admin-ca":  "value admin ca",
   481  					"admin-jks": "value admin jks",
   482  				},
   483  			},
   484  		},
   485  		{
   486  			name:        "Index",
   487  			fieldVal:    `{{ index .admin "admin-ca" }}, \\ "Hello world", {{ index .admin "admin-jks" }}`,
   488  			expectedVal: `value "admin" ca with \, \\ "Hello world", value admin jks`,
   489  			params: map[string]any{
   490  				"admin": map[string]any{
   491  					"admin-ca":  `value "admin" ca with \`,
   492  					"admin-jks": "value admin jks",
   493  				},
   494  			},
   495  		},
   496  		{
   497  			name:        "quote",
   498  			fieldVal:    `{{.quote}}`,
   499  			expectedVal: `"`,
   500  			params: map[string]any{
   501  				"quote": `"`,
   502  			},
   503  		},
   504  		{
   505  			name:        "Test No Data",
   506  			fieldVal:    `{{.data}}`,
   507  			expectedVal: "{{.data}}",
   508  			params:      map[string]any{},
   509  		},
   510  		{
   511  			name:        "Test Parse Error",
   512  			fieldVal:    `{{functiondoesnotexist}}`,
   513  			expectedVal: "",
   514  			params: map[string]any{
   515  				"data": `a data string`,
   516  			},
   517  			errorMessage: `failed to parse template {{functiondoesnotexist}}: template: :1: function "functiondoesnotexist" not defined`,
   518  		},
   519  		{
   520  			name:        "Test template error",
   521  			fieldVal:    `{{.data.test}}`,
   522  			expectedVal: "",
   523  			params: map[string]any{
   524  				"data": `a data string`,
   525  			},
   526  			errorMessage: `failed to execute go template {{.data.test}}: template: :1:7: executing "" at <.data.test>: can't evaluate field test in type interface {}`,
   527  		},
   528  		{
   529  			name:        "lookup missing value with missingkey=default",
   530  			fieldVal:    `--> {{.doesnotexist}} <--`,
   531  			expectedVal: `--> <no value> <--`,
   532  			params: map[string]any{
   533  				// if no params are passed then for some reason templating is skipped
   534  				"unused": "this is not used",
   535  			},
   536  		},
   537  		{
   538  			name:        "lookup missing value with missingkey=error",
   539  			fieldVal:    `--> {{.doesnotexist}} <--`,
   540  			expectedVal: "",
   541  			params: map[string]any{
   542  				// if no params are passed then for some reason templating is skipped
   543  				"unused": "this is not used",
   544  			},
   545  			templateOptions: []string{"missingkey=error"},
   546  			errorMessage:    `failed to execute go template --> {{.doesnotexist}} <--: template: :1:6: executing "" at <.doesnotexist>: map has no entry for key "doesnotexist"`,
   547  		},
   548  		{
   549  			name:        "toYaml",
   550  			fieldVal:    `{{ toYaml . | indent 2 }}`,
   551  			expectedVal: "  foo:\n    bar:\n      bool: true\n      number: 2\n      str: Hello world",
   552  			params: map[string]any{
   553  				"foo": map[string]any{
   554  					"bar": map[string]any{
   555  						"bool":   true,
   556  						"number": 2,
   557  						"str":    "Hello world",
   558  					},
   559  				},
   560  			},
   561  		},
   562  		{
   563  			name:         "toYaml Error",
   564  			fieldVal:     `{{ toYaml . | indent 2 }}`,
   565  			expectedVal:  "  foo:\n    bar:\n      bool: true\n      number: 2\n      str: Hello world",
   566  			errorMessage: "failed to execute go template {{ toYaml . | indent 2 }}: template: :1:3: executing \"\" at <toYaml .>: error calling toYaml: error marshaling into JSON: json: unsupported type: func(*string)",
   567  			params: map[string]any{
   568  				"foo": func(_ *string) {
   569  				},
   570  			},
   571  		},
   572  		{
   573  			name:        "fromYaml",
   574  			fieldVal:    `{{ get (fromYaml .value) "hello" }}`,
   575  			expectedVal: "world",
   576  			params: map[string]any{
   577  				"value": "hello: world",
   578  			},
   579  		},
   580  		{
   581  			name:         "fromYaml error",
   582  			fieldVal:     `{{ get (fromYaml .value) "hello" }}`,
   583  			expectedVal:  "world",
   584  			errorMessage: "failed to execute go template {{ get (fromYaml .value) \"hello\" }}: template: :1:8: executing \"\" at <fromYaml .value>: error calling fromYaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map[string]interface {}",
   585  			params: map[string]any{
   586  				"value": "non\n compliant\n yaml",
   587  			},
   588  		},
   589  		{
   590  			name:        "fromYamlArray",
   591  			fieldVal:    `{{ fromYamlArray .value | last }}`,
   592  			expectedVal: "bonjour tout le monde",
   593  			params: map[string]any{
   594  				"value": "- hello world\n- bonjour tout le monde",
   595  			},
   596  		},
   597  		{
   598  			name:         "fromYamlArray error",
   599  			fieldVal:     `{{ fromYamlArray .value | last }}`,
   600  			expectedVal:  "bonjour tout le monde",
   601  			errorMessage: "failed to execute go template {{ fromYamlArray .value | last }}: template: :1:3: executing \"\" at <fromYamlArray .value>: error calling fromYamlArray: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type []interface {}",
   602  			params: map[string]any{
   603  				"value": "non\n compliant\n yaml",
   604  			},
   605  		},
   606  	}
   607  
   608  	for _, test := range tests {
   609  		t.Run(test.name, func(t *testing.T) {
   610  			for fieldName, getPtrFunc := range fieldMap {
   611  				// Clone the template application
   612  				application := emptyApplication.DeepCopy()
   613  
   614  				// Set the value of the target field, to the test value
   615  				*getPtrFunc(application) = test.fieldVal
   616  
   617  				// Render the cloned application, into a new application
   618  				render := Render{}
   619  				newApplication, err := render.RenderTemplateParams(application, nil, test.params, true, test.templateOptions)
   620  
   621  				// Retrieve the value of the target field from the newApplication, then verify that
   622  				// the target field has been templated into the expected value
   623  				if test.errorMessage != "" {
   624  					require.Error(t, err)
   625  					assert.Equal(t, test.errorMessage, err.Error())
   626  				} else {
   627  					require.NoError(t, err)
   628  					actualValue := *getPtrFunc(newApplication)
   629  					assert.Equal(t, test.expectedVal, actualValue, "Field '%s' had an unexpected value. expected: '%s' value: '%s'", fieldName, test.expectedVal, actualValue)
   630  					assert.Equal(t, "annotation-value", newApplication.Annotations["annotation-key"])
   631  					assert.Equal(t, "annotation-value2", newApplication.Annotations["annotation-key2"])
   632  					assert.Equal(t, "label-value", newApplication.Labels["label-key"])
   633  					assert.Equal(t, "label-value2", newApplication.Labels["label-key2"])
   634  					assert.Equal(t, "application-one", newApplication.Name)
   635  					assert.Equal(t, "default", newApplication.Namespace)
   636  					assert.Equal(t, newApplication.UID, types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"))
   637  					assert.Equal(t, newApplication.CreationTimestamp, application.CreationTimestamp)
   638  				}
   639  			}
   640  		})
   641  	}
   642  }
   643  
   644  func TestRenderGeneratorParams_does_not_panic(t *testing.T) {
   645  	// This test verifies that the RenderGeneratorParams function does not panic when the value in a map is a non-
   646  	// nillable type. This is a regression test.
   647  	render := Render{}
   648  	params := map[string]any{
   649  		"branch": "master",
   650  	}
   651  	generator := &argoappsv1.ApplicationSetGenerator{
   652  		Plugin: &argoappsv1.PluginGenerator{
   653  			ConfigMapRef: argoappsv1.PluginConfigMapRef{
   654  				Name: "cm-plugin",
   655  			},
   656  			Input: argoappsv1.PluginInput{
   657  				Parameters: map[string]apiextensionsv1.JSON{
   658  					"branch": {
   659  						Raw: []byte(`"{{.branch}}"`),
   660  					},
   661  					"repo": {
   662  						Raw: []byte(`"argo-test"`),
   663  					},
   664  				},
   665  			},
   666  		},
   667  	}
   668  	_, err := render.RenderGeneratorParams(generator, params, true, []string{})
   669  	require.NoError(t, err)
   670  }
   671  
   672  func TestRenderTemplateKeys(t *testing.T) {
   673  	t.Run("fasttemplate", func(t *testing.T) {
   674  		application := &argoappsv1.Application{
   675  			ObjectMeta: metav1.ObjectMeta{
   676  				Annotations: map[string]string{
   677  					"annotation-{{key}}": "annotation-{{value}}",
   678  				},
   679  			},
   680  		}
   681  
   682  		params := map[string]any{
   683  			"key":   "some-key",
   684  			"value": "some-value",
   685  		}
   686  
   687  		render := Render{}
   688  		newApplication, err := render.RenderTemplateParams(application, nil, params, false, nil)
   689  		require.NoError(t, err)
   690  		require.Contains(t, newApplication.Annotations, "annotation-some-key")
   691  		assert.Equal(t, "annotation-some-value", newApplication.Annotations["annotation-some-key"])
   692  	})
   693  	t.Run("gotemplate", func(t *testing.T) {
   694  		application := &argoappsv1.Application{
   695  			ObjectMeta: metav1.ObjectMeta{
   696  				Annotations: map[string]string{
   697  					"annotation-{{ .key }}": "annotation-{{ .value }}",
   698  				},
   699  			},
   700  		}
   701  
   702  		params := map[string]any{
   703  			"key":   "some-key",
   704  			"value": "some-value",
   705  		}
   706  
   707  		render := Render{}
   708  		newApplication, err := render.RenderTemplateParams(application, nil, params, true, nil)
   709  		require.NoError(t, err)
   710  		require.Contains(t, newApplication.Annotations, "annotation-some-key")
   711  		assert.Equal(t, "annotation-some-value", newApplication.Annotations["annotation-some-key"])
   712  	})
   713  }
   714  
   715  func Test_Render_Replace_no_panic_on_missing_closing_brace(t *testing.T) {
   716  	r := &Render{}
   717  	assert.NotPanics(t, func() {
   718  		_, err := r.Replace("{{properly.closed}} {{improperly.closed}", nil, false, []string{})
   719  		require.Error(t, err)
   720  	})
   721  }
   722  
   723  func TestRenderTemplateParamsFinalizers(t *testing.T) {
   724  	emptyApplication := &argoappsv1.Application{
   725  		Spec: argoappsv1.ApplicationSpec{
   726  			Source: &argoappsv1.ApplicationSource{
   727  				Path:           "",
   728  				RepoURL:        "",
   729  				TargetRevision: "",
   730  				Chart:          "",
   731  			},
   732  			Destination: argoappsv1.ApplicationDestination{
   733  				Server:    "",
   734  				Namespace: "",
   735  				Name:      "",
   736  			},
   737  			Project: "",
   738  		},
   739  	}
   740  
   741  	for _, c := range []struct {
   742  		testName           string
   743  		syncPolicy         *argoappsv1.ApplicationSetSyncPolicy
   744  		existingFinalizers []string
   745  		expectedFinalizers []string
   746  	}{
   747  		{
   748  			testName:           "existing finalizer should be preserved",
   749  			existingFinalizers: []string{"existing-finalizer"},
   750  			syncPolicy:         nil,
   751  			expectedFinalizers: []string{"existing-finalizer"},
   752  		},
   753  		{
   754  			testName:           "background finalizer should be preserved",
   755  			existingFinalizers: []string{argoappsv1.BackgroundPropagationPolicyFinalizer},
   756  			syncPolicy:         nil,
   757  			expectedFinalizers: []string{argoappsv1.BackgroundPropagationPolicyFinalizer},
   758  		},
   759  
   760  		{
   761  			testName:           "empty finalizer and empty sync should use standard finalizer",
   762  			existingFinalizers: nil,
   763  			syncPolicy:         nil,
   764  			expectedFinalizers: []string{argoappsv1.ResourcesFinalizerName},
   765  		},
   766  
   767  		{
   768  			testName:           "standard finalizer should be preserved",
   769  			existingFinalizers: []string{argoappsv1.ResourcesFinalizerName},
   770  			syncPolicy:         nil,
   771  			expectedFinalizers: []string{argoappsv1.ResourcesFinalizerName},
   772  		},
   773  		{
   774  			testName:           "empty array finalizers should use standard finalizer",
   775  			existingFinalizers: []string{},
   776  			syncPolicy:         nil,
   777  			expectedFinalizers: []string{argoappsv1.ResourcesFinalizerName},
   778  		},
   779  		{
   780  			testName:           "non-nil sync policy should use standard finalizer",
   781  			existingFinalizers: nil,
   782  			syncPolicy:         &argoappsv1.ApplicationSetSyncPolicy{},
   783  			expectedFinalizers: []string{argoappsv1.ResourcesFinalizerName},
   784  		},
   785  		{
   786  			testName:           "preserveResourcesOnDeletion should not have a finalizer",
   787  			existingFinalizers: nil,
   788  			syncPolicy: &argoappsv1.ApplicationSetSyncPolicy{
   789  				PreserveResourcesOnDeletion: true,
   790  			},
   791  			expectedFinalizers: nil,
   792  		},
   793  		{
   794  			testName:           "user-specified finalizer should overwrite preserveResourcesOnDeletion",
   795  			existingFinalizers: []string{argoappsv1.BackgroundPropagationPolicyFinalizer},
   796  			syncPolicy: &argoappsv1.ApplicationSetSyncPolicy{
   797  				PreserveResourcesOnDeletion: true,
   798  			},
   799  			expectedFinalizers: []string{argoappsv1.BackgroundPropagationPolicyFinalizer},
   800  		},
   801  	} {
   802  		t.Run(c.testName, func(t *testing.T) {
   803  			// Clone the template application
   804  			application := emptyApplication.DeepCopy()
   805  			application.Finalizers = c.existingFinalizers
   806  
   807  			params := map[string]any{
   808  				"one": "two",
   809  			}
   810  
   811  			// Render the cloned application, into a new application
   812  			render := Render{}
   813  
   814  			res, err := render.RenderTemplateParams(application, c.syncPolicy, params, true, nil)
   815  			require.NoError(t, err)
   816  
   817  			assert.ElementsMatch(t, res.Finalizers, c.expectedFinalizers)
   818  		})
   819  	}
   820  }
   821  
   822  func TestCheckInvalidGenerators(t *testing.T) {
   823  	scheme := runtime.NewScheme()
   824  	err := argoappsv1.AddToScheme(scheme)
   825  	require.NoError(t, err)
   826  	err = argoappsv1.AddToScheme(scheme)
   827  	require.NoError(t, err)
   828  
   829  	for _, c := range []struct {
   830  		testName    string
   831  		appSet      argoappsv1.ApplicationSet
   832  		expectedMsg string
   833  	}{
   834  		{
   835  			testName: "invalid generator, without annotation",
   836  			appSet: argoappsv1.ApplicationSet{
   837  				ObjectMeta: metav1.ObjectMeta{
   838  					Name:      "test-app-set",
   839  					Namespace: "namespace",
   840  				},
   841  				Spec: argoappsv1.ApplicationSetSpec{
   842  					Generators: []argoappsv1.ApplicationSetGenerator{
   843  						{
   844  							List:     &argoappsv1.ListGenerator{},
   845  							Clusters: nil,
   846  							Git:      nil,
   847  						},
   848  						{
   849  							List:     nil,
   850  							Clusters: nil,
   851  							Git:      nil,
   852  						},
   853  						{
   854  							List:     nil,
   855  							Clusters: nil,
   856  							Git:      &argoappsv1.GitGenerator{},
   857  						},
   858  					},
   859  				},
   860  			},
   861  			expectedMsg: "ApplicationSet test-app-set contains unrecognized generators",
   862  		},
   863  		{
   864  			testName: "invalid generator, with annotation",
   865  			appSet: argoappsv1.ApplicationSet{
   866  				ObjectMeta: metav1.ObjectMeta{
   867  					Name:      "test-app-set",
   868  					Namespace: "namespace",
   869  					Annotations: map[string]string{
   870  						"kubectl.kubernetes.io/last-applied-configuration": `{
   871  							"spec":{
   872  								"generators":[
   873  									{"list":{}},
   874  									{"bbb":{}},
   875  									{"git":{}},
   876  									{"aaa":{}}
   877  								]
   878  							}
   879  						}`,
   880  					},
   881  				},
   882  				Spec: argoappsv1.ApplicationSetSpec{
   883  					Generators: []argoappsv1.ApplicationSetGenerator{
   884  						{
   885  							List:     &argoappsv1.ListGenerator{},
   886  							Clusters: nil,
   887  							Git:      nil,
   888  						},
   889  						{
   890  							List:     nil,
   891  							Clusters: nil,
   892  							Git:      nil,
   893  						},
   894  						{
   895  							List:     nil,
   896  							Clusters: nil,
   897  							Git:      &argoappsv1.GitGenerator{},
   898  						},
   899  						{
   900  							List:     nil,
   901  							Clusters: nil,
   902  							Git:      nil,
   903  						},
   904  					},
   905  				},
   906  			},
   907  			expectedMsg: "ApplicationSet test-app-set contains unrecognized generators: aaa, bbb",
   908  		},
   909  	} {
   910  		oldhooks := logrus.StandardLogger().ReplaceHooks(logrus.LevelHooks{})
   911  		defer logrus.StandardLogger().ReplaceHooks(oldhooks)
   912  		hook := logtest.NewGlobal()
   913  
   914  		_ = CheckInvalidGenerators(&c.appSet)
   915  		assert.GreaterOrEqual(t, len(hook.Entries), 1, c.testName)
   916  		assert.NotNil(t, hook.LastEntry(), c.testName)
   917  		if hook.LastEntry() != nil {
   918  			assert.Equal(t, logrus.WarnLevel, hook.LastEntry().Level, c.testName)
   919  			assert.Equal(t, c.expectedMsg, hook.LastEntry().Message, c.testName)
   920  		}
   921  		hook.Reset()
   922  	}
   923  }
   924  
   925  func TestInvalidGenerators(t *testing.T) {
   926  	scheme := runtime.NewScheme()
   927  	err := argoappsv1.AddToScheme(scheme)
   928  	require.NoError(t, err)
   929  	err = argoappsv1.AddToScheme(scheme)
   930  	require.NoError(t, err)
   931  
   932  	for _, c := range []struct {
   933  		testName        string
   934  		appSet          argoappsv1.ApplicationSet
   935  		expectedInvalid bool
   936  		expectedNames   map[string]bool
   937  	}{
   938  		{
   939  			testName: "valid generators, with annotation",
   940  			appSet: argoappsv1.ApplicationSet{
   941  				ObjectMeta: metav1.ObjectMeta{
   942  					Name:      "name",
   943  					Namespace: "namespace",
   944  					Annotations: map[string]string{
   945  						"kubectl.kubernetes.io/last-applied-configuration": `{
   946  							"spec":{
   947  								"generators":[
   948  									{"list":{}},
   949  									{"cluster":{}},
   950  									{"git":{}}
   951  								]
   952  							}
   953  						}`,
   954  					},
   955  				},
   956  				Spec: argoappsv1.ApplicationSetSpec{
   957  					Generators: []argoappsv1.ApplicationSetGenerator{
   958  						{
   959  							List:     &argoappsv1.ListGenerator{},
   960  							Clusters: nil,
   961  							Git:      nil,
   962  						},
   963  						{
   964  							List:     nil,
   965  							Clusters: &argoappsv1.ClusterGenerator{},
   966  							Git:      nil,
   967  						},
   968  						{
   969  							List:     nil,
   970  							Clusters: nil,
   971  							Git:      &argoappsv1.GitGenerator{},
   972  						},
   973  					},
   974  				},
   975  			},
   976  			expectedInvalid: false,
   977  			expectedNames:   map[string]bool{},
   978  		},
   979  		{
   980  			testName: "invalid generators, no annotation",
   981  			appSet: argoappsv1.ApplicationSet{
   982  				ObjectMeta: metav1.ObjectMeta{
   983  					Name:      "name",
   984  					Namespace: "namespace",
   985  				},
   986  				Spec: argoappsv1.ApplicationSetSpec{
   987  					Generators: []argoappsv1.ApplicationSetGenerator{
   988  						{
   989  							List:     nil,
   990  							Clusters: nil,
   991  							Git:      nil,
   992  						},
   993  						{
   994  							List:     nil,
   995  							Clusters: nil,
   996  							Git:      nil,
   997  						},
   998  					},
   999  				},
  1000  			},
  1001  			expectedInvalid: true,
  1002  			expectedNames:   map[string]bool{},
  1003  		},
  1004  		{
  1005  			testName: "valid and invalid generators, no annotation",
  1006  			appSet: argoappsv1.ApplicationSet{
  1007  				ObjectMeta: metav1.ObjectMeta{
  1008  					Name:      "name",
  1009  					Namespace: "namespace",
  1010  				},
  1011  				Spec: argoappsv1.ApplicationSetSpec{
  1012  					Generators: []argoappsv1.ApplicationSetGenerator{
  1013  						{
  1014  							List:     nil,
  1015  							Clusters: &argoappsv1.ClusterGenerator{},
  1016  							Git:      nil,
  1017  						},
  1018  						{
  1019  							List:     nil,
  1020  							Clusters: nil,
  1021  							Git:      nil,
  1022  						},
  1023  						{
  1024  							List:     nil,
  1025  							Clusters: nil,
  1026  							Git:      &argoappsv1.GitGenerator{},
  1027  						},
  1028  					},
  1029  				},
  1030  			},
  1031  			expectedInvalid: true,
  1032  			expectedNames:   map[string]bool{},
  1033  		},
  1034  		{
  1035  			testName: "valid and invalid generators, with annotation",
  1036  			appSet: argoappsv1.ApplicationSet{
  1037  				ObjectMeta: metav1.ObjectMeta{
  1038  					Name:      "name",
  1039  					Namespace: "namespace",
  1040  					Annotations: map[string]string{
  1041  						"kubectl.kubernetes.io/last-applied-configuration": `{
  1042  							"spec":{
  1043  								"generators":[
  1044  									{"cluster":{}},
  1045  									{"bbb":{}},
  1046  									{"git":{}},
  1047  									{"aaa":{}}
  1048  								]
  1049  							}
  1050  						}`,
  1051  					},
  1052  				},
  1053  				Spec: argoappsv1.ApplicationSetSpec{
  1054  					Generators: []argoappsv1.ApplicationSetGenerator{
  1055  						{
  1056  							List:     nil,
  1057  							Clusters: &argoappsv1.ClusterGenerator{},
  1058  							Git:      nil,
  1059  						},
  1060  						{
  1061  							List:     nil,
  1062  							Clusters: nil,
  1063  							Git:      nil,
  1064  						},
  1065  						{
  1066  							List:     nil,
  1067  							Clusters: nil,
  1068  							Git:      &argoappsv1.GitGenerator{},
  1069  						},
  1070  						{
  1071  							List:     nil,
  1072  							Clusters: nil,
  1073  							Git:      nil,
  1074  						},
  1075  					},
  1076  				},
  1077  			},
  1078  			expectedInvalid: true,
  1079  			expectedNames: map[string]bool{
  1080  				"aaa": true,
  1081  				"bbb": true,
  1082  			},
  1083  		},
  1084  		{
  1085  			testName: "invalid generator, annotation with missing spec",
  1086  			appSet: argoappsv1.ApplicationSet{
  1087  				ObjectMeta: metav1.ObjectMeta{
  1088  					Name:      "name",
  1089  					Namespace: "namespace",
  1090  					Annotations: map[string]string{
  1091  						"kubectl.kubernetes.io/last-applied-configuration": `{
  1092  						}`,
  1093  					},
  1094  				},
  1095  				Spec: argoappsv1.ApplicationSetSpec{
  1096  					Generators: []argoappsv1.ApplicationSetGenerator{
  1097  						{
  1098  							List:     nil,
  1099  							Clusters: nil,
  1100  							Git:      nil,
  1101  						},
  1102  					},
  1103  				},
  1104  			},
  1105  			expectedInvalid: true,
  1106  			expectedNames:   map[string]bool{},
  1107  		},
  1108  		{
  1109  			testName: "invalid generator, annotation with missing generators array",
  1110  			appSet: argoappsv1.ApplicationSet{
  1111  				ObjectMeta: metav1.ObjectMeta{
  1112  					Name:      "name",
  1113  					Namespace: "namespace",
  1114  					Annotations: map[string]string{
  1115  						"kubectl.kubernetes.io/last-applied-configuration": `{
  1116  							"spec":{
  1117  							}
  1118  						}`,
  1119  					},
  1120  				},
  1121  				Spec: argoappsv1.ApplicationSetSpec{
  1122  					Generators: []argoappsv1.ApplicationSetGenerator{
  1123  						{
  1124  							List:     nil,
  1125  							Clusters: nil,
  1126  							Git:      nil,
  1127  						},
  1128  					},
  1129  				},
  1130  			},
  1131  			expectedInvalid: true,
  1132  			expectedNames:   map[string]bool{},
  1133  		},
  1134  		{
  1135  			testName: "invalid generator, annotation with empty generators array",
  1136  			appSet: argoappsv1.ApplicationSet{
  1137  				ObjectMeta: metav1.ObjectMeta{
  1138  					Name:      "name",
  1139  					Namespace: "namespace",
  1140  					Annotations: map[string]string{
  1141  						"kubectl.kubernetes.io/last-applied-configuration": `{
  1142  							"spec":{
  1143  								"generators":[
  1144  								]
  1145  							}
  1146  						}`,
  1147  					},
  1148  				},
  1149  				Spec: argoappsv1.ApplicationSetSpec{
  1150  					Generators: []argoappsv1.ApplicationSetGenerator{
  1151  						{
  1152  							List:     nil,
  1153  							Clusters: nil,
  1154  							Git:      nil,
  1155  						},
  1156  					},
  1157  				},
  1158  			},
  1159  			expectedInvalid: true,
  1160  			expectedNames:   map[string]bool{},
  1161  		},
  1162  		{
  1163  			testName: "invalid generator, annotation with empty generator",
  1164  			appSet: argoappsv1.ApplicationSet{
  1165  				ObjectMeta: metav1.ObjectMeta{
  1166  					Name:      "name",
  1167  					Namespace: "namespace",
  1168  					Annotations: map[string]string{
  1169  						"kubectl.kubernetes.io/last-applied-configuration": `{
  1170  							"spec":{
  1171  								"generators":[
  1172  									{}
  1173  								]
  1174  							}
  1175  						}`,
  1176  					},
  1177  				},
  1178  				Spec: argoappsv1.ApplicationSetSpec{
  1179  					Generators: []argoappsv1.ApplicationSetGenerator{
  1180  						{
  1181  							List:     nil,
  1182  							Clusters: nil,
  1183  							Git:      nil,
  1184  						},
  1185  					},
  1186  				},
  1187  			},
  1188  			expectedInvalid: true,
  1189  			expectedNames:   map[string]bool{},
  1190  		},
  1191  	} {
  1192  		hasInvalid, names := invalidGenerators(&c.appSet)
  1193  		assert.Equal(t, c.expectedInvalid, hasInvalid, c.testName)
  1194  		assert.Equal(t, c.expectedNames, names, c.testName)
  1195  	}
  1196  }
  1197  
  1198  func TestNormalizeBitbucketBasePath(t *testing.T) {
  1199  	for _, c := range []struct {
  1200  		testName         string
  1201  		basePath         string
  1202  		expectedBasePath string
  1203  	}{
  1204  		{
  1205  			testName:         "default api url",
  1206  			basePath:         "https://company.bitbucket.com",
  1207  			expectedBasePath: "https://company.bitbucket.com/rest",
  1208  		},
  1209  		{
  1210  			testName:         "with /rest suffix",
  1211  			basePath:         "https://company.bitbucket.com/rest",
  1212  			expectedBasePath: "https://company.bitbucket.com/rest",
  1213  		},
  1214  		{
  1215  			testName:         "with /rest/ suffix",
  1216  			basePath:         "https://company.bitbucket.com/rest/",
  1217  			expectedBasePath: "https://company.bitbucket.com/rest",
  1218  		},
  1219  	} {
  1220  		result := NormalizeBitbucketBasePath(c.basePath)
  1221  		assert.Equal(t, c.expectedBasePath, result, c.testName)
  1222  	}
  1223  }
  1224  
  1225  func TestSlugify(t *testing.T) {
  1226  	for _, c := range []struct {
  1227  		branch           string
  1228  		smartTruncate    bool
  1229  		length           int
  1230  		expectedBasePath string
  1231  	}{
  1232  		{
  1233  			branch:           "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
  1234  			smartTruncate:    false,
  1235  			length:           50,
  1236  			expectedBasePath: "feat-a-really-long-pull-request-name-to-test-argo",
  1237  		},
  1238  		{
  1239  			branch:           "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
  1240  			smartTruncate:    true,
  1241  			length:           53,
  1242  			expectedBasePath: "feat-a-really-long-pull-request-name-to-test-argo",
  1243  		},
  1244  		{
  1245  			branch:           "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature",
  1246  			smartTruncate:    true,
  1247  			length:           50,
  1248  			expectedBasePath: "feat",
  1249  		},
  1250  		{
  1251  			branch:           "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature",
  1252  			smartTruncate:    false,
  1253  			length:           50,
  1254  			expectedBasePath: "feat-areallylongpullrequestnametotestargoslugifica",
  1255  		},
  1256  	} {
  1257  		result := SlugifyName(c.length, c.smartTruncate, c.branch)
  1258  		assert.Equal(t, c.expectedBasePath, result, c.branch)
  1259  	}
  1260  }
  1261  
  1262  func TestGetTLSConfig(t *testing.T) {
  1263  	temppath := t.TempDir()
  1264  	certFromFile := `
  1265  -----BEGIN CERTIFICATE-----
  1266  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
  1297  -----END CERTIFICATE-----
  1298  `
  1299  
  1300  	certFromCM := `
  1301  -----BEGIN CERTIFICATE-----
  1302  MIIDOTCCAiGgAwIBAgIQSRJrEpBGFc7tNb1fb5pKFzANBgkqhkiG9w0BAQsFADAS
  1303  MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
  1304  MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
  1305  MIIBCgKCAQEA6Gba5tHV1dAKouAaXO3/ebDUU4rvwCUg/CNaJ2PT5xLD4N1Vcb8r
  1306  bFSW2HXKq+MPfVdwIKR/1DczEoAGf/JWQTW7EgzlXrCd3rlajEX2D73faWJekD0U
  1307  aUgz5vtrTXZ90BQL7WvRICd7FlEZ6FPOcPlumiyNmzUqtwGhO+9ad1W5BqJaRI6P
  1308  YfouNkwR6Na4TzSj5BrqUfP0FwDizKSJ0XXmh8g8G9mtwxOSN3Ru1QFc61Xyeluk
  1309  POGKBV/q6RBNklTNe0gI8usUMlYyoC7ytppNMW7X2vodAelSu25jgx2anj9fDVZu
  1310  h7AXF5+4nJS4AAt0n1lNY7nGSsdZas8PbQIDAQABo4GIMIGFMA4GA1UdDwEB/wQE
  1311  AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
  1312  DgQWBBStsdjh3/JCXXYlQryOrL4Sh7BW5TAuBgNVHREEJzAlggtleGFtcGxlLmNv
  1313  bYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAxWGI
  1314  5NhpF3nwwy/4yB4i/CwwSpLrWUa70NyhvprUBC50PxiXav1TeDzwzLx/o5HyNwsv
  1315  cxv3HdkLW59i/0SlJSrNnWdfZ19oTcS+6PtLoVyISgtyN6DpkKpdG1cOkW3Cy2P2
  1316  +tK/tKHRP1Y/Ra0RiDpOAmqn0gCOFGz8+lqDIor/T7MTpibL3IxqWfPrvfVRHL3B
  1317  grw/ZQTTIVjjh4JBSW3WyWgNo/ikC1lrVxzl4iPUGptxT36Cr7Zk2Bsg0XqwbOvK
  1318  5d+NTDREkSnUbie4GeutujmX3Dsx88UiV6UY/4lHJa6I5leHUNOHahRbpbWeOfs/
  1319  WkBKOclmOV2xlTVuPw==
  1320  -----END CERTIFICATE-----
  1321  `
  1322  
  1323  	rootCAPath := path.Join(temppath, "foo.example.com")
  1324  	err := os.WriteFile(rootCAPath, []byte(certFromFile), 0o666)
  1325  	if err != nil {
  1326  		panic(err)
  1327  	}
  1328  
  1329  	testCases := []struct {
  1330  		name                    string
  1331  		scmRootCAPath           string
  1332  		insecure                bool
  1333  		caCerts                 []byte
  1334  		validateCertInTLSConfig bool
  1335  	}{
  1336  		{
  1337  			name:                    "Insecure mode configured, SCM Root CA Path not set",
  1338  			scmRootCAPath:           "",
  1339  			insecure:                true,
  1340  			caCerts:                 nil,
  1341  			validateCertInTLSConfig: false,
  1342  		},
  1343  		{
  1344  			name:                    "SCM Root CA Path set, Insecure mode set to false",
  1345  			scmRootCAPath:           rootCAPath,
  1346  			insecure:                false,
  1347  			caCerts:                 nil,
  1348  			validateCertInTLSConfig: true,
  1349  		},
  1350  		{
  1351  			name:                    "SCM Root CA Path set, Insecure mode set to true",
  1352  			scmRootCAPath:           rootCAPath,
  1353  			insecure:                true,
  1354  			caCerts:                 nil,
  1355  			validateCertInTLSConfig: true,
  1356  		},
  1357  		{
  1358  			name:                    "Cert passed, Insecure mode set to false",
  1359  			scmRootCAPath:           "",
  1360  			insecure:                false,
  1361  			caCerts:                 []byte(certFromCM),
  1362  			validateCertInTLSConfig: true,
  1363  		},
  1364  		{
  1365  			name:                    "SCM Root CA Path set, cert passed, Insecure mode set to false",
  1366  			scmRootCAPath:           rootCAPath,
  1367  			insecure:                false,
  1368  			caCerts:                 []byte(certFromCM),
  1369  			validateCertInTLSConfig: true,
  1370  		},
  1371  	}
  1372  
  1373  	for _, testCase := range testCases {
  1374  		t.Run(testCase.name, func(t *testing.T) {
  1375  			certPool := x509.NewCertPool()
  1376  			tlsConfig := GetTlsConfig(testCase.scmRootCAPath, testCase.insecure, testCase.caCerts)
  1377  			assert.Equal(t, testCase.insecure, tlsConfig.InsecureSkipVerify)
  1378  			if testCase.caCerts != nil {
  1379  				ok := certPool.AppendCertsFromPEM([]byte(certFromCM))
  1380  				assert.True(t, ok)
  1381  			}
  1382  			if testCase.scmRootCAPath != "" {
  1383  				ok := certPool.AppendCertsFromPEM([]byte(certFromFile))
  1384  				assert.True(t, ok)
  1385  			}
  1386  			assert.NotNil(t, tlsConfig)
  1387  			if testCase.validateCertInTLSConfig {
  1388  				assert.True(t, tlsConfig.RootCAs.Equal(certPool))
  1389  			}
  1390  		})
  1391  	}
  1392  }