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