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 }