github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/generators/merge_test.go (about)

     1  package generators
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    10  
    11  	argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    12  )
    13  
    14  func getNestedListGenerator(json string) *argoprojiov1alpha1.ApplicationSetNestedGenerator {
    15  	return &argoprojiov1alpha1.ApplicationSetNestedGenerator{
    16  		List: &argoprojiov1alpha1.ListGenerator{
    17  			Elements: []apiextensionsv1.JSON{{Raw: []byte(json)}},
    18  		},
    19  	}
    20  }
    21  
    22  func getTerminalListGeneratorMultiple(jsons []string) argoprojiov1alpha1.ApplicationSetTerminalGenerator {
    23  	elements := make([]apiextensionsv1.JSON, len(jsons))
    24  
    25  	for i, json := range jsons {
    26  		elements[i] = apiextensionsv1.JSON{Raw: []byte(json)}
    27  	}
    28  
    29  	generator := argoprojiov1alpha1.ApplicationSetTerminalGenerator{
    30  		List: &argoprojiov1alpha1.ListGenerator{
    31  			Elements: elements,
    32  		},
    33  	}
    34  
    35  	return generator
    36  }
    37  
    38  func listOfMapsToSet(maps []map[string]interface{}) (map[string]bool, error) {
    39  	set := make(map[string]bool, len(maps))
    40  	for _, paramMap := range maps {
    41  		paramMapAsJson, err := json.Marshal(paramMap)
    42  		if err != nil {
    43  			return nil, err
    44  		}
    45  
    46  		set[string(paramMapAsJson)] = false
    47  	}
    48  	return set, nil
    49  }
    50  
    51  func TestMergeGenerate(t *testing.T) {
    52  
    53  	testCases := []struct {
    54  		name           string
    55  		baseGenerators []argoprojiov1alpha1.ApplicationSetNestedGenerator
    56  		mergeKeys      []string
    57  		expectedErr    error
    58  		expected       []map[string]interface{}
    59  	}{
    60  		{
    61  			name:           "no generators",
    62  			baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{},
    63  			mergeKeys:      []string{"b"},
    64  			expectedErr:    ErrLessThanTwoGeneratorsInMerge,
    65  		},
    66  		{
    67  			name: "one generator",
    68  			baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
    69  				*getNestedListGenerator(`{"a": "1_1","b": "same","c": "1_3"}`),
    70  			},
    71  			mergeKeys:   []string{"b"},
    72  			expectedErr: ErrLessThanTwoGeneratorsInMerge,
    73  		},
    74  		{
    75  			name: "happy flow - generate paramSets",
    76  			baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
    77  				*getNestedListGenerator(`{"a": "1_1","b": "same","c": "1_3"}`),
    78  				*getNestedListGenerator(`{"a": "2_1","b": "same"}`),
    79  				*getNestedListGenerator(`{"a": "3_1","b": "different","c": "3_3"}`), // gets ignored because its merge key value isn't in the base params set
    80  			},
    81  			mergeKeys: []string{"b"},
    82  			expected: []map[string]interface{}{
    83  				{"a": "2_1", "b": "same", "c": "1_3"},
    84  			},
    85  		},
    86  		{
    87  			name: "merge keys absent - do not merge",
    88  			baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
    89  				*getNestedListGenerator(`{"a": "a"}`),
    90  				*getNestedListGenerator(`{"a": "a"}`),
    91  			},
    92  			mergeKeys: []string{"b"},
    93  			expected: []map[string]interface{}{
    94  				{"a": "a"},
    95  			},
    96  		},
    97  		{
    98  			name: "merge key present in first set, absent in second - do not merge",
    99  			baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
   100  				*getNestedListGenerator(`{"a": "a"}`),
   101  				*getNestedListGenerator(`{"b": "b"}`),
   102  			},
   103  			mergeKeys: []string{"b"},
   104  			expected: []map[string]interface{}{
   105  				{"a": "a"},
   106  			},
   107  		},
   108  		{
   109  			name: "merge nested matrix with some lists",
   110  			baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
   111  				{
   112  					Matrix: toAPIExtensionsJSON(t, &argoprojiov1alpha1.NestedMatrixGenerator{
   113  						Generators: []argoprojiov1alpha1.ApplicationSetTerminalGenerator{
   114  							getTerminalListGeneratorMultiple([]string{`{"a": "1"}`, `{"a": "2"}`}),
   115  							getTerminalListGeneratorMultiple([]string{`{"b": "1"}`, `{"b": "2"}`}),
   116  						},
   117  					}),
   118  				},
   119  				*getNestedListGenerator(`{"a": "1", "b": "1", "c": "added"}`),
   120  			},
   121  			mergeKeys: []string{"a", "b"},
   122  			expected: []map[string]interface{}{
   123  				{"a": "1", "b": "1", "c": "added"},
   124  				{"a": "1", "b": "2"},
   125  				{"a": "2", "b": "1"},
   126  				{"a": "2", "b": "2"},
   127  			},
   128  		},
   129  		{
   130  			name: "merge nested merge with some lists",
   131  			baseGenerators: []argoprojiov1alpha1.ApplicationSetNestedGenerator{
   132  				{
   133  					Merge: toAPIExtensionsJSON(t, &argoprojiov1alpha1.NestedMergeGenerator{
   134  						MergeKeys: []string{"a"},
   135  						Generators: []argoprojiov1alpha1.ApplicationSetTerminalGenerator{
   136  							getTerminalListGeneratorMultiple([]string{`{"a": "1", "b": "1"}`, `{"a": "2", "b": "2"}`}),
   137  							getTerminalListGeneratorMultiple([]string{`{"a": "1", "b": "3", "c": "added"}`, `{"a": "3", "b": "2"}`}), // First gets merged, second gets ignored
   138  						},
   139  					}),
   140  				},
   141  				*getNestedListGenerator(`{"a": "1", "b": "3", "d": "added"}`),
   142  			},
   143  			mergeKeys: []string{"a", "b"},
   144  			expected: []map[string]interface{}{
   145  				{"a": "1", "b": "3", "c": "added", "d": "added"},
   146  				{"a": "2", "b": "2"},
   147  			},
   148  		},
   149  	}
   150  
   151  	for _, testCase := range testCases {
   152  		testCaseCopy := testCase // since tests may run in parallel
   153  
   154  		t.Run(testCaseCopy.name, func(t *testing.T) {
   155  			t.Parallel()
   156  
   157  			appSet := &argoprojiov1alpha1.ApplicationSet{}
   158  
   159  			var mergeGenerator = NewMergeGenerator(
   160  				map[string]Generator{
   161  					"List": &ListGenerator{},
   162  					"Matrix": &MatrixGenerator{
   163  						supportedGenerators: map[string]Generator{
   164  							"List": &ListGenerator{},
   165  						},
   166  					},
   167  					"Merge": &MergeGenerator{
   168  						supportedGenerators: map[string]Generator{
   169  							"List": &ListGenerator{},
   170  						},
   171  					},
   172  				},
   173  			)
   174  
   175  			got, err := mergeGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
   176  				Merge: &argoprojiov1alpha1.MergeGenerator{
   177  					Generators: testCaseCopy.baseGenerators,
   178  					MergeKeys:  testCaseCopy.mergeKeys,
   179  					Template:   argoprojiov1alpha1.ApplicationSetTemplate{},
   180  				},
   181  			}, appSet)
   182  
   183  			if testCaseCopy.expectedErr != nil {
   184  				assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
   185  			} else {
   186  				expectedSet, err := listOfMapsToSet(testCaseCopy.expected)
   187  				assert.NoError(t, err)
   188  
   189  				actualSet, err := listOfMapsToSet(got)
   190  				assert.NoError(t, err)
   191  
   192  				assert.NoError(t, err)
   193  				assert.Equal(t, expectedSet, actualSet)
   194  			}
   195  		})
   196  	}
   197  }
   198  
   199  func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON {
   200  
   201  	resVal, err := json.Marshal(g)
   202  	if err != nil {
   203  		t.Error("unable to unmarshal json", g)
   204  		return nil
   205  	}
   206  
   207  	res := &apiextensionsv1.JSON{Raw: resVal}
   208  
   209  	return res
   210  }
   211  
   212  func TestParamSetsAreUniqueByMergeKeys(t *testing.T) {
   213  	testCases := []struct {
   214  		name        string
   215  		mergeKeys   []string
   216  		paramSets   []map[string]interface{}
   217  		expectedErr error
   218  		expected    map[string]map[string]interface{}
   219  	}{
   220  		{
   221  			name:        "no merge keys",
   222  			mergeKeys:   []string{},
   223  			expectedErr: ErrNoMergeKeys,
   224  		},
   225  		{
   226  			name:      "no paramSets",
   227  			mergeKeys: []string{"key"},
   228  			expected:  make(map[string]map[string]interface{}),
   229  		},
   230  		{
   231  			name:      "simple key, unique paramSets",
   232  			mergeKeys: []string{"key"},
   233  			paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}},
   234  			expected: map[string]map[string]interface{}{
   235  				`{"key":"a"}`: {"key": "a"},
   236  				`{"key":"b"}`: {"key": "b"},
   237  			},
   238  		},
   239  		{
   240  			name:      "simple key object, unique paramSets",
   241  			mergeKeys: []string{"key"},
   242  			paramSets: []map[string]interface{}{{"key": map[string]interface{}{"hello": "world"}}, {"key": "b"}},
   243  			expected: map[string]map[string]interface{}{
   244  				`{"key":{"hello":"world"}}`: {"key": map[string]interface{}{"hello": "world"}},
   245  				`{"key":"b"}`:               {"key": "b"},
   246  			},
   247  		},
   248  		{
   249  			name:        "simple key, non-unique paramSets",
   250  			mergeKeys:   []string{"key"},
   251  			paramSets:   []map[string]interface{}{{"key": "a"}, {"key": "b"}, {"key": "b"}},
   252  			expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key":"b"}`),
   253  		},
   254  		{
   255  			name:      "simple key, duplicated key name, unique paramSets",
   256  			mergeKeys: []string{"key", "key"},
   257  			paramSets: []map[string]interface{}{{"key": "a"}, {"key": "b"}},
   258  			expected: map[string]map[string]interface{}{
   259  				`{"key":"a"}`: {"key": "a"},
   260  				`{"key":"b"}`: {"key": "b"},
   261  			},
   262  		},
   263  		{
   264  			name:        "simple key, duplicated key name, non-unique paramSets",
   265  			mergeKeys:   []string{"key", "key"},
   266  			paramSets:   []map[string]interface{}{{"key": "a"}, {"key": "b"}, {"key": "b"}},
   267  			expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key":"b"}`),
   268  		},
   269  		{
   270  			name:      "compound key, unique paramSets",
   271  			mergeKeys: []string{"key1", "key2"},
   272  			paramSets: []map[string]interface{}{
   273  				{"key1": "a", "key2": "a"},
   274  				{"key1": "a", "key2": "b"},
   275  				{"key1": "b", "key2": "a"},
   276  			},
   277  			expected: map[string]map[string]interface{}{
   278  				`{"key1":"a","key2":"a"}`: {"key1": "a", "key2": "a"},
   279  				`{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"},
   280  				`{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"},
   281  			},
   282  		},
   283  		{
   284  			name:      "compound key object, unique paramSets",
   285  			mergeKeys: []string{"key1", "key2"},
   286  			paramSets: []map[string]interface{}{
   287  				{"key1": "a", "key2": map[string]interface{}{"hello": "world"}},
   288  				{"key1": "a", "key2": "b"},
   289  				{"key1": "b", "key2": "a"},
   290  			},
   291  			expected: map[string]map[string]interface{}{
   292  				`{"key1":"a","key2":{"hello":"world"}}`: {"key1": "a", "key2": map[string]interface{}{"hello": "world"}},
   293  				`{"key1":"a","key2":"b"}`:               {"key1": "a", "key2": "b"},
   294  				`{"key1":"b","key2":"a"}`:               {"key1": "b", "key2": "a"},
   295  			},
   296  		},
   297  		{
   298  			name:      "compound key, duplicate key names, unique paramSets",
   299  			mergeKeys: []string{"key1", "key1", "key2"},
   300  			paramSets: []map[string]interface{}{
   301  				{"key1": "a", "key2": "a"},
   302  				{"key1": "a", "key2": "b"},
   303  				{"key1": "b", "key2": "a"},
   304  			},
   305  			expected: map[string]map[string]interface{}{
   306  				`{"key1":"a","key2":"a"}`: {"key1": "a", "key2": "a"},
   307  				`{"key1":"a","key2":"b"}`: {"key1": "a", "key2": "b"},
   308  				`{"key1":"b","key2":"a"}`: {"key1": "b", "key2": "a"},
   309  			},
   310  		},
   311  		{
   312  			name:      "compound key, non-unique paramSets",
   313  			mergeKeys: []string{"key1", "key2"},
   314  			paramSets: []map[string]interface{}{
   315  				{"key1": "a", "key2": "a"},
   316  				{"key1": "a", "key2": "a"},
   317  				{"key1": "b", "key2": "a"},
   318  			},
   319  			expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key1":"a","key2":"a"}`),
   320  		},
   321  		{
   322  			name:      "compound key, duplicate key names, non-unique paramSets",
   323  			mergeKeys: []string{"key1", "key1", "key2"},
   324  			paramSets: []map[string]interface{}{
   325  				{"key1": "a", "key2": "a"},
   326  				{"key1": "a", "key2": "a"},
   327  				{"key1": "b", "key2": "a"},
   328  			},
   329  			expectedErr: fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, `{"key1":"a","key2":"a"}`),
   330  		},
   331  	}
   332  
   333  	for _, testCase := range testCases {
   334  		testCaseCopy := testCase // since tests may run in parallel
   335  
   336  		t.Run(testCaseCopy.name, func(t *testing.T) {
   337  			t.Parallel()
   338  
   339  			got, err := getParamSetsByMergeKey(testCaseCopy.mergeKeys, testCaseCopy.paramSets)
   340  
   341  			if testCaseCopy.expectedErr != nil {
   342  				assert.EqualError(t, err, testCaseCopy.expectedErr.Error())
   343  			} else {
   344  				assert.NoError(t, err)
   345  				assert.Equal(t, testCaseCopy.expected, got)
   346  			}
   347  
   348  		})
   349  
   350  	}
   351  }