github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/services/scm_provider/aws_codecommit_test.go (about) 1 package scm_provider 2 3 import ( 4 "errors" 5 "sort" 6 "testing" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/service/codecommit" 10 "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" 11 "github.com/google/go-cmp/cmp" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/mock" 14 15 "github.com/argoproj/argo-cd/v3/applicationset/services/scm_provider/aws_codecommit/mocks" 16 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 17 ) 18 19 type awsCodeCommitTestRepository struct { 20 name string 21 id string 22 arn string 23 accountId string 24 defaultBranch string 25 expectedCloneURL string 26 getRepositoryError error 27 getRepositoryNilMetadata bool 28 valid bool 29 } 30 31 func TestAWSCodeCommitListRepos(t *testing.T) { 32 testCases := []struct { 33 name string 34 repositories []*awsCodeCommitTestRepository 35 cloneProtocol string 36 tagFilters []*v1alpha1.TagFilter 37 expectTagFilters []*resourcegroupstaggingapi.TagFilter 38 listRepositoryError error 39 expectOverallError bool 40 expectListAtCodeCommit bool 41 }{ 42 { 43 name: "ListRepos by tag with https", 44 cloneProtocol: "https", 45 repositories: []*awsCodeCommitTestRepository{ 46 { 47 name: "repo1", 48 id: "8235624d-d248-4df9-a983-2558b01dbe83", 49 arn: "arn:aws:codecommit:us-east-1:111111111111:repo1", 50 defaultBranch: "main", 51 expectedCloneURL: "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1", 52 valid: true, 53 }, 54 }, 55 tagFilters: []*v1alpha1.TagFilter{ 56 {Key: "key1", Value: "value1"}, 57 {Key: "key1", Value: "value2"}, 58 {Key: "key2"}, 59 }, 60 expectTagFilters: []*resourcegroupstaggingapi.TagFilter{ 61 {Key: aws.String("key1"), Values: aws.StringSlice([]string{"value1", "value2"})}, 62 {Key: aws.String("key2")}, 63 }, 64 expectOverallError: false, 65 expectListAtCodeCommit: false, 66 }, 67 { 68 name: "ListRepos by tag with https-fips", 69 cloneProtocol: "https-fips", 70 repositories: []*awsCodeCommitTestRepository{ 71 { 72 name: "repo1", 73 id: "8235624d-d248-4df9-a983-2558b01dbe83", 74 arn: "arn:aws:codecommit:us-east-1:111111111111:repo1", 75 defaultBranch: "main", 76 expectedCloneURL: "https://git-codecommit-fips.us-east-1.amazonaws.com/v1/repos/repo1", 77 valid: true, 78 }, 79 }, 80 tagFilters: []*v1alpha1.TagFilter{ 81 {Key: "key1"}, 82 }, 83 expectTagFilters: []*resourcegroupstaggingapi.TagFilter{ 84 {Key: aws.String("key1")}, 85 }, 86 expectOverallError: false, 87 expectListAtCodeCommit: false, 88 }, 89 { 90 name: "ListRepos without tag with invalid repo", 91 cloneProtocol: "ssh", 92 repositories: []*awsCodeCommitTestRepository{ 93 { 94 name: "repo1", 95 id: "8235624d-d248-4df9-a983-2558b01dbe83", 96 arn: "arn:aws:codecommit:us-east-1:111111111111:repo1", 97 defaultBranch: "main", 98 expectedCloneURL: "ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1", 99 valid: true, 100 }, 101 { 102 name: "repo2", 103 id: "640d5859-d265-4e27-a9fa-e0731eb13ed7", 104 arn: "arn:aws:codecommit:us-east-1:111111111111:repo2", 105 valid: false, 106 }, 107 { 108 name: "repo3-nil-metadata", 109 id: "24a6ee96-d3a0-4be6-a595-c5e5b1ab1617", 110 arn: "arn:aws:codecommit:us-east-1:111111111111:repo3-nil-metadata", 111 getRepositoryNilMetadata: true, 112 valid: false, 113 }, 114 }, 115 expectOverallError: false, 116 expectListAtCodeCommit: true, 117 }, 118 { 119 name: "ListRepos with invalid protocol", 120 cloneProtocol: "invalid-protocol", 121 repositories: []*awsCodeCommitTestRepository{ 122 { 123 name: "repo1", 124 id: "8235624d-d248-4df9-a983-2558b01dbe83", 125 arn: "arn:aws:codecommit:us-east-1:111111111111:repo1", 126 defaultBranch: "main", 127 valid: true, 128 }, 129 }, 130 expectOverallError: true, 131 expectListAtCodeCommit: true, 132 }, 133 { 134 name: "ListRepos error on listRepos", 135 cloneProtocol: "https", 136 listRepositoryError: errors.New("list repo error"), 137 expectOverallError: true, 138 expectListAtCodeCommit: true, 139 }, 140 { 141 name: "ListRepos error on getRepo", 142 cloneProtocol: "https", 143 repositories: []*awsCodeCommitTestRepository{ 144 { 145 name: "repo1", 146 id: "8235624d-d248-4df9-a983-2558b01dbe83", 147 arn: "arn:aws:codecommit:us-east-1:111111111111:repo1", 148 defaultBranch: "main", 149 getRepositoryError: errors.New("get repo error"), 150 valid: true, 151 }, 152 }, 153 expectOverallError: true, 154 expectListAtCodeCommit: true, 155 }, 156 } 157 158 for _, testCase := range testCases { 159 t.Run(testCase.name, func(t *testing.T) { 160 codeCommitClient := mocks.NewAWSCodeCommitClient(t) 161 taggingClient := mocks.NewAWSTaggingClient(t) 162 ctx := t.Context() 163 codecommitRepoNameIdPairs := make([]*codecommit.RepositoryNameIdPair, 0) 164 resourceTaggings := make([]*resourcegroupstaggingapi.ResourceTagMapping, 0) 165 validRepositories := make([]*awsCodeCommitTestRepository, 0) 166 167 for _, repo := range testCase.repositories { 168 repoMetadata := &codecommit.RepositoryMetadata{ 169 AccountId: aws.String(repo.accountId), 170 Arn: aws.String(repo.arn), 171 CloneUrlHttp: aws.String("https://git-codecommit.us-east-1.amazonaws.com/v1/repos/" + repo.name), 172 CloneUrlSsh: aws.String("ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/" + repo.name), 173 DefaultBranch: aws.String(repo.defaultBranch), 174 RepositoryId: aws.String(repo.id), 175 RepositoryName: aws.String(repo.name), 176 } 177 if repo.getRepositoryNilMetadata { 178 repoMetadata = nil 179 } 180 codeCommitClient. 181 On("GetRepositoryWithContext", ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(repo.name)}). 182 Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: repoMetadata}, repo.getRepositoryError) 183 codecommitRepoNameIdPairs = append(codecommitRepoNameIdPairs, &codecommit.RepositoryNameIdPair{ 184 RepositoryId: aws.String(repo.id), 185 RepositoryName: aws.String(repo.name), 186 }) 187 resourceTaggings = append(resourceTaggings, &resourcegroupstaggingapi.ResourceTagMapping{ 188 ResourceARN: aws.String(repo.arn), 189 }) 190 if repo.valid { 191 validRepositories = append(validRepositories, repo) 192 } 193 } 194 195 if testCase.expectListAtCodeCommit { 196 codeCommitClient. 197 On("ListRepositoriesWithContext", ctx, &codecommit.ListRepositoriesInput{}). 198 Return(&codecommit.ListRepositoriesOutput{ 199 Repositories: codecommitRepoNameIdPairs, 200 }, testCase.listRepositoryError) 201 } else { 202 taggingClient. 203 On("GetResourcesWithContext", ctx, mock.MatchedBy(equalIgnoringTagFilterOrder(&resourcegroupstaggingapi.GetResourcesInput{ 204 TagFilters: testCase.expectTagFilters, 205 ResourceTypeFilters: aws.StringSlice([]string{resourceTypeCodeCommitRepository}), 206 }))). 207 Return(&resourcegroupstaggingapi.GetResourcesOutput{ 208 ResourceTagMappingList: resourceTaggings, 209 }, testCase.listRepositoryError) 210 } 211 212 provider := &AWSCodeCommitProvider{ 213 codeCommitClient: codeCommitClient, 214 taggingClient: taggingClient, 215 tagFilters: testCase.tagFilters, 216 } 217 repos, err := provider.ListRepos(ctx, testCase.cloneProtocol) 218 if testCase.expectOverallError { 219 assert.Error(t, err) 220 } else { 221 assert.Len(t, repos, len(validRepositories)) 222 for i, repo := range repos { 223 originRepo := validRepositories[i] 224 assert.Equal(t, originRepo.accountId, repo.Organization) 225 assert.Equal(t, originRepo.name, repo.Repository) 226 assert.Equal(t, originRepo.id, repo.RepositoryId) 227 assert.Equal(t, originRepo.defaultBranch, repo.Branch) 228 assert.Equal(t, originRepo.expectedCloneURL, repo.URL) 229 assert.Empty(t, repo.SHA, "SHA is always empty") 230 } 231 } 232 }) 233 } 234 } 235 236 func TestAWSCodeCommitRepoHasPath(t *testing.T) { 237 organization := "111111111111" 238 repoName := "repo1" 239 branch := "main" 240 241 testCases := []struct { 242 name string 243 path string 244 expectedGetFolderPath string 245 getFolderOutput *codecommit.GetFolderOutput 246 getFolderError error 247 expectOverallError bool 248 expectedResult bool 249 }{ 250 { 251 name: "RepoHasPath on regular file", 252 path: "lib/config.yaml", 253 expectedGetFolderPath: "/lib", 254 getFolderOutput: &codecommit.GetFolderOutput{ 255 Files: []*codecommit.File{ 256 {RelativePath: aws.String("config.yaml")}, 257 }, 258 }, 259 expectOverallError: false, 260 expectedResult: true, 261 }, 262 { 263 name: "RepoHasPath on folder", 264 path: "lib/config", 265 expectedGetFolderPath: "/lib", 266 getFolderOutput: &codecommit.GetFolderOutput{ 267 SubFolders: []*codecommit.Folder{ 268 {RelativePath: aws.String("config")}, 269 }, 270 }, 271 expectOverallError: false, 272 expectedResult: true, 273 }, 274 { 275 name: "RepoHasPath on submodules", 276 path: "/lib/submodule/", 277 expectedGetFolderPath: "/lib", 278 getFolderOutput: &codecommit.GetFolderOutput{ 279 SubModules: []*codecommit.SubModule{ 280 {RelativePath: aws.String("submodule")}, 281 }, 282 }, 283 expectOverallError: false, 284 expectedResult: true, 285 }, 286 { 287 name: "RepoHasPath on symlink", 288 path: "./lib/service.json", 289 expectedGetFolderPath: "/lib", 290 getFolderOutput: &codecommit.GetFolderOutput{ 291 SymbolicLinks: []*codecommit.SymbolicLink{ 292 {RelativePath: aws.String("service.json")}, 293 }, 294 }, 295 expectOverallError: false, 296 expectedResult: true, 297 }, 298 { 299 name: "RepoHasPath when no match", 300 path: "no-match.json", 301 expectedGetFolderPath: "/", 302 getFolderOutput: &codecommit.GetFolderOutput{ 303 Files: []*codecommit.File{ 304 {RelativePath: aws.String("config.yaml")}, 305 }, 306 SubFolders: []*codecommit.Folder{ 307 {RelativePath: aws.String("config")}, 308 }, 309 SubModules: []*codecommit.SubModule{ 310 {RelativePath: aws.String("submodule")}, 311 }, 312 SymbolicLinks: []*codecommit.SymbolicLink{ 313 {RelativePath: aws.String("service.json")}, 314 }, 315 }, 316 expectOverallError: false, 317 expectedResult: false, 318 }, 319 { 320 name: "RepoHasPath when parent folder not found", 321 path: "lib/submodule", 322 expectedGetFolderPath: "/lib", 323 getFolderError: &codecommit.FolderDoesNotExistException{}, 324 expectOverallError: false, 325 }, 326 { 327 name: "RepoHasPath when unknown error", 328 path: "lib/submodule", 329 expectedGetFolderPath: "/lib", 330 getFolderError: errors.New("unknown error"), 331 expectOverallError: true, 332 }, 333 { 334 name: "RepoHasPath on root folder - './'", 335 path: "./", 336 expectOverallError: false, 337 expectedResult: true, 338 }, 339 { 340 name: "RepoHasPath on root folder - '/'", 341 path: "/", 342 expectOverallError: false, 343 expectedResult: true, 344 }, 345 } 346 347 for _, testCase := range testCases { 348 t.Run(testCase.name, func(t *testing.T) { 349 codeCommitClient := mocks.NewAWSCodeCommitClient(t) 350 taggingClient := mocks.NewAWSTaggingClient(t) 351 ctx := t.Context() 352 if testCase.expectedGetFolderPath != "" { 353 codeCommitClient. 354 On("GetFolderWithContext", ctx, &codecommit.GetFolderInput{ 355 CommitSpecifier: aws.String(branch), 356 FolderPath: aws.String(testCase.expectedGetFolderPath), 357 RepositoryName: aws.String(repoName), 358 }). 359 Return(testCase.getFolderOutput, testCase.getFolderError) 360 } 361 provider := &AWSCodeCommitProvider{ 362 codeCommitClient: codeCommitClient, 363 taggingClient: taggingClient, 364 } 365 actual, err := provider.RepoHasPath(ctx, &Repository{ 366 Organization: organization, 367 Repository: repoName, 368 Branch: branch, 369 }, testCase.path) 370 if testCase.expectOverallError { 371 assert.Error(t, err) 372 } else { 373 assert.Equal(t, testCase.expectedResult, actual) 374 } 375 }) 376 } 377 } 378 379 func TestAWSCodeCommitGetBranches(t *testing.T) { 380 name := "repo1" 381 id := "1a64adc4-2fb5-4abd-afe7-127984ba83c0" 382 defaultBranch := "main" 383 organization := "111111111111" 384 cloneURL := "https://git-codecommit.us-east-1.amazonaws.com/v1/repos/repo1" 385 386 testCases := []struct { 387 name string 388 branches []string 389 apiError error 390 expectOverallError bool 391 allBranches bool 392 }{ 393 { 394 name: "GetBranches all branches", 395 branches: []string{"main", "feature/codecommit", "chore/go-upgrade"}, 396 allBranches: true, 397 }, 398 { 399 name: "GetBranches default branch only", 400 allBranches: false, 401 }, 402 { 403 name: "GetBranches default branch only", 404 allBranches: false, 405 }, 406 { 407 name: "GetBranches all branches on api error", 408 apiError: errors.New("api error"), 409 expectOverallError: true, 410 allBranches: true, 411 }, 412 { 413 name: "GetBranches default branch on api error", 414 apiError: errors.New("api error"), 415 expectOverallError: true, 416 allBranches: false, 417 }, 418 } 419 420 for _, testCase := range testCases { 421 t.Run(testCase.name, func(t *testing.T) { 422 codeCommitClient := mocks.NewAWSCodeCommitClient(t) 423 taggingClient := mocks.NewAWSTaggingClient(t) 424 ctx := t.Context() 425 if testCase.allBranches { 426 codeCommitClient. 427 On("ListBranchesWithContext", ctx, &codecommit.ListBranchesInput{ 428 RepositoryName: aws.String(name), 429 }). 430 Return(&codecommit.ListBranchesOutput{Branches: aws.StringSlice(testCase.branches)}, testCase.apiError) 431 } else { 432 codeCommitClient. 433 On("GetRepositoryWithContext", ctx, &codecommit.GetRepositoryInput{RepositoryName: aws.String(name)}). 434 Return(&codecommit.GetRepositoryOutput{RepositoryMetadata: &codecommit.RepositoryMetadata{ 435 AccountId: aws.String(organization), 436 DefaultBranch: aws.String(defaultBranch), 437 }}, testCase.apiError) 438 } 439 provider := &AWSCodeCommitProvider{ 440 codeCommitClient: codeCommitClient, 441 taggingClient: taggingClient, 442 allBranches: testCase.allBranches, 443 } 444 actual, err := provider.GetBranches(ctx, &Repository{ 445 Organization: organization, 446 Repository: name, 447 URL: cloneURL, 448 RepositoryId: id, 449 }) 450 if testCase.expectOverallError { 451 assert.Error(t, err) 452 } else { 453 assertCopiedProperties := func(repo *Repository) { 454 assert.Equal(t, id, repo.RepositoryId) 455 assert.Equal(t, name, repo.Repository) 456 assert.Equal(t, cloneURL, repo.URL) 457 assert.Equal(t, organization, repo.Organization) 458 assert.Empty(t, repo.SHA) 459 } 460 actualBranches := make([]string, 0) 461 for _, repo := range actual { 462 assertCopiedProperties(repo) 463 actualBranches = append(actualBranches, repo.Branch) 464 } 465 if testCase.allBranches { 466 assert.ElementsMatch(t, testCase.branches, actualBranches) 467 } else { 468 assert.ElementsMatch(t, []string{defaultBranch}, actualBranches) 469 } 470 } 471 }) 472 } 473 } 474 475 // equalIgnoringTagFilterOrder provides an argumentMatcher function that can be used to compare equality of GetResourcesInput ignoring the tagFilter ordering. 476 func equalIgnoringTagFilterOrder(expected *resourcegroupstaggingapi.GetResourcesInput) func(*resourcegroupstaggingapi.GetResourcesInput) bool { 477 return func(actual *resourcegroupstaggingapi.GetResourcesInput) bool { 478 sort.Slice(actual.TagFilters, func(i, j int) bool { 479 return *actual.TagFilters[i].Key < *actual.TagFilters[j].Key 480 }) 481 return cmp.Equal(expected, actual) 482 } 483 }