github.com/argoproj/argo-cd@v1.8.7/server/application/application_test.go (about) 1 package application 2 3 import ( 4 "context" 5 coreerrors "errors" 6 "testing" 7 "time" 8 9 synccommon "github.com/argoproj/gitops-engine/pkg/sync/common" 10 "github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest" 11 "github.com/argoproj/pkg/sync" 12 "github.com/dgrijalva/jwt-go/v4" 13 "github.com/ghodss/yaml" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/mock" 16 "google.golang.org/grpc/codes" 17 "google.golang.org/grpc/status" 18 v1 "k8s.io/api/core/v1" 19 apierrors "k8s.io/apimachinery/pkg/api/errors" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/runtime" 22 "k8s.io/apimachinery/pkg/watch" 23 "k8s.io/client-go/kubernetes/fake" 24 kubetesting "k8s.io/client-go/testing" 25 k8scache "k8s.io/client-go/tools/cache" 26 "k8s.io/utils/pointer" 27 28 "github.com/argoproj/argo-cd/common" 29 "github.com/argoproj/argo-cd/pkg/apiclient/application" 30 appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 31 apps "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake" 32 appinformer "github.com/argoproj/argo-cd/pkg/client/informers/externalversions" 33 "github.com/argoproj/argo-cd/reposerver/apiclient" 34 "github.com/argoproj/argo-cd/reposerver/apiclient/mocks" 35 "github.com/argoproj/argo-cd/server/rbacpolicy" 36 "github.com/argoproj/argo-cd/test" 37 "github.com/argoproj/argo-cd/util/assets" 38 "github.com/argoproj/argo-cd/util/cache" 39 "github.com/argoproj/argo-cd/util/db" 40 "github.com/argoproj/argo-cd/util/errors" 41 "github.com/argoproj/argo-cd/util/rbac" 42 "github.com/argoproj/argo-cd/util/settings" 43 ) 44 45 const ( 46 testNamespace = "default" 47 fakeRepoURL = "https://git.com/repo.git" 48 ) 49 50 func fakeRepo() *appsv1.Repository { 51 return &appsv1.Repository{ 52 Repo: fakeRepoURL, 53 } 54 } 55 56 func fakeCluster() *appsv1.Cluster { 57 return &appsv1.Cluster{ 58 Server: "https://cluster-api.com", 59 Name: "fake-cluster", 60 Config: appsv1.ClusterConfig{}, 61 } 62 } 63 64 func fakeAppList() *apiclient.AppList { 65 return &apiclient.AppList{ 66 Apps: map[string]string{ 67 "some/path": "Ksonnet", 68 }, 69 } 70 } 71 72 // return an ApplicationServiceServer which returns fake data 73 func newTestAppServer(objects ...runtime.Object) *Server { 74 kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{ 75 ObjectMeta: metav1.ObjectMeta{ 76 Namespace: testNamespace, 77 Name: "argocd-cm", 78 Labels: map[string]string{ 79 "app.kubernetes.io/part-of": "argocd", 80 }, 81 }, 82 }, &v1.Secret{ 83 ObjectMeta: metav1.ObjectMeta{ 84 Name: "argocd-secret", 85 Namespace: testNamespace, 86 }, 87 Data: map[string][]byte{ 88 "admin.password": []byte("test"), 89 "server.secretkey": []byte("test"), 90 }, 91 }) 92 ctx := context.Background() 93 db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset) 94 _, err := db.CreateRepository(ctx, fakeRepo()) 95 errors.CheckError(err) 96 _, err = db.CreateCluster(ctx, fakeCluster()) 97 errors.CheckError(err) 98 99 mockRepoServiceClient := mocks.RepoServerServiceClient{} 100 mockRepoServiceClient.On("ListApps", mock.Anything, mock.Anything).Return(fakeAppList(), nil) 101 mockRepoServiceClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(&apiclient.ManifestResponse{}, nil) 102 mockRepoServiceClient.On("GetAppDetails", mock.Anything, mock.Anything).Return(&apiclient.RepoAppDetailsResponse{}, nil) 103 104 mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mockRepoServiceClient} 105 106 defaultProj := &appsv1.AppProject{ 107 ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"}, 108 Spec: appsv1.AppProjectSpec{ 109 SourceRepos: []string{"*"}, 110 Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, 111 }, 112 } 113 myProj := &appsv1.AppProject{ 114 ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"}, 115 Spec: appsv1.AppProjectSpec{ 116 SourceRepos: []string{"*"}, 117 Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, 118 }, 119 } 120 projWithSyncWindows := &appsv1.AppProject{ 121 ObjectMeta: metav1.ObjectMeta{Name: "proj-maint", Namespace: "default"}, 122 Spec: appsv1.AppProjectSpec{ 123 SourceRepos: []string{"*"}, 124 Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}}, 125 SyncWindows: appsv1.SyncWindows{}, 126 }, 127 } 128 matchingWindow := &appsv1.SyncWindow{ 129 Kind: "allow", 130 Schedule: "* * * * *", 131 Duration: "1h", 132 Applications: []string{"test-app"}, 133 } 134 projWithSyncWindows.Spec.SyncWindows = append(projWithSyncWindows.Spec.SyncWindows, matchingWindow) 135 136 objects = append(objects, defaultProj, myProj, projWithSyncWindows) 137 138 fakeAppsClientset := apps.NewSimpleClientset(objects...) 139 factory := appinformer.NewFilteredSharedInformerFactory(fakeAppsClientset, 0, "", func(options *metav1.ListOptions) {}) 140 fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace) 141 142 enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil) 143 _ = enforcer.SetBuiltinPolicy(assets.BuiltinPolicyCSV) 144 enforcer.SetDefaultRole("role:admin") 145 enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims) 146 147 settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace) 148 149 // populate the app informer with the fake objects 150 appInformer := factory.Argoproj().V1alpha1().Applications().Informer() 151 // TODO(jessesuen): probably should return cancel function so tests can stop background informer 152 //ctx, cancel := context.WithCancel(context.Background()) 153 go appInformer.Run(ctx.Done()) 154 if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) { 155 panic("Timed out waiting for caches to sync") 156 } 157 158 projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer() 159 go projInformer.Run(ctx.Done()) 160 if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) { 161 panic("Timed out waiting for caches to sync") 162 } 163 164 server := NewServer( 165 testNamespace, 166 kubeclientset, 167 fakeAppsClientset, 168 factory.Argoproj().V1alpha1().Applications().Lister().Applications(testNamespace), 169 appInformer, 170 mockRepoClient, 171 nil, 172 &kubetest.MockKubectlCmd{}, 173 db, 174 enforcer, 175 sync.NewKeyLock(), 176 settingsMgr, 177 projInformer, 178 ) 179 return server.(*Server) 180 } 181 182 const fakeApp = ` 183 apiVersion: argoproj.io/v1alpha1 184 kind: Application 185 metadata: 186 name: test-app 187 namespace: default 188 spec: 189 source: 190 path: some/path 191 repoURL: https://github.com/argoproj/argocd-example-apps.git 192 targetRevision: HEAD 193 ksonnet: 194 environment: default 195 destination: 196 namespace: ` + test.FakeDestNamespace + ` 197 server: https://cluster-api.com 198 ` 199 200 const fakeAppWithDestName = ` 201 apiVersion: argoproj.io/v1alpha1 202 kind: Application 203 metadata: 204 name: test-app 205 namespace: default 206 spec: 207 source: 208 path: some/path 209 repoURL: https://github.com/argoproj/argocd-example-apps.git 210 targetRevision: HEAD 211 ksonnet: 212 environment: default 213 destination: 214 namespace: ` + test.FakeDestNamespace + ` 215 name: fake-cluster 216 ` 217 218 const fakeAppWithAnnotations = ` 219 apiVersion: argoproj.io/v1alpha1 220 kind: Application 221 metadata: 222 name: test-app 223 namespace: default 224 annotations: 225 test.annotation: test 226 spec: 227 source: 228 path: some/path 229 repoURL: https://github.com/argoproj/argocd-example-apps.git 230 targetRevision: HEAD 231 ksonnet: 232 environment: default 233 destination: 234 namespace: ` + test.FakeDestNamespace + ` 235 server: https://cluster-api.com 236 ` 237 238 func newTestAppWithDestName(opts ...func(app *appsv1.Application)) *appsv1.Application { 239 return createTestApp(fakeAppWithDestName, opts...) 240 } 241 242 func newTestApp(opts ...func(app *appsv1.Application)) *appsv1.Application { 243 return createTestApp(fakeApp, opts...) 244 } 245 246 func newTestAppWithAnnotations(opts ...func(app *appsv1.Application)) *appsv1.Application { 247 return createTestApp(fakeAppWithAnnotations, opts...) 248 } 249 250 func createTestApp(testApp string, opts ...func(app *appsv1.Application)) *appsv1.Application { 251 var app appsv1.Application 252 err := yaml.Unmarshal([]byte(testApp), &app) 253 if err != nil { 254 panic(err) 255 } 256 for i := range opts { 257 opts[i](&app) 258 } 259 return &app 260 } 261 262 func TestListApps(t *testing.T) { 263 appServer := newTestAppServer(newTestApp(func(app *appsv1.Application) { 264 app.Name = "bcd" 265 }), newTestApp(func(app *appsv1.Application) { 266 app.Name = "abc" 267 }), newTestApp(func(app *appsv1.Application) { 268 app.Name = "def" 269 })) 270 271 res, err := appServer.List(context.Background(), &application.ApplicationQuery{}) 272 assert.NoError(t, err) 273 var names []string 274 for i := range res.Items { 275 names = append(names, res.Items[i].Name) 276 } 277 assert.Equal(t, []string{"abc", "bcd", "def"}, names) 278 } 279 280 func TestCreateApp(t *testing.T) { 281 testApp := newTestApp() 282 appServer := newTestAppServer() 283 testApp.Spec.Project = "" 284 createReq := application.ApplicationCreateRequest{ 285 Application: *testApp, 286 } 287 app, err := appServer.Create(context.Background(), &createReq) 288 assert.NoError(t, err) 289 assert.NotNil(t, app) 290 assert.NotNil(t, app.Spec) 291 assert.Equal(t, app.Spec.Project, "default") 292 } 293 294 func TestCreateAppWithDestName(t *testing.T) { 295 appServer := newTestAppServer() 296 testApp := newTestAppWithDestName() 297 createReq := application.ApplicationCreateRequest{ 298 Application: *testApp, 299 } 300 app, err := appServer.Create(context.Background(), &createReq) 301 assert.NoError(t, err) 302 assert.NotNil(t, app) 303 assert.Equal(t, app.Spec.Destination.Server, "https://cluster-api.com") 304 } 305 306 func TestUpdateApp(t *testing.T) { 307 testApp := newTestApp() 308 appServer := newTestAppServer(testApp) 309 testApp.Spec.Project = "" 310 app, err := appServer.Update(context.Background(), &application.ApplicationUpdateRequest{ 311 Application: testApp, 312 }) 313 assert.Nil(t, err) 314 assert.Equal(t, app.Spec.Project, "default") 315 } 316 317 func TestUpdateAppSpec(t *testing.T) { 318 testApp := newTestApp() 319 appServer := newTestAppServer(testApp) 320 testApp.Spec.Project = "" 321 spec, err := appServer.UpdateSpec(context.Background(), &application.ApplicationUpdateSpecRequest{ 322 Name: &testApp.Name, 323 Spec: testApp.Spec, 324 }) 325 assert.NoError(t, err) 326 assert.Equal(t, "default", spec.Project) 327 app, err := appServer.Get(context.Background(), &application.ApplicationQuery{Name: &testApp.Name}) 328 assert.NoError(t, err) 329 assert.Equal(t, "default", app.Spec.Project) 330 } 331 332 func TestDeleteApp(t *testing.T) { 333 ctx := context.Background() 334 appServer := newTestAppServer() 335 createReq := application.ApplicationCreateRequest{ 336 Application: *newTestApp(), 337 } 338 app, err := appServer.Create(ctx, &createReq) 339 assert.Nil(t, err) 340 341 app, err = appServer.Get(ctx, &application.ApplicationQuery{Name: &app.Name}) 342 assert.Nil(t, err) 343 assert.NotNil(t, app) 344 345 fakeAppCs := appServer.appclientset.(*apps.Clientset) 346 // this removes the default */* reactor so we can set our own patch/delete reactor 347 fakeAppCs.ReactionChain = nil 348 patched := false 349 deleted := false 350 fakeAppCs.AddReactor("patch", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { 351 patched = true 352 return true, nil, nil 353 }) 354 fakeAppCs.AddReactor("delete", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { 355 deleted = true 356 return true, nil, nil 357 }) 358 appServer.appclientset = fakeAppCs 359 360 trueVar := true 361 _, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &trueVar}) 362 assert.Nil(t, err) 363 assert.True(t, patched) 364 assert.True(t, deleted) 365 366 // now call delete with cascade=false. patch should not be called 367 falseVar := false 368 patched = false 369 deleted = false 370 _, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &falseVar}) 371 assert.Nil(t, err) 372 assert.False(t, patched) 373 assert.True(t, deleted) 374 } 375 376 func TestDeleteApp_InvalidName(t *testing.T) { 377 appServer := newTestAppServer() 378 _, err := appServer.Delete(context.Background(), &application.ApplicationDeleteRequest{ 379 Name: pointer.StringPtr("foo"), 380 }) 381 if !assert.Error(t, err) { 382 return 383 } 384 assert.True(t, apierrors.IsNotFound(err)) 385 } 386 387 func TestSyncAndTerminate(t *testing.T) { 388 ctx := context.Background() 389 appServer := newTestAppServer() 390 testApp := newTestApp() 391 testApp.Spec.Source.RepoURL = "https://github.com/argoproj/argo-cd.git" 392 createReq := application.ApplicationCreateRequest{ 393 Application: *testApp, 394 } 395 app, err := appServer.Create(ctx, &createReq) 396 assert.Nil(t, err) 397 398 app, err = appServer.Sync(ctx, &application.ApplicationSyncRequest{Name: &app.Name}) 399 assert.Nil(t, err) 400 assert.NotNil(t, app) 401 assert.NotNil(t, app.Operation) 402 403 events, err := appServer.kubeclientset.CoreV1().Events(appServer.ns).List(context.Background(), metav1.ListOptions{}) 404 assert.Nil(t, err) 405 event := events.Items[1] 406 407 assert.Regexp(t, ".*initiated sync to HEAD \\([0-9A-Fa-f]{40}\\).*", event.Message) 408 409 // set status.operationState to pretend that an operation has started by controller 410 app.Status.OperationState = &appsv1.OperationState{ 411 Operation: *app.Operation, 412 Phase: synccommon.OperationRunning, 413 StartedAt: metav1.NewTime(time.Now()), 414 } 415 _, err = appServer.appclientset.ArgoprojV1alpha1().Applications(appServer.ns).Update(context.Background(), app, metav1.UpdateOptions{}) 416 assert.Nil(t, err) 417 418 resp, err := appServer.TerminateOperation(ctx, &application.OperationTerminateRequest{Name: &app.Name}) 419 assert.Nil(t, err) 420 assert.NotNil(t, resp) 421 422 app, err = appServer.Get(ctx, &application.ApplicationQuery{Name: &app.Name}) 423 assert.Nil(t, err) 424 assert.NotNil(t, app) 425 assert.Equal(t, synccommon.OperationTerminating, app.Status.OperationState.Phase) 426 } 427 428 func TestSyncHelm(t *testing.T) { 429 ctx := context.Background() 430 appServer := newTestAppServer() 431 testApp := newTestApp() 432 testApp.Spec.Source.RepoURL = "https://argoproj.github.io/argo-helm" 433 testApp.Spec.Source.Path = "" 434 testApp.Spec.Source.Chart = "argo-cd" 435 testApp.Spec.Source.TargetRevision = "0.7.*" 436 437 app, err := appServer.Create(ctx, &application.ApplicationCreateRequest{Application: *testApp}) 438 assert.NoError(t, err) 439 440 app, err = appServer.Sync(ctx, &application.ApplicationSyncRequest{Name: &app.Name}) 441 assert.NoError(t, err) 442 assert.NotNil(t, app) 443 assert.NotNil(t, app.Operation) 444 445 events, err := appServer.kubeclientset.CoreV1().Events(appServer.ns).List(context.Background(), metav1.ListOptions{}) 446 assert.NoError(t, err) 447 assert.Equal(t, "Unknown user initiated sync to 0.7.* (0.7.2)", events.Items[1].Message) 448 } 449 450 func TestRollbackApp(t *testing.T) { 451 testApp := newTestApp() 452 testApp.Status.History = []appsv1.RevisionHistory{{ 453 ID: 1, 454 Revision: "abc", 455 Source: *testApp.Spec.Source.DeepCopy(), 456 }} 457 appServer := newTestAppServer(testApp) 458 459 updatedApp, err := appServer.Rollback(context.Background(), &application.ApplicationRollbackRequest{ 460 Name: &testApp.Name, 461 ID: 1, 462 }) 463 464 assert.Nil(t, err) 465 466 assert.NotNil(t, updatedApp.Operation) 467 assert.NotNil(t, updatedApp.Operation.Sync) 468 assert.NotNil(t, updatedApp.Operation.Sync.Source) 469 assert.Equal(t, "abc", updatedApp.Operation.Sync.Revision) 470 } 471 472 func TestUpdateAppProject(t *testing.T) { 473 testApp := newTestApp() 474 ctx := context.Background() 475 // nolint:staticcheck 476 ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"}) 477 appServer := newTestAppServer(testApp) 478 appServer.enf.SetDefaultRole("") 479 480 // Verify normal update works (without changing project) 481 _ = appServer.enf.SetBuiltinPolicy(`p, admin, applications, update, default/test-app, allow`) 482 _, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp}) 483 assert.NoError(t, err) 484 485 // Verify caller cannot update to another project 486 testApp.Spec.Project = "my-proj" 487 _, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp}) 488 assert.Equal(t, status.Code(err), codes.PermissionDenied) 489 490 // Verify inability to change projects without create privileges in new project 491 _ = appServer.enf.SetBuiltinPolicy(` 492 p, admin, applications, update, default/test-app, allow 493 p, admin, applications, update, my-proj/test-app, allow 494 `) 495 _, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp}) 496 assert.Equal(t, status.Code(err), codes.PermissionDenied) 497 498 // Verify inability to change projects without update privileges in new project 499 _ = appServer.enf.SetBuiltinPolicy(` 500 p, admin, applications, update, default/test-app, allow 501 p, admin, applications, create, my-proj/test-app, allow 502 `) 503 _, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp}) 504 assert.Equal(t, status.Code(err), codes.PermissionDenied) 505 506 // Verify inability to change projects without update privileges in old project 507 _ = appServer.enf.SetBuiltinPolicy(` 508 p, admin, applications, create, my-proj/test-app, allow 509 p, admin, applications, update, my-proj/test-app, allow 510 `) 511 _, err = appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp}) 512 assert.Equal(t, status.Code(err), codes.PermissionDenied) 513 514 // Verify can update project with proper permissions 515 _ = appServer.enf.SetBuiltinPolicy(` 516 p, admin, applications, update, default/test-app, allow 517 p, admin, applications, create, my-proj/test-app, allow 518 p, admin, applications, update, my-proj/test-app, allow 519 `) 520 updatedApp, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp}) 521 assert.NoError(t, err) 522 assert.Equal(t, "my-proj", updatedApp.Spec.Project) 523 } 524 525 func TestAppJsonPatch(t *testing.T) { 526 testApp := newTestAppWithAnnotations() 527 ctx := context.Background() 528 // nolint:staticcheck 529 ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"}) 530 appServer := newTestAppServer(testApp) 531 appServer.enf.SetDefaultRole("") 532 533 app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: "garbage"}) 534 assert.Error(t, err) 535 assert.Nil(t, app) 536 537 app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: "[]"}) 538 assert.NoError(t, err) 539 assert.NotNil(t, app) 540 541 app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: `[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`}) 542 assert.NoError(t, err) 543 assert.Equal(t, "foo", app.Spec.Source.Path) 544 545 app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: `[{"op": "remove", "path": "/metadata/annotations/test.annotation"}]`}) 546 assert.NoError(t, err) 547 assert.NotContains(t, app.Annotations, "test.annotation") 548 } 549 550 func TestAppMergePatch(t *testing.T) { 551 testApp := newTestApp() 552 ctx := context.Background() 553 // nolint:staticcheck 554 ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"}) 555 appServer := newTestAppServer(testApp) 556 appServer.enf.SetDefaultRole("") 557 558 app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{ 559 Name: &testApp.Name, Patch: `{"spec": { "source": { "path": "foo" } }}`, PatchType: "merge"}) 560 assert.NoError(t, err) 561 assert.Equal(t, "foo", app.Spec.Source.Path) 562 } 563 564 func TestServer_GetApplicationSyncWindowsState(t *testing.T) { 565 t.Run("Active", func(t *testing.T) { 566 testApp := newTestApp() 567 testApp.Spec.Project = "proj-maint" 568 appServer := newTestAppServer(testApp) 569 570 active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name}) 571 assert.NoError(t, err) 572 assert.Equal(t, 1, len(active.ActiveWindows)) 573 }) 574 t.Run("Inactive", func(t *testing.T) { 575 testApp := newTestApp() 576 testApp.Spec.Project = "default" 577 appServer := newTestAppServer(testApp) 578 579 active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name}) 580 assert.NoError(t, err) 581 assert.Equal(t, 0, len(active.ActiveWindows)) 582 }) 583 t.Run("ProjectDoesNotExist", func(t *testing.T) { 584 testApp := newTestApp() 585 testApp.Spec.Project = "none" 586 appServer := newTestAppServer(testApp) 587 588 active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name}) 589 assert.Contains(t, err.Error(), "not found") 590 assert.Nil(t, active) 591 }) 592 } 593 594 func TestGetCachedAppState(t *testing.T) { 595 testApp := newTestApp() 596 testApp.ObjectMeta.ResourceVersion = "1" 597 testApp.Spec.Project = "none" 598 appServer := newTestAppServer(testApp) 599 600 fakeClientSet := appServer.appclientset.(*apps.Clientset) 601 602 t.Run("NoError", func(t *testing.T) { 603 err := appServer.getCachedAppState(context.Background(), testApp, func() error { 604 return nil 605 }) 606 assert.NoError(t, err) 607 }) 608 609 t.Run("CacheMissErrorTriggersRefresh", func(t *testing.T) { 610 retryCount := 0 611 patched := false 612 watcher := watch.NewFakeWithChanSize(1, true) 613 614 // Configure fakeClientSet within lock, before requesting cached app state, to avoid data race 615 { 616 fakeClientSet.Lock() 617 fakeClientSet.ReactionChain = nil 618 fakeClientSet.WatchReactionChain = nil 619 fakeClientSet.AddReactor("patch", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { 620 patched = true 621 updated := testApp.DeepCopy() 622 updated.ResourceVersion = "2" 623 appServer.appBroadcaster.OnUpdate(testApp, updated) 624 return true, testApp, nil 625 }) 626 fakeClientSet.AddWatchReactor("applications", func(action kubetesting.Action) (handled bool, ret watch.Interface, err error) { 627 return true, watcher, nil 628 }) 629 fakeClientSet.Unlock() 630 } 631 632 err := appServer.getCachedAppState(context.Background(), testApp, func() error { 633 res := cache.ErrCacheMiss 634 if retryCount == 1 { 635 res = nil 636 } 637 retryCount++ 638 return res 639 }) 640 assert.Equal(t, nil, err) 641 assert.Equal(t, 2, retryCount) 642 assert.True(t, patched) 643 }) 644 645 t.Run("NonCacheErrorDoesNotTriggerRefresh", func(t *testing.T) { 646 randomError := coreerrors.New("random error") 647 err := appServer.getCachedAppState(context.Background(), testApp, func() error { 648 return randomError 649 }) 650 assert.Equal(t, randomError, err) 651 }) 652 } 653 654 func TestSplitStatusPatch(t *testing.T) { 655 specPatch := `{"spec":{"aaa":"bbb"}}` 656 statusPatch := `{"status":{"ccc":"ddd"}}` 657 { 658 nonStatus, status, err := splitStatusPatch([]byte(specPatch)) 659 assert.NoError(t, err) 660 assert.Equal(t, specPatch, string(nonStatus)) 661 assert.Nil(t, status) 662 } 663 { 664 nonStatus, status, err := splitStatusPatch([]byte(statusPatch)) 665 assert.NoError(t, err) 666 assert.Nil(t, nonStatus) 667 assert.Equal(t, statusPatch, string(status)) 668 } 669 { 670 bothPatch := `{"spec":{"aaa":"bbb"},"status":{"ccc":"ddd"}}` 671 nonStatus, status, err := splitStatusPatch([]byte(bothPatch)) 672 assert.NoError(t, err) 673 assert.Equal(t, specPatch, string(nonStatus)) 674 assert.Equal(t, statusPatch, string(status)) 675 } 676 { 677 otherFields := `{"operation":{"eee":"fff"},"spec":{"aaa":"bbb"},"status":{"ccc":"ddd"}}` 678 nonStatus, status, err := splitStatusPatch([]byte(otherFields)) 679 assert.NoError(t, err) 680 assert.Equal(t, `{"operation":{"eee":"fff"},"spec":{"aaa":"bbb"}}`, string(nonStatus)) 681 assert.Equal(t, statusPatch, string(status)) 682 } 683 }