github.com/argoproj/argo-cd/v3@v3.2.1/commitserver/commit/commit_test.go (about)

     1  package commit
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  	"github.com/stretchr/testify/mock"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/argoproj/argo-cd/v3/commitserver/apiclient"
    11  	"github.com/argoproj/argo-cd/v3/commitserver/commit/mocks"
    12  	"github.com/argoproj/argo-cd/v3/commitserver/metrics"
    13  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    14  	"github.com/argoproj/argo-cd/v3/util/git"
    15  	gitmocks "github.com/argoproj/argo-cd/v3/util/git/mocks"
    16  )
    17  
    18  func Test_CommitHydratedManifests(t *testing.T) {
    19  	t.Parallel()
    20  
    21  	validRequest := &apiclient.CommitHydratedManifestsRequest{
    22  		Repo: &v1alpha1.Repository{
    23  			Repo: "https://github.com/argoproj/argocd-example-apps.git",
    24  		},
    25  		TargetBranch:  "main",
    26  		SyncBranch:    "env/test",
    27  		CommitMessage: "test commit message",
    28  	}
    29  
    30  	t.Run("missing repo", func(t *testing.T) {
    31  		t.Parallel()
    32  
    33  		service, _ := newServiceWithMocks(t)
    34  		request := &apiclient.CommitHydratedManifestsRequest{}
    35  		_, err := service.CommitHydratedManifests(t.Context(), request)
    36  		require.Error(t, err)
    37  		assert.ErrorContains(t, err, "repo is required")
    38  	})
    39  
    40  	t.Run("missing repo URL", func(t *testing.T) {
    41  		t.Parallel()
    42  
    43  		service, _ := newServiceWithMocks(t)
    44  		request := &apiclient.CommitHydratedManifestsRequest{
    45  			Repo: &v1alpha1.Repository{},
    46  		}
    47  		_, err := service.CommitHydratedManifests(t.Context(), request)
    48  		require.Error(t, err)
    49  		assert.ErrorContains(t, err, "repo URL is required")
    50  	})
    51  
    52  	t.Run("missing target branch", func(t *testing.T) {
    53  		t.Parallel()
    54  
    55  		service, _ := newServiceWithMocks(t)
    56  		request := &apiclient.CommitHydratedManifestsRequest{
    57  			Repo: &v1alpha1.Repository{
    58  				Repo: "https://github.com/argoproj/argocd-example-apps.git",
    59  			},
    60  		}
    61  		_, err := service.CommitHydratedManifests(t.Context(), request)
    62  		require.Error(t, err)
    63  		assert.ErrorContains(t, err, "target branch is required")
    64  	})
    65  
    66  	t.Run("missing sync branch", func(t *testing.T) {
    67  		t.Parallel()
    68  
    69  		service, _ := newServiceWithMocks(t)
    70  		request := &apiclient.CommitHydratedManifestsRequest{
    71  			Repo: &v1alpha1.Repository{
    72  				Repo: "https://github.com/argoproj/argocd-example-apps.git",
    73  			},
    74  			TargetBranch: "main",
    75  		}
    76  		_, err := service.CommitHydratedManifests(t.Context(), request)
    77  		require.Error(t, err)
    78  		assert.ErrorContains(t, err, "sync branch is required")
    79  	})
    80  
    81  	t.Run("failed to create git client", func(t *testing.T) {
    82  		t.Parallel()
    83  
    84  		service, mockRepoClientFactory := newServiceWithMocks(t)
    85  		mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(nil, assert.AnError).Once()
    86  
    87  		_, err := service.CommitHydratedManifests(t.Context(), validRequest)
    88  		require.Error(t, err)
    89  		assert.ErrorIs(t, err, assert.AnError)
    90  	})
    91  
    92  	t.Run("happy path", func(t *testing.T) {
    93  		t.Parallel()
    94  
    95  		service, mockRepoClientFactory := newServiceWithMocks(t)
    96  		mockGitClient := gitmocks.NewClient(t)
    97  		mockGitClient.On("Init").Return(nil).Once()
    98  		mockGitClient.On("Fetch", mock.Anything).Return(nil).Once()
    99  		mockGitClient.On("SetAuthor", "Argo CD", "argo-cd@example.com").Return("", nil).Once()
   100  		mockGitClient.On("CheckoutOrOrphan", "env/test", false).Return("", nil).Once()
   101  		mockGitClient.On("CheckoutOrNew", "main", "env/test", false).Return("", nil).Once()
   102  		mockGitClient.On("CommitAndPush", "main", "test commit message").Return("", nil).Once()
   103  		mockGitClient.On("CommitSHA").Return("it-worked!", nil).Once()
   104  		mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(mockGitClient, nil).Once()
   105  
   106  		resp, err := service.CommitHydratedManifests(t.Context(), validRequest)
   107  		require.NoError(t, err)
   108  		require.NotNil(t, resp)
   109  		assert.Equal(t, "it-worked!", resp.HydratedSha)
   110  	})
   111  
   112  	t.Run("root path with dot and blank - no directory removal", func(t *testing.T) {
   113  		t.Parallel()
   114  
   115  		service, mockRepoClientFactory := newServiceWithMocks(t)
   116  		mockGitClient := gitmocks.NewClient(t)
   117  		mockGitClient.On("Init").Return(nil).Once()
   118  		mockGitClient.On("Fetch", mock.Anything).Return(nil).Once()
   119  		mockGitClient.On("SetAuthor", "Argo CD", "argo-cd@example.com").Return("", nil).Once()
   120  		mockGitClient.On("CheckoutOrOrphan", "env/test", false).Return("", nil).Once()
   121  		mockGitClient.On("CheckoutOrNew", "main", "env/test", false).Return("", nil).Once()
   122  		mockGitClient.On("CommitAndPush", "main", "test commit message").Return("", nil).Once()
   123  		mockGitClient.On("CommitSHA").Return("root-and-blank-sha", nil).Once()
   124  		mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(mockGitClient, nil).Once()
   125  
   126  		requestWithRootAndBlank := &apiclient.CommitHydratedManifestsRequest{
   127  			Repo: &v1alpha1.Repository{
   128  				Repo: "https://github.com/argoproj/argocd-example-apps.git",
   129  			},
   130  			TargetBranch:  "main",
   131  			SyncBranch:    "env/test",
   132  			CommitMessage: "test commit message",
   133  			Paths: []*apiclient.PathDetails{
   134  				{
   135  					Path: ".",
   136  					Manifests: []*apiclient.HydratedManifestDetails{
   137  						{
   138  							ManifestJSON: `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"test-dot"}}`,
   139  						},
   140  					},
   141  				},
   142  				{
   143  					Path: "",
   144  					Manifests: []*apiclient.HydratedManifestDetails{
   145  						{
   146  							ManifestJSON: `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"test-blank"}}`,
   147  						},
   148  					},
   149  				},
   150  			},
   151  		}
   152  
   153  		resp, err := service.CommitHydratedManifests(t.Context(), requestWithRootAndBlank)
   154  		require.NoError(t, err)
   155  		require.NotNil(t, resp)
   156  		assert.Equal(t, "root-and-blank-sha", resp.HydratedSha)
   157  	})
   158  
   159  	t.Run("subdirectory path - triggers directory removal", func(t *testing.T) {
   160  		t.Parallel()
   161  
   162  		service, mockRepoClientFactory := newServiceWithMocks(t)
   163  		mockGitClient := gitmocks.NewClient(t)
   164  		mockGitClient.On("Init").Return(nil).Once()
   165  		mockGitClient.On("Fetch", mock.Anything).Return(nil).Once()
   166  		mockGitClient.On("SetAuthor", "Argo CD", "argo-cd@example.com").Return("", nil).Once()
   167  		mockGitClient.On("CheckoutOrOrphan", "env/test", false).Return("", nil).Once()
   168  		mockGitClient.On("CheckoutOrNew", "main", "env/test", false).Return("", nil).Once()
   169  		mockGitClient.On("RemoveContents", []string{"apps/staging"}).Return("", nil).Once()
   170  		mockGitClient.On("CommitAndPush", "main", "test commit message").Return("", nil).Once()
   171  		mockGitClient.On("CommitSHA").Return("subdir-path-sha", nil).Once()
   172  		mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(mockGitClient, nil).Once()
   173  
   174  		requestWithSubdirPath := &apiclient.CommitHydratedManifestsRequest{
   175  			Repo: &v1alpha1.Repository{
   176  				Repo: "https://github.com/argoproj/argocd-example-apps.git",
   177  			},
   178  			TargetBranch:  "main",
   179  			SyncBranch:    "env/test",
   180  			CommitMessage: "test commit message",
   181  			Paths: []*apiclient.PathDetails{
   182  				{
   183  					Path: "apps/staging", // subdirectory path
   184  					Manifests: []*apiclient.HydratedManifestDetails{
   185  						{
   186  							ManifestJSON: `{"apiVersion":"v1","kind":"Deployment","metadata":{"name":"test-app"}}`,
   187  						},
   188  					},
   189  				},
   190  			},
   191  		}
   192  
   193  		resp, err := service.CommitHydratedManifests(t.Context(), requestWithSubdirPath)
   194  		require.NoError(t, err)
   195  		require.NotNil(t, resp)
   196  		assert.Equal(t, "subdir-path-sha", resp.HydratedSha)
   197  	})
   198  
   199  	t.Run("mixed paths - root and subdirectory", func(t *testing.T) {
   200  		t.Parallel()
   201  
   202  		service, mockRepoClientFactory := newServiceWithMocks(t)
   203  		mockGitClient := gitmocks.NewClient(t)
   204  		mockGitClient.On("Init").Return(nil).Once()
   205  		mockGitClient.On("Fetch", mock.Anything).Return(nil).Once()
   206  		mockGitClient.On("SetAuthor", "Argo CD", "argo-cd@example.com").Return("", nil).Once()
   207  		mockGitClient.On("CheckoutOrOrphan", "env/test", false).Return("", nil).Once()
   208  		mockGitClient.On("CheckoutOrNew", "main", "env/test", false).Return("", nil).Once()
   209  		mockGitClient.On("RemoveContents", []string{"apps/production", "apps/staging"}).Return("", nil).Once()
   210  		mockGitClient.On("CommitAndPush", "main", "test commit message").Return("", nil).Once()
   211  		mockGitClient.On("CommitSHA").Return("mixed-paths-sha", nil).Once()
   212  		mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(mockGitClient, nil).Once()
   213  
   214  		requestWithMixedPaths := &apiclient.CommitHydratedManifestsRequest{
   215  			Repo: &v1alpha1.Repository{
   216  				Repo: "https://github.com/argoproj/argocd-example-apps.git",
   217  			},
   218  			TargetBranch:  "main",
   219  			SyncBranch:    "env/test",
   220  			CommitMessage: "test commit message",
   221  			Paths: []*apiclient.PathDetails{
   222  				{
   223  					Path: ".", // root path - should NOT trigger removal
   224  					Manifests: []*apiclient.HydratedManifestDetails{
   225  						{
   226  							ManifestJSON: `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"global-config"}}`,
   227  						},
   228  					},
   229  				},
   230  				{
   231  					Path: "apps/production", // subdirectory path - SHOULD trigger removal
   232  					Manifests: []*apiclient.HydratedManifestDetails{
   233  						{
   234  							ManifestJSON: `{"apiVersion":"v1","kind":"Deployment","metadata":{"name":"prod-app"}}`,
   235  						},
   236  					},
   237  				},
   238  				{
   239  					Path: "apps/staging", // another subdirectory path - SHOULD trigger removal
   240  					Manifests: []*apiclient.HydratedManifestDetails{
   241  						{
   242  							ManifestJSON: `{"apiVersion":"v1","kind":"Deployment","metadata":{"name":"staging-app"}}`,
   243  						},
   244  					},
   245  				},
   246  			},
   247  		}
   248  
   249  		resp, err := service.CommitHydratedManifests(t.Context(), requestWithMixedPaths)
   250  		require.NoError(t, err)
   251  		require.NotNil(t, resp)
   252  		assert.Equal(t, "mixed-paths-sha", resp.HydratedSha)
   253  	})
   254  
   255  	t.Run("empty paths array", func(t *testing.T) {
   256  		t.Parallel()
   257  
   258  		service, mockRepoClientFactory := newServiceWithMocks(t)
   259  		mockGitClient := gitmocks.NewClient(t)
   260  		mockGitClient.On("Init").Return(nil).Once()
   261  		mockGitClient.On("Fetch", mock.Anything).Return(nil).Once()
   262  		mockGitClient.On("SetAuthor", "Argo CD", "argo-cd@example.com").Return("", nil).Once()
   263  		mockGitClient.On("CheckoutOrOrphan", "env/test", false).Return("", nil).Once()
   264  		mockGitClient.On("CheckoutOrNew", "main", "env/test", false).Return("", nil).Once()
   265  		mockGitClient.On("CommitAndPush", "main", "test commit message").Return("", nil).Once()
   266  		mockGitClient.On("CommitSHA").Return("it-worked!", nil).Once()
   267  		mockRepoClientFactory.On("NewClient", mock.Anything, mock.Anything).Return(mockGitClient, nil).Once()
   268  
   269  		requestWithEmptyPaths := &apiclient.CommitHydratedManifestsRequest{
   270  			Repo: &v1alpha1.Repository{
   271  				Repo: "https://github.com/argoproj/argocd-example-apps.git",
   272  			},
   273  			TargetBranch:  "main",
   274  			SyncBranch:    "env/test",
   275  			CommitMessage: "test commit message",
   276  		}
   277  
   278  		resp, err := service.CommitHydratedManifests(t.Context(), requestWithEmptyPaths)
   279  		require.NoError(t, err)
   280  		require.NotNil(t, resp)
   281  		assert.Equal(t, "it-worked!", resp.HydratedSha)
   282  	})
   283  }
   284  
   285  func newServiceWithMocks(t *testing.T) (*Service, *mocks.RepoClientFactory) {
   286  	t.Helper()
   287  
   288  	metricsServer := metrics.NewMetricsServer()
   289  	mockCredsStore := git.NoopCredsStore{}
   290  	service := NewService(mockCredsStore, metricsServer)
   291  	mockRepoClientFactory := mocks.NewRepoClientFactory(t)
   292  	service.repoClientFactory = mockRepoClientFactory
   293  
   294  	return service, mockRepoClientFactory
   295  }