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 }