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  }