github.com/argoproj/argo-cd/v2@v2.10.9/applicationset/controllers/applicationset_controller_test.go (about)

     1  package controllers
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"reflect"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	log "github.com/sirupsen/logrus"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/mock"
    15  	corev1 "k8s.io/api/core/v1"
    16  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/runtime"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	"k8s.io/apimachinery/pkg/util/intstr"
    21  	kubefake "k8s.io/client-go/kubernetes/fake"
    22  	k8scache "k8s.io/client-go/tools/cache"
    23  	"k8s.io/client-go/tools/record"
    24  	ctrl "sigs.k8s.io/controller-runtime"
    25  	"sigs.k8s.io/controller-runtime/pkg/cache"
    26  	crtclient "sigs.k8s.io/controller-runtime/pkg/client"
    27  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    28  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    29  	"sigs.k8s.io/controller-runtime/pkg/event"
    30  
    31  	"github.com/argoproj/gitops-engine/pkg/health"
    32  	"github.com/argoproj/gitops-engine/pkg/sync/common"
    33  
    34  	"github.com/argoproj/argo-cd/v2/applicationset/generators"
    35  	"github.com/argoproj/argo-cd/v2/applicationset/utils"
    36  
    37  	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    38  	appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
    39  	"github.com/argoproj/argo-cd/v2/util/collections"
    40  	dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
    41  
    42  	"github.com/argoproj/argo-cd/v2/pkg/apis/application"
    43  )
    44  
    45  type fakeStore struct {
    46  	k8scache.Store
    47  }
    48  
    49  func (f *fakeStore) Update(obj interface{}) error {
    50  	return nil
    51  }
    52  
    53  type fakeInformer struct {
    54  	k8scache.SharedInformer
    55  }
    56  
    57  func (f *fakeInformer) AddIndexers(indexers k8scache.Indexers) error {
    58  	return nil
    59  }
    60  
    61  func (f *fakeInformer) GetStore() k8scache.Store {
    62  	return &fakeStore{}
    63  }
    64  
    65  type fakeCache struct {
    66  	cache.Cache
    67  }
    68  
    69  func (f *fakeCache) GetInformer(ctx context.Context, obj crtclient.Object) (cache.Informer, error) {
    70  	return &fakeInformer{}, nil
    71  }
    72  
    73  type generatorMock struct {
    74  	mock.Mock
    75  }
    76  
    77  func (g *generatorMock) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
    78  	args := g.Called(appSetGenerator)
    79  
    80  	return args.Get(0).(*v1alpha1.ApplicationSetTemplate)
    81  }
    82  
    83  func (g *generatorMock) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, _ *v1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
    84  	args := g.Called(appSetGenerator)
    85  
    86  	return args.Get(0).([]map[string]interface{}), args.Error(1)
    87  }
    88  
    89  func (g *generatorMock) Replace(tmpl string, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) {
    90  	args := g.Called(tmpl, replaceMap, useGoTemplate, goTemplateOptions)
    91  
    92  	return args.Get(0).(string), args.Error(1)
    93  }
    94  
    95  type rendererMock struct {
    96  	mock.Mock
    97  }
    98  
    99  func (g *generatorMock) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
   100  	args := g.Called(appSetGenerator)
   101  
   102  	return args.Get(0).(time.Duration)
   103  }
   104  
   105  func (r *rendererMock) RenderTemplateParams(tmpl *v1alpha1.Application, syncPolicy *v1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (*v1alpha1.Application, error) {
   106  	args := r.Called(tmpl, params, useGoTemplate, goTemplateOptions)
   107  
   108  	if args.Error(1) != nil {
   109  		return nil, args.Error(1)
   110  	}
   111  
   112  	return args.Get(0).(*v1alpha1.Application), args.Error(1)
   113  
   114  }
   115  
   116  func (r *rendererMock) Replace(tmpl string, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) (string, error) {
   117  	args := r.Called(tmpl, replaceMap, useGoTemplate, goTemplateOptions)
   118  
   119  	return args.Get(0).(string), args.Error(1)
   120  }
   121  
   122  func TestExtractApplications(t *testing.T) {
   123  	scheme := runtime.NewScheme()
   124  	err := v1alpha1.AddToScheme(scheme)
   125  	assert.Nil(t, err)
   126  
   127  	err = v1alpha1.AddToScheme(scheme)
   128  	assert.Nil(t, err)
   129  
   130  	for _, c := range []struct {
   131  		name                string
   132  		params              []map[string]interface{}
   133  		template            v1alpha1.ApplicationSetTemplate
   134  		generateParamsError error
   135  		rendererError       error
   136  		expectErr           bool
   137  		expectedReason      v1alpha1.ApplicationSetReasonType
   138  	}{
   139  		{
   140  			name:   "Generate two applications",
   141  			params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}},
   142  			template: v1alpha1.ApplicationSetTemplate{
   143  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   144  					Name:      "name",
   145  					Namespace: "namespace",
   146  					Labels:    map[string]string{"label_name": "label_value"},
   147  				},
   148  				Spec: v1alpha1.ApplicationSpec{},
   149  			},
   150  			expectedReason: "",
   151  		},
   152  		{
   153  			name:                "Handles error from the generator",
   154  			generateParamsError: fmt.Errorf("error"),
   155  			expectErr:           true,
   156  			expectedReason:      v1alpha1.ApplicationSetReasonApplicationParamsGenerationError,
   157  		},
   158  		{
   159  			name:   "Handles error from the render",
   160  			params: []map[string]interface{}{{"name": "app1"}, {"name": "app2"}},
   161  			template: v1alpha1.ApplicationSetTemplate{
   162  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   163  					Name:      "name",
   164  					Namespace: "namespace",
   165  					Labels:    map[string]string{"label_name": "label_value"},
   166  				},
   167  				Spec: v1alpha1.ApplicationSpec{},
   168  			},
   169  			rendererError:  fmt.Errorf("error"),
   170  			expectErr:      true,
   171  			expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
   172  		},
   173  	} {
   174  		cc := c
   175  		app := v1alpha1.Application{
   176  			ObjectMeta: metav1.ObjectMeta{
   177  				Name: "test",
   178  			},
   179  		}
   180  
   181  		t.Run(cc.name, func(t *testing.T) {
   182  
   183  			appSet := &v1alpha1.ApplicationSet{
   184  				ObjectMeta: metav1.ObjectMeta{
   185  					Name:      "name",
   186  					Namespace: "namespace",
   187  				},
   188  			}
   189  
   190  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(appSet).Build()
   191  
   192  			generatorMock := generatorMock{}
   193  			generator := v1alpha1.ApplicationSetGenerator{
   194  				List: &v1alpha1.ListGenerator{},
   195  			}
   196  
   197  			generatorMock.On("GenerateParams", &generator).
   198  				Return(cc.params, cc.generateParamsError)
   199  
   200  			generatorMock.On("GetTemplate", &generator).
   201  				Return(&v1alpha1.ApplicationSetTemplate{})
   202  
   203  			rendererMock := rendererMock{}
   204  
   205  			var expectedApps []v1alpha1.Application
   206  
   207  			if cc.generateParamsError == nil {
   208  				for _, p := range cc.params {
   209  
   210  					if cc.rendererError != nil {
   211  						rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)).
   212  							Return(nil, cc.rendererError)
   213  					} else {
   214  						rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)).
   215  							Return(&app, nil)
   216  						expectedApps = append(expectedApps, app)
   217  					}
   218  				}
   219  			}
   220  
   221  			r := ApplicationSetReconciler{
   222  				Client:   client,
   223  				Scheme:   scheme,
   224  				Recorder: record.NewFakeRecorder(1),
   225  				Generators: map[string]generators.Generator{
   226  					"List": &generatorMock,
   227  				},
   228  				Renderer:      &rendererMock,
   229  				KubeClientset: kubefake.NewSimpleClientset(),
   230  				Cache:         &fakeCache{},
   231  			}
   232  
   233  			got, reason, err := r.generateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
   234  				ObjectMeta: metav1.ObjectMeta{
   235  					Name:      "name",
   236  					Namespace: "namespace",
   237  				},
   238  				Spec: v1alpha1.ApplicationSetSpec{
   239  					Generators: []v1alpha1.ApplicationSetGenerator{generator},
   240  					Template:   cc.template,
   241  				},
   242  			})
   243  
   244  			if cc.expectErr {
   245  				assert.Error(t, err)
   246  			} else {
   247  				assert.NoError(t, err)
   248  			}
   249  			assert.Equal(t, expectedApps, got)
   250  			assert.Equal(t, cc.expectedReason, reason)
   251  			generatorMock.AssertNumberOfCalls(t, "GenerateParams", 1)
   252  
   253  			if cc.generateParamsError == nil {
   254  				rendererMock.AssertNumberOfCalls(t, "RenderTemplateParams", len(cc.params))
   255  			}
   256  
   257  		})
   258  	}
   259  
   260  }
   261  
   262  func TestMergeTemplateApplications(t *testing.T) {
   263  	scheme := runtime.NewScheme()
   264  	_ = v1alpha1.AddToScheme(scheme)
   265  	_ = v1alpha1.AddToScheme(scheme)
   266  
   267  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
   268  
   269  	for _, c := range []struct {
   270  		name             string
   271  		params           []map[string]interface{}
   272  		template         v1alpha1.ApplicationSetTemplate
   273  		overrideTemplate v1alpha1.ApplicationSetTemplate
   274  		expectedMerged   v1alpha1.ApplicationSetTemplate
   275  		expectedApps     []v1alpha1.Application
   276  	}{
   277  		{
   278  			name:   "Generate app",
   279  			params: []map[string]interface{}{{"name": "app1"}},
   280  			template: v1alpha1.ApplicationSetTemplate{
   281  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   282  					Name:      "name",
   283  					Namespace: "namespace",
   284  					Labels:    map[string]string{"label_name": "label_value"},
   285  				},
   286  				Spec: v1alpha1.ApplicationSpec{},
   287  			},
   288  			overrideTemplate: v1alpha1.ApplicationSetTemplate{
   289  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   290  					Name:   "test",
   291  					Labels: map[string]string{"foo": "bar"},
   292  				},
   293  				Spec: v1alpha1.ApplicationSpec{},
   294  			},
   295  			expectedMerged: v1alpha1.ApplicationSetTemplate{
   296  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   297  					Name:      "test",
   298  					Namespace: "namespace",
   299  					Labels:    map[string]string{"label_name": "label_value", "foo": "bar"},
   300  				},
   301  				Spec: v1alpha1.ApplicationSpec{},
   302  			},
   303  			expectedApps: []v1alpha1.Application{
   304  				{
   305  					ObjectMeta: metav1.ObjectMeta{
   306  						Name:      "test",
   307  						Namespace: "test",
   308  						Labels:    map[string]string{"foo": "bar"},
   309  					},
   310  					Spec: v1alpha1.ApplicationSpec{},
   311  				},
   312  			},
   313  		},
   314  	} {
   315  		cc := c
   316  
   317  		t.Run(cc.name, func(t *testing.T) {
   318  
   319  			generatorMock := generatorMock{}
   320  			generator := v1alpha1.ApplicationSetGenerator{
   321  				List: &v1alpha1.ListGenerator{},
   322  			}
   323  
   324  			generatorMock.On("GenerateParams", &generator).
   325  				Return(cc.params, nil)
   326  
   327  			generatorMock.On("GetTemplate", &generator).
   328  				Return(&cc.overrideTemplate)
   329  
   330  			rendererMock := rendererMock{}
   331  
   332  			rendererMock.On("RenderTemplateParams", getTempApplication(cc.expectedMerged), cc.params[0], false, []string(nil)).
   333  				Return(&cc.expectedApps[0], nil)
   334  
   335  			r := ApplicationSetReconciler{
   336  				Client:   client,
   337  				Scheme:   scheme,
   338  				Recorder: record.NewFakeRecorder(1),
   339  				Generators: map[string]generators.Generator{
   340  					"List": &generatorMock,
   341  				},
   342  				Renderer:      &rendererMock,
   343  				KubeClientset: kubefake.NewSimpleClientset(),
   344  			}
   345  
   346  			got, _, _ := r.generateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
   347  				ObjectMeta: metav1.ObjectMeta{
   348  					Name:      "name",
   349  					Namespace: "namespace",
   350  				},
   351  				Spec: v1alpha1.ApplicationSetSpec{
   352  					Generators: []v1alpha1.ApplicationSetGenerator{generator},
   353  					Template:   cc.template,
   354  				},
   355  			},
   356  			)
   357  
   358  			assert.Equal(t, cc.expectedApps, got)
   359  		})
   360  	}
   361  
   362  }
   363  
   364  func TestCreateOrUpdateInCluster(t *testing.T) {
   365  
   366  	scheme := runtime.NewScheme()
   367  	err := v1alpha1.AddToScheme(scheme)
   368  	assert.Nil(t, err)
   369  
   370  	err = v1alpha1.AddToScheme(scheme)
   371  	assert.Nil(t, err)
   372  
   373  	for _, c := range []struct {
   374  		// name is human-readable test name
   375  		name string
   376  		// appSet is the ApplicationSet we are generating resources for
   377  		appSet v1alpha1.ApplicationSet
   378  		// existingApps are the apps that already exist on the cluster
   379  		existingApps []v1alpha1.Application
   380  		// desiredApps are the generated apps to create/update
   381  		desiredApps []v1alpha1.Application
   382  		// expected is what we expect the cluster Applications to look like, after createOrUpdateInCluster
   383  		expected []v1alpha1.Application
   384  	}{
   385  		{
   386  			name: "Create an app that doesn't exist",
   387  			appSet: v1alpha1.ApplicationSet{
   388  				ObjectMeta: metav1.ObjectMeta{
   389  					Name:      "name",
   390  					Namespace: "namespace",
   391  				},
   392  			},
   393  			existingApps: nil,
   394  			desiredApps: []v1alpha1.Application{
   395  				{
   396  					ObjectMeta: metav1.ObjectMeta{
   397  						Name: "app1",
   398  					},
   399  				},
   400  			},
   401  			expected: []v1alpha1.Application{
   402  				{
   403  					TypeMeta: metav1.TypeMeta{
   404  						Kind:       application.ApplicationKind,
   405  						APIVersion: "argoproj.io/v1alpha1",
   406  					},
   407  					ObjectMeta: metav1.ObjectMeta{
   408  						Name:            "app1",
   409  						Namespace:       "namespace",
   410  						ResourceVersion: "1",
   411  					},
   412  					Spec: v1alpha1.ApplicationSpec{Project: "default"},
   413  				},
   414  			},
   415  		},
   416  		{
   417  			name: "Update an existing app with a different project name",
   418  			appSet: v1alpha1.ApplicationSet{
   419  				ObjectMeta: metav1.ObjectMeta{
   420  					Name:      "name",
   421  					Namespace: "namespace",
   422  				},
   423  				Spec: v1alpha1.ApplicationSetSpec{
   424  					Template: v1alpha1.ApplicationSetTemplate{
   425  						Spec: v1alpha1.ApplicationSpec{
   426  							Project: "project",
   427  						},
   428  					},
   429  				},
   430  			},
   431  			existingApps: []v1alpha1.Application{
   432  				{
   433  					TypeMeta: metav1.TypeMeta{
   434  						Kind:       application.ApplicationKind,
   435  						APIVersion: "argoproj.io/v1alpha1",
   436  					},
   437  					ObjectMeta: metav1.ObjectMeta{
   438  						Name:            "app1",
   439  						Namespace:       "namespace",
   440  						ResourceVersion: "2",
   441  					},
   442  					Spec: v1alpha1.ApplicationSpec{
   443  						Project: "test",
   444  					},
   445  				},
   446  			},
   447  			desiredApps: []v1alpha1.Application{
   448  				{
   449  					ObjectMeta: metav1.ObjectMeta{
   450  						Name: "app1",
   451  					},
   452  					Spec: v1alpha1.ApplicationSpec{
   453  						Project: "project",
   454  					},
   455  				},
   456  			},
   457  			expected: []v1alpha1.Application{
   458  				{
   459  					TypeMeta: metav1.TypeMeta{
   460  						Kind:       application.ApplicationKind,
   461  						APIVersion: "argoproj.io/v1alpha1",
   462  					},
   463  					ObjectMeta: metav1.ObjectMeta{
   464  						Name:            "app1",
   465  						Namespace:       "namespace",
   466  						ResourceVersion: "3",
   467  					},
   468  					Spec: v1alpha1.ApplicationSpec{
   469  						Project: "project",
   470  					},
   471  				},
   472  			},
   473  		},
   474  		{
   475  			name: "Create a new app and check it doesn't replace the existing app",
   476  			appSet: v1alpha1.ApplicationSet{
   477  				ObjectMeta: metav1.ObjectMeta{
   478  					Name:      "name",
   479  					Namespace: "namespace",
   480  				},
   481  				Spec: v1alpha1.ApplicationSetSpec{
   482  					Template: v1alpha1.ApplicationSetTemplate{
   483  						Spec: v1alpha1.ApplicationSpec{
   484  							Project: "project",
   485  						},
   486  					},
   487  				},
   488  			},
   489  			existingApps: []v1alpha1.Application{
   490  				{
   491  					TypeMeta: metav1.TypeMeta{
   492  						Kind:       application.ApplicationKind,
   493  						APIVersion: "argoproj.io/v1alpha1",
   494  					},
   495  					ObjectMeta: metav1.ObjectMeta{
   496  						Name:            "app1",
   497  						Namespace:       "namespace",
   498  						ResourceVersion: "2",
   499  					},
   500  					Spec: v1alpha1.ApplicationSpec{
   501  						Project: "test",
   502  					},
   503  				},
   504  			},
   505  			desiredApps: []v1alpha1.Application{
   506  				{
   507  					ObjectMeta: metav1.ObjectMeta{
   508  						Name: "app2",
   509  					},
   510  					Spec: v1alpha1.ApplicationSpec{
   511  						Project: "project",
   512  					},
   513  				},
   514  			},
   515  			expected: []v1alpha1.Application{
   516  				{
   517  					TypeMeta: metav1.TypeMeta{
   518  						Kind:       application.ApplicationKind,
   519  						APIVersion: "argoproj.io/v1alpha1",
   520  					},
   521  					ObjectMeta: metav1.ObjectMeta{
   522  						Name:            "app2",
   523  						Namespace:       "namespace",
   524  						ResourceVersion: "1",
   525  					},
   526  					Spec: v1alpha1.ApplicationSpec{
   527  						Project: "project",
   528  					},
   529  				},
   530  			},
   531  		},
   532  		{
   533  			name: "Ensure that labels and annotations are added (via update) into an exiting application",
   534  			appSet: v1alpha1.ApplicationSet{
   535  				ObjectMeta: metav1.ObjectMeta{
   536  					Name:      "name",
   537  					Namespace: "namespace",
   538  				},
   539  				Spec: v1alpha1.ApplicationSetSpec{
   540  					Template: v1alpha1.ApplicationSetTemplate{
   541  						Spec: v1alpha1.ApplicationSpec{
   542  							Project: "project",
   543  						},
   544  					},
   545  				},
   546  			},
   547  			existingApps: []v1alpha1.Application{
   548  				{
   549  					TypeMeta: metav1.TypeMeta{
   550  						Kind:       application.ApplicationKind,
   551  						APIVersion: "argoproj.io/v1alpha1",
   552  					},
   553  					ObjectMeta: metav1.ObjectMeta{
   554  						Name:            "app1",
   555  						Namespace:       "namespace",
   556  						ResourceVersion: "2",
   557  					},
   558  					Spec: v1alpha1.ApplicationSpec{
   559  						Project: "project",
   560  					},
   561  				},
   562  			},
   563  			desiredApps: []v1alpha1.Application{
   564  				{
   565  					ObjectMeta: metav1.ObjectMeta{
   566  						Name:        "app1",
   567  						Labels:      map[string]string{"label-key": "label-value"},
   568  						Annotations: map[string]string{"annot-key": "annot-value"},
   569  					},
   570  					Spec: v1alpha1.ApplicationSpec{
   571  						Project: "project",
   572  					},
   573  				},
   574  			},
   575  			expected: []v1alpha1.Application{
   576  				{
   577  					TypeMeta: metav1.TypeMeta{
   578  						Kind:       application.ApplicationKind,
   579  						APIVersion: "argoproj.io/v1alpha1",
   580  					},
   581  					ObjectMeta: metav1.ObjectMeta{
   582  						Name:            "app1",
   583  						Namespace:       "namespace",
   584  						Labels:          map[string]string{"label-key": "label-value"},
   585  						Annotations:     map[string]string{"annot-key": "annot-value"},
   586  						ResourceVersion: "3",
   587  					},
   588  					Spec: v1alpha1.ApplicationSpec{
   589  						Project: "project",
   590  					},
   591  				},
   592  			},
   593  		},
   594  		{
   595  			name: "Ensure that labels and annotations are removed from an existing app",
   596  			appSet: v1alpha1.ApplicationSet{
   597  				ObjectMeta: metav1.ObjectMeta{
   598  					Name:      "name",
   599  					Namespace: "namespace",
   600  				},
   601  				Spec: v1alpha1.ApplicationSetSpec{
   602  					Template: v1alpha1.ApplicationSetTemplate{
   603  						Spec: v1alpha1.ApplicationSpec{
   604  							Project: "project",
   605  						},
   606  					},
   607  				},
   608  			},
   609  			existingApps: []v1alpha1.Application{
   610  				{
   611  					TypeMeta: metav1.TypeMeta{
   612  						Kind:       application.ApplicationKind,
   613  						APIVersion: "argoproj.io/v1alpha1",
   614  					},
   615  					ObjectMeta: metav1.ObjectMeta{
   616  						Name:            "app1",
   617  						Namespace:       "namespace",
   618  						ResourceVersion: "2",
   619  						Labels:          map[string]string{"label-key": "label-value"},
   620  						Annotations:     map[string]string{"annot-key": "annot-value"},
   621  					},
   622  					Spec: v1alpha1.ApplicationSpec{
   623  						Project: "project",
   624  					},
   625  				},
   626  			},
   627  			desiredApps: []v1alpha1.Application{
   628  				{
   629  					ObjectMeta: metav1.ObjectMeta{
   630  						Name: "app1",
   631  					},
   632  					Spec: v1alpha1.ApplicationSpec{
   633  						Project: "project",
   634  					},
   635  				},
   636  			},
   637  			expected: []v1alpha1.Application{
   638  				{
   639  					TypeMeta: metav1.TypeMeta{
   640  						Kind:       application.ApplicationKind,
   641  						APIVersion: "argoproj.io/v1alpha1",
   642  					},
   643  					ObjectMeta: metav1.ObjectMeta{
   644  						Name:            "app1",
   645  						Namespace:       "namespace",
   646  						ResourceVersion: "3",
   647  					},
   648  					Spec: v1alpha1.ApplicationSpec{
   649  						Project: "project",
   650  					},
   651  				},
   652  			},
   653  		},
   654  		{
   655  			name: "Ensure that status and operation fields are not overridden by an update, when removing labels/annotations",
   656  			appSet: v1alpha1.ApplicationSet{
   657  				ObjectMeta: metav1.ObjectMeta{
   658  					Name:      "name",
   659  					Namespace: "namespace",
   660  				},
   661  				Spec: v1alpha1.ApplicationSetSpec{
   662  					Template: v1alpha1.ApplicationSetTemplate{
   663  						Spec: v1alpha1.ApplicationSpec{
   664  							Project: "project",
   665  						},
   666  					},
   667  				},
   668  			},
   669  			existingApps: []v1alpha1.Application{
   670  				{
   671  					TypeMeta: metav1.TypeMeta{
   672  						Kind:       application.ApplicationKind,
   673  						APIVersion: "argoproj.io/v1alpha1",
   674  					},
   675  					ObjectMeta: metav1.ObjectMeta{
   676  						Name:            "app1",
   677  						Namespace:       "namespace",
   678  						ResourceVersion: "2",
   679  						Labels:          map[string]string{"label-key": "label-value"},
   680  						Annotations:     map[string]string{"annot-key": "annot-value"},
   681  					},
   682  					Spec: v1alpha1.ApplicationSpec{
   683  						Project: "project",
   684  					},
   685  					Status: v1alpha1.ApplicationStatus{
   686  						Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}},
   687  					},
   688  					Operation: &v1alpha1.Operation{
   689  						Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"},
   690  					},
   691  				},
   692  			},
   693  			desiredApps: []v1alpha1.Application{
   694  				{
   695  					ObjectMeta: metav1.ObjectMeta{
   696  						Name: "app1",
   697  					},
   698  					Spec: v1alpha1.ApplicationSpec{
   699  						Project: "project",
   700  					},
   701  				},
   702  			},
   703  			expected: []v1alpha1.Application{
   704  				{
   705  					TypeMeta: metav1.TypeMeta{
   706  						Kind:       application.ApplicationKind,
   707  						APIVersion: "argoproj.io/v1alpha1",
   708  					},
   709  					ObjectMeta: metav1.ObjectMeta{
   710  						Name:            "app1",
   711  						Namespace:       "namespace",
   712  						ResourceVersion: "3",
   713  					},
   714  					Spec: v1alpha1.ApplicationSpec{
   715  						Project: "project",
   716  					},
   717  					Status: v1alpha1.ApplicationStatus{
   718  						Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}},
   719  					},
   720  					Operation: &v1alpha1.Operation{
   721  						Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"},
   722  					},
   723  				},
   724  			},
   725  		},
   726  		{
   727  			name: "Ensure that status and operation fields are not overridden by an update, when removing labels/annotations and adding other fields",
   728  			appSet: v1alpha1.ApplicationSet{
   729  				ObjectMeta: metav1.ObjectMeta{
   730  					Name:      "name",
   731  					Namespace: "namespace",
   732  				},
   733  				Spec: v1alpha1.ApplicationSetSpec{
   734  					Template: v1alpha1.ApplicationSetTemplate{
   735  						Spec: v1alpha1.ApplicationSpec{
   736  							Project:     "project",
   737  							Source:      &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
   738  							Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"},
   739  						},
   740  					},
   741  				},
   742  			},
   743  			existingApps: []v1alpha1.Application{
   744  				{
   745  					TypeMeta: metav1.TypeMeta{
   746  						Kind:       application.ApplicationKind,
   747  						APIVersion: "argoproj.io/v1alpha1",
   748  					},
   749  					ObjectMeta: metav1.ObjectMeta{
   750  						Name:            "app1",
   751  						Namespace:       "namespace",
   752  						ResourceVersion: "2",
   753  					},
   754  					Spec: v1alpha1.ApplicationSpec{
   755  						Project: "project",
   756  					},
   757  					Status: v1alpha1.ApplicationStatus{
   758  						Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}},
   759  					},
   760  					Operation: &v1alpha1.Operation{
   761  						Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"},
   762  					},
   763  				},
   764  			},
   765  			desiredApps: []v1alpha1.Application{
   766  				{
   767  					ObjectMeta: metav1.ObjectMeta{
   768  						Name:        "app1",
   769  						Labels:      map[string]string{"label-key": "label-value"},
   770  						Annotations: map[string]string{"annot-key": "annot-value"},
   771  					},
   772  					Spec: v1alpha1.ApplicationSpec{
   773  						Project:     "project",
   774  						Source:      &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
   775  						Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"},
   776  					},
   777  				},
   778  			},
   779  			expected: []v1alpha1.Application{
   780  				{
   781  					TypeMeta: metav1.TypeMeta{
   782  						Kind:       application.ApplicationKind,
   783  						APIVersion: "argoproj.io/v1alpha1",
   784  					},
   785  					ObjectMeta: metav1.ObjectMeta{
   786  						Name:            "app1",
   787  						Namespace:       "namespace",
   788  						Labels:          map[string]string{"label-key": "label-value"},
   789  						Annotations:     map[string]string{"annot-key": "annot-value"},
   790  						ResourceVersion: "3",
   791  					},
   792  					Spec: v1alpha1.ApplicationSpec{
   793  						Project:     "project",
   794  						Source:      &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
   795  						Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"},
   796  					},
   797  					Status: v1alpha1.ApplicationStatus{
   798  						Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}},
   799  					},
   800  					Operation: &v1alpha1.Operation{
   801  						Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"},
   802  					},
   803  				},
   804  			},
   805  		},
   806  		{
   807  			name: "Ensure that argocd notifications state and refresh annotation is preserved from an existing app",
   808  			appSet: v1alpha1.ApplicationSet{
   809  				ObjectMeta: metav1.ObjectMeta{
   810  					Name:      "name",
   811  					Namespace: "namespace",
   812  				},
   813  				Spec: v1alpha1.ApplicationSetSpec{
   814  					Template: v1alpha1.ApplicationSetTemplate{
   815  						Spec: v1alpha1.ApplicationSpec{
   816  							Project: "project",
   817  						},
   818  					},
   819  				},
   820  			},
   821  			existingApps: []v1alpha1.Application{
   822  				{
   823  					TypeMeta: metav1.TypeMeta{
   824  						Kind:       application.ApplicationKind,
   825  						APIVersion: "argoproj.io/v1alpha1",
   826  					},
   827  					ObjectMeta: metav1.ObjectMeta{
   828  						Name:            "app1",
   829  						Namespace:       "namespace",
   830  						ResourceVersion: "2",
   831  						Labels:          map[string]string{"label-key": "label-value"},
   832  						Annotations: map[string]string{
   833  							"annot-key":                   "annot-value",
   834  							NotifiedAnnotationKey:         `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`,
   835  							v1alpha1.AnnotationKeyRefresh: string(v1alpha1.RefreshTypeNormal),
   836  						},
   837  					},
   838  					Spec: v1alpha1.ApplicationSpec{
   839  						Project: "project",
   840  					},
   841  				},
   842  			},
   843  			desiredApps: []v1alpha1.Application{
   844  				{
   845  					ObjectMeta: metav1.ObjectMeta{
   846  						Name: "app1",
   847  					},
   848  					Spec: v1alpha1.ApplicationSpec{
   849  						Project: "project",
   850  					},
   851  				},
   852  			},
   853  			expected: []v1alpha1.Application{
   854  				{
   855  					TypeMeta: metav1.TypeMeta{
   856  						Kind:       application.ApplicationKind,
   857  						APIVersion: "argoproj.io/v1alpha1",
   858  					},
   859  					ObjectMeta: metav1.ObjectMeta{
   860  						Name:            "app1",
   861  						Namespace:       "namespace",
   862  						ResourceVersion: "3",
   863  						Annotations: map[string]string{
   864  							NotifiedAnnotationKey:         `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`,
   865  							v1alpha1.AnnotationKeyRefresh: string(v1alpha1.RefreshTypeNormal),
   866  						},
   867  					},
   868  					Spec: v1alpha1.ApplicationSpec{
   869  						Project: "project",
   870  					},
   871  				},
   872  			},
   873  		}, {
   874  			name: "Ensure that configured preserved annotations are preserved from an existing app",
   875  			appSet: v1alpha1.ApplicationSet{
   876  				ObjectMeta: metav1.ObjectMeta{
   877  					Name:      "name",
   878  					Namespace: "namespace",
   879  				},
   880  				Spec: v1alpha1.ApplicationSetSpec{
   881  					Template: v1alpha1.ApplicationSetTemplate{
   882  						Spec: v1alpha1.ApplicationSpec{
   883  							Project: "project",
   884  						},
   885  					},
   886  					PreservedFields: &v1alpha1.ApplicationPreservedFields{
   887  						Annotations: []string{"preserved-annot-key"},
   888  					},
   889  				},
   890  			},
   891  			existingApps: []v1alpha1.Application{
   892  				{
   893  					TypeMeta: metav1.TypeMeta{
   894  						Kind:       "Application",
   895  						APIVersion: "argoproj.io/v1alpha1",
   896  					},
   897  					ObjectMeta: metav1.ObjectMeta{
   898  						Name:            "app1",
   899  						Namespace:       "namespace",
   900  						ResourceVersion: "2",
   901  						Annotations: map[string]string{
   902  							"annot-key":           "annot-value",
   903  							"preserved-annot-key": "preserved-annot-value",
   904  						},
   905  					},
   906  					Spec: v1alpha1.ApplicationSpec{
   907  						Project: "project",
   908  					},
   909  				},
   910  			},
   911  			desiredApps: []v1alpha1.Application{
   912  				{
   913  					ObjectMeta: metav1.ObjectMeta{
   914  						Name: "app1",
   915  					},
   916  					Spec: v1alpha1.ApplicationSpec{
   917  						Project: "project",
   918  					},
   919  				},
   920  			},
   921  			expected: []v1alpha1.Application{
   922  				{
   923  					TypeMeta: metav1.TypeMeta{
   924  						Kind:       "Application",
   925  						APIVersion: "argoproj.io/v1alpha1",
   926  					},
   927  					ObjectMeta: metav1.ObjectMeta{
   928  						Name:            "app1",
   929  						Namespace:       "namespace",
   930  						ResourceVersion: "3",
   931  						Annotations: map[string]string{
   932  							"preserved-annot-key": "preserved-annot-value",
   933  						},
   934  					},
   935  					Spec: v1alpha1.ApplicationSpec{
   936  						Project: "project",
   937  					},
   938  				},
   939  			},
   940  		}, {
   941  			name: "Ensure that the app spec is normalized before applying",
   942  			appSet: v1alpha1.ApplicationSet{
   943  				ObjectMeta: metav1.ObjectMeta{
   944  					Name:      "name",
   945  					Namespace: "namespace",
   946  				},
   947  				Spec: v1alpha1.ApplicationSetSpec{
   948  					Template: v1alpha1.ApplicationSetTemplate{
   949  						Spec: v1alpha1.ApplicationSpec{
   950  							Project: "project",
   951  							Source: &v1alpha1.ApplicationSource{
   952  								Directory: &v1alpha1.ApplicationSourceDirectory{
   953  									Jsonnet: v1alpha1.ApplicationSourceJsonnet{},
   954  								},
   955  							},
   956  						},
   957  					},
   958  				},
   959  			},
   960  			desiredApps: []v1alpha1.Application{
   961  				{
   962  					ObjectMeta: metav1.ObjectMeta{
   963  						Name: "app1",
   964  					},
   965  					Spec: v1alpha1.ApplicationSpec{
   966  						Project: "project",
   967  						Source: &v1alpha1.ApplicationSource{
   968  							Directory: &v1alpha1.ApplicationSourceDirectory{
   969  								Jsonnet: v1alpha1.ApplicationSourceJsonnet{},
   970  							},
   971  						},
   972  					},
   973  				},
   974  			},
   975  			expected: []v1alpha1.Application{
   976  				{
   977  					TypeMeta: metav1.TypeMeta{
   978  						Kind:       "Application",
   979  						APIVersion: "argoproj.io/v1alpha1",
   980  					},
   981  					ObjectMeta: metav1.ObjectMeta{
   982  						Name:            "app1",
   983  						Namespace:       "namespace",
   984  						ResourceVersion: "1",
   985  					},
   986  					Spec: v1alpha1.ApplicationSpec{
   987  						Project: "project",
   988  						Source:  &v1alpha1.ApplicationSource{
   989  							// Directory and jsonnet block are removed
   990  						},
   991  					},
   992  				},
   993  			},
   994  		}, {
   995  			// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
   996  			name: "Ensure that ignored targetRevision difference doesn't cause an update, even if another field changes",
   997  			appSet: v1alpha1.ApplicationSet{
   998  				ObjectMeta: metav1.ObjectMeta{
   999  					Name:      "name",
  1000  					Namespace: "namespace",
  1001  				},
  1002  				Spec: v1alpha1.ApplicationSetSpec{
  1003  					IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
  1004  						{JQPathExpressions: []string{".spec.source.targetRevision"}},
  1005  					},
  1006  					Template: v1alpha1.ApplicationSetTemplate{
  1007  						Spec: v1alpha1.ApplicationSpec{
  1008  							Project: "project",
  1009  							Source: &v1alpha1.ApplicationSource{
  1010  								RepoURL:        "https://git.example.com/test-org/test-repo.git",
  1011  								TargetRevision: "foo",
  1012  							},
  1013  						},
  1014  					},
  1015  				},
  1016  			},
  1017  			existingApps: []v1alpha1.Application{
  1018  				{
  1019  					TypeMeta: metav1.TypeMeta{
  1020  						Kind:       "Application",
  1021  						APIVersion: "argoproj.io/v1alpha1",
  1022  					},
  1023  					ObjectMeta: metav1.ObjectMeta{
  1024  						Name:            "app1",
  1025  						Namespace:       "namespace",
  1026  						ResourceVersion: "2",
  1027  					},
  1028  					Spec: v1alpha1.ApplicationSpec{
  1029  						Project: "project",
  1030  						Source: &v1alpha1.ApplicationSource{
  1031  							RepoURL:        "https://git.example.com/test-org/test-repo.git",
  1032  							TargetRevision: "bar",
  1033  						},
  1034  					},
  1035  				},
  1036  			},
  1037  			desiredApps: []v1alpha1.Application{
  1038  				{
  1039  					ObjectMeta: metav1.ObjectMeta{
  1040  						Name: "app1",
  1041  					},
  1042  					Spec: v1alpha1.ApplicationSpec{
  1043  						Project: "project",
  1044  						Source: &v1alpha1.ApplicationSource{
  1045  							RepoURL: "https://git.example.com/test-org/test-repo.git",
  1046  							// The targetRevision is ignored, so this should not be updated.
  1047  							TargetRevision: "foo",
  1048  							// This should be updated.
  1049  							Helm: &v1alpha1.ApplicationSourceHelm{
  1050  								Parameters: []v1alpha1.HelmParameter{
  1051  									{Name: "hi", Value: "there"},
  1052  								},
  1053  							},
  1054  						},
  1055  					},
  1056  				},
  1057  			},
  1058  			expected: []v1alpha1.Application{
  1059  				{
  1060  					TypeMeta: metav1.TypeMeta{
  1061  						Kind:       "Application",
  1062  						APIVersion: "argoproj.io/v1alpha1",
  1063  					},
  1064  					ObjectMeta: metav1.ObjectMeta{
  1065  						Name:            "app1",
  1066  						Namespace:       "namespace",
  1067  						ResourceVersion: "3",
  1068  					},
  1069  					Spec: v1alpha1.ApplicationSpec{
  1070  						Project: "project",
  1071  						Source: &v1alpha1.ApplicationSource{
  1072  							RepoURL: "https://git.example.com/test-org/test-repo.git",
  1073  							// This is the existing value from the cluster, which should not be updated because the field is ignored.
  1074  							TargetRevision: "bar",
  1075  							// This was missing on the cluster, so it should be added.
  1076  							Helm: &v1alpha1.ApplicationSourceHelm{
  1077  								Parameters: []v1alpha1.HelmParameter{
  1078  									{Name: "hi", Value: "there"},
  1079  								},
  1080  							},
  1081  						},
  1082  					},
  1083  				},
  1084  			},
  1085  		}, {
  1086  			// For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799
  1087  			name: "ignore parameters added to a multi-source app in the cluster",
  1088  			appSet: v1alpha1.ApplicationSet{
  1089  				ObjectMeta: metav1.ObjectMeta{
  1090  					Name:      "name",
  1091  					Namespace: "namespace",
  1092  				},
  1093  				Spec: v1alpha1.ApplicationSetSpec{
  1094  					IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
  1095  						{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
  1096  					},
  1097  					Template: v1alpha1.ApplicationSetTemplate{
  1098  						Spec: v1alpha1.ApplicationSpec{
  1099  							Project: "project",
  1100  							Sources: []v1alpha1.ApplicationSource{
  1101  								{
  1102  									RepoURL: "https://git.example.com/test-org/test-repo.git",
  1103  									Helm: &v1alpha1.ApplicationSourceHelm{
  1104  										Values: "foo: bar",
  1105  									},
  1106  								},
  1107  							},
  1108  						},
  1109  					},
  1110  				},
  1111  			},
  1112  			existingApps: []v1alpha1.Application{
  1113  				{
  1114  					TypeMeta: metav1.TypeMeta{
  1115  						Kind:       "Application",
  1116  						APIVersion: "argoproj.io/v1alpha1",
  1117  					},
  1118  					ObjectMeta: metav1.ObjectMeta{
  1119  						Name:            "app1",
  1120  						Namespace:       "namespace",
  1121  						ResourceVersion: "2",
  1122  					},
  1123  					Spec: v1alpha1.ApplicationSpec{
  1124  						Project: "project",
  1125  						Sources: []v1alpha1.ApplicationSource{
  1126  							{
  1127  								RepoURL: "https://git.example.com/test-org/test-repo.git",
  1128  								Helm: &v1alpha1.ApplicationSourceHelm{
  1129  									Values: "foo: bar",
  1130  									Parameters: []v1alpha1.HelmParameter{
  1131  										{Name: "hi", Value: "there"},
  1132  									},
  1133  								},
  1134  							},
  1135  						},
  1136  					},
  1137  				},
  1138  			},
  1139  			desiredApps: []v1alpha1.Application{
  1140  				{
  1141  					ObjectMeta: metav1.ObjectMeta{
  1142  						Name: "app1",
  1143  					},
  1144  					Spec: v1alpha1.ApplicationSpec{
  1145  						Project: "project",
  1146  						Sources: []v1alpha1.ApplicationSource{
  1147  							{
  1148  								RepoURL: "https://git.example.com/test-org/test-repo.git",
  1149  								Helm: &v1alpha1.ApplicationSourceHelm{
  1150  									Values: "foo: bar",
  1151  								},
  1152  							},
  1153  						},
  1154  					},
  1155  				},
  1156  			},
  1157  			expected: []v1alpha1.Application{
  1158  				{
  1159  					TypeMeta: metav1.TypeMeta{
  1160  						Kind:       "Application",
  1161  						APIVersion: "argoproj.io/v1alpha1",
  1162  					},
  1163  					ObjectMeta: metav1.ObjectMeta{
  1164  						Name:      "app1",
  1165  						Namespace: "namespace",
  1166  						// This should not be updated, because reconciliation shouldn't modify the App.
  1167  						ResourceVersion: "2",
  1168  					},
  1169  					Spec: v1alpha1.ApplicationSpec{
  1170  						Project: "project",
  1171  						Sources: []v1alpha1.ApplicationSource{
  1172  							{
  1173  								RepoURL: "https://git.example.com/test-org/test-repo.git",
  1174  								Helm: &v1alpha1.ApplicationSourceHelm{
  1175  									Values: "foo: bar",
  1176  									Parameters: []v1alpha1.HelmParameter{
  1177  										// This existed only in the cluster, but it shouldn't be removed, because the field is ignored.
  1178  										{Name: "hi", Value: "there"},
  1179  									},
  1180  								},
  1181  							},
  1182  						},
  1183  					},
  1184  				},
  1185  			},
  1186  		}, {
  1187  			name: "Demonstrate limitation of MergePatch", // Maybe we can fix this in Argo CD 3.0: https://github.com/argoproj/argo-cd/issues/15975
  1188  			appSet: v1alpha1.ApplicationSet{
  1189  				ObjectMeta: metav1.ObjectMeta{
  1190  					Name:      "name",
  1191  					Namespace: "namespace",
  1192  				},
  1193  				Spec: v1alpha1.ApplicationSetSpec{
  1194  					IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
  1195  						{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
  1196  					},
  1197  					Template: v1alpha1.ApplicationSetTemplate{
  1198  						Spec: v1alpha1.ApplicationSpec{
  1199  							Project: "project",
  1200  							Sources: []v1alpha1.ApplicationSource{
  1201  								{
  1202  									RepoURL: "https://git.example.com/test-org/test-repo.git",
  1203  									Helm: &v1alpha1.ApplicationSourceHelm{
  1204  										Values: "new: values",
  1205  									},
  1206  								},
  1207  							},
  1208  						},
  1209  					},
  1210  				},
  1211  			},
  1212  			existingApps: []v1alpha1.Application{
  1213  				{
  1214  					TypeMeta: metav1.TypeMeta{
  1215  						Kind:       "Application",
  1216  						APIVersion: "argoproj.io/v1alpha1",
  1217  					},
  1218  					ObjectMeta: metav1.ObjectMeta{
  1219  						Name:            "app1",
  1220  						Namespace:       "namespace",
  1221  						ResourceVersion: "2",
  1222  					},
  1223  					Spec: v1alpha1.ApplicationSpec{
  1224  						Project: "project",
  1225  						Sources: []v1alpha1.ApplicationSource{
  1226  							{
  1227  								RepoURL: "https://git.example.com/test-org/test-repo.git",
  1228  								Helm: &v1alpha1.ApplicationSourceHelm{
  1229  									Values: "foo: bar",
  1230  									Parameters: []v1alpha1.HelmParameter{
  1231  										{Name: "hi", Value: "there"},
  1232  									},
  1233  								},
  1234  							},
  1235  						},
  1236  					},
  1237  				},
  1238  			},
  1239  			desiredApps: []v1alpha1.Application{
  1240  				{
  1241  					ObjectMeta: metav1.ObjectMeta{
  1242  						Name: "app1",
  1243  					},
  1244  					Spec: v1alpha1.ApplicationSpec{
  1245  						Project: "project",
  1246  						Sources: []v1alpha1.ApplicationSource{
  1247  							{
  1248  								RepoURL: "https://git.example.com/test-org/test-repo.git",
  1249  								Helm: &v1alpha1.ApplicationSourceHelm{
  1250  									Values: "new: values",
  1251  								},
  1252  							},
  1253  						},
  1254  					},
  1255  				},
  1256  			},
  1257  			expected: []v1alpha1.Application{
  1258  				{
  1259  					TypeMeta: metav1.TypeMeta{
  1260  						Kind:       "Application",
  1261  						APIVersion: "argoproj.io/v1alpha1",
  1262  					},
  1263  					ObjectMeta: metav1.ObjectMeta{
  1264  						Name:            "app1",
  1265  						Namespace:       "namespace",
  1266  						ResourceVersion: "3",
  1267  					},
  1268  					Spec: v1alpha1.ApplicationSpec{
  1269  						Project: "project",
  1270  						Sources: []v1alpha1.ApplicationSource{
  1271  							{
  1272  								RepoURL: "https://git.example.com/test-org/test-repo.git",
  1273  								Helm: &v1alpha1.ApplicationSourceHelm{
  1274  									Values: "new: values",
  1275  									// The Parameters field got blown away, because the values field changed. MergePatch
  1276  									// doesn't merge list items, it replaces the whole list if an item changes.
  1277  									// If we eventually add a `name` field to Sources, we can use StrategicMergePatch.
  1278  								},
  1279  							},
  1280  						},
  1281  					},
  1282  				},
  1283  			},
  1284  		},
  1285  	} {
  1286  
  1287  		t.Run(c.name, func(t *testing.T) {
  1288  
  1289  			initObjs := []crtclient.Object{&c.appSet}
  1290  
  1291  			for _, a := range c.existingApps {
  1292  				err = controllerutil.SetControllerReference(&c.appSet, &a, scheme)
  1293  				assert.Nil(t, err)
  1294  				initObjs = append(initObjs, &a)
  1295  			}
  1296  
  1297  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1298  
  1299  			r := ApplicationSetReconciler{
  1300  				Client:   client,
  1301  				Scheme:   scheme,
  1302  				Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
  1303  				Cache:    &fakeCache{},
  1304  			}
  1305  
  1306  			err = r.createOrUpdateInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps)
  1307  			assert.NoError(t, err)
  1308  
  1309  			for _, obj := range c.expected {
  1310  				got := &v1alpha1.Application{}
  1311  				_ = client.Get(context.Background(), crtclient.ObjectKey{
  1312  					Namespace: obj.Namespace,
  1313  					Name:      obj.Name,
  1314  				}, got)
  1315  
  1316  				err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
  1317  				assert.Equal(t, obj, *got)
  1318  			}
  1319  		})
  1320  	}
  1321  }
  1322  
  1323  func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
  1324  
  1325  	scheme := runtime.NewScheme()
  1326  	err := v1alpha1.AddToScheme(scheme)
  1327  	assert.Nil(t, err)
  1328  
  1329  	err = v1alpha1.AddToScheme(scheme)
  1330  	assert.Nil(t, err)
  1331  
  1332  	for _, c := range []struct {
  1333  		// name is human-readable test name
  1334  		name               string
  1335  		existingFinalizers []string
  1336  		expectedFinalizers []string
  1337  	}{
  1338  		{
  1339  			name:               "no finalizers",
  1340  			existingFinalizers: []string{},
  1341  			expectedFinalizers: nil,
  1342  		},
  1343  		{
  1344  			name:               "contains only argo finalizer",
  1345  			existingFinalizers: []string{v1alpha1.ResourcesFinalizerName},
  1346  			expectedFinalizers: nil,
  1347  		},
  1348  		{
  1349  			name:               "contains only non-argo finalizer",
  1350  			existingFinalizers: []string{"non-argo-finalizer"},
  1351  			expectedFinalizers: []string{"non-argo-finalizer"},
  1352  		},
  1353  		{
  1354  			name:               "contains both argo and non-argo finalizer",
  1355  			existingFinalizers: []string{"non-argo-finalizer", v1alpha1.ResourcesFinalizerName},
  1356  			expectedFinalizers: []string{"non-argo-finalizer"},
  1357  		},
  1358  	} {
  1359  		t.Run(c.name, func(t *testing.T) {
  1360  
  1361  			appSet := v1alpha1.ApplicationSet{
  1362  				ObjectMeta: metav1.ObjectMeta{
  1363  					Name:      "name",
  1364  					Namespace: "namespace",
  1365  				},
  1366  				Spec: v1alpha1.ApplicationSetSpec{
  1367  					Template: v1alpha1.ApplicationSetTemplate{
  1368  						Spec: v1alpha1.ApplicationSpec{
  1369  							Project: "project",
  1370  						},
  1371  					},
  1372  				},
  1373  			}
  1374  
  1375  			app := v1alpha1.Application{
  1376  				ObjectMeta: metav1.ObjectMeta{
  1377  					Name:       "app1",
  1378  					Finalizers: c.existingFinalizers,
  1379  				},
  1380  				Spec: v1alpha1.ApplicationSpec{
  1381  					Project: "project",
  1382  					Source:  &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
  1383  					// Destination is always invalid, for this test:
  1384  					Destination: v1alpha1.ApplicationDestination{Name: "my-cluster", Namespace: "namespace"},
  1385  				},
  1386  			}
  1387  
  1388  			initObjs := []crtclient.Object{&app, &appSet}
  1389  
  1390  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1391  			secret := &corev1.Secret{
  1392  				ObjectMeta: metav1.ObjectMeta{
  1393  					Name:      "my-secret",
  1394  					Namespace: "namespace",
  1395  					Labels: map[string]string{
  1396  						generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
  1397  					},
  1398  				},
  1399  				Data: map[string][]byte{
  1400  					// Since this test requires the cluster to be an invalid destination, we
  1401  					// always return a cluster named 'my-cluster2' (different from app 'my-cluster', above)
  1402  					"name":   []byte("mycluster2"),
  1403  					"server": []byte("https://kubernetes.default.svc"),
  1404  					"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
  1405  				},
  1406  			}
  1407  
  1408  			objects := append([]runtime.Object{}, secret)
  1409  			kubeclientset := kubefake.NewSimpleClientset(objects...)
  1410  
  1411  			r := ApplicationSetReconciler{
  1412  				Client:        client,
  1413  				Scheme:        scheme,
  1414  				Recorder:      record.NewFakeRecorder(10),
  1415  				KubeClientset: kubeclientset,
  1416  				Cache:         &fakeCache{},
  1417  			}
  1418  			//settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "namespace")
  1419  			//argoDB := db.NewDB("namespace", settingsMgr, r.KubeClientset)
  1420  			//clusterList, err := argoDB.ListClusters(context.Background())
  1421  			clusterList, err := utils.ListClusters(context.Background(), kubeclientset, "namespace")
  1422  			assert.NoError(t, err, "Unexpected error")
  1423  
  1424  			appLog := log.WithFields(log.Fields{"app": app.Name, "appSet": ""})
  1425  
  1426  			appInputParam := app.DeepCopy()
  1427  
  1428  			err = r.removeFinalizerOnInvalidDestination(context.Background(), appSet, appInputParam, clusterList, appLog)
  1429  			assert.NoError(t, err, "Unexpected error")
  1430  
  1431  			retrievedApp := v1alpha1.Application{}
  1432  			err = client.Get(context.Background(), crtclient.ObjectKeyFromObject(&app), &retrievedApp)
  1433  			assert.NoError(t, err, "Unexpected error")
  1434  
  1435  			// App on the cluster should have the expected finalizers
  1436  			assert.ElementsMatch(t, c.expectedFinalizers, retrievedApp.Finalizers)
  1437  
  1438  			// App object passed in as a parameter should have the expected finaliers
  1439  			assert.ElementsMatch(t, c.expectedFinalizers, appInputParam.Finalizers)
  1440  
  1441  			bytes, _ := json.MarshalIndent(retrievedApp, "", "  ")
  1442  			t.Log("Contents of app after call:", string(bytes))
  1443  
  1444  		})
  1445  	}
  1446  }
  1447  
  1448  func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
  1449  
  1450  	scheme := runtime.NewScheme()
  1451  	err := v1alpha1.AddToScheme(scheme)
  1452  	assert.Nil(t, err)
  1453  
  1454  	err = v1alpha1.AddToScheme(scheme)
  1455  	assert.Nil(t, err)
  1456  
  1457  	for _, c := range []struct {
  1458  		// name is human-readable test name
  1459  		name                   string
  1460  		destinationField       v1alpha1.ApplicationDestination
  1461  		expectFinalizerRemoved bool
  1462  	}{
  1463  		{
  1464  			name: "invalid cluster: empty destination",
  1465  			destinationField: v1alpha1.ApplicationDestination{
  1466  				Namespace: "namespace",
  1467  			},
  1468  			expectFinalizerRemoved: true,
  1469  		},
  1470  		{
  1471  			name: "invalid cluster: invalid server url",
  1472  			destinationField: v1alpha1.ApplicationDestination{
  1473  				Namespace: "namespace",
  1474  				Server:    "https://1.2.3.4",
  1475  			},
  1476  			expectFinalizerRemoved: true,
  1477  		},
  1478  		{
  1479  			name: "invalid cluster: invalid cluster name",
  1480  			destinationField: v1alpha1.ApplicationDestination{
  1481  				Namespace: "namespace",
  1482  				Name:      "invalid-cluster",
  1483  			},
  1484  			expectFinalizerRemoved: true,
  1485  		},
  1486  		{
  1487  			name: "invalid cluster by both valid",
  1488  			destinationField: v1alpha1.ApplicationDestination{
  1489  				Namespace: "namespace",
  1490  				Name:      "mycluster2",
  1491  				Server:    "https://kubernetes.default.svc",
  1492  			},
  1493  			expectFinalizerRemoved: true,
  1494  		},
  1495  		{
  1496  			name: "invalid cluster by both invalid",
  1497  			destinationField: v1alpha1.ApplicationDestination{
  1498  				Namespace: "namespace",
  1499  				Name:      "mycluster3",
  1500  				Server:    "https://4.5.6.7",
  1501  			},
  1502  			expectFinalizerRemoved: true,
  1503  		},
  1504  		{
  1505  			name: "valid cluster by name",
  1506  			destinationField: v1alpha1.ApplicationDestination{
  1507  				Namespace: "namespace",
  1508  				Name:      "mycluster2",
  1509  			},
  1510  			expectFinalizerRemoved: false,
  1511  		},
  1512  		{
  1513  			name: "valid cluster by server",
  1514  			destinationField: v1alpha1.ApplicationDestination{
  1515  				Namespace: "namespace",
  1516  				Server:    "https://kubernetes.default.svc",
  1517  			},
  1518  			expectFinalizerRemoved: false,
  1519  		},
  1520  	} {
  1521  
  1522  		t.Run(c.name, func(t *testing.T) {
  1523  
  1524  			appSet := v1alpha1.ApplicationSet{
  1525  				ObjectMeta: metav1.ObjectMeta{
  1526  					Name:      "name",
  1527  					Namespace: "namespace",
  1528  				},
  1529  				Spec: v1alpha1.ApplicationSetSpec{
  1530  					Template: v1alpha1.ApplicationSetTemplate{
  1531  						Spec: v1alpha1.ApplicationSpec{
  1532  							Project: "project",
  1533  						},
  1534  					},
  1535  				},
  1536  			}
  1537  
  1538  			app := v1alpha1.Application{
  1539  				ObjectMeta: metav1.ObjectMeta{
  1540  					Name:       "app1",
  1541  					Finalizers: []string{v1alpha1.ResourcesFinalizerName},
  1542  				},
  1543  				Spec: v1alpha1.ApplicationSpec{
  1544  					Project:     "project",
  1545  					Source:      &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
  1546  					Destination: c.destinationField,
  1547  				},
  1548  			}
  1549  
  1550  			initObjs := []crtclient.Object{&app, &appSet}
  1551  
  1552  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1553  			secret := &corev1.Secret{
  1554  				ObjectMeta: metav1.ObjectMeta{
  1555  					Name:      "my-secret",
  1556  					Namespace: "namespace",
  1557  					Labels: map[string]string{
  1558  						generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
  1559  					},
  1560  				},
  1561  				Data: map[string][]byte{
  1562  					// Since this test requires the cluster to be an invalid destination, we
  1563  					// always return a cluster named 'my-cluster2' (different from app 'my-cluster', above)
  1564  					"name":   []byte("mycluster2"),
  1565  					"server": []byte("https://kubernetes.default.svc"),
  1566  					"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
  1567  				},
  1568  			}
  1569  
  1570  			objects := append([]runtime.Object{}, secret)
  1571  			kubeclientset := kubefake.NewSimpleClientset(objects...)
  1572  
  1573  			r := ApplicationSetReconciler{
  1574  				Client:        client,
  1575  				Scheme:        scheme,
  1576  				Recorder:      record.NewFakeRecorder(10),
  1577  				KubeClientset: kubeclientset,
  1578  				Cache:         &fakeCache{},
  1579  			}
  1580  			// settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "argocd")
  1581  			// argoDB := db.NewDB("argocd", settingsMgr, r.KubeClientset)
  1582  			// clusterList, err := argoDB.ListClusters(context.Background())
  1583  			clusterList, err := utils.ListClusters(context.Background(), kubeclientset, "namespace")
  1584  			assert.NoError(t, err, "Unexpected error")
  1585  
  1586  			appLog := log.WithFields(log.Fields{"app": app.Name, "appSet": ""})
  1587  
  1588  			appInputParam := app.DeepCopy()
  1589  
  1590  			err = r.removeFinalizerOnInvalidDestination(context.Background(), appSet, appInputParam, clusterList, appLog)
  1591  			assert.NoError(t, err, "Unexpected error")
  1592  
  1593  			retrievedApp := v1alpha1.Application{}
  1594  			err = client.Get(context.Background(), crtclient.ObjectKeyFromObject(&app), &retrievedApp)
  1595  			assert.NoError(t, err, "Unexpected error")
  1596  
  1597  			finalizerRemoved := len(retrievedApp.Finalizers) == 0
  1598  
  1599  			assert.True(t, c.expectFinalizerRemoved == finalizerRemoved)
  1600  
  1601  			bytes, _ := json.MarshalIndent(retrievedApp, "", "  ")
  1602  			t.Log("Contents of app after call:", string(bytes))
  1603  
  1604  		})
  1605  	}
  1606  }
  1607  
  1608  func TestRemoveOwnerReferencesOnDeleteAppSet(t *testing.T) {
  1609  	scheme := runtime.NewScheme()
  1610  	err := v1alpha1.AddToScheme(scheme)
  1611  	assert.Nil(t, err)
  1612  
  1613  	err = v1alpha1.AddToScheme(scheme)
  1614  	assert.Nil(t, err)
  1615  
  1616  	for _, c := range []struct {
  1617  		// name is human-readable test name
  1618  		name string
  1619  	}{
  1620  		{
  1621  			name: "ownerReferences cleared",
  1622  		},
  1623  	} {
  1624  		t.Run(c.name, func(t *testing.T) {
  1625  			appSet := v1alpha1.ApplicationSet{
  1626  				ObjectMeta: metav1.ObjectMeta{
  1627  					Name:       "name",
  1628  					Namespace:  "namespace",
  1629  					Finalizers: []string{v1alpha1.ResourcesFinalizerName},
  1630  				},
  1631  				Spec: v1alpha1.ApplicationSetSpec{
  1632  					Template: v1alpha1.ApplicationSetTemplate{
  1633  						Spec: v1alpha1.ApplicationSpec{
  1634  							Project: "project",
  1635  						},
  1636  					},
  1637  				},
  1638  			}
  1639  
  1640  			app := v1alpha1.Application{
  1641  				ObjectMeta: metav1.ObjectMeta{
  1642  					Name:      "app1",
  1643  					Namespace: "namespace",
  1644  				},
  1645  				Spec: v1alpha1.ApplicationSpec{
  1646  					Project: "project",
  1647  					Source:  &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
  1648  					Destination: v1alpha1.ApplicationDestination{
  1649  						Namespace: "namespace",
  1650  						Server:    "https://kubernetes.default.svc",
  1651  					},
  1652  				},
  1653  			}
  1654  
  1655  			err := controllerutil.SetControllerReference(&appSet, &app, scheme)
  1656  			assert.NoError(t, err, "Unexpected error")
  1657  
  1658  			initObjs := []crtclient.Object{&app, &appSet}
  1659  
  1660  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1661  
  1662  			r := ApplicationSetReconciler{
  1663  				Client:        client,
  1664  				Scheme:        scheme,
  1665  				Recorder:      record.NewFakeRecorder(10),
  1666  				KubeClientset: nil,
  1667  				Cache:         &fakeCache{},
  1668  			}
  1669  
  1670  			err = r.removeOwnerReferencesOnDeleteAppSet(context.Background(), appSet)
  1671  			assert.NoError(t, err, "Unexpected error")
  1672  
  1673  			retrievedApp := v1alpha1.Application{}
  1674  			err = client.Get(context.Background(), crtclient.ObjectKeyFromObject(&app), &retrievedApp)
  1675  			assert.NoError(t, err, "Unexpected error")
  1676  
  1677  			ownerReferencesRemoved := len(retrievedApp.OwnerReferences) == 0
  1678  			assert.True(t, ownerReferencesRemoved)
  1679  		})
  1680  	}
  1681  }
  1682  
  1683  func TestCreateApplications(t *testing.T) {
  1684  
  1685  	scheme := runtime.NewScheme()
  1686  	err := v1alpha1.AddToScheme(scheme)
  1687  	assert.Nil(t, err)
  1688  
  1689  	err = v1alpha1.AddToScheme(scheme)
  1690  	assert.Nil(t, err)
  1691  
  1692  	testCases := []struct {
  1693  		name       string
  1694  		appSet     v1alpha1.ApplicationSet
  1695  		existsApps []v1alpha1.Application
  1696  		apps       []v1alpha1.Application
  1697  		expected   []v1alpha1.Application
  1698  	}{
  1699  		{
  1700  			name: "no existing apps",
  1701  			appSet: v1alpha1.ApplicationSet{
  1702  				ObjectMeta: metav1.ObjectMeta{
  1703  					Name:      "name",
  1704  					Namespace: "namespace",
  1705  				},
  1706  			},
  1707  			existsApps: nil,
  1708  			apps: []v1alpha1.Application{
  1709  				{
  1710  					ObjectMeta: metav1.ObjectMeta{
  1711  						Name: "app1",
  1712  					},
  1713  				},
  1714  			},
  1715  			expected: []v1alpha1.Application{
  1716  				{
  1717  					TypeMeta: metav1.TypeMeta{
  1718  						Kind:       application.ApplicationKind,
  1719  						APIVersion: "argoproj.io/v1alpha1",
  1720  					},
  1721  					ObjectMeta: metav1.ObjectMeta{
  1722  						Name:            "app1",
  1723  						Namespace:       "namespace",
  1724  						ResourceVersion: "1",
  1725  					},
  1726  					Spec: v1alpha1.ApplicationSpec{
  1727  						Project: "default",
  1728  					},
  1729  				},
  1730  			},
  1731  		},
  1732  		{
  1733  			name: "existing apps",
  1734  			appSet: v1alpha1.ApplicationSet{
  1735  				ObjectMeta: metav1.ObjectMeta{
  1736  					Name:      "name",
  1737  					Namespace: "namespace",
  1738  				},
  1739  				Spec: v1alpha1.ApplicationSetSpec{
  1740  					Template: v1alpha1.ApplicationSetTemplate{
  1741  						Spec: v1alpha1.ApplicationSpec{
  1742  							Project: "project",
  1743  						},
  1744  					},
  1745  				},
  1746  			},
  1747  			existsApps: []v1alpha1.Application{
  1748  				{
  1749  					TypeMeta: metav1.TypeMeta{
  1750  						Kind:       application.ApplicationKind,
  1751  						APIVersion: "argoproj.io/v1alpha1",
  1752  					},
  1753  					ObjectMeta: metav1.ObjectMeta{
  1754  						Name:            "app1",
  1755  						Namespace:       "namespace",
  1756  						ResourceVersion: "2",
  1757  					},
  1758  					Spec: v1alpha1.ApplicationSpec{
  1759  						Project: "test",
  1760  					},
  1761  				},
  1762  			},
  1763  			apps: []v1alpha1.Application{
  1764  				{
  1765  					ObjectMeta: metav1.ObjectMeta{
  1766  						Name: "app1",
  1767  					},
  1768  					Spec: v1alpha1.ApplicationSpec{
  1769  						Project: "project",
  1770  					},
  1771  				},
  1772  			},
  1773  			expected: []v1alpha1.Application{
  1774  				{
  1775  					TypeMeta: metav1.TypeMeta{
  1776  						Kind:       application.ApplicationKind,
  1777  						APIVersion: "argoproj.io/v1alpha1",
  1778  					},
  1779  					ObjectMeta: metav1.ObjectMeta{
  1780  						Name:            "app1",
  1781  						Namespace:       "namespace",
  1782  						ResourceVersion: "2",
  1783  					},
  1784  					Spec: v1alpha1.ApplicationSpec{
  1785  						Project: "test",
  1786  					},
  1787  				},
  1788  			},
  1789  		},
  1790  		{
  1791  			name: "existing apps with different project",
  1792  			appSet: v1alpha1.ApplicationSet{
  1793  				ObjectMeta: metav1.ObjectMeta{
  1794  					Name:      "name",
  1795  					Namespace: "namespace",
  1796  				},
  1797  				Spec: v1alpha1.ApplicationSetSpec{
  1798  					Template: v1alpha1.ApplicationSetTemplate{
  1799  						Spec: v1alpha1.ApplicationSpec{
  1800  							Project: "project",
  1801  						},
  1802  					},
  1803  				},
  1804  			},
  1805  			existsApps: []v1alpha1.Application{
  1806  				{
  1807  					TypeMeta: metav1.TypeMeta{
  1808  						Kind:       application.ApplicationKind,
  1809  						APIVersion: "argoproj.io/v1alpha1",
  1810  					},
  1811  					ObjectMeta: metav1.ObjectMeta{
  1812  						Name:            "app1",
  1813  						Namespace:       "namespace",
  1814  						ResourceVersion: "2",
  1815  					},
  1816  					Spec: v1alpha1.ApplicationSpec{
  1817  						Project: "test",
  1818  					},
  1819  				},
  1820  			},
  1821  			apps: []v1alpha1.Application{
  1822  				{
  1823  					ObjectMeta: metav1.ObjectMeta{
  1824  						Name: "app2",
  1825  					},
  1826  					Spec: v1alpha1.ApplicationSpec{
  1827  						Project: "project",
  1828  					},
  1829  				},
  1830  			},
  1831  			expected: []v1alpha1.Application{
  1832  				{
  1833  					TypeMeta: metav1.TypeMeta{
  1834  						Kind:       application.ApplicationKind,
  1835  						APIVersion: "argoproj.io/v1alpha1",
  1836  					},
  1837  					ObjectMeta: metav1.ObjectMeta{
  1838  						Name:            "app2",
  1839  						Namespace:       "namespace",
  1840  						ResourceVersion: "1",
  1841  					},
  1842  					Spec: v1alpha1.ApplicationSpec{
  1843  						Project: "project",
  1844  					},
  1845  				},
  1846  			},
  1847  		},
  1848  	}
  1849  
  1850  	for _, c := range testCases {
  1851  		t.Run(c.name, func(t *testing.T) {
  1852  			initObjs := []crtclient.Object{&c.appSet}
  1853  			for _, a := range c.existsApps {
  1854  				err = controllerutil.SetControllerReference(&c.appSet, &a, scheme)
  1855  				assert.Nil(t, err)
  1856  				initObjs = append(initObjs, &a)
  1857  			}
  1858  
  1859  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1860  
  1861  			r := ApplicationSetReconciler{
  1862  				Client:   client,
  1863  				Scheme:   scheme,
  1864  				Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
  1865  				Cache:    &fakeCache{},
  1866  			}
  1867  
  1868  			err = r.createInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.apps)
  1869  			assert.Nil(t, err)
  1870  
  1871  			for _, obj := range c.expected {
  1872  				got := &v1alpha1.Application{}
  1873  				_ = client.Get(context.Background(), crtclient.ObjectKey{
  1874  					Namespace: obj.Namespace,
  1875  					Name:      obj.Name,
  1876  				}, got)
  1877  
  1878  				err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
  1879  				assert.Nil(t, err)
  1880  
  1881  				assert.Equal(t, obj, *got)
  1882  			}
  1883  		})
  1884  	}
  1885  }
  1886  
  1887  func TestDeleteInCluster(t *testing.T) {
  1888  
  1889  	scheme := runtime.NewScheme()
  1890  	err := v1alpha1.AddToScheme(scheme)
  1891  	assert.Nil(t, err)
  1892  	err = v1alpha1.AddToScheme(scheme)
  1893  	assert.Nil(t, err)
  1894  
  1895  	for _, c := range []struct {
  1896  		// appSet is the application set on which the delete function is called
  1897  		appSet v1alpha1.ApplicationSet
  1898  		// existingApps is the current state of Applications on the cluster
  1899  		existingApps []v1alpha1.Application
  1900  		// desireApps is the apps generated by the generator that we wish to keep alive
  1901  		desiredApps []v1alpha1.Application
  1902  		// expected is the list of applications that we expect to exist after calling delete
  1903  		expected []v1alpha1.Application
  1904  		// notExpected is the list of applications that we expect not to exist after calling delete
  1905  		notExpected []v1alpha1.Application
  1906  	}{
  1907  		{
  1908  			appSet: v1alpha1.ApplicationSet{
  1909  				ObjectMeta: metav1.ObjectMeta{
  1910  					Name:      "name",
  1911  					Namespace: "namespace",
  1912  				},
  1913  				Spec: v1alpha1.ApplicationSetSpec{
  1914  					Template: v1alpha1.ApplicationSetTemplate{
  1915  						Spec: v1alpha1.ApplicationSpec{
  1916  							Project: "project",
  1917  						},
  1918  					},
  1919  				},
  1920  			},
  1921  			existingApps: []v1alpha1.Application{
  1922  				{
  1923  					TypeMeta: metav1.TypeMeta{
  1924  						Kind:       application.ApplicationKind,
  1925  						APIVersion: "argoproj.io/v1alpha1",
  1926  					},
  1927  					ObjectMeta: metav1.ObjectMeta{
  1928  						Name:            "delete",
  1929  						Namespace:       "namespace",
  1930  						ResourceVersion: "2",
  1931  					},
  1932  					Spec: v1alpha1.ApplicationSpec{
  1933  						Project: "project",
  1934  					},
  1935  				},
  1936  				{
  1937  					TypeMeta: metav1.TypeMeta{
  1938  						Kind:       application.ApplicationKind,
  1939  						APIVersion: "argoproj.io/v1alpha1",
  1940  					},
  1941  					ObjectMeta: metav1.ObjectMeta{
  1942  						Name:            "keep",
  1943  						Namespace:       "namespace",
  1944  						ResourceVersion: "2",
  1945  					},
  1946  					Spec: v1alpha1.ApplicationSpec{
  1947  						Project: "project",
  1948  					},
  1949  				},
  1950  			},
  1951  			desiredApps: []v1alpha1.Application{
  1952  				{
  1953  					ObjectMeta: metav1.ObjectMeta{
  1954  						Name: "keep",
  1955  					},
  1956  					Spec: v1alpha1.ApplicationSpec{
  1957  						Project: "project",
  1958  					},
  1959  				},
  1960  			},
  1961  			expected: []v1alpha1.Application{
  1962  				{
  1963  					TypeMeta: metav1.TypeMeta{
  1964  						Kind:       application.ApplicationKind,
  1965  						APIVersion: "argoproj.io/v1alpha1",
  1966  					},
  1967  					ObjectMeta: metav1.ObjectMeta{
  1968  						Name:            "keep",
  1969  						Namespace:       "namespace",
  1970  						ResourceVersion: "2",
  1971  					},
  1972  					Spec: v1alpha1.ApplicationSpec{
  1973  						Project: "project",
  1974  					},
  1975  				},
  1976  			},
  1977  			notExpected: []v1alpha1.Application{
  1978  				{
  1979  					TypeMeta: metav1.TypeMeta{
  1980  						Kind:       application.ApplicationKind,
  1981  						APIVersion: "argoproj.io/v1alpha1",
  1982  					},
  1983  					ObjectMeta: metav1.ObjectMeta{
  1984  						Name:            "delete",
  1985  						Namespace:       "namespace",
  1986  						ResourceVersion: "1",
  1987  					},
  1988  					Spec: v1alpha1.ApplicationSpec{
  1989  						Project: "project",
  1990  					},
  1991  				},
  1992  			},
  1993  		},
  1994  	} {
  1995  		initObjs := []crtclient.Object{&c.appSet}
  1996  		for _, a := range c.existingApps {
  1997  			temp := a
  1998  			err = controllerutil.SetControllerReference(&c.appSet, &temp, scheme)
  1999  			assert.Nil(t, err)
  2000  			initObjs = append(initObjs, &temp)
  2001  		}
  2002  
  2003  		client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  2004  
  2005  		r := ApplicationSetReconciler{
  2006  			Client:        client,
  2007  			Scheme:        scheme,
  2008  			Recorder:      record.NewFakeRecorder(len(initObjs) + len(c.expected)),
  2009  			KubeClientset: kubefake.NewSimpleClientset(),
  2010  		}
  2011  
  2012  		err = r.deleteInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps)
  2013  		assert.Nil(t, err)
  2014  
  2015  		// For each of the expected objects, verify they exist on the cluster
  2016  		for _, obj := range c.expected {
  2017  			got := &v1alpha1.Application{}
  2018  			_ = client.Get(context.Background(), crtclient.ObjectKey{
  2019  				Namespace: obj.Namespace,
  2020  				Name:      obj.Name,
  2021  			}, got)
  2022  
  2023  			err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
  2024  			assert.Nil(t, err)
  2025  
  2026  			assert.Equal(t, obj, *got)
  2027  		}
  2028  
  2029  		// Verify each of the unexpected objs cannot be found
  2030  		for _, obj := range c.notExpected {
  2031  			got := &v1alpha1.Application{}
  2032  			err := client.Get(context.Background(), crtclient.ObjectKey{
  2033  				Namespace: obj.Namespace,
  2034  				Name:      obj.Name,
  2035  			}, got)
  2036  
  2037  			assert.EqualError(t, err, fmt.Sprintf("applications.argoproj.io \"%s\" not found", obj.Name))
  2038  		}
  2039  	}
  2040  }
  2041  
  2042  func TestGetMinRequeueAfter(t *testing.T) {
  2043  	scheme := runtime.NewScheme()
  2044  	err := v1alpha1.AddToScheme(scheme)
  2045  	assert.Nil(t, err)
  2046  	err = v1alpha1.AddToScheme(scheme)
  2047  	assert.Nil(t, err)
  2048  
  2049  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
  2050  
  2051  	generator := v1alpha1.ApplicationSetGenerator{
  2052  		List:     &v1alpha1.ListGenerator{},
  2053  		Git:      &v1alpha1.GitGenerator{},
  2054  		Clusters: &v1alpha1.ClusterGenerator{},
  2055  	}
  2056  
  2057  	generatorMock0 := generatorMock{}
  2058  	generatorMock0.On("GetRequeueAfter", &generator).
  2059  		Return(generators.NoRequeueAfter)
  2060  
  2061  	generatorMock1 := generatorMock{}
  2062  	generatorMock1.On("GetRequeueAfter", &generator).
  2063  		Return(time.Duration(1) * time.Second)
  2064  
  2065  	generatorMock10 := generatorMock{}
  2066  	generatorMock10.On("GetRequeueAfter", &generator).
  2067  		Return(time.Duration(10) * time.Second)
  2068  
  2069  	r := ApplicationSetReconciler{
  2070  		Client:   client,
  2071  		Scheme:   scheme,
  2072  		Recorder: record.NewFakeRecorder(0),
  2073  		Cache:    &fakeCache{},
  2074  		Generators: map[string]generators.Generator{
  2075  			"List":     &generatorMock10,
  2076  			"Git":      &generatorMock1,
  2077  			"Clusters": &generatorMock1,
  2078  		},
  2079  	}
  2080  
  2081  	got := r.getMinRequeueAfter(&v1alpha1.ApplicationSet{
  2082  		Spec: v1alpha1.ApplicationSetSpec{
  2083  			Generators: []v1alpha1.ApplicationSetGenerator{generator},
  2084  		},
  2085  	})
  2086  
  2087  	assert.Equal(t, time.Duration(1)*time.Second, got)
  2088  }
  2089  
  2090  func TestValidateGeneratedApplications(t *testing.T) {
  2091  
  2092  	scheme := runtime.NewScheme()
  2093  	err := v1alpha1.AddToScheme(scheme)
  2094  	assert.Nil(t, err)
  2095  
  2096  	err = v1alpha1.AddToScheme(scheme)
  2097  	assert.Nil(t, err)
  2098  
  2099  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
  2100  
  2101  	// Valid cluster
  2102  	myCluster := v1alpha1.Cluster{
  2103  		Server: "https://kubernetes.default.svc",
  2104  		Name:   "my-cluster",
  2105  	}
  2106  
  2107  	// Valid project
  2108  	myProject := &v1alpha1.AppProject{
  2109  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "namespace"},
  2110  		Spec: v1alpha1.AppProjectSpec{
  2111  			SourceRepos: []string{"*"},
  2112  			Destinations: []v1alpha1.ApplicationDestination{
  2113  				{
  2114  					Namespace: "*",
  2115  					Server:    "*",
  2116  				},
  2117  			},
  2118  			ClusterResourceWhitelist: []metav1.GroupKind{
  2119  				{
  2120  					Group: "*",
  2121  					Kind:  "*",
  2122  				},
  2123  			},
  2124  		},
  2125  	}
  2126  
  2127  	// Test a subset of the validations that 'validateGeneratedApplications' performs
  2128  	for _, cc := range []struct {
  2129  		name             string
  2130  		apps             []v1alpha1.Application
  2131  		expectedErrors   []string
  2132  		validationErrors map[int]error
  2133  	}{
  2134  		{
  2135  			name: "valid app should return true",
  2136  			apps: []v1alpha1.Application{
  2137  				{
  2138  					TypeMeta:   metav1.TypeMeta{},
  2139  					ObjectMeta: metav1.ObjectMeta{},
  2140  					Spec: v1alpha1.ApplicationSpec{
  2141  						Project: "default",
  2142  						Source: &v1alpha1.ApplicationSource{
  2143  							RepoURL:        "https://url",
  2144  							Path:           "/",
  2145  							TargetRevision: "HEAD",
  2146  						},
  2147  						Destination: v1alpha1.ApplicationDestination{
  2148  							Namespace: "namespace",
  2149  							Name:      "my-cluster",
  2150  						},
  2151  					},
  2152  				},
  2153  			},
  2154  			expectedErrors:   []string{},
  2155  			validationErrors: map[int]error{},
  2156  		},
  2157  		{
  2158  			name: "can't have both name and server defined",
  2159  			apps: []v1alpha1.Application{
  2160  				{
  2161  					TypeMeta:   metav1.TypeMeta{},
  2162  					ObjectMeta: metav1.ObjectMeta{},
  2163  					Spec: v1alpha1.ApplicationSpec{
  2164  						Project: "default",
  2165  						Source: &v1alpha1.ApplicationSource{
  2166  							RepoURL:        "https://url",
  2167  							Path:           "/",
  2168  							TargetRevision: "HEAD",
  2169  						},
  2170  						Destination: v1alpha1.ApplicationDestination{
  2171  							Namespace: "namespace",
  2172  							Server:    "my-server",
  2173  							Name:      "my-cluster",
  2174  						},
  2175  					},
  2176  				},
  2177  			},
  2178  			expectedErrors:   []string{"application destination can't have both name and server defined"},
  2179  			validationErrors: map[int]error{0: fmt.Errorf("application destination spec is invalid: application destination can't have both name and server defined: my-cluster my-server")},
  2180  		},
  2181  		{
  2182  			name: "project mismatch should return error",
  2183  			apps: []v1alpha1.Application{
  2184  				{
  2185  					TypeMeta:   metav1.TypeMeta{},
  2186  					ObjectMeta: metav1.ObjectMeta{},
  2187  					Spec: v1alpha1.ApplicationSpec{
  2188  						Project: "DOES-NOT-EXIST",
  2189  						Source: &v1alpha1.ApplicationSource{
  2190  							RepoURL:        "https://url",
  2191  							Path:           "/",
  2192  							TargetRevision: "HEAD",
  2193  						},
  2194  						Destination: v1alpha1.ApplicationDestination{
  2195  							Namespace: "namespace",
  2196  							Name:      "my-cluster",
  2197  						},
  2198  					},
  2199  				},
  2200  			},
  2201  			expectedErrors:   []string{"application references project DOES-NOT-EXIST which does not exist"},
  2202  			validationErrors: map[int]error{0: fmt.Errorf("application references project DOES-NOT-EXIST which does not exist")},
  2203  		},
  2204  		{
  2205  			name: "valid app should return true",
  2206  			apps: []v1alpha1.Application{
  2207  				{
  2208  					TypeMeta:   metav1.TypeMeta{},
  2209  					ObjectMeta: metav1.ObjectMeta{},
  2210  					Spec: v1alpha1.ApplicationSpec{
  2211  						Project: "default",
  2212  						Source: &v1alpha1.ApplicationSource{
  2213  							RepoURL:        "https://url",
  2214  							Path:           "/",
  2215  							TargetRevision: "HEAD",
  2216  						},
  2217  						Destination: v1alpha1.ApplicationDestination{
  2218  							Namespace: "namespace",
  2219  							Name:      "my-cluster",
  2220  						},
  2221  					},
  2222  				},
  2223  			},
  2224  			expectedErrors:   []string{},
  2225  			validationErrors: map[int]error{},
  2226  		},
  2227  		{
  2228  			name: "cluster should match",
  2229  			apps: []v1alpha1.Application{
  2230  				{
  2231  					TypeMeta:   metav1.TypeMeta{},
  2232  					ObjectMeta: metav1.ObjectMeta{},
  2233  					Spec: v1alpha1.ApplicationSpec{
  2234  						Project: "default",
  2235  						Source: &v1alpha1.ApplicationSource{
  2236  							RepoURL:        "https://url",
  2237  							Path:           "/",
  2238  							TargetRevision: "HEAD",
  2239  						},
  2240  						Destination: v1alpha1.ApplicationDestination{
  2241  							Namespace: "namespace",
  2242  							Name:      "nonexistent-cluster",
  2243  						},
  2244  					},
  2245  				},
  2246  			},
  2247  			expectedErrors:   []string{"there are no clusters with this name: nonexistent-cluster"},
  2248  			validationErrors: map[int]error{0: fmt.Errorf("application destination spec is invalid: unable to find destination server: there are no clusters with this name: nonexistent-cluster")},
  2249  		},
  2250  	} {
  2251  
  2252  		t.Run(cc.name, func(t *testing.T) {
  2253  
  2254  			secret := &corev1.Secret{
  2255  				ObjectMeta: metav1.ObjectMeta{
  2256  					Name:      "my-secret",
  2257  					Namespace: "namespace",
  2258  					Labels: map[string]string{
  2259  						generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
  2260  					},
  2261  				},
  2262  				Data: map[string][]byte{
  2263  					"name":   []byte("my-cluster"),
  2264  					"server": []byte("https://kubernetes.default.svc"),
  2265  					"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
  2266  				},
  2267  			}
  2268  
  2269  			objects := append([]runtime.Object{}, secret)
  2270  			kubeclientset := kubefake.NewSimpleClientset(objects...)
  2271  
  2272  			argoDBMock := dbmocks.ArgoDB{}
  2273  			argoDBMock.On("GetCluster", mock.Anything, "https://kubernetes.default.svc").Return(&myCluster, nil)
  2274  			argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{
  2275  				myCluster,
  2276  			}}, nil)
  2277  
  2278  			argoObjs := []runtime.Object{myProject}
  2279  			for _, app := range cc.apps {
  2280  				argoObjs = append(argoObjs, &app)
  2281  			}
  2282  
  2283  			r := ApplicationSetReconciler{
  2284  				Client:           client,
  2285  				Scheme:           scheme,
  2286  				Recorder:         record.NewFakeRecorder(1),
  2287  				Cache:            &fakeCache{},
  2288  				Generators:       map[string]generators.Generator{},
  2289  				ArgoDB:           &argoDBMock,
  2290  				ArgoCDNamespace:  "namespace",
  2291  				ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  2292  				KubeClientset:    kubeclientset,
  2293  			}
  2294  
  2295  			appSetInfo := v1alpha1.ApplicationSet{}
  2296  
  2297  			validationErrors, _ := r.validateGeneratedApplications(context.TODO(), cc.apps, appSetInfo)
  2298  			var errorMessages []string
  2299  			for _, v := range validationErrors {
  2300  				errorMessages = append(errorMessages, v.Error())
  2301  			}
  2302  
  2303  			if len(errorMessages) == 0 {
  2304  				assert.Equal(t, len(cc.expectedErrors), 0, "Expected errors but none were seen")
  2305  			} else {
  2306  				// An error was returned: it should be expected
  2307  				matched := false
  2308  				for _, expectedErr := range cc.expectedErrors {
  2309  					foundMatch := strings.Contains(strings.Join(errorMessages, ";"), expectedErr)
  2310  					assert.True(t, foundMatch, "Unble to locate expected error: %s", cc.expectedErrors)
  2311  					matched = matched || foundMatch
  2312  				}
  2313  				assert.True(t, matched, "An unexpected error occurrred: %v", err)
  2314  				// validation message was returned: it should be expected
  2315  				matched = false
  2316  				foundMatch := reflect.DeepEqual(validationErrors, cc.validationErrors)
  2317  				var message string
  2318  				for _, v := range validationErrors {
  2319  					message = v.Error()
  2320  					break
  2321  				}
  2322  				assert.True(t, foundMatch, "Unble to locate validation message: %s", message)
  2323  				matched = matched || foundMatch
  2324  				assert.True(t, matched, "An unexpected error occurrred: %v", err)
  2325  			}
  2326  		})
  2327  	}
  2328  }
  2329  
  2330  func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
  2331  
  2332  	scheme := runtime.NewScheme()
  2333  	err := v1alpha1.AddToScheme(scheme)
  2334  	assert.Nil(t, err)
  2335  	err = v1alpha1.AddToScheme(scheme)
  2336  	assert.Nil(t, err)
  2337  
  2338  	project := v1alpha1.AppProject{
  2339  		ObjectMeta: metav1.ObjectMeta{Name: "good-project", Namespace: "argocd"},
  2340  	}
  2341  	appSet := v1alpha1.ApplicationSet{
  2342  		ObjectMeta: metav1.ObjectMeta{
  2343  			Name:      "name",
  2344  			Namespace: "argocd",
  2345  		},
  2346  		Spec: v1alpha1.ApplicationSetSpec{
  2347  			GoTemplate: true,
  2348  			Generators: []v1alpha1.ApplicationSetGenerator{
  2349  				{
  2350  					List: &v1alpha1.ListGenerator{
  2351  						Elements: []apiextensionsv1.JSON{{
  2352  							Raw: []byte(`{"project": "good-project"}`),
  2353  						}, {
  2354  							Raw: []byte(`{"project": "bad-project"}`),
  2355  						}},
  2356  					},
  2357  				},
  2358  			},
  2359  			Template: v1alpha1.ApplicationSetTemplate{
  2360  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  2361  					Name:      "{{.project}}",
  2362  					Namespace: "argocd",
  2363  				},
  2364  				Spec: v1alpha1.ApplicationSpec{
  2365  					Source:      &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
  2366  					Project:     "{{.project}}",
  2367  					Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"},
  2368  				},
  2369  			},
  2370  		},
  2371  	}
  2372  
  2373  	kubeclientset := kubefake.NewSimpleClientset()
  2374  	argoDBMock := dbmocks.ArgoDB{}
  2375  	argoObjs := []runtime.Object{&project}
  2376  
  2377  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  2378  	goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"}
  2379  	badCluster := v1alpha1.Cluster{Server: "https://bad-cluster", Name: "bad-cluster"}
  2380  	argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil)
  2381  	argoDBMock.On("GetCluster", mock.Anything, "https://bad-cluster").Return(&badCluster, nil)
  2382  	argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{
  2383  		goodCluster,
  2384  	}}, nil)
  2385  
  2386  	r := ApplicationSetReconciler{
  2387  		Client:   client,
  2388  		Scheme:   scheme,
  2389  		Renderer: &utils.Render{},
  2390  		Recorder: record.NewFakeRecorder(1),
  2391  		Cache:    &fakeCache{},
  2392  		Generators: map[string]generators.Generator{
  2393  			"List": generators.NewListGenerator(),
  2394  		},
  2395  		ArgoDB:           &argoDBMock,
  2396  		ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  2397  		KubeClientset:    kubeclientset,
  2398  		Policy:           v1alpha1.ApplicationsSyncPolicySync,
  2399  		ArgoCDNamespace:  "argocd",
  2400  	}
  2401  
  2402  	req := ctrl.Request{
  2403  		NamespacedName: types.NamespacedName{
  2404  			Namespace: "argocd",
  2405  			Name:      "name",
  2406  		},
  2407  	}
  2408  
  2409  	// Verify that on validation error, no error is returned, but the object is requeued
  2410  	res, err := r.Reconcile(context.Background(), req)
  2411  	assert.Nil(t, err)
  2412  	assert.True(t, res.RequeueAfter == ReconcileRequeueOnValidationError)
  2413  
  2414  	var app v1alpha1.Application
  2415  
  2416  	// make sure good app got created
  2417  	err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-project"}, &app)
  2418  	assert.NoError(t, err)
  2419  	assert.Equal(t, app.Name, "good-project")
  2420  
  2421  	// make sure bad app was not created
  2422  	err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "bad-project"}, &app)
  2423  	assert.Error(t, err)
  2424  }
  2425  
  2426  func TestSetApplicationSetStatusCondition(t *testing.T) {
  2427  	scheme := runtime.NewScheme()
  2428  	err := v1alpha1.AddToScheme(scheme)
  2429  	assert.Nil(t, err)
  2430  	err = v1alpha1.AddToScheme(scheme)
  2431  	assert.Nil(t, err)
  2432  
  2433  	appSet := v1alpha1.ApplicationSet{
  2434  		ObjectMeta: metav1.ObjectMeta{
  2435  			Name:      "name",
  2436  			Namespace: "argocd",
  2437  		},
  2438  		Spec: v1alpha1.ApplicationSetSpec{
  2439  			Generators: []v1alpha1.ApplicationSetGenerator{
  2440  				{List: &v1alpha1.ListGenerator{
  2441  					Elements: []apiextensionsv1.JSON{{
  2442  						Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2443  					}},
  2444  				}},
  2445  			},
  2446  			Template: v1alpha1.ApplicationSetTemplate{},
  2447  		},
  2448  	}
  2449  
  2450  	appCondition := v1alpha1.ApplicationSetCondition{
  2451  		Type:    v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2452  		Message: "All applications have been generated successfully",
  2453  		Reason:  v1alpha1.ApplicationSetReasonApplicationSetUpToDate,
  2454  		Status:  v1alpha1.ApplicationSetConditionStatusTrue,
  2455  	}
  2456  
  2457  	kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  2458  	argoDBMock := dbmocks.ArgoDB{}
  2459  	argoObjs := []runtime.Object{}
  2460  
  2461  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  2462  
  2463  	r := ApplicationSetReconciler{
  2464  		Client:   client,
  2465  		Scheme:   scheme,
  2466  		Renderer: &utils.Render{},
  2467  		Recorder: record.NewFakeRecorder(1),
  2468  		Cache:    &fakeCache{},
  2469  		Generators: map[string]generators.Generator{
  2470  			"List": generators.NewListGenerator(),
  2471  		},
  2472  		ArgoDB:           &argoDBMock,
  2473  		ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  2474  		KubeClientset:    kubeclientset,
  2475  	}
  2476  
  2477  	err = r.setApplicationSetStatusCondition(context.TODO(), &appSet, appCondition, true)
  2478  	assert.Nil(t, err)
  2479  
  2480  	assert.Len(t, appSet.Status.Conditions, 3)
  2481  }
  2482  
  2483  func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.Application {
  2484  
  2485  	scheme := runtime.NewScheme()
  2486  	err := v1alpha1.AddToScheme(scheme)
  2487  	assert.Nil(t, err)
  2488  	err = v1alpha1.AddToScheme(scheme)
  2489  	assert.Nil(t, err)
  2490  
  2491  	defaultProject := v1alpha1.AppProject{
  2492  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
  2493  		Spec:       v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}},
  2494  	}
  2495  	appSet := v1alpha1.ApplicationSet{
  2496  		ObjectMeta: metav1.ObjectMeta{
  2497  			Name:      "name",
  2498  			Namespace: "argocd",
  2499  		},
  2500  		Spec: v1alpha1.ApplicationSetSpec{
  2501  			Generators: []v1alpha1.ApplicationSetGenerator{
  2502  				{
  2503  					List: &v1alpha1.ListGenerator{
  2504  						Elements: []apiextensionsv1.JSON{{
  2505  							Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`),
  2506  						}},
  2507  					},
  2508  				},
  2509  			},
  2510  			SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{
  2511  				ApplicationsSync: &applicationsSyncPolicy,
  2512  			},
  2513  			Template: v1alpha1.ApplicationSetTemplate{
  2514  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  2515  					Name:      "{{cluster}}",
  2516  					Namespace: "argocd",
  2517  				},
  2518  				Spec: v1alpha1.ApplicationSpec{
  2519  					Source:      &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
  2520  					Project:     "default",
  2521  					Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"},
  2522  				},
  2523  			},
  2524  		},
  2525  	}
  2526  
  2527  	kubeclientset := kubefake.NewSimpleClientset()
  2528  	argoDBMock := dbmocks.ArgoDB{}
  2529  	argoObjs := []runtime.Object{&defaultProject}
  2530  
  2531  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  2532  	goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"}
  2533  	argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil)
  2534  	argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{
  2535  		goodCluster,
  2536  	}}, nil)
  2537  
  2538  	r := ApplicationSetReconciler{
  2539  		Client:   client,
  2540  		Scheme:   scheme,
  2541  		Renderer: &utils.Render{},
  2542  		Recorder: record.NewFakeRecorder(recordBuffer),
  2543  		Cache:    &fakeCache{},
  2544  		Generators: map[string]generators.Generator{
  2545  			"List": generators.NewListGenerator(),
  2546  		},
  2547  		ArgoDB:               &argoDBMock,
  2548  		ArgoCDNamespace:      "argocd",
  2549  		ArgoAppClientset:     appclientset.NewSimpleClientset(argoObjs...),
  2550  		KubeClientset:        kubeclientset,
  2551  		Policy:               v1alpha1.ApplicationsSyncPolicySync,
  2552  		EnablePolicyOverride: allowPolicyOverride,
  2553  	}
  2554  
  2555  	req := ctrl.Request{
  2556  		NamespacedName: types.NamespacedName{
  2557  			Namespace: "argocd",
  2558  			Name:      "name",
  2559  		},
  2560  	}
  2561  
  2562  	// Verify that on validation error, no error is returned, but the object is requeued
  2563  	resCreate, err := r.Reconcile(context.Background(), req)
  2564  	assert.Nil(t, err)
  2565  	assert.True(t, resCreate.RequeueAfter == 0)
  2566  
  2567  	var app v1alpha1.Application
  2568  
  2569  	// make sure good app got created
  2570  	err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
  2571  	assert.Nil(t, err)
  2572  	assert.Equal(t, app.Name, "good-cluster")
  2573  
  2574  	// Update resource
  2575  	var retrievedApplicationSet v1alpha1.ApplicationSet
  2576  	err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet)
  2577  	assert.Nil(t, err)
  2578  
  2579  	retrievedApplicationSet.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
  2580  	retrievedApplicationSet.Spec.Template.Labels = map[string]string{"label-key": "label-value"}
  2581  
  2582  	retrievedApplicationSet.Spec.Template.Spec.Source.Helm = &v1alpha1.ApplicationSourceHelm{
  2583  		Values: "global.test: test",
  2584  	}
  2585  
  2586  	err = r.Client.Update(context.TODO(), &retrievedApplicationSet)
  2587  	assert.Nil(t, err)
  2588  
  2589  	resUpdate, err := r.Reconcile(context.Background(), req)
  2590  	assert.Nil(t, err)
  2591  
  2592  	err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
  2593  	assert.Nil(t, err)
  2594  	assert.True(t, resUpdate.RequeueAfter == 0)
  2595  	assert.Equal(t, app.Name, "good-cluster")
  2596  
  2597  	return app
  2598  }
  2599  
  2600  func TestUpdateNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
  2601  
  2602  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
  2603  
  2604  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
  2605  
  2606  	assert.Nil(t, app.Spec.Source.Helm)
  2607  	assert.Nil(t, app.ObjectMeta.Annotations)
  2608  }
  2609  
  2610  func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) {
  2611  
  2612  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete
  2613  
  2614  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
  2615  
  2616  	assert.Nil(t, app.Spec.Source.Helm)
  2617  	assert.Nil(t, app.ObjectMeta.Annotations)
  2618  }
  2619  
  2620  func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) {
  2621  
  2622  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate
  2623  
  2624  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
  2625  
  2626  	assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
  2627  	assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
  2628  	assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
  2629  }
  2630  
  2631  func TestUpdatePerformedWithSyncPolicySync(t *testing.T) {
  2632  
  2633  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync
  2634  
  2635  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
  2636  
  2637  	assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
  2638  	assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
  2639  	assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
  2640  }
  2641  
  2642  func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
  2643  
  2644  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
  2645  
  2646  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, false)
  2647  
  2648  	assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
  2649  	assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
  2650  	assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
  2651  }
  2652  
  2653  func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.ApplicationList {
  2654  
  2655  	scheme := runtime.NewScheme()
  2656  	err := v1alpha1.AddToScheme(scheme)
  2657  	assert.Nil(t, err)
  2658  	err = v1alpha1.AddToScheme(scheme)
  2659  	assert.Nil(t, err)
  2660  
  2661  	defaultProject := v1alpha1.AppProject{
  2662  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
  2663  		Spec:       v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}},
  2664  	}
  2665  	appSet := v1alpha1.ApplicationSet{
  2666  		ObjectMeta: metav1.ObjectMeta{
  2667  			Name:      "name",
  2668  			Namespace: "argocd",
  2669  		},
  2670  		Spec: v1alpha1.ApplicationSetSpec{
  2671  			Generators: []v1alpha1.ApplicationSetGenerator{
  2672  				{
  2673  					List: &v1alpha1.ListGenerator{
  2674  						Elements: []apiextensionsv1.JSON{{
  2675  							Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`),
  2676  						}},
  2677  					},
  2678  				},
  2679  			},
  2680  			SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{
  2681  				ApplicationsSync: &applicationsSyncPolicy,
  2682  			},
  2683  			Template: v1alpha1.ApplicationSetTemplate{
  2684  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  2685  					Name:      "{{cluster}}",
  2686  					Namespace: "argocd",
  2687  				},
  2688  				Spec: v1alpha1.ApplicationSpec{
  2689  					Source:      &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
  2690  					Project:     "default",
  2691  					Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"},
  2692  				},
  2693  			},
  2694  		},
  2695  	}
  2696  
  2697  	kubeclientset := kubefake.NewSimpleClientset()
  2698  	argoDBMock := dbmocks.ArgoDB{}
  2699  	argoObjs := []runtime.Object{&defaultProject}
  2700  
  2701  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  2702  	goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"}
  2703  	argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil)
  2704  	argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{
  2705  		goodCluster,
  2706  	}}, nil)
  2707  
  2708  	r := ApplicationSetReconciler{
  2709  		Client:   client,
  2710  		Scheme:   scheme,
  2711  		Renderer: &utils.Render{},
  2712  		Recorder: record.NewFakeRecorder(recordBuffer),
  2713  		Cache:    &fakeCache{},
  2714  		Generators: map[string]generators.Generator{
  2715  			"List": generators.NewListGenerator(),
  2716  		},
  2717  		ArgoDB:               &argoDBMock,
  2718  		ArgoCDNamespace:      "argocd",
  2719  		ArgoAppClientset:     appclientset.NewSimpleClientset(argoObjs...),
  2720  		KubeClientset:        kubeclientset,
  2721  		Policy:               v1alpha1.ApplicationsSyncPolicySync,
  2722  		EnablePolicyOverride: allowPolicyOverride,
  2723  	}
  2724  
  2725  	req := ctrl.Request{
  2726  		NamespacedName: types.NamespacedName{
  2727  			Namespace: "argocd",
  2728  			Name:      "name",
  2729  		},
  2730  	}
  2731  
  2732  	// Verify that on validation error, no error is returned, but the object is requeued
  2733  	resCreate, err := r.Reconcile(context.Background(), req)
  2734  	assert.Nil(t, err)
  2735  	assert.True(t, resCreate.RequeueAfter == 0)
  2736  
  2737  	var app v1alpha1.Application
  2738  
  2739  	// make sure good app got created
  2740  	err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
  2741  	assert.Nil(t, err)
  2742  	assert.Equal(t, app.Name, "good-cluster")
  2743  
  2744  	// Update resource
  2745  	var retrievedApplicationSet v1alpha1.ApplicationSet
  2746  	err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet)
  2747  	assert.Nil(t, err)
  2748  	retrievedApplicationSet.Spec.Generators = []v1alpha1.ApplicationSetGenerator{
  2749  		{
  2750  			List: &v1alpha1.ListGenerator{
  2751  				Elements: []apiextensionsv1.JSON{},
  2752  			},
  2753  		},
  2754  	}
  2755  
  2756  	err = r.Client.Update(context.TODO(), &retrievedApplicationSet)
  2757  	assert.Nil(t, err)
  2758  
  2759  	resUpdate, err := r.Reconcile(context.Background(), req)
  2760  	assert.Nil(t, err)
  2761  
  2762  	var apps v1alpha1.ApplicationList
  2763  
  2764  	err = r.Client.List(context.TODO(), &apps)
  2765  	assert.Nil(t, err)
  2766  	assert.True(t, resUpdate.RequeueAfter == 0)
  2767  
  2768  	return apps
  2769  }
  2770  
  2771  func TestDeleteNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
  2772  
  2773  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
  2774  
  2775  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
  2776  
  2777  	assert.Equal(t, "good-cluster", apps.Items[0].Name)
  2778  }
  2779  
  2780  func TestDeleteNotPerformedWithSyncPolicyCreateUpdate(t *testing.T) {
  2781  
  2782  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate
  2783  
  2784  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
  2785  
  2786  	assert.Equal(t, "good-cluster", apps.Items[0].Name)
  2787  }
  2788  
  2789  func TestDeletePerformedWithSyncPolicyCreateDelete(t *testing.T) {
  2790  
  2791  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete
  2792  
  2793  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true)
  2794  
  2795  	assert.Equal(t, 0, len(apps.Items))
  2796  }
  2797  
  2798  func TestDeletePerformedWithSyncPolicySync(t *testing.T) {
  2799  
  2800  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync
  2801  
  2802  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true)
  2803  
  2804  	assert.Equal(t, 0, len(apps.Items))
  2805  }
  2806  
  2807  func TestDeletePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
  2808  
  2809  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
  2810  
  2811  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, false)
  2812  
  2813  	assert.Equal(t, 0, len(apps.Items))
  2814  }
  2815  
  2816  // Test app generation from a go template application set using a pull request generator
  2817  func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
  2818  	scheme := runtime.NewScheme()
  2819  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
  2820  
  2821  	for _, cases := range []struct {
  2822  		name        string
  2823  		params      []map[string]interface{}
  2824  		template    v1alpha1.ApplicationSetTemplate
  2825  		expectedApp []v1alpha1.Application
  2826  	}{
  2827  		{
  2828  			name: "Generate an application from a go template application set manifest using a pull request generator",
  2829  			params: []map[string]interface{}{{
  2830  				"number":                                "1",
  2831  				"branch":                                "branch1",
  2832  				"branch_slug":                           "branchSlug1",
  2833  				"head_sha":                              "089d92cbf9ff857a39e6feccd32798ca700fb958",
  2834  				"head_short_sha":                        "089d92cb",
  2835  				"branch_slugify_default":                "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
  2836  				"branch_slugify_smarttruncate_disabled": "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature",
  2837  				"branch_slugify_smarttruncate_enabled":  "feat/testwithsmarttruncateenabledramdomlonglistofcharacters",
  2838  				"labels":                                []string{"label1"}},
  2839  			},
  2840  			template: v1alpha1.ApplicationSetTemplate{
  2841  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  2842  					Name: "AppSet-{{.branch}}-{{.number}}",
  2843  					Labels: map[string]string{
  2844  						"app1":         "{{index .labels 0}}",
  2845  						"branch-test1": "AppSet-{{.branch_slugify_default | slugify }}",
  2846  						"branch-test2": "AppSet-{{.branch_slugify_smarttruncate_disabled | slugify 49 false }}",
  2847  						"branch-test3": "AppSet-{{.branch_slugify_smarttruncate_enabled | slugify 50 true }}",
  2848  					},
  2849  				},
  2850  				Spec: v1alpha1.ApplicationSpec{
  2851  					Source: &v1alpha1.ApplicationSource{
  2852  						RepoURL:        "https://testurl/testRepo",
  2853  						TargetRevision: "{{.head_short_sha}}",
  2854  					},
  2855  					Destination: v1alpha1.ApplicationDestination{
  2856  						Server:    "https://kubernetes.default.svc",
  2857  						Namespace: "AppSet-{{.branch_slug}}-{{.head_sha}}",
  2858  					},
  2859  				},
  2860  			},
  2861  			expectedApp: []v1alpha1.Application{
  2862  				{
  2863  					ObjectMeta: metav1.ObjectMeta{
  2864  						Name: "AppSet-branch1-1",
  2865  						Labels: map[string]string{
  2866  							"app1":         "label1",
  2867  							"branch-test1": "AppSet-feat-a-really-long-pull-request-name-to-test-argo",
  2868  							"branch-test2": "AppSet-feat-areallylongpullrequestnametotestargoslugific",
  2869  							"branch-test3": "AppSet-feat",
  2870  						},
  2871  					},
  2872  					Spec: v1alpha1.ApplicationSpec{
  2873  						Source: &v1alpha1.ApplicationSource{
  2874  							RepoURL:        "https://testurl/testRepo",
  2875  							TargetRevision: "089d92cb",
  2876  						},
  2877  						Destination: v1alpha1.ApplicationDestination{
  2878  							Server:    "https://kubernetes.default.svc",
  2879  							Namespace: "AppSet-branchSlug1-089d92cbf9ff857a39e6feccd32798ca700fb958",
  2880  						},
  2881  					},
  2882  				},
  2883  			},
  2884  		},
  2885  	} {
  2886  
  2887  		t.Run(cases.name, func(t *testing.T) {
  2888  
  2889  			generatorMock := generatorMock{}
  2890  			generator := v1alpha1.ApplicationSetGenerator{
  2891  				PullRequest: &v1alpha1.PullRequestGenerator{},
  2892  			}
  2893  
  2894  			generatorMock.On("GenerateParams", &generator).
  2895  				Return(cases.params, nil)
  2896  
  2897  			generatorMock.On("GetTemplate", &generator).
  2898  				Return(&cases.template, nil)
  2899  
  2900  			appSetReconciler := ApplicationSetReconciler{
  2901  				Client:   client,
  2902  				Scheme:   scheme,
  2903  				Recorder: record.NewFakeRecorder(1),
  2904  				Cache:    &fakeCache{},
  2905  				Generators: map[string]generators.Generator{
  2906  					"PullRequest": &generatorMock,
  2907  				},
  2908  				Renderer:      &utils.Render{},
  2909  				KubeClientset: kubefake.NewSimpleClientset(),
  2910  			}
  2911  
  2912  			gotApp, _, _ := appSetReconciler.generateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
  2913  				Spec: v1alpha1.ApplicationSetSpec{
  2914  					GoTemplate: true,
  2915  					Generators: []v1alpha1.ApplicationSetGenerator{{
  2916  						PullRequest: &v1alpha1.PullRequestGenerator{},
  2917  					}},
  2918  					Template: cases.template,
  2919  				},
  2920  			},
  2921  			)
  2922  			assert.EqualValues(t, cases.expectedApp[0].ObjectMeta.Name, gotApp[0].ObjectMeta.Name)
  2923  			assert.EqualValues(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
  2924  			assert.EqualValues(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
  2925  			assert.True(t, collections.StringMapsEqual(cases.expectedApp[0].ObjectMeta.Labels, gotApp[0].ObjectMeta.Labels))
  2926  		})
  2927  	}
  2928  }
  2929  
  2930  func TestPolicies(t *testing.T) {
  2931  	scheme := runtime.NewScheme()
  2932  	err := v1alpha1.AddToScheme(scheme)
  2933  	assert.Nil(t, err)
  2934  
  2935  	err = v1alpha1.AddToScheme(scheme)
  2936  	assert.Nil(t, err)
  2937  
  2938  	defaultProject := v1alpha1.AppProject{
  2939  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
  2940  		Spec:       v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://kubernetes.default.svc"}}},
  2941  	}
  2942  	myCluster := v1alpha1.Cluster{
  2943  		Server: "https://kubernetes.default.svc",
  2944  		Name:   "my-cluster",
  2945  	}
  2946  
  2947  	kubeclientset := kubefake.NewSimpleClientset()
  2948  	argoDBMock := dbmocks.ArgoDB{}
  2949  	argoDBMock.On("GetCluster", mock.Anything, "https://kubernetes.default.svc").Return(&myCluster, nil)
  2950  	argoObjs := []runtime.Object{&defaultProject}
  2951  
  2952  	for _, c := range []struct {
  2953  		name          string
  2954  		policyName    string
  2955  		allowedUpdate bool
  2956  		allowedDelete bool
  2957  	}{
  2958  		{
  2959  			name:          "Apps are allowed to update and delete",
  2960  			policyName:    "sync",
  2961  			allowedUpdate: true,
  2962  			allowedDelete: true,
  2963  		},
  2964  		{
  2965  			name:          "Apps are not allowed to update and delete",
  2966  			policyName:    "create-only",
  2967  			allowedUpdate: false,
  2968  			allowedDelete: false,
  2969  		},
  2970  		{
  2971  			name:          "Apps are allowed to update, not allowed to delete",
  2972  			policyName:    "create-update",
  2973  			allowedUpdate: true,
  2974  			allowedDelete: false,
  2975  		},
  2976  		{
  2977  			name:          "Apps are allowed to delete, not allowed to update",
  2978  			policyName:    "create-delete",
  2979  			allowedUpdate: false,
  2980  			allowedDelete: true,
  2981  		},
  2982  	} {
  2983  		t.Run(c.name, func(t *testing.T) {
  2984  			policy := utils.Policies[c.policyName]
  2985  			assert.NotNil(t, policy)
  2986  
  2987  			appSet := v1alpha1.ApplicationSet{
  2988  				ObjectMeta: metav1.ObjectMeta{
  2989  					Name:      "name",
  2990  					Namespace: "argocd",
  2991  				},
  2992  				Spec: v1alpha1.ApplicationSetSpec{
  2993  					GoTemplate: true,
  2994  					Generators: []v1alpha1.ApplicationSetGenerator{
  2995  						{
  2996  							List: &v1alpha1.ListGenerator{
  2997  								Elements: []apiextensionsv1.JSON{
  2998  									{
  2999  										Raw: []byte(`{"name": "my-app"}`),
  3000  									},
  3001  								},
  3002  							},
  3003  						},
  3004  					},
  3005  					Template: v1alpha1.ApplicationSetTemplate{
  3006  						ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  3007  							Name:      "{{.name}}",
  3008  							Namespace: "argocd",
  3009  							Annotations: map[string]string{
  3010  								"key": "value",
  3011  							},
  3012  						},
  3013  						Spec: v1alpha1.ApplicationSpec{
  3014  							Source:      &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
  3015  							Project:     "default",
  3016  							Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"},
  3017  						},
  3018  					},
  3019  				},
  3020  			}
  3021  
  3022  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  3023  
  3024  			r := ApplicationSetReconciler{
  3025  				Client:   client,
  3026  				Scheme:   scheme,
  3027  				Renderer: &utils.Render{},
  3028  				Recorder: record.NewFakeRecorder(10),
  3029  				Cache:    &fakeCache{},
  3030  				Generators: map[string]generators.Generator{
  3031  					"List": generators.NewListGenerator(),
  3032  				},
  3033  				ArgoDB:           &argoDBMock,
  3034  				ArgoCDNamespace:  "argocd",
  3035  				ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  3036  				KubeClientset:    kubeclientset,
  3037  				Policy:           policy,
  3038  			}
  3039  
  3040  			req := ctrl.Request{
  3041  				NamespacedName: types.NamespacedName{
  3042  					Namespace: "argocd",
  3043  					Name:      "name",
  3044  				},
  3045  			}
  3046  
  3047  			// Check if Application is created
  3048  			res, err := r.Reconcile(context.Background(), req)
  3049  			assert.Nil(t, err)
  3050  			assert.True(t, res.RequeueAfter == 0)
  3051  
  3052  			var app v1alpha1.Application
  3053  			err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app)
  3054  			assert.NoError(t, err)
  3055  			assert.Equal(t, app.Annotations["key"], "value")
  3056  
  3057  			// Check if Application is updated
  3058  			app.Annotations["key"] = "edited"
  3059  			err = r.Client.Update(context.TODO(), &app)
  3060  			assert.NoError(t, err)
  3061  
  3062  			res, err = r.Reconcile(context.Background(), req)
  3063  			assert.Nil(t, err)
  3064  			assert.True(t, res.RequeueAfter == 0)
  3065  
  3066  			err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app)
  3067  			assert.NoError(t, err)
  3068  
  3069  			if c.allowedUpdate {
  3070  				assert.Equal(t, app.Annotations["key"], "value")
  3071  			} else {
  3072  				assert.Equal(t, app.Annotations["key"], "edited")
  3073  			}
  3074  
  3075  			// Check if Application is deleted
  3076  			err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &appSet)
  3077  			assert.NoError(t, err)
  3078  			appSet.Spec.Generators[0] = v1alpha1.ApplicationSetGenerator{
  3079  				List: &v1alpha1.ListGenerator{
  3080  					Elements: []apiextensionsv1.JSON{},
  3081  				},
  3082  			}
  3083  			err = r.Client.Update(context.TODO(), &appSet)
  3084  			assert.NoError(t, err)
  3085  
  3086  			res, err = r.Reconcile(context.Background(), req)
  3087  			assert.Nil(t, err)
  3088  			assert.True(t, res.RequeueAfter == 0)
  3089  
  3090  			err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app)
  3091  			assert.NoError(t, err)
  3092  			if c.allowedDelete {
  3093  				assert.NotNil(t, app.DeletionTimestamp)
  3094  			} else {
  3095  				assert.Nil(t, app.DeletionTimestamp)
  3096  			}
  3097  		})
  3098  	}
  3099  }
  3100  
  3101  func TestSetApplicationSetApplicationStatus(t *testing.T) {
  3102  	scheme := runtime.NewScheme()
  3103  	err := v1alpha1.AddToScheme(scheme)
  3104  	assert.Nil(t, err)
  3105  	err = v1alpha1.AddToScheme(scheme)
  3106  	assert.Nil(t, err)
  3107  
  3108  	kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  3109  	argoDBMock := dbmocks.ArgoDB{}
  3110  	argoObjs := []runtime.Object{}
  3111  
  3112  	for _, cc := range []struct {
  3113  		name                string
  3114  		appSet              v1alpha1.ApplicationSet
  3115  		appStatuses         []v1alpha1.ApplicationSetApplicationStatus
  3116  		expectedAppStatuses []v1alpha1.ApplicationSetApplicationStatus
  3117  	}{
  3118  		{
  3119  			name: "sets a single appstatus",
  3120  			appSet: v1alpha1.ApplicationSet{
  3121  				ObjectMeta: metav1.ObjectMeta{
  3122  					Name:      "name",
  3123  					Namespace: "argocd",
  3124  				},
  3125  				Spec: v1alpha1.ApplicationSetSpec{
  3126  					Generators: []v1alpha1.ApplicationSetGenerator{
  3127  						{List: &v1alpha1.ListGenerator{
  3128  							Elements: []apiextensionsv1.JSON{{
  3129  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  3130  							}},
  3131  						}},
  3132  					},
  3133  					Template: v1alpha1.ApplicationSetTemplate{},
  3134  				},
  3135  			},
  3136  			appStatuses: []v1alpha1.ApplicationSetApplicationStatus{
  3137  				{
  3138  					Application: "app1",
  3139  					Message:     "testing SetApplicationSetApplicationStatus to Healthy",
  3140  					Status:      "Healthy",
  3141  				},
  3142  			},
  3143  			expectedAppStatuses: []v1alpha1.ApplicationSetApplicationStatus{
  3144  				{
  3145  					Application: "app1",
  3146  					Message:     "testing SetApplicationSetApplicationStatus to Healthy",
  3147  					Status:      "Healthy",
  3148  				},
  3149  			},
  3150  		},
  3151  		{
  3152  			name: "removes an appstatus",
  3153  			appSet: v1alpha1.ApplicationSet{
  3154  				ObjectMeta: metav1.ObjectMeta{
  3155  					Name:      "name",
  3156  					Namespace: "argocd",
  3157  				},
  3158  				Spec: v1alpha1.ApplicationSetSpec{
  3159  					Generators: []v1alpha1.ApplicationSetGenerator{
  3160  						{List: &v1alpha1.ListGenerator{
  3161  							Elements: []apiextensionsv1.JSON{{
  3162  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  3163  							}},
  3164  						}},
  3165  					},
  3166  					Template: v1alpha1.ApplicationSetTemplate{},
  3167  				},
  3168  				Status: v1alpha1.ApplicationSetStatus{
  3169  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  3170  						{
  3171  							Application: "app1",
  3172  							Message:     "testing SetApplicationSetApplicationStatus to Healthy",
  3173  							Status:      "Healthy",
  3174  						},
  3175  					},
  3176  				},
  3177  			},
  3178  			appStatuses:         []v1alpha1.ApplicationSetApplicationStatus{},
  3179  			expectedAppStatuses: nil,
  3180  		},
  3181  	} {
  3182  
  3183  		t.Run(cc.name, func(t *testing.T) {
  3184  
  3185  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build()
  3186  
  3187  			r := ApplicationSetReconciler{
  3188  				Client:   client,
  3189  				Scheme:   scheme,
  3190  				Renderer: &utils.Render{},
  3191  				Recorder: record.NewFakeRecorder(1),
  3192  				Cache:    &fakeCache{},
  3193  				Generators: map[string]generators.Generator{
  3194  					"List": generators.NewListGenerator(),
  3195  				},
  3196  				ArgoDB:           &argoDBMock,
  3197  				ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  3198  				KubeClientset:    kubeclientset,
  3199  			}
  3200  
  3201  			err = r.setAppSetApplicationStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appStatuses)
  3202  			assert.Nil(t, err)
  3203  
  3204  			assert.Equal(t, cc.expectedAppStatuses, cc.appSet.Status.ApplicationStatus)
  3205  		})
  3206  	}
  3207  }
  3208  
  3209  func TestBuildAppDependencyList(t *testing.T) {
  3210  
  3211  	scheme := runtime.NewScheme()
  3212  	err := v1alpha1.AddToScheme(scheme)
  3213  	assert.Nil(t, err)
  3214  
  3215  	err = v1alpha1.AddToScheme(scheme)
  3216  	assert.Nil(t, err)
  3217  
  3218  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
  3219  
  3220  	for _, cc := range []struct {
  3221  		name            string
  3222  		appSet          v1alpha1.ApplicationSet
  3223  		apps            []v1alpha1.Application
  3224  		expectedList    [][]string
  3225  		expectedStepMap map[string]int
  3226  	}{
  3227  		{
  3228  			name: "handles an empty set of applications and no strategy",
  3229  			appSet: v1alpha1.ApplicationSet{
  3230  				ObjectMeta: metav1.ObjectMeta{
  3231  					Name:      "name",
  3232  					Namespace: "argocd",
  3233  				},
  3234  				Spec: v1alpha1.ApplicationSetSpec{},
  3235  			},
  3236  			apps:            []v1alpha1.Application{},
  3237  			expectedList:    [][]string{},
  3238  			expectedStepMap: map[string]int{},
  3239  		},
  3240  		{
  3241  			name: "handles an empty set of applications and ignores AllAtOnce strategy",
  3242  			appSet: v1alpha1.ApplicationSet{
  3243  				ObjectMeta: metav1.ObjectMeta{
  3244  					Name:      "name",
  3245  					Namespace: "argocd",
  3246  				},
  3247  				Spec: v1alpha1.ApplicationSetSpec{
  3248  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3249  						Type: "AllAtOnce",
  3250  					},
  3251  				},
  3252  			},
  3253  			apps:            []v1alpha1.Application{},
  3254  			expectedList:    [][]string{},
  3255  			expectedStepMap: map[string]int{},
  3256  		},
  3257  		{
  3258  			name: "handles an empty set of applications with good 'In' selectors",
  3259  			appSet: v1alpha1.ApplicationSet{
  3260  				ObjectMeta: metav1.ObjectMeta{
  3261  					Name:      "name",
  3262  					Namespace: "argocd",
  3263  				},
  3264  				Spec: v1alpha1.ApplicationSetSpec{
  3265  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3266  						Type: "RollingSync",
  3267  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3268  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3269  								{
  3270  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3271  										{
  3272  											Key:      "env",
  3273  											Operator: "In",
  3274  											Values: []string{
  3275  												"dev",
  3276  											},
  3277  										},
  3278  									},
  3279  								},
  3280  							},
  3281  						},
  3282  					},
  3283  				},
  3284  			},
  3285  			apps: []v1alpha1.Application{},
  3286  			expectedList: [][]string{
  3287  				{},
  3288  			},
  3289  			expectedStepMap: map[string]int{},
  3290  		},
  3291  		{
  3292  			name: "handles selecting 1 application with 1 'In' selector",
  3293  			appSet: v1alpha1.ApplicationSet{
  3294  				ObjectMeta: metav1.ObjectMeta{
  3295  					Name:      "name",
  3296  					Namespace: "argocd",
  3297  				},
  3298  				Spec: v1alpha1.ApplicationSetSpec{
  3299  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3300  						Type: "RollingSync",
  3301  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3302  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3303  								{
  3304  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3305  										{
  3306  											Key:      "env",
  3307  											Operator: "In",
  3308  											Values: []string{
  3309  												"dev",
  3310  											},
  3311  										},
  3312  									},
  3313  								},
  3314  							},
  3315  						},
  3316  					},
  3317  				},
  3318  			},
  3319  			apps: []v1alpha1.Application{
  3320  				{
  3321  					ObjectMeta: metav1.ObjectMeta{
  3322  						Name: "app-dev",
  3323  						Labels: map[string]string{
  3324  							"env": "dev",
  3325  						},
  3326  					},
  3327  				},
  3328  			},
  3329  			expectedList: [][]string{
  3330  				{"app-dev"},
  3331  			},
  3332  			expectedStepMap: map[string]int{
  3333  				"app-dev": 0,
  3334  			},
  3335  		},
  3336  		{
  3337  			name: "handles 'In' selectors that select no applications",
  3338  			appSet: v1alpha1.ApplicationSet{
  3339  				ObjectMeta: metav1.ObjectMeta{
  3340  					Name:      "name",
  3341  					Namespace: "argocd",
  3342  				},
  3343  				Spec: v1alpha1.ApplicationSetSpec{
  3344  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3345  						Type: "RollingSync",
  3346  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3347  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3348  								{
  3349  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3350  										{
  3351  											Key:      "env",
  3352  											Operator: "In",
  3353  											Values: []string{
  3354  												"dev",
  3355  											},
  3356  										},
  3357  									},
  3358  								},
  3359  								{
  3360  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3361  										{
  3362  											Key:      "env",
  3363  											Operator: "In",
  3364  											Values: []string{
  3365  												"qa",
  3366  											},
  3367  										},
  3368  									},
  3369  								},
  3370  								{
  3371  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3372  										{
  3373  											Key:      "env",
  3374  											Operator: "In",
  3375  											Values: []string{
  3376  												"prod",
  3377  											},
  3378  										},
  3379  									},
  3380  								},
  3381  							},
  3382  						},
  3383  					},
  3384  				},
  3385  			},
  3386  			apps: []v1alpha1.Application{
  3387  				{
  3388  					ObjectMeta: metav1.ObjectMeta{
  3389  						Name: "app-qa",
  3390  						Labels: map[string]string{
  3391  							"env": "qa",
  3392  						},
  3393  					},
  3394  				},
  3395  				{
  3396  					ObjectMeta: metav1.ObjectMeta{
  3397  						Name: "app-prod",
  3398  						Labels: map[string]string{
  3399  							"env": "prod",
  3400  						},
  3401  					},
  3402  				},
  3403  			},
  3404  			expectedList: [][]string{
  3405  				{},
  3406  				{"app-qa"},
  3407  				{"app-prod"},
  3408  			},
  3409  			expectedStepMap: map[string]int{
  3410  				"app-qa":   1,
  3411  				"app-prod": 2,
  3412  			},
  3413  		},
  3414  		{
  3415  			name: "multiple 'In' selectors in the same matchExpression only select Applications that match all selectors",
  3416  			appSet: v1alpha1.ApplicationSet{
  3417  				ObjectMeta: metav1.ObjectMeta{
  3418  					Name:      "name",
  3419  					Namespace: "argocd",
  3420  				},
  3421  				Spec: v1alpha1.ApplicationSetSpec{
  3422  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3423  						Type: "RollingSync",
  3424  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3425  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3426  								{
  3427  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3428  										{
  3429  											Key:      "region",
  3430  											Operator: "In",
  3431  											Values: []string{
  3432  												"us-east-2",
  3433  											},
  3434  										},
  3435  										{
  3436  											Key:      "env",
  3437  											Operator: "In",
  3438  											Values: []string{
  3439  												"qa",
  3440  											},
  3441  										},
  3442  									},
  3443  								},
  3444  							},
  3445  						},
  3446  					},
  3447  				},
  3448  			},
  3449  			apps: []v1alpha1.Application{
  3450  				{
  3451  					ObjectMeta: metav1.ObjectMeta{
  3452  						Name: "app-qa1",
  3453  						Labels: map[string]string{
  3454  							"env": "qa",
  3455  						},
  3456  					},
  3457  				},
  3458  				{
  3459  					ObjectMeta: metav1.ObjectMeta{
  3460  						Name: "app-qa2",
  3461  						Labels: map[string]string{
  3462  							"env":    "qa",
  3463  							"region": "us-east-2",
  3464  						},
  3465  					},
  3466  				},
  3467  			},
  3468  			expectedList: [][]string{
  3469  				{"app-qa2"},
  3470  			},
  3471  			expectedStepMap: map[string]int{
  3472  				"app-qa2": 0,
  3473  			},
  3474  		},
  3475  		{
  3476  			name: "multiple values in the same 'In' matchExpression can match on any value",
  3477  			appSet: v1alpha1.ApplicationSet{
  3478  				ObjectMeta: metav1.ObjectMeta{
  3479  					Name:      "name",
  3480  					Namespace: "argocd",
  3481  				},
  3482  				Spec: v1alpha1.ApplicationSetSpec{
  3483  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3484  						Type: "RollingSync",
  3485  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3486  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3487  								{
  3488  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3489  										{
  3490  											Key:      "env",
  3491  											Operator: "In",
  3492  											Values: []string{
  3493  												"qa",
  3494  												"prod",
  3495  											},
  3496  										},
  3497  									},
  3498  								},
  3499  							},
  3500  						},
  3501  					},
  3502  				},
  3503  			},
  3504  			apps: []v1alpha1.Application{
  3505  				{
  3506  					ObjectMeta: metav1.ObjectMeta{
  3507  						Name: "app-dev",
  3508  						Labels: map[string]string{
  3509  							"env": "dev",
  3510  						},
  3511  					},
  3512  				},
  3513  				{
  3514  					ObjectMeta: metav1.ObjectMeta{
  3515  						Name: "app-qa",
  3516  						Labels: map[string]string{
  3517  							"env": "qa",
  3518  						},
  3519  					},
  3520  				},
  3521  				{
  3522  					ObjectMeta: metav1.ObjectMeta{
  3523  						Name: "app-prod",
  3524  						Labels: map[string]string{
  3525  							"env":    "prod",
  3526  							"region": "us-east-2",
  3527  						},
  3528  					},
  3529  				},
  3530  			},
  3531  			expectedList: [][]string{
  3532  				{"app-qa", "app-prod"},
  3533  			},
  3534  			expectedStepMap: map[string]int{
  3535  				"app-qa":   0,
  3536  				"app-prod": 0,
  3537  			},
  3538  		},
  3539  		{
  3540  			name: "handles an empty set of applications with good 'NotIn' selectors",
  3541  			appSet: v1alpha1.ApplicationSet{
  3542  				ObjectMeta: metav1.ObjectMeta{
  3543  					Name:      "name",
  3544  					Namespace: "argocd",
  3545  				},
  3546  				Spec: v1alpha1.ApplicationSetSpec{
  3547  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3548  						Type: "RollingSync",
  3549  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3550  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3551  								{
  3552  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3553  										{
  3554  											Key:      "env",
  3555  											Operator: "In",
  3556  											Values: []string{
  3557  												"dev",
  3558  											},
  3559  										},
  3560  									},
  3561  								},
  3562  							},
  3563  						},
  3564  					},
  3565  				},
  3566  			},
  3567  			apps: []v1alpha1.Application{},
  3568  			expectedList: [][]string{
  3569  				{},
  3570  			},
  3571  			expectedStepMap: map[string]int{},
  3572  		},
  3573  		{
  3574  			name: "selects 1 application with 1 'NotIn' selector",
  3575  			appSet: v1alpha1.ApplicationSet{
  3576  				ObjectMeta: metav1.ObjectMeta{
  3577  					Name:      "name",
  3578  					Namespace: "argocd",
  3579  				},
  3580  				Spec: v1alpha1.ApplicationSetSpec{
  3581  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3582  						Type: "RollingSync",
  3583  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3584  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3585  								{
  3586  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3587  										{
  3588  											Key:      "env",
  3589  											Operator: "NotIn",
  3590  											Values: []string{
  3591  												"qa",
  3592  											},
  3593  										},
  3594  									},
  3595  								},
  3596  							},
  3597  						},
  3598  					},
  3599  				},
  3600  			},
  3601  			apps: []v1alpha1.Application{
  3602  				{
  3603  					ObjectMeta: metav1.ObjectMeta{
  3604  						Name: "app-dev",
  3605  						Labels: map[string]string{
  3606  							"env": "dev",
  3607  						},
  3608  					},
  3609  				},
  3610  			},
  3611  			expectedList: [][]string{
  3612  				{"app-dev"},
  3613  			},
  3614  			expectedStepMap: map[string]int{
  3615  				"app-dev": 0,
  3616  			},
  3617  		},
  3618  		{
  3619  			name: "'NotIn' selectors that select no applications",
  3620  			appSet: v1alpha1.ApplicationSet{
  3621  				ObjectMeta: metav1.ObjectMeta{
  3622  					Name:      "name",
  3623  					Namespace: "argocd",
  3624  				},
  3625  				Spec: v1alpha1.ApplicationSetSpec{
  3626  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3627  						Type: "RollingSync",
  3628  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3629  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3630  								{
  3631  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3632  										{
  3633  											Key:      "env",
  3634  											Operator: "NotIn",
  3635  											Values: []string{
  3636  												"dev",
  3637  											},
  3638  										},
  3639  									},
  3640  								},
  3641  							},
  3642  						},
  3643  					},
  3644  				},
  3645  			},
  3646  			apps: []v1alpha1.Application{
  3647  				{
  3648  					ObjectMeta: metav1.ObjectMeta{
  3649  						Name: "app-qa",
  3650  						Labels: map[string]string{
  3651  							"env": "qa",
  3652  						},
  3653  					},
  3654  				},
  3655  				{
  3656  					ObjectMeta: metav1.ObjectMeta{
  3657  						Name: "app-prod",
  3658  						Labels: map[string]string{
  3659  							"env": "prod",
  3660  						},
  3661  					},
  3662  				},
  3663  			},
  3664  			expectedList: [][]string{
  3665  				{"app-qa", "app-prod"},
  3666  			},
  3667  			expectedStepMap: map[string]int{
  3668  				"app-qa":   0,
  3669  				"app-prod": 0,
  3670  			},
  3671  		},
  3672  		{
  3673  			name: "multiple 'NotIn' selectors remove Applications with mising labels on any match",
  3674  			appSet: v1alpha1.ApplicationSet{
  3675  				ObjectMeta: metav1.ObjectMeta{
  3676  					Name:      "name",
  3677  					Namespace: "argocd",
  3678  				},
  3679  				Spec: v1alpha1.ApplicationSetSpec{
  3680  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3681  						Type: "RollingSync",
  3682  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3683  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3684  								{
  3685  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3686  										{
  3687  											Key:      "region",
  3688  											Operator: "NotIn",
  3689  											Values: []string{
  3690  												"us-east-2",
  3691  											},
  3692  										},
  3693  										{
  3694  											Key:      "env",
  3695  											Operator: "NotIn",
  3696  											Values: []string{
  3697  												"qa",
  3698  											},
  3699  										},
  3700  									},
  3701  								},
  3702  							},
  3703  						},
  3704  					},
  3705  				},
  3706  			},
  3707  			apps: []v1alpha1.Application{
  3708  				{
  3709  					ObjectMeta: metav1.ObjectMeta{
  3710  						Name: "app-qa1",
  3711  						Labels: map[string]string{
  3712  							"env": "qa",
  3713  						},
  3714  					},
  3715  				},
  3716  				{
  3717  					ObjectMeta: metav1.ObjectMeta{
  3718  						Name: "app-qa2",
  3719  						Labels: map[string]string{
  3720  							"env":    "qa",
  3721  							"region": "us-east-2",
  3722  						},
  3723  					},
  3724  				},
  3725  			},
  3726  			expectedList: [][]string{
  3727  				{},
  3728  			},
  3729  			expectedStepMap: map[string]int{},
  3730  		},
  3731  		{
  3732  			name: "multiple 'NotIn' selectors filter all matching Applications",
  3733  			appSet: v1alpha1.ApplicationSet{
  3734  				ObjectMeta: metav1.ObjectMeta{
  3735  					Name:      "name",
  3736  					Namespace: "argocd",
  3737  				},
  3738  				Spec: v1alpha1.ApplicationSetSpec{
  3739  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3740  						Type: "RollingSync",
  3741  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3742  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3743  								{
  3744  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3745  										{
  3746  											Key:      "region",
  3747  											Operator: "NotIn",
  3748  											Values: []string{
  3749  												"us-east-2",
  3750  											},
  3751  										},
  3752  										{
  3753  											Key:      "env",
  3754  											Operator: "NotIn",
  3755  											Values: []string{
  3756  												"qa",
  3757  											},
  3758  										},
  3759  									},
  3760  								},
  3761  							},
  3762  						},
  3763  					},
  3764  				},
  3765  			},
  3766  			apps: []v1alpha1.Application{
  3767  				{
  3768  					ObjectMeta: metav1.ObjectMeta{
  3769  						Name: "app-qa1",
  3770  						Labels: map[string]string{
  3771  							"env":    "qa",
  3772  							"region": "us-east-1",
  3773  						},
  3774  					},
  3775  				},
  3776  				{
  3777  					ObjectMeta: metav1.ObjectMeta{
  3778  						Name: "app-qa2",
  3779  						Labels: map[string]string{
  3780  							"env":    "qa",
  3781  							"region": "us-east-2",
  3782  						},
  3783  					},
  3784  				},
  3785  				{
  3786  					ObjectMeta: metav1.ObjectMeta{
  3787  						Name: "app-prod1",
  3788  						Labels: map[string]string{
  3789  							"env":    "prod",
  3790  							"region": "us-east-1",
  3791  						},
  3792  					},
  3793  				},
  3794  				{
  3795  					ObjectMeta: metav1.ObjectMeta{
  3796  						Name: "app-prod2",
  3797  						Labels: map[string]string{
  3798  							"env":    "prod",
  3799  							"region": "us-east-2",
  3800  						},
  3801  					},
  3802  				},
  3803  			},
  3804  			expectedList: [][]string{
  3805  				{"app-prod1"},
  3806  			},
  3807  			expectedStepMap: map[string]int{
  3808  				"app-prod1": 0,
  3809  			},
  3810  		},
  3811  		{
  3812  			name: "multiple values in the same 'NotIn' matchExpression exclude a match from any value",
  3813  			appSet: v1alpha1.ApplicationSet{
  3814  				ObjectMeta: metav1.ObjectMeta{
  3815  					Name:      "name",
  3816  					Namespace: "argocd",
  3817  				},
  3818  				Spec: v1alpha1.ApplicationSetSpec{
  3819  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3820  						Type: "RollingSync",
  3821  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3822  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3823  								{
  3824  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3825  										{
  3826  											Key:      "env",
  3827  											Operator: "NotIn",
  3828  											Values: []string{
  3829  												"qa",
  3830  												"prod",
  3831  											},
  3832  										},
  3833  									},
  3834  								},
  3835  							},
  3836  						},
  3837  					},
  3838  				},
  3839  			},
  3840  			apps: []v1alpha1.Application{
  3841  				{
  3842  					ObjectMeta: metav1.ObjectMeta{
  3843  						Name: "app-dev",
  3844  						Labels: map[string]string{
  3845  							"env": "dev",
  3846  						},
  3847  					},
  3848  				},
  3849  				{
  3850  					ObjectMeta: metav1.ObjectMeta{
  3851  						Name: "app-qa",
  3852  						Labels: map[string]string{
  3853  							"env": "qa",
  3854  						},
  3855  					},
  3856  				},
  3857  				{
  3858  					ObjectMeta: metav1.ObjectMeta{
  3859  						Name: "app-prod",
  3860  						Labels: map[string]string{
  3861  							"env":    "prod",
  3862  							"region": "us-east-2",
  3863  						},
  3864  					},
  3865  				},
  3866  			},
  3867  			expectedList: [][]string{
  3868  				{"app-dev"},
  3869  			},
  3870  			expectedStepMap: map[string]int{
  3871  				"app-dev": 0,
  3872  			},
  3873  		},
  3874  		{
  3875  			name: "in a mix of 'In' and 'NotIn' selectors, 'NotIn' takes precedence",
  3876  			appSet: v1alpha1.ApplicationSet{
  3877  				ObjectMeta: metav1.ObjectMeta{
  3878  					Name:      "name",
  3879  					Namespace: "argocd",
  3880  				},
  3881  				Spec: v1alpha1.ApplicationSetSpec{
  3882  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3883  						Type: "RollingSync",
  3884  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3885  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3886  								{
  3887  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3888  										{
  3889  											Key:      "env",
  3890  											Operator: "In",
  3891  											Values: []string{
  3892  												"qa",
  3893  												"prod",
  3894  											},
  3895  										},
  3896  										{
  3897  											Key:      "region",
  3898  											Operator: "NotIn",
  3899  											Values: []string{
  3900  												"us-west-2",
  3901  											},
  3902  										},
  3903  									},
  3904  								},
  3905  							},
  3906  						},
  3907  					},
  3908  				},
  3909  			},
  3910  			apps: []v1alpha1.Application{
  3911  				{
  3912  					ObjectMeta: metav1.ObjectMeta{
  3913  						Name: "app-dev",
  3914  						Labels: map[string]string{
  3915  							"env": "dev",
  3916  						},
  3917  					},
  3918  				},
  3919  				{
  3920  					ObjectMeta: metav1.ObjectMeta{
  3921  						Name: "app-qa1",
  3922  						Labels: map[string]string{
  3923  							"env":    "qa",
  3924  							"region": "us-west-2",
  3925  						},
  3926  					},
  3927  				},
  3928  				{
  3929  					ObjectMeta: metav1.ObjectMeta{
  3930  						Name: "app-qa2",
  3931  						Labels: map[string]string{
  3932  							"env":    "qa",
  3933  							"region": "us-east-2",
  3934  						},
  3935  					},
  3936  				},
  3937  			},
  3938  			expectedList: [][]string{
  3939  				{"app-qa2"},
  3940  			},
  3941  			expectedStepMap: map[string]int{
  3942  				"app-qa2": 0,
  3943  			},
  3944  		},
  3945  	} {
  3946  
  3947  		t.Run(cc.name, func(t *testing.T) {
  3948  
  3949  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  3950  			argoDBMock := dbmocks.ArgoDB{}
  3951  			argoObjs := []runtime.Object{}
  3952  
  3953  			r := ApplicationSetReconciler{
  3954  				Client:           client,
  3955  				Scheme:           scheme,
  3956  				Recorder:         record.NewFakeRecorder(1),
  3957  				Cache:            &fakeCache{},
  3958  				Generators:       map[string]generators.Generator{},
  3959  				ArgoDB:           &argoDBMock,
  3960  				ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  3961  				KubeClientset:    kubeclientset,
  3962  			}
  3963  
  3964  			appDependencyList, appStepMap, err := r.buildAppDependencyList(log.NewEntry(log.StandardLogger()), cc.appSet, cc.apps)
  3965  			assert.Equal(t, err, nil, "expected no errors, but errors occured")
  3966  			assert.Equal(t, cc.expectedList, appDependencyList, "expected appDependencyList did not match actual")
  3967  			assert.Equal(t, cc.expectedStepMap, appStepMap, "expected appStepMap did not match actual")
  3968  		})
  3969  	}
  3970  }
  3971  
  3972  func TestBuildAppSyncMap(t *testing.T) {
  3973  
  3974  	scheme := runtime.NewScheme()
  3975  	err := v1alpha1.AddToScheme(scheme)
  3976  	assert.Nil(t, err)
  3977  
  3978  	err = v1alpha1.AddToScheme(scheme)
  3979  	assert.Nil(t, err)
  3980  
  3981  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
  3982  
  3983  	for _, cc := range []struct {
  3984  		name              string
  3985  		appSet            v1alpha1.ApplicationSet
  3986  		appMap            map[string]v1alpha1.Application
  3987  		appDependencyList [][]string
  3988  		expectedMap       map[string]bool
  3989  	}{
  3990  		{
  3991  			name: "handles an empty app dependency list",
  3992  			appSet: v1alpha1.ApplicationSet{
  3993  				ObjectMeta: metav1.ObjectMeta{
  3994  					Name:      "name",
  3995  					Namespace: "argocd",
  3996  				},
  3997  				Spec: v1alpha1.ApplicationSetSpec{
  3998  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3999  						Type:        "RollingSync",
  4000  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4001  					},
  4002  				},
  4003  			},
  4004  			appDependencyList: [][]string{},
  4005  			expectedMap:       map[string]bool{},
  4006  		},
  4007  		{
  4008  			name: "handles two applications with no statuses",
  4009  			appSet: v1alpha1.ApplicationSet{
  4010  				ObjectMeta: metav1.ObjectMeta{
  4011  					Name:      "name",
  4012  					Namespace: "argocd",
  4013  				},
  4014  				Spec: v1alpha1.ApplicationSetSpec{
  4015  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4016  						Type:        "RollingSync",
  4017  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4018  					},
  4019  				},
  4020  			},
  4021  			appDependencyList: [][]string{
  4022  				{"app1"},
  4023  				{"app2"},
  4024  			},
  4025  			expectedMap: map[string]bool{
  4026  				"app1": true,
  4027  				"app2": false,
  4028  			},
  4029  		},
  4030  		{
  4031  			name: "handles applications after an empty selection",
  4032  			appSet: v1alpha1.ApplicationSet{
  4033  				ObjectMeta: metav1.ObjectMeta{
  4034  					Name:      "name",
  4035  					Namespace: "argocd",
  4036  				},
  4037  				Spec: v1alpha1.ApplicationSetSpec{
  4038  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4039  						Type:        "RollingSync",
  4040  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4041  					},
  4042  				},
  4043  			},
  4044  			appDependencyList: [][]string{
  4045  				{},
  4046  				{"app1", "app2"},
  4047  			},
  4048  			expectedMap: map[string]bool{
  4049  				"app1": true,
  4050  				"app2": true,
  4051  			},
  4052  		},
  4053  		{
  4054  			name: "handles RollingSync applications that are healthy and have no changes",
  4055  			appSet: v1alpha1.ApplicationSet{
  4056  				ObjectMeta: metav1.ObjectMeta{
  4057  					Name:      "name",
  4058  					Namespace: "argocd",
  4059  				},
  4060  				Spec: v1alpha1.ApplicationSetSpec{
  4061  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4062  						Type:        "RollingSync",
  4063  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4064  					},
  4065  				},
  4066  				Status: v1alpha1.ApplicationSetStatus{
  4067  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4068  						{
  4069  							Application: "app1",
  4070  							Status:      "Healthy",
  4071  						},
  4072  						{
  4073  							Application: "app2",
  4074  							Status:      "Healthy",
  4075  						},
  4076  					},
  4077  				},
  4078  			},
  4079  			appMap: map[string]v1alpha1.Application{
  4080  				"app1": {
  4081  					ObjectMeta: metav1.ObjectMeta{
  4082  						Name: "app1",
  4083  					},
  4084  					Status: v1alpha1.ApplicationStatus{
  4085  						Health: v1alpha1.HealthStatus{
  4086  							Status: health.HealthStatusHealthy,
  4087  						},
  4088  						OperationState: &v1alpha1.OperationState{
  4089  							Phase: common.OperationSucceeded,
  4090  						},
  4091  						Sync: v1alpha1.SyncStatus{
  4092  							Status: v1alpha1.SyncStatusCodeSynced,
  4093  						},
  4094  					},
  4095  				},
  4096  				"app2": {
  4097  					ObjectMeta: metav1.ObjectMeta{
  4098  						Name: "app2",
  4099  					},
  4100  					Status: v1alpha1.ApplicationStatus{
  4101  						Health: v1alpha1.HealthStatus{
  4102  							Status: health.HealthStatusHealthy,
  4103  						},
  4104  						OperationState: &v1alpha1.OperationState{
  4105  							Phase: common.OperationSucceeded,
  4106  						},
  4107  						Sync: v1alpha1.SyncStatus{
  4108  							Status: v1alpha1.SyncStatusCodeSynced,
  4109  						},
  4110  					},
  4111  				},
  4112  			},
  4113  			appDependencyList: [][]string{
  4114  				{"app1"},
  4115  				{"app2"},
  4116  			},
  4117  			expectedMap: map[string]bool{
  4118  				"app1": true,
  4119  				"app2": true,
  4120  			},
  4121  		},
  4122  		{
  4123  			name: "blocks RollingSync applications that are healthy and have no changes, but are still pending",
  4124  			appSet: v1alpha1.ApplicationSet{
  4125  				ObjectMeta: metav1.ObjectMeta{
  4126  					Name:      "name",
  4127  					Namespace: "argocd",
  4128  				},
  4129  				Spec: v1alpha1.ApplicationSetSpec{
  4130  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4131  						Type:        "RollingSync",
  4132  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4133  					},
  4134  				},
  4135  				Status: v1alpha1.ApplicationSetStatus{
  4136  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4137  						{
  4138  							Application: "app1",
  4139  							Status:      "Pending",
  4140  						},
  4141  						{
  4142  							Application: "app2",
  4143  							Status:      "Healthy",
  4144  						},
  4145  					},
  4146  				},
  4147  			},
  4148  			appMap: map[string]v1alpha1.Application{
  4149  				"app1": {
  4150  					ObjectMeta: metav1.ObjectMeta{
  4151  						Name: "app1",
  4152  					},
  4153  					Status: v1alpha1.ApplicationStatus{
  4154  						Health: v1alpha1.HealthStatus{
  4155  							Status: health.HealthStatusHealthy,
  4156  						},
  4157  						OperationState: &v1alpha1.OperationState{
  4158  							Phase: common.OperationSucceeded,
  4159  						},
  4160  						Sync: v1alpha1.SyncStatus{
  4161  							Status: v1alpha1.SyncStatusCodeSynced,
  4162  						},
  4163  					},
  4164  				},
  4165  				"app2": {
  4166  					ObjectMeta: metav1.ObjectMeta{
  4167  						Name: "app2",
  4168  					},
  4169  					Status: v1alpha1.ApplicationStatus{
  4170  						Health: v1alpha1.HealthStatus{
  4171  							Status: health.HealthStatusHealthy,
  4172  						},
  4173  						OperationState: &v1alpha1.OperationState{
  4174  							Phase: common.OperationSucceeded,
  4175  						},
  4176  						Sync: v1alpha1.SyncStatus{
  4177  							Status: v1alpha1.SyncStatusCodeSynced,
  4178  						},
  4179  					},
  4180  				},
  4181  			},
  4182  			appDependencyList: [][]string{
  4183  				{"app1"},
  4184  				{"app2"},
  4185  			},
  4186  			expectedMap: map[string]bool{
  4187  				"app1": true,
  4188  				"app2": false,
  4189  			},
  4190  		},
  4191  		{
  4192  			name: "handles RollingSync applications that are up to date and healthy, but still syncing",
  4193  			appSet: v1alpha1.ApplicationSet{
  4194  				ObjectMeta: metav1.ObjectMeta{
  4195  					Name:      "name",
  4196  					Namespace: "argocd",
  4197  				},
  4198  				Spec: v1alpha1.ApplicationSetSpec{
  4199  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4200  						Type:        "RollingSync",
  4201  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4202  					},
  4203  				},
  4204  				Status: v1alpha1.ApplicationSetStatus{
  4205  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4206  						{
  4207  							Application: "app1",
  4208  							Status:      "Progressing",
  4209  						},
  4210  						{
  4211  							Application: "app2",
  4212  							Status:      "Progressing",
  4213  						},
  4214  					},
  4215  				},
  4216  			},
  4217  			appMap: map[string]v1alpha1.Application{
  4218  				"app1": {
  4219  					ObjectMeta: metav1.ObjectMeta{
  4220  						Name: "app1",
  4221  					},
  4222  					Status: v1alpha1.ApplicationStatus{
  4223  						Health: v1alpha1.HealthStatus{
  4224  							Status: health.HealthStatusHealthy,
  4225  						},
  4226  						OperationState: &v1alpha1.OperationState{
  4227  							Phase: common.OperationRunning,
  4228  						},
  4229  						Sync: v1alpha1.SyncStatus{
  4230  							Status: v1alpha1.SyncStatusCodeSynced,
  4231  						},
  4232  					},
  4233  				},
  4234  				"app2": {
  4235  					ObjectMeta: metav1.ObjectMeta{
  4236  						Name: "app2",
  4237  					},
  4238  					Status: v1alpha1.ApplicationStatus{
  4239  						Health: v1alpha1.HealthStatus{
  4240  							Status: health.HealthStatusHealthy,
  4241  						},
  4242  						OperationState: &v1alpha1.OperationState{
  4243  							Phase: common.OperationRunning,
  4244  						},
  4245  						Sync: v1alpha1.SyncStatus{
  4246  							Status: v1alpha1.SyncStatusCodeSynced,
  4247  						},
  4248  					},
  4249  				},
  4250  			},
  4251  			appDependencyList: [][]string{
  4252  				{"app1"},
  4253  				{"app2"},
  4254  			},
  4255  			expectedMap: map[string]bool{
  4256  				"app1": true,
  4257  				"app2": false,
  4258  			},
  4259  		},
  4260  		{
  4261  			name: "handles RollingSync applications that are up to date and synced, but degraded",
  4262  			appSet: v1alpha1.ApplicationSet{
  4263  				ObjectMeta: metav1.ObjectMeta{
  4264  					Name:      "name",
  4265  					Namespace: "argocd",
  4266  				},
  4267  				Spec: v1alpha1.ApplicationSetSpec{
  4268  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4269  						Type:        "RollingSync",
  4270  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4271  					},
  4272  				},
  4273  				Status: v1alpha1.ApplicationSetStatus{
  4274  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4275  						{
  4276  							Application: "app1",
  4277  							Status:      "Progressing",
  4278  						},
  4279  						{
  4280  							Application: "app2",
  4281  							Status:      "Progressing",
  4282  						},
  4283  					},
  4284  				},
  4285  			},
  4286  			appMap: map[string]v1alpha1.Application{
  4287  				"app1": {
  4288  					ObjectMeta: metav1.ObjectMeta{
  4289  						Name: "app1",
  4290  					},
  4291  					Status: v1alpha1.ApplicationStatus{
  4292  						Health: v1alpha1.HealthStatus{
  4293  							Status: health.HealthStatusDegraded,
  4294  						},
  4295  						OperationState: &v1alpha1.OperationState{
  4296  							Phase: common.OperationRunning,
  4297  						},
  4298  						Sync: v1alpha1.SyncStatus{
  4299  							Status: v1alpha1.SyncStatusCodeSynced,
  4300  						},
  4301  					},
  4302  				},
  4303  				"app2": {
  4304  					ObjectMeta: metav1.ObjectMeta{
  4305  						Name: "app2",
  4306  					},
  4307  					Status: v1alpha1.ApplicationStatus{
  4308  						Health: v1alpha1.HealthStatus{
  4309  							Status: health.HealthStatusDegraded,
  4310  						},
  4311  						OperationState: &v1alpha1.OperationState{
  4312  							Phase: common.OperationRunning,
  4313  						},
  4314  						Sync: v1alpha1.SyncStatus{
  4315  							Status: v1alpha1.SyncStatusCodeSynced,
  4316  						},
  4317  					},
  4318  				},
  4319  			},
  4320  			appDependencyList: [][]string{
  4321  				{"app1"},
  4322  				{"app2"},
  4323  			},
  4324  			expectedMap: map[string]bool{
  4325  				"app1": true,
  4326  				"app2": false,
  4327  			},
  4328  		},
  4329  		{
  4330  			name: "handles RollingSync applications that are OutOfSync and healthy",
  4331  			appSet: v1alpha1.ApplicationSet{
  4332  				ObjectMeta: metav1.ObjectMeta{
  4333  					Name:      "name",
  4334  					Namespace: "argocd",
  4335  				},
  4336  				Spec: v1alpha1.ApplicationSetSpec{
  4337  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4338  						Type:        "RollingSync",
  4339  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4340  					},
  4341  				},
  4342  				Status: v1alpha1.ApplicationSetStatus{
  4343  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4344  						{
  4345  							Application: "app1",
  4346  							Status:      "Healthy",
  4347  						},
  4348  						{
  4349  							Application: "app2",
  4350  							Status:      "Healthy",
  4351  						},
  4352  					},
  4353  				},
  4354  			},
  4355  			appDependencyList: [][]string{
  4356  				{"app1"},
  4357  				{"app2"},
  4358  			},
  4359  			appMap: map[string]v1alpha1.Application{
  4360  				"app1": {
  4361  					ObjectMeta: metav1.ObjectMeta{
  4362  						Name: "app1",
  4363  					},
  4364  					Status: v1alpha1.ApplicationStatus{
  4365  						Health: v1alpha1.HealthStatus{
  4366  							Status: health.HealthStatusHealthy,
  4367  						},
  4368  						OperationState: &v1alpha1.OperationState{
  4369  							Phase: common.OperationSucceeded,
  4370  						},
  4371  						Sync: v1alpha1.SyncStatus{
  4372  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  4373  						},
  4374  					},
  4375  				},
  4376  				"app2": {
  4377  					ObjectMeta: metav1.ObjectMeta{
  4378  						Name: "app2",
  4379  					},
  4380  					Status: v1alpha1.ApplicationStatus{
  4381  						Health: v1alpha1.HealthStatus{
  4382  							Status: health.HealthStatusHealthy,
  4383  						},
  4384  						OperationState: &v1alpha1.OperationState{
  4385  							Phase: common.OperationSucceeded,
  4386  						},
  4387  						Sync: v1alpha1.SyncStatus{
  4388  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  4389  						},
  4390  					},
  4391  				},
  4392  			},
  4393  			expectedMap: map[string]bool{
  4394  				"app1": true,
  4395  				"app2": false,
  4396  			},
  4397  		},
  4398  		{
  4399  			name: "handles a lot of applications",
  4400  			appSet: v1alpha1.ApplicationSet{
  4401  				ObjectMeta: metav1.ObjectMeta{
  4402  					Name:      "name",
  4403  					Namespace: "argocd",
  4404  				},
  4405  				Spec: v1alpha1.ApplicationSetSpec{
  4406  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4407  						Type:        "RollingSync",
  4408  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4409  					},
  4410  				},
  4411  				Status: v1alpha1.ApplicationSetStatus{
  4412  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4413  						{
  4414  							Application: "app1",
  4415  							Status:      "Healthy",
  4416  						},
  4417  						{
  4418  							Application: "app2",
  4419  							Status:      "Healthy",
  4420  						},
  4421  						{
  4422  							Application: "app3",
  4423  							Status:      "Healthy",
  4424  						},
  4425  						{
  4426  							Application: "app4",
  4427  							Status:      "Healthy",
  4428  						},
  4429  						{
  4430  							Application: "app5",
  4431  							Status:      "Healthy",
  4432  						},
  4433  						{
  4434  							Application: "app7",
  4435  							Status:      "Healthy",
  4436  						},
  4437  					},
  4438  				},
  4439  			},
  4440  			appMap: map[string]v1alpha1.Application{
  4441  				"app1": {
  4442  					ObjectMeta: metav1.ObjectMeta{
  4443  						Name: "app1",
  4444  					},
  4445  					Status: v1alpha1.ApplicationStatus{
  4446  						Health: v1alpha1.HealthStatus{
  4447  							Status: health.HealthStatusHealthy,
  4448  						},
  4449  						OperationState: &v1alpha1.OperationState{
  4450  							Phase: common.OperationSucceeded,
  4451  						},
  4452  						Sync: v1alpha1.SyncStatus{
  4453  							Status: v1alpha1.SyncStatusCodeSynced,
  4454  						},
  4455  					},
  4456  				},
  4457  				"app2": {
  4458  					ObjectMeta: metav1.ObjectMeta{
  4459  						Name: "app2",
  4460  					},
  4461  					Status: v1alpha1.ApplicationStatus{
  4462  						Health: v1alpha1.HealthStatus{
  4463  							Status: health.HealthStatusHealthy,
  4464  						},
  4465  						OperationState: &v1alpha1.OperationState{
  4466  							Phase: common.OperationSucceeded,
  4467  						},
  4468  						Sync: v1alpha1.SyncStatus{
  4469  							Status: v1alpha1.SyncStatusCodeSynced,
  4470  						},
  4471  					},
  4472  				},
  4473  				"app3": {
  4474  					ObjectMeta: metav1.ObjectMeta{
  4475  						Name: "app3",
  4476  					},
  4477  					Status: v1alpha1.ApplicationStatus{
  4478  						Health: v1alpha1.HealthStatus{
  4479  							Status: health.HealthStatusHealthy,
  4480  						},
  4481  						OperationState: &v1alpha1.OperationState{
  4482  							Phase: common.OperationSucceeded,
  4483  						},
  4484  						Sync: v1alpha1.SyncStatus{
  4485  							Status: v1alpha1.SyncStatusCodeSynced,
  4486  						},
  4487  					},
  4488  				},
  4489  				"app5": {
  4490  					ObjectMeta: metav1.ObjectMeta{
  4491  						Name: "app5",
  4492  					},
  4493  					Status: v1alpha1.ApplicationStatus{
  4494  						Health: v1alpha1.HealthStatus{
  4495  							Status: health.HealthStatusHealthy,
  4496  						},
  4497  						OperationState: &v1alpha1.OperationState{
  4498  							Phase: common.OperationSucceeded,
  4499  						},
  4500  						Sync: v1alpha1.SyncStatus{
  4501  							Status: v1alpha1.SyncStatusCodeSynced,
  4502  						},
  4503  					},
  4504  				},
  4505  				"app6": {
  4506  					ObjectMeta: metav1.ObjectMeta{
  4507  						Name: "app6",
  4508  					},
  4509  					Status: v1alpha1.ApplicationStatus{
  4510  						Health: v1alpha1.HealthStatus{
  4511  							Status: health.HealthStatusDegraded,
  4512  						},
  4513  						OperationState: &v1alpha1.OperationState{
  4514  							Phase: common.OperationSucceeded,
  4515  						},
  4516  						Sync: v1alpha1.SyncStatus{
  4517  							Status: v1alpha1.SyncStatusCodeSynced,
  4518  						},
  4519  					},
  4520  				},
  4521  			},
  4522  			appDependencyList: [][]string{
  4523  				{"app1", "app2", "app3"},
  4524  				{"app4", "app5", "app6"},
  4525  				{"app7", "app8", "app9"},
  4526  			},
  4527  			expectedMap: map[string]bool{
  4528  				"app1": true,
  4529  				"app2": true,
  4530  				"app3": true,
  4531  				"app4": true,
  4532  				"app5": true,
  4533  				"app6": true,
  4534  				"app7": false,
  4535  				"app8": false,
  4536  				"app9": false,
  4537  			},
  4538  		},
  4539  	} {
  4540  
  4541  		t.Run(cc.name, func(t *testing.T) {
  4542  
  4543  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  4544  			argoDBMock := dbmocks.ArgoDB{}
  4545  			argoObjs := []runtime.Object{}
  4546  
  4547  			r := ApplicationSetReconciler{
  4548  				Client:           client,
  4549  				Scheme:           scheme,
  4550  				Recorder:         record.NewFakeRecorder(1),
  4551  				Cache:            &fakeCache{},
  4552  				Generators:       map[string]generators.Generator{},
  4553  				ArgoDB:           &argoDBMock,
  4554  				ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  4555  				KubeClientset:    kubeclientset,
  4556  			}
  4557  
  4558  			appSyncMap, err := r.buildAppSyncMap(context.TODO(), cc.appSet, cc.appDependencyList, cc.appMap)
  4559  			assert.Equal(t, err, nil, "expected no errors, but errors occured")
  4560  			assert.Equal(t, cc.expectedMap, appSyncMap, "expected appSyncMap did not match actual")
  4561  		})
  4562  	}
  4563  }
  4564  
  4565  func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
  4566  
  4567  	scheme := runtime.NewScheme()
  4568  	err := v1alpha1.AddToScheme(scheme)
  4569  	assert.Nil(t, err)
  4570  
  4571  	err = v1alpha1.AddToScheme(scheme)
  4572  	assert.Nil(t, err)
  4573  
  4574  	for _, cc := range []struct {
  4575  		name              string
  4576  		appSet            v1alpha1.ApplicationSet
  4577  		apps              []v1alpha1.Application
  4578  		appStepMap        map[string]int
  4579  		expectedAppStatus []v1alpha1.ApplicationSetApplicationStatus
  4580  	}{
  4581  		{
  4582  			name: "handles a nil list of statuses and no applications",
  4583  			appSet: v1alpha1.ApplicationSet{
  4584  				ObjectMeta: metav1.ObjectMeta{
  4585  					Name:      "name",
  4586  					Namespace: "argocd",
  4587  				},
  4588  				Spec: v1alpha1.ApplicationSetSpec{
  4589  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4590  						Type:        "RollingSync",
  4591  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4592  					},
  4593  				},
  4594  			},
  4595  			apps:              []v1alpha1.Application{},
  4596  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  4597  		},
  4598  		{
  4599  			name: "handles a nil list of statuses with a healthy application",
  4600  			appSet: v1alpha1.ApplicationSet{
  4601  				ObjectMeta: metav1.ObjectMeta{
  4602  					Name:      "name",
  4603  					Namespace: "argocd",
  4604  				},
  4605  				Spec: v1alpha1.ApplicationSetSpec{
  4606  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4607  						Type:        "RollingSync",
  4608  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4609  					},
  4610  				},
  4611  			},
  4612  			apps: []v1alpha1.Application{
  4613  				{
  4614  					ObjectMeta: metav1.ObjectMeta{
  4615  						Name: "app1",
  4616  					},
  4617  					Status: v1alpha1.ApplicationStatus{
  4618  						Health: v1alpha1.HealthStatus{
  4619  							Status: health.HealthStatusHealthy,
  4620  						},
  4621  						OperationState: &v1alpha1.OperationState{
  4622  							Phase: common.OperationSucceeded,
  4623  						},
  4624  						Sync: v1alpha1.SyncStatus{
  4625  							Status: v1alpha1.SyncStatusCodeSynced,
  4626  						},
  4627  					},
  4628  				},
  4629  			},
  4630  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4631  				{
  4632  					Application: "app1",
  4633  					Message:     "Application resource is already Healthy, updating status from Waiting to Healthy.",
  4634  					Status:      "Healthy",
  4635  					Step:        "1",
  4636  				},
  4637  			},
  4638  		},
  4639  		{
  4640  			name: "handles an empty list of statuses with a healthy application",
  4641  			appSet: v1alpha1.ApplicationSet{
  4642  				ObjectMeta: metav1.ObjectMeta{
  4643  					Name:      "name",
  4644  					Namespace: "argocd",
  4645  				},
  4646  				Spec: v1alpha1.ApplicationSetSpec{
  4647  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4648  						Type:        "RollingSync",
  4649  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4650  					},
  4651  				},
  4652  				Status: v1alpha1.ApplicationSetStatus{},
  4653  			},
  4654  			apps: []v1alpha1.Application{
  4655  				{
  4656  					ObjectMeta: metav1.ObjectMeta{
  4657  						Name: "app1",
  4658  					},
  4659  					Status: v1alpha1.ApplicationStatus{
  4660  						Health: v1alpha1.HealthStatus{
  4661  							Status: health.HealthStatusHealthy,
  4662  						},
  4663  						OperationState: &v1alpha1.OperationState{
  4664  							Phase: common.OperationSucceeded,
  4665  						},
  4666  						Sync: v1alpha1.SyncStatus{
  4667  							Status: v1alpha1.SyncStatusCodeSynced,
  4668  						},
  4669  					},
  4670  				},
  4671  			},
  4672  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4673  				{
  4674  					Application: "app1",
  4675  					Message:     "Application resource is already Healthy, updating status from Waiting to Healthy.",
  4676  					Status:      "Healthy",
  4677  					Step:        "1",
  4678  				},
  4679  			},
  4680  		},
  4681  		{
  4682  			name: "progresses an OutOfSync RollingSync application to waiting",
  4683  			appSet: v1alpha1.ApplicationSet{
  4684  				ObjectMeta: metav1.ObjectMeta{
  4685  					Name:      "name",
  4686  					Namespace: "argocd",
  4687  				},
  4688  				Spec: v1alpha1.ApplicationSetSpec{
  4689  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4690  						Type:        "RollingSync",
  4691  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4692  					},
  4693  				},
  4694  				Status: v1alpha1.ApplicationSetStatus{
  4695  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4696  						{
  4697  							Application: "app1",
  4698  							Message:     "",
  4699  							Status:      "Healthy",
  4700  							Step:        "1",
  4701  						},
  4702  					},
  4703  				},
  4704  			},
  4705  			apps: []v1alpha1.Application{
  4706  				{
  4707  					ObjectMeta: metav1.ObjectMeta{
  4708  						Name: "app1",
  4709  					},
  4710  					Status: v1alpha1.ApplicationStatus{
  4711  						Sync: v1alpha1.SyncStatus{
  4712  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  4713  						},
  4714  					},
  4715  				},
  4716  			},
  4717  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4718  				{
  4719  					Application: "app1",
  4720  					Message:     "Application has pending changes, setting status to Waiting.",
  4721  					Status:      "Waiting",
  4722  					Step:        "1",
  4723  				},
  4724  			},
  4725  		},
  4726  		{
  4727  			name: "progresses a pending progressing application to progressing",
  4728  			appSet: v1alpha1.ApplicationSet{
  4729  				ObjectMeta: metav1.ObjectMeta{
  4730  					Name:      "name",
  4731  					Namespace: "argocd",
  4732  				},
  4733  				Spec: v1alpha1.ApplicationSetSpec{
  4734  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4735  						Type:        "RollingSync",
  4736  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4737  					},
  4738  				},
  4739  				Status: v1alpha1.ApplicationSetStatus{
  4740  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4741  						{
  4742  							Application: "app1",
  4743  							Message:     "",
  4744  							Status:      "Pending",
  4745  							Step:        "1",
  4746  						},
  4747  					},
  4748  				},
  4749  			},
  4750  			apps: []v1alpha1.Application{
  4751  				{
  4752  					ObjectMeta: metav1.ObjectMeta{
  4753  						Name: "app1",
  4754  					},
  4755  					Status: v1alpha1.ApplicationStatus{
  4756  						Health: v1alpha1.HealthStatus{
  4757  							Status: health.HealthStatusProgressing,
  4758  						},
  4759  					},
  4760  				},
  4761  			},
  4762  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4763  				{
  4764  					Application: "app1",
  4765  					Message:     "Application resource became Progressing, updating status from Pending to Progressing.",
  4766  					Status:      "Progressing",
  4767  					Step:        "1",
  4768  				},
  4769  			},
  4770  		},
  4771  		{
  4772  			name: "progresses a pending syncing application to progressing",
  4773  			appSet: v1alpha1.ApplicationSet{
  4774  				ObjectMeta: metav1.ObjectMeta{
  4775  					Name:      "name",
  4776  					Namespace: "argocd",
  4777  				},
  4778  				Spec: v1alpha1.ApplicationSetSpec{
  4779  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4780  						Type:        "RollingSync",
  4781  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4782  					},
  4783  				},
  4784  				Status: v1alpha1.ApplicationSetStatus{
  4785  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4786  						{
  4787  							Application: "app1",
  4788  							Message:     "",
  4789  							Status:      "Pending",
  4790  							Step:        "1",
  4791  						},
  4792  					},
  4793  				},
  4794  			},
  4795  			apps: []v1alpha1.Application{
  4796  				{
  4797  					ObjectMeta: metav1.ObjectMeta{
  4798  						Name: "app1",
  4799  					},
  4800  					Status: v1alpha1.ApplicationStatus{
  4801  						Health: v1alpha1.HealthStatus{
  4802  							Status: health.HealthStatusHealthy,
  4803  						},
  4804  						OperationState: &v1alpha1.OperationState{
  4805  							Phase: common.OperationRunning,
  4806  						},
  4807  						Sync: v1alpha1.SyncStatus{
  4808  							Status: v1alpha1.SyncStatusCodeSynced,
  4809  						},
  4810  					},
  4811  				},
  4812  			},
  4813  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4814  				{
  4815  					Application: "app1",
  4816  					Message:     "Application resource became Progressing, updating status from Pending to Progressing.",
  4817  					Status:      "Progressing",
  4818  					Step:        "1",
  4819  				},
  4820  			},
  4821  		},
  4822  		{
  4823  			name: "progresses a progressing application to healthy",
  4824  			appSet: v1alpha1.ApplicationSet{
  4825  				ObjectMeta: metav1.ObjectMeta{
  4826  					Name:      "name",
  4827  					Namespace: "argocd",
  4828  				},
  4829  				Spec: v1alpha1.ApplicationSetSpec{
  4830  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4831  						Type:        "RollingSync",
  4832  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4833  					},
  4834  				},
  4835  				Status: v1alpha1.ApplicationSetStatus{
  4836  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4837  						{
  4838  							Application: "app1",
  4839  							Message:     "",
  4840  							Status:      "Progressing",
  4841  							Step:        "1",
  4842  						},
  4843  					},
  4844  				},
  4845  			},
  4846  			apps: []v1alpha1.Application{
  4847  				{
  4848  					ObjectMeta: metav1.ObjectMeta{
  4849  						Name: "app1",
  4850  					},
  4851  					Status: v1alpha1.ApplicationStatus{
  4852  						Health: v1alpha1.HealthStatus{
  4853  							Status: health.HealthStatusHealthy,
  4854  						},
  4855  						OperationState: &v1alpha1.OperationState{
  4856  							Phase: common.OperationSucceeded,
  4857  						},
  4858  						Sync: v1alpha1.SyncStatus{
  4859  							Status: v1alpha1.SyncStatusCodeSynced,
  4860  						},
  4861  					},
  4862  				},
  4863  			},
  4864  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4865  				{
  4866  					Application: "app1",
  4867  					Message:     "Application resource became Healthy, updating status from Progressing to Healthy.",
  4868  					Status:      "Healthy",
  4869  					Step:        "1",
  4870  				},
  4871  			},
  4872  		},
  4873  		{
  4874  			name: "progresses a waiting healthy application to healthy",
  4875  			appSet: v1alpha1.ApplicationSet{
  4876  				ObjectMeta: metav1.ObjectMeta{
  4877  					Name:      "name",
  4878  					Namespace: "argocd",
  4879  				},
  4880  				Spec: v1alpha1.ApplicationSetSpec{
  4881  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4882  						Type:        "RollingSync",
  4883  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4884  					},
  4885  				},
  4886  				Status: v1alpha1.ApplicationSetStatus{
  4887  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4888  						{
  4889  							Application: "app1",
  4890  							Message:     "",
  4891  							Status:      "Waiting",
  4892  							Step:        "1",
  4893  						},
  4894  					},
  4895  				},
  4896  			},
  4897  			apps: []v1alpha1.Application{
  4898  				{
  4899  					ObjectMeta: metav1.ObjectMeta{
  4900  						Name: "app1",
  4901  					},
  4902  					Status: v1alpha1.ApplicationStatus{
  4903  						Health: v1alpha1.HealthStatus{
  4904  							Status: health.HealthStatusHealthy,
  4905  						},
  4906  						OperationState: &v1alpha1.OperationState{
  4907  							Phase: common.OperationSucceeded,
  4908  						},
  4909  						Sync: v1alpha1.SyncStatus{
  4910  							Status: v1alpha1.SyncStatusCodeSynced,
  4911  						},
  4912  					},
  4913  				},
  4914  			},
  4915  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4916  				{
  4917  					Application: "app1",
  4918  					Message:     "Application resource is already Healthy, updating status from Waiting to Healthy.",
  4919  					Status:      "Healthy",
  4920  					Step:        "1",
  4921  				},
  4922  			},
  4923  		},
  4924  		{
  4925  			name: "progresses a new outofsync application in a later step to waiting",
  4926  			appSet: v1alpha1.ApplicationSet{
  4927  				ObjectMeta: metav1.ObjectMeta{
  4928  					Name:      "name",
  4929  					Namespace: "argocd",
  4930  				},
  4931  				Spec: v1alpha1.ApplicationSetSpec{
  4932  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4933  						Type:        "RollingSync",
  4934  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4935  					},
  4936  				},
  4937  			},
  4938  			apps: []v1alpha1.Application{
  4939  				{
  4940  					ObjectMeta: metav1.ObjectMeta{
  4941  						Name: "app1",
  4942  					},
  4943  					Status: v1alpha1.ApplicationStatus{
  4944  						Health: v1alpha1.HealthStatus{
  4945  							Status: health.HealthStatusHealthy,
  4946  						},
  4947  						OperationState: &v1alpha1.OperationState{
  4948  							Phase: common.OperationSucceeded,
  4949  						},
  4950  						Sync: v1alpha1.SyncStatus{
  4951  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  4952  						},
  4953  					},
  4954  				},
  4955  			},
  4956  			appStepMap: map[string]int{
  4957  				"app1": 1,
  4958  				"app2": 0,
  4959  			},
  4960  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4961  				{
  4962  					Application: "app1",
  4963  					Message:     "No Application status found, defaulting status to Waiting.",
  4964  					Status:      "Waiting",
  4965  					Step:        "2",
  4966  				},
  4967  			},
  4968  		},
  4969  		{
  4970  			name: "progresses a pending application with a successful sync to progressing",
  4971  			appSet: v1alpha1.ApplicationSet{
  4972  				ObjectMeta: metav1.ObjectMeta{
  4973  					Name:      "name",
  4974  					Namespace: "argocd",
  4975  				},
  4976  				Spec: v1alpha1.ApplicationSetSpec{
  4977  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4978  						Type:        "RollingSync",
  4979  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  4980  					},
  4981  				},
  4982  				Status: v1alpha1.ApplicationSetStatus{
  4983  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4984  						{
  4985  							Application: "app1",
  4986  							LastTransitionTime: &metav1.Time{
  4987  								Time: time.Now().Add(time.Duration(-1) * time.Minute),
  4988  							},
  4989  							Message: "",
  4990  							Status:  "Pending",
  4991  							Step:    "1",
  4992  						},
  4993  					},
  4994  				},
  4995  			},
  4996  			apps: []v1alpha1.Application{
  4997  				{
  4998  					ObjectMeta: metav1.ObjectMeta{
  4999  						Name: "app1",
  5000  					},
  5001  					Status: v1alpha1.ApplicationStatus{
  5002  						Health: v1alpha1.HealthStatus{
  5003  							Status: health.HealthStatusDegraded,
  5004  						},
  5005  						OperationState: &v1alpha1.OperationState{
  5006  							Phase: common.OperationSucceeded,
  5007  							StartedAt: metav1.Time{
  5008  								Time: time.Now(),
  5009  							},
  5010  						},
  5011  						Sync: v1alpha1.SyncStatus{
  5012  							Status: v1alpha1.SyncStatusCodeSynced,
  5013  						},
  5014  					},
  5015  				},
  5016  			},
  5017  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5018  				{
  5019  					Application: "app1",
  5020  					Message:     "Application resource completed a sync successfully, updating status from Pending to Progressing.",
  5021  					Status:      "Progressing",
  5022  					Step:        "1",
  5023  				},
  5024  			},
  5025  		},
  5026  		{
  5027  			name: "progresses a pending application with a successful sync <1s ago to progressing",
  5028  			appSet: v1alpha1.ApplicationSet{
  5029  				ObjectMeta: metav1.ObjectMeta{
  5030  					Name:      "name",
  5031  					Namespace: "argocd",
  5032  				},
  5033  				Spec: v1alpha1.ApplicationSetSpec{
  5034  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5035  						Type:        "RollingSync",
  5036  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  5037  					},
  5038  				},
  5039  				Status: v1alpha1.ApplicationSetStatus{
  5040  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5041  						{
  5042  							Application: "app1",
  5043  							LastTransitionTime: &metav1.Time{
  5044  								Time: time.Now(),
  5045  							},
  5046  							Message: "",
  5047  							Status:  "Pending",
  5048  							Step:    "1",
  5049  						},
  5050  					},
  5051  				},
  5052  			},
  5053  			apps: []v1alpha1.Application{
  5054  				{
  5055  					ObjectMeta: metav1.ObjectMeta{
  5056  						Name: "app1",
  5057  					},
  5058  					Status: v1alpha1.ApplicationStatus{
  5059  						Health: v1alpha1.HealthStatus{
  5060  							Status: health.HealthStatusDegraded,
  5061  						},
  5062  						OperationState: &v1alpha1.OperationState{
  5063  							Phase: common.OperationSucceeded,
  5064  							StartedAt: metav1.Time{
  5065  								Time: time.Now().Add(time.Duration(-1) * time.Second),
  5066  							},
  5067  						},
  5068  						Sync: v1alpha1.SyncStatus{
  5069  							Status: v1alpha1.SyncStatusCodeSynced,
  5070  						},
  5071  					},
  5072  				},
  5073  			},
  5074  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5075  				{
  5076  					Application: "app1",
  5077  					Message:     "Application resource completed a sync successfully, updating status from Pending to Progressing.",
  5078  					Status:      "Progressing",
  5079  					Step:        "1",
  5080  				},
  5081  			},
  5082  		},
  5083  		{
  5084  			name: "does not progresses a pending application with an old successful sync to progressing",
  5085  			appSet: v1alpha1.ApplicationSet{
  5086  				ObjectMeta: metav1.ObjectMeta{
  5087  					Name:      "name",
  5088  					Namespace: "argocd",
  5089  				},
  5090  				Spec: v1alpha1.ApplicationSetSpec{
  5091  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5092  						Type:        "RollingSync",
  5093  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  5094  					},
  5095  				},
  5096  				Status: v1alpha1.ApplicationSetStatus{
  5097  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5098  						{
  5099  							Application: "app1",
  5100  							LastTransitionTime: &metav1.Time{
  5101  								Time: time.Now(),
  5102  							},
  5103  							Message: "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5104  							Status:  "Pending",
  5105  							Step:    "1",
  5106  						},
  5107  					},
  5108  				},
  5109  			},
  5110  			apps: []v1alpha1.Application{
  5111  				{
  5112  					ObjectMeta: metav1.ObjectMeta{
  5113  						Name: "app1",
  5114  					},
  5115  					Status: v1alpha1.ApplicationStatus{
  5116  						Health: v1alpha1.HealthStatus{
  5117  							Status: health.HealthStatusDegraded,
  5118  						},
  5119  						OperationState: &v1alpha1.OperationState{
  5120  							Phase: common.OperationSucceeded,
  5121  							StartedAt: metav1.Time{
  5122  								Time: time.Now().Add(time.Duration(-11) * time.Second),
  5123  							},
  5124  						},
  5125  						Sync: v1alpha1.SyncStatus{
  5126  							Status: v1alpha1.SyncStatusCodeSynced,
  5127  						},
  5128  					},
  5129  				},
  5130  			},
  5131  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5132  				{
  5133  					Application: "app1",
  5134  					Message:     "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5135  					Status:      "Pending",
  5136  					Step:        "1",
  5137  				},
  5138  			},
  5139  		},
  5140  		{
  5141  			name: "removes the appStatus for applications that no longer exist",
  5142  			appSet: v1alpha1.ApplicationSet{
  5143  				ObjectMeta: metav1.ObjectMeta{
  5144  					Name:      "name",
  5145  					Namespace: "argocd",
  5146  				},
  5147  				Spec: v1alpha1.ApplicationSetSpec{
  5148  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5149  						Type:        "RollingSync",
  5150  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  5151  					},
  5152  				},
  5153  				Status: v1alpha1.ApplicationSetStatus{
  5154  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5155  						{
  5156  							Application: "app1",
  5157  							Message:     "Application has pending changes, setting status to Waiting.",
  5158  							Status:      "Waiting",
  5159  							Step:        "1",
  5160  						},
  5161  						{
  5162  							Application: "app2",
  5163  							Message:     "Application has pending changes, setting status to Waiting.",
  5164  							Status:      "Waiting",
  5165  							Step:        "1",
  5166  						},
  5167  					},
  5168  				},
  5169  			},
  5170  			apps: []v1alpha1.Application{
  5171  				{
  5172  					ObjectMeta: metav1.ObjectMeta{
  5173  						Name: "app1",
  5174  					},
  5175  					Status: v1alpha1.ApplicationStatus{
  5176  						Health: v1alpha1.HealthStatus{
  5177  							Status: health.HealthStatusHealthy,
  5178  						},
  5179  						OperationState: &v1alpha1.OperationState{
  5180  							Phase: common.OperationSucceeded,
  5181  						},
  5182  						Sync: v1alpha1.SyncStatus{
  5183  							Status: v1alpha1.SyncStatusCodeSynced,
  5184  						},
  5185  					},
  5186  				},
  5187  			},
  5188  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5189  				{
  5190  					Application: "app1",
  5191  					Message:     "Application resource is already Healthy, updating status from Waiting to Healthy.",
  5192  					Status:      "Healthy",
  5193  					Step:        "1",
  5194  				},
  5195  			},
  5196  		},
  5197  	} {
  5198  
  5199  		t.Run(cc.name, func(t *testing.T) {
  5200  
  5201  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  5202  			argoDBMock := dbmocks.ArgoDB{}
  5203  			argoObjs := []runtime.Object{}
  5204  
  5205  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build()
  5206  
  5207  			r := ApplicationSetReconciler{
  5208  				Client:           client,
  5209  				Scheme:           scheme,
  5210  				Recorder:         record.NewFakeRecorder(1),
  5211  				Cache:            &fakeCache{},
  5212  				Generators:       map[string]generators.Generator{},
  5213  				ArgoDB:           &argoDBMock,
  5214  				ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  5215  				KubeClientset:    kubeclientset,
  5216  			}
  5217  
  5218  			appStatuses, err := r.updateApplicationSetApplicationStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps, cc.appStepMap)
  5219  
  5220  			// opt out of testing the LastTransitionTime is accurate
  5221  			for i := range appStatuses {
  5222  				appStatuses[i].LastTransitionTime = nil
  5223  			}
  5224  
  5225  			assert.Equal(t, err, nil, "expected no errors, but errors occured")
  5226  			assert.Equal(t, cc.expectedAppStatus, appStatuses, "expected appStatuses did not match actual")
  5227  		})
  5228  	}
  5229  }
  5230  
  5231  func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
  5232  
  5233  	scheme := runtime.NewScheme()
  5234  	err := v1alpha1.AddToScheme(scheme)
  5235  	assert.Nil(t, err)
  5236  
  5237  	err = v1alpha1.AddToScheme(scheme)
  5238  	assert.Nil(t, err)
  5239  
  5240  	for _, cc := range []struct {
  5241  		name              string
  5242  		appSet            v1alpha1.ApplicationSet
  5243  		appSyncMap        map[string]bool
  5244  		appStepMap        map[string]int
  5245  		appMap            map[string]v1alpha1.Application
  5246  		expectedAppStatus []v1alpha1.ApplicationSetApplicationStatus
  5247  	}{
  5248  		{
  5249  			name: "handles an empty appSync and appStepMap",
  5250  			appSet: v1alpha1.ApplicationSet{
  5251  				ObjectMeta: metav1.ObjectMeta{
  5252  					Name:      "name",
  5253  					Namespace: "argocd",
  5254  				},
  5255  				Spec: v1alpha1.ApplicationSetSpec{
  5256  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5257  						Type: "RollingSync",
  5258  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5259  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5260  								{
  5261  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5262  								},
  5263  								{
  5264  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5265  								},
  5266  							},
  5267  						},
  5268  					},
  5269  				},
  5270  				Status: v1alpha1.ApplicationSetStatus{
  5271  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5272  				},
  5273  			},
  5274  			appSyncMap:        map[string]bool{},
  5275  			appStepMap:        map[string]int{},
  5276  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5277  		},
  5278  		{
  5279  			name: "handles an empty strategy",
  5280  			appSet: v1alpha1.ApplicationSet{
  5281  				ObjectMeta: metav1.ObjectMeta{
  5282  					Name:      "name",
  5283  					Namespace: "argocd",
  5284  				},
  5285  				Spec: v1alpha1.ApplicationSetSpec{},
  5286  				Status: v1alpha1.ApplicationSetStatus{
  5287  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5288  				},
  5289  			},
  5290  			appSyncMap:        map[string]bool{},
  5291  			appStepMap:        map[string]int{},
  5292  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5293  		},
  5294  		{
  5295  			name: "handles an empty applicationset strategy",
  5296  			appSet: v1alpha1.ApplicationSet{
  5297  				ObjectMeta: metav1.ObjectMeta{
  5298  					Name:      "name",
  5299  					Namespace: "argocd",
  5300  				},
  5301  				Spec: v1alpha1.ApplicationSetSpec{
  5302  					Strategy: &v1alpha1.ApplicationSetStrategy{},
  5303  				},
  5304  				Status: v1alpha1.ApplicationSetStatus{
  5305  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5306  				},
  5307  			},
  5308  			appSyncMap:        map[string]bool{},
  5309  			appStepMap:        map[string]int{},
  5310  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5311  		},
  5312  		{
  5313  			name: "handles an appSyncMap with no existing statuses",
  5314  			appSet: v1alpha1.ApplicationSet{
  5315  				ObjectMeta: metav1.ObjectMeta{
  5316  					Name:      "name",
  5317  					Namespace: "argocd",
  5318  				},
  5319  				Status: v1alpha1.ApplicationSetStatus{
  5320  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5321  				},
  5322  			},
  5323  			appSyncMap: map[string]bool{
  5324  				"app1": true,
  5325  				"app2": false,
  5326  			},
  5327  			appStepMap: map[string]int{
  5328  				"app1": 0,
  5329  				"app2": 1,
  5330  			},
  5331  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5332  		},
  5333  		{
  5334  			name: "handles updating a RollingSync status from Waiting to Pending",
  5335  			appSet: v1alpha1.ApplicationSet{
  5336  				ObjectMeta: metav1.ObjectMeta{
  5337  					Name:      "name",
  5338  					Namespace: "argocd",
  5339  				},
  5340  				Spec: v1alpha1.ApplicationSetSpec{
  5341  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5342  						Type: "RollingSync",
  5343  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5344  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5345  								{
  5346  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5347  								},
  5348  								{
  5349  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5350  								},
  5351  							},
  5352  						},
  5353  					},
  5354  				},
  5355  				Status: v1alpha1.ApplicationSetStatus{
  5356  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5357  						{
  5358  							Application: "app1",
  5359  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5360  							Status:      "Waiting",
  5361  						},
  5362  					},
  5363  				},
  5364  			},
  5365  			appSyncMap: map[string]bool{
  5366  				"app1": true,
  5367  			},
  5368  			appStepMap: map[string]int{
  5369  				"app1": 0,
  5370  			},
  5371  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5372  				{
  5373  					Application:        "app1",
  5374  					LastTransitionTime: nil,
  5375  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5376  					Status:             "Pending",
  5377  					Step:               "1",
  5378  				},
  5379  			},
  5380  		},
  5381  		{
  5382  			name: "does not update a RollingSync status if appSyncMap is false",
  5383  			appSet: v1alpha1.ApplicationSet{
  5384  				ObjectMeta: metav1.ObjectMeta{
  5385  					Name:      "name",
  5386  					Namespace: "argocd",
  5387  				},
  5388  				Spec: v1alpha1.ApplicationSetSpec{
  5389  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5390  						Type: "RollingSync",
  5391  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5392  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5393  								{
  5394  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5395  								},
  5396  								{
  5397  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5398  								},
  5399  							},
  5400  						},
  5401  					},
  5402  				},
  5403  				Status: v1alpha1.ApplicationSetStatus{
  5404  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5405  						{
  5406  							Application: "app1",
  5407  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5408  							Status:      "Waiting",
  5409  							Step:        "1",
  5410  						},
  5411  					},
  5412  				},
  5413  			},
  5414  			appSyncMap: map[string]bool{
  5415  				"app1": false,
  5416  			},
  5417  			appStepMap: map[string]int{
  5418  				"app1": 0,
  5419  			},
  5420  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5421  				{
  5422  					Application:        "app1",
  5423  					LastTransitionTime: nil,
  5424  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5425  					Status:             "Waiting",
  5426  					Step:               "1",
  5427  				},
  5428  			},
  5429  		},
  5430  		{
  5431  			name: "does not update a status if status is not pending",
  5432  			appSet: v1alpha1.ApplicationSet{
  5433  				ObjectMeta: metav1.ObjectMeta{
  5434  					Name:      "name",
  5435  					Namespace: "argocd",
  5436  				},
  5437  				Spec: v1alpha1.ApplicationSetSpec{
  5438  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5439  						Type: "RollingSync",
  5440  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5441  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5442  								{
  5443  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5444  								},
  5445  								{
  5446  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5447  								},
  5448  							},
  5449  						},
  5450  					},
  5451  				},
  5452  				Status: v1alpha1.ApplicationSetStatus{
  5453  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5454  						{
  5455  							Application: "app1",
  5456  							Message:     "Application Pending status timed out while waiting to become Progressing, reset status to Healthy.",
  5457  							Status:      "Healthy",
  5458  							Step:        "1",
  5459  						},
  5460  					},
  5461  				},
  5462  			},
  5463  			appSyncMap: map[string]bool{
  5464  				"app1": true,
  5465  			},
  5466  			appStepMap: map[string]int{
  5467  				"app1": 0,
  5468  			},
  5469  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5470  				{
  5471  					Application:        "app1",
  5472  					LastTransitionTime: nil,
  5473  					Message:            "Application Pending status timed out while waiting to become Progressing, reset status to Healthy.",
  5474  					Status:             "Healthy",
  5475  					Step:               "1",
  5476  				},
  5477  			},
  5478  		},
  5479  		{
  5480  			name: "does not update a status if maxUpdate has already been reached with RollingSync",
  5481  			appSet: v1alpha1.ApplicationSet{
  5482  				ObjectMeta: metav1.ObjectMeta{
  5483  					Name:      "name",
  5484  					Namespace: "argocd",
  5485  				},
  5486  				Spec: v1alpha1.ApplicationSetSpec{
  5487  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5488  						Type: "RollingSync",
  5489  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5490  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5491  								{
  5492  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5493  									MaxUpdate: &intstr.IntOrString{
  5494  										Type:   intstr.Int,
  5495  										IntVal: 3,
  5496  									},
  5497  								},
  5498  								{
  5499  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5500  								},
  5501  							},
  5502  						},
  5503  					},
  5504  				},
  5505  				Status: v1alpha1.ApplicationSetStatus{
  5506  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5507  						{
  5508  							Application: "app1",
  5509  							Message:     "Application resource became Progressing, updating status from Pending to Progressing.",
  5510  							Status:      "Progressing",
  5511  							Step:        "1",
  5512  						},
  5513  						{
  5514  							Application: "app2",
  5515  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5516  							Status:      "Waiting",
  5517  							Step:        "1",
  5518  						},
  5519  						{
  5520  							Application: "app3",
  5521  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5522  							Status:      "Waiting",
  5523  							Step:        "1",
  5524  						},
  5525  						{
  5526  							Application: "app4",
  5527  							Message:     "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5528  							Status:      "Pending",
  5529  							Step:        "1",
  5530  						},
  5531  					},
  5532  				},
  5533  			},
  5534  			appSyncMap: map[string]bool{
  5535  				"app1": true,
  5536  				"app2": true,
  5537  				"app3": true,
  5538  				"app4": true,
  5539  			},
  5540  			appStepMap: map[string]int{
  5541  				"app1": 0,
  5542  				"app2": 0,
  5543  				"app3": 0,
  5544  				"app4": 0,
  5545  			},
  5546  			appMap: map[string]v1alpha1.Application{
  5547  				"app1": {
  5548  					ObjectMeta: metav1.ObjectMeta{
  5549  						Name: "app1",
  5550  					},
  5551  					Status: v1alpha1.ApplicationStatus{
  5552  						Sync: v1alpha1.SyncStatus{
  5553  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  5554  						},
  5555  					},
  5556  				},
  5557  				"app2": {
  5558  					ObjectMeta: metav1.ObjectMeta{
  5559  						Name: "app2",
  5560  					},
  5561  					Status: v1alpha1.ApplicationStatus{
  5562  						Sync: v1alpha1.SyncStatus{
  5563  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  5564  						},
  5565  					},
  5566  				},
  5567  				"app3": {
  5568  					ObjectMeta: metav1.ObjectMeta{
  5569  						Name: "app3",
  5570  					},
  5571  					Status: v1alpha1.ApplicationStatus{
  5572  						Sync: v1alpha1.SyncStatus{
  5573  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  5574  						},
  5575  					},
  5576  				},
  5577  				"app4": {
  5578  					ObjectMeta: metav1.ObjectMeta{
  5579  						Name: "app4",
  5580  					},
  5581  					Status: v1alpha1.ApplicationStatus{
  5582  						Sync: v1alpha1.SyncStatus{
  5583  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  5584  						},
  5585  					},
  5586  				},
  5587  			},
  5588  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5589  				{
  5590  					Application:        "app1",
  5591  					LastTransitionTime: nil,
  5592  					Message:            "Application resource became Progressing, updating status from Pending to Progressing.",
  5593  					Status:             "Progressing",
  5594  					Step:               "1",
  5595  				},
  5596  				{
  5597  					Application:        "app2",
  5598  					LastTransitionTime: nil,
  5599  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5600  					Status:             "Pending",
  5601  					Step:               "1",
  5602  				},
  5603  				{
  5604  					Application:        "app3",
  5605  					LastTransitionTime: nil,
  5606  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5607  					Status:             "Waiting",
  5608  					Step:               "1",
  5609  				},
  5610  				{
  5611  					Application:        "app4",
  5612  					LastTransitionTime: nil,
  5613  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5614  					Status:             "Pending",
  5615  					Step:               "1",
  5616  				},
  5617  			},
  5618  		},
  5619  		{
  5620  			name: "rounds down for maxUpdate set to percentage string",
  5621  			appSet: v1alpha1.ApplicationSet{
  5622  				ObjectMeta: metav1.ObjectMeta{
  5623  					Name:      "name",
  5624  					Namespace: "argocd",
  5625  				},
  5626  				Spec: v1alpha1.ApplicationSetSpec{
  5627  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5628  						Type: "RollingSync",
  5629  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5630  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5631  								{
  5632  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5633  									MaxUpdate: &intstr.IntOrString{
  5634  										Type:   intstr.String,
  5635  										StrVal: "50%",
  5636  									},
  5637  								},
  5638  								{
  5639  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5640  								},
  5641  							},
  5642  						},
  5643  					},
  5644  				},
  5645  				Status: v1alpha1.ApplicationSetStatus{
  5646  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5647  						{
  5648  							Application: "app1",
  5649  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5650  							Status:      "Waiting",
  5651  							Step:        "1",
  5652  						},
  5653  						{
  5654  							Application: "app2",
  5655  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5656  							Status:      "Waiting",
  5657  							Step:        "1",
  5658  						},
  5659  						{
  5660  							Application: "app3",
  5661  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5662  							Status:      "Waiting",
  5663  							Step:        "1",
  5664  						},
  5665  					},
  5666  				},
  5667  			},
  5668  			appSyncMap: map[string]bool{
  5669  				"app1": true,
  5670  				"app2": true,
  5671  				"app3": true,
  5672  			},
  5673  			appStepMap: map[string]int{
  5674  				"app1": 0,
  5675  				"app2": 0,
  5676  				"app3": 0,
  5677  			},
  5678  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5679  				{
  5680  					Application:        "app1",
  5681  					LastTransitionTime: nil,
  5682  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5683  					Status:             "Pending",
  5684  					Step:               "1",
  5685  				},
  5686  				{
  5687  					Application:        "app2",
  5688  					LastTransitionTime: nil,
  5689  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5690  					Status:             "Waiting",
  5691  					Step:               "1",
  5692  				},
  5693  				{
  5694  					Application:        "app3",
  5695  					LastTransitionTime: nil,
  5696  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5697  					Status:             "Waiting",
  5698  					Step:               "1",
  5699  				},
  5700  			},
  5701  		},
  5702  		{
  5703  			name: "does not update any applications with maxUpdate set to 0",
  5704  			appSet: v1alpha1.ApplicationSet{
  5705  				ObjectMeta: metav1.ObjectMeta{
  5706  					Name:      "name",
  5707  					Namespace: "argocd",
  5708  				},
  5709  				Spec: v1alpha1.ApplicationSetSpec{
  5710  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5711  						Type: "RollingSync",
  5712  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5713  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5714  								{
  5715  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5716  									MaxUpdate: &intstr.IntOrString{
  5717  										Type:   intstr.Int,
  5718  										IntVal: 0,
  5719  									},
  5720  								},
  5721  								{
  5722  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5723  								},
  5724  							},
  5725  						},
  5726  					},
  5727  				},
  5728  				Status: v1alpha1.ApplicationSetStatus{
  5729  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5730  						{
  5731  							Application: "app1",
  5732  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5733  							Status:      "Waiting",
  5734  							Step:        "1",
  5735  						},
  5736  						{
  5737  							Application: "app2",
  5738  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5739  							Status:      "Waiting",
  5740  							Step:        "1",
  5741  						},
  5742  						{
  5743  							Application: "app3",
  5744  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5745  							Status:      "Waiting",
  5746  							Step:        "1",
  5747  						},
  5748  					},
  5749  				},
  5750  			},
  5751  			appSyncMap: map[string]bool{
  5752  				"app1": true,
  5753  				"app2": true,
  5754  				"app3": true,
  5755  			},
  5756  			appStepMap: map[string]int{
  5757  				"app1": 0,
  5758  				"app2": 0,
  5759  				"app3": 0,
  5760  			},
  5761  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5762  				{
  5763  					Application:        "app1",
  5764  					LastTransitionTime: nil,
  5765  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5766  					Status:             "Waiting",
  5767  					Step:               "1",
  5768  				},
  5769  				{
  5770  					Application:        "app2",
  5771  					LastTransitionTime: nil,
  5772  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5773  					Status:             "Waiting",
  5774  					Step:               "1",
  5775  				},
  5776  				{
  5777  					Application:        "app3",
  5778  					LastTransitionTime: nil,
  5779  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5780  					Status:             "Waiting",
  5781  					Step:               "1",
  5782  				},
  5783  			},
  5784  		},
  5785  		{
  5786  			name: "updates all applications with maxUpdate set to 100%",
  5787  			appSet: v1alpha1.ApplicationSet{
  5788  				ObjectMeta: metav1.ObjectMeta{
  5789  					Name:      "name",
  5790  					Namespace: "argocd",
  5791  				},
  5792  				Spec: v1alpha1.ApplicationSetSpec{
  5793  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5794  						Type: "RollingSync",
  5795  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5796  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5797  								{
  5798  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5799  									MaxUpdate: &intstr.IntOrString{
  5800  										Type:   intstr.String,
  5801  										StrVal: "100%",
  5802  									},
  5803  								},
  5804  								{
  5805  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5806  								},
  5807  							},
  5808  						},
  5809  					},
  5810  				},
  5811  				Status: v1alpha1.ApplicationSetStatus{
  5812  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5813  						{
  5814  							Application: "app1",
  5815  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5816  							Status:      "Waiting",
  5817  							Step:        "1",
  5818  						},
  5819  						{
  5820  							Application: "app2",
  5821  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5822  							Status:      "Waiting",
  5823  							Step:        "1",
  5824  						},
  5825  						{
  5826  							Application: "app3",
  5827  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5828  							Status:      "Waiting",
  5829  							Step:        "1",
  5830  						},
  5831  					},
  5832  				},
  5833  			},
  5834  			appSyncMap: map[string]bool{
  5835  				"app1": true,
  5836  				"app2": true,
  5837  				"app3": true,
  5838  			},
  5839  			appStepMap: map[string]int{
  5840  				"app1": 0,
  5841  				"app2": 0,
  5842  				"app3": 0,
  5843  			},
  5844  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5845  				{
  5846  					Application:        "app1",
  5847  					LastTransitionTime: nil,
  5848  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5849  					Status:             "Pending",
  5850  					Step:               "1",
  5851  				},
  5852  				{
  5853  					Application:        "app2",
  5854  					LastTransitionTime: nil,
  5855  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5856  					Status:             "Pending",
  5857  					Step:               "1",
  5858  				},
  5859  				{
  5860  					Application:        "app3",
  5861  					LastTransitionTime: nil,
  5862  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5863  					Status:             "Pending",
  5864  					Step:               "1",
  5865  				},
  5866  			},
  5867  		},
  5868  		{
  5869  			name: "updates at least 1 application with maxUpdate >0%",
  5870  			appSet: v1alpha1.ApplicationSet{
  5871  				ObjectMeta: metav1.ObjectMeta{
  5872  					Name:      "name",
  5873  					Namespace: "argocd",
  5874  				},
  5875  				Spec: v1alpha1.ApplicationSetSpec{
  5876  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5877  						Type: "RollingSync",
  5878  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5879  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5880  								{
  5881  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5882  									MaxUpdate: &intstr.IntOrString{
  5883  										Type:   intstr.String,
  5884  										StrVal: "1%",
  5885  									},
  5886  								},
  5887  								{
  5888  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5889  								},
  5890  							},
  5891  						},
  5892  					},
  5893  				},
  5894  				Status: v1alpha1.ApplicationSetStatus{
  5895  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5896  						{
  5897  							Application: "app1",
  5898  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5899  							Status:      "Waiting",
  5900  							Step:        "1",
  5901  						},
  5902  						{
  5903  							Application: "app2",
  5904  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5905  							Status:      "Waiting",
  5906  							Step:        "1",
  5907  						},
  5908  						{
  5909  							Application: "app3",
  5910  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5911  							Status:      "Waiting",
  5912  							Step:        "1",
  5913  						},
  5914  					},
  5915  				},
  5916  			},
  5917  			appSyncMap: map[string]bool{
  5918  				"app1": true,
  5919  				"app2": true,
  5920  				"app3": true,
  5921  			},
  5922  			appStepMap: map[string]int{
  5923  				"app1": 0,
  5924  				"app2": 0,
  5925  				"app3": 0,
  5926  			},
  5927  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5928  				{
  5929  					Application:        "app1",
  5930  					LastTransitionTime: nil,
  5931  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5932  					Status:             "Pending",
  5933  					Step:               "1",
  5934  				},
  5935  				{
  5936  					Application:        "app2",
  5937  					LastTransitionTime: nil,
  5938  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5939  					Status:             "Waiting",
  5940  					Step:               "1",
  5941  				},
  5942  				{
  5943  					Application:        "app3",
  5944  					LastTransitionTime: nil,
  5945  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5946  					Status:             "Waiting",
  5947  					Step:               "1",
  5948  				},
  5949  			},
  5950  		},
  5951  	} {
  5952  
  5953  		t.Run(cc.name, func(t *testing.T) {
  5954  
  5955  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  5956  			argoDBMock := dbmocks.ArgoDB{}
  5957  			argoObjs := []runtime.Object{}
  5958  
  5959  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).Build()
  5960  
  5961  			r := ApplicationSetReconciler{
  5962  				Client:           client,
  5963  				Scheme:           scheme,
  5964  				Recorder:         record.NewFakeRecorder(1),
  5965  				Cache:            &fakeCache{},
  5966  				Generators:       map[string]generators.Generator{},
  5967  				ArgoDB:           &argoDBMock,
  5968  				ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
  5969  				KubeClientset:    kubeclientset,
  5970  			}
  5971  
  5972  			appStatuses, err := r.updateApplicationSetApplicationStatusProgress(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appSyncMap, cc.appStepMap, cc.appMap)
  5973  
  5974  			// opt out of testing the LastTransitionTime is accurate
  5975  			for i := range appStatuses {
  5976  				appStatuses[i].LastTransitionTime = nil
  5977  			}
  5978  
  5979  			assert.Equal(t, err, nil, "expected no errors, but errors occured")
  5980  			assert.Equal(t, cc.expectedAppStatus, appStatuses, "expected appStatuses did not match actual")
  5981  		})
  5982  	}
  5983  }
  5984  
  5985  func TestOwnsHandler(t *testing.T) {
  5986  	// progressive syncs do not affect create, delete, or generic
  5987  	ownsHandler := getOwnsHandlerPredicates(true)
  5988  	assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{}))
  5989  	assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{}))
  5990  	assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{}))
  5991  	ownsHandler = getOwnsHandlerPredicates(false)
  5992  	assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{}))
  5993  	assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{}))
  5994  	assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{}))
  5995  
  5996  	now := metav1.Now()
  5997  	type args struct {
  5998  		e                      event.UpdateEvent
  5999  		enableProgressiveSyncs bool
  6000  	}
  6001  	tests := []struct {
  6002  		name string
  6003  		args args
  6004  		want bool
  6005  	}{
  6006  		{name: "SameApplicationReconciledAtDiff", args: args{e: event.UpdateEvent{
  6007  			ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ReconciledAt: &now}},
  6008  			ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ReconciledAt: &now}},
  6009  		}}, want: false},
  6010  		{name: "SameApplicationResourceVersionDiff", args: args{e: event.UpdateEvent{
  6011  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
  6012  				ResourceVersion: "foo",
  6013  			}},
  6014  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
  6015  				ResourceVersion: "bar",
  6016  			}},
  6017  		}}, want: false},
  6018  		{name: "ApplicationHealthStatusDiff", args: args{e: event.UpdateEvent{
  6019  			ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6020  				Health: v1alpha1.HealthStatus{
  6021  					Status: "Unknown",
  6022  				},
  6023  			}},
  6024  			ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6025  				Health: v1alpha1.HealthStatus{
  6026  					Status: "Healthy",
  6027  				},
  6028  			}},
  6029  		},
  6030  			enableProgressiveSyncs: true,
  6031  		}, want: true},
  6032  		{name: "ApplicationSyncStatusDiff", args: args{e: event.UpdateEvent{
  6033  			ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6034  				Sync: v1alpha1.SyncStatus{
  6035  					Status: "OutOfSync",
  6036  				},
  6037  			}},
  6038  			ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6039  				Sync: v1alpha1.SyncStatus{
  6040  					Status: "Synced",
  6041  				},
  6042  			}},
  6043  		},
  6044  			enableProgressiveSyncs: true,
  6045  		}, want: true},
  6046  		{name: "ApplicationOperationStateDiff", args: args{e: event.UpdateEvent{
  6047  			ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6048  				OperationState: &v1alpha1.OperationState{
  6049  					Phase: "foo",
  6050  				},
  6051  			}},
  6052  			ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6053  				OperationState: &v1alpha1.OperationState{
  6054  					Phase: "bar",
  6055  				},
  6056  			}},
  6057  		},
  6058  			enableProgressiveSyncs: true,
  6059  		}, want: true},
  6060  		{name: "ApplicationOperationStartedAtDiff", args: args{e: event.UpdateEvent{
  6061  			ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6062  				OperationState: &v1alpha1.OperationState{
  6063  					StartedAt: now,
  6064  				},
  6065  			}},
  6066  			ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6067  				OperationState: &v1alpha1.OperationState{
  6068  					StartedAt: metav1.NewTime(now.Add(time.Minute * 1)),
  6069  				},
  6070  			}},
  6071  		},
  6072  			enableProgressiveSyncs: true,
  6073  		}, want: true},
  6074  		{name: "SameApplicationGeneration", args: args{e: event.UpdateEvent{
  6075  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
  6076  				Generation: 1,
  6077  			}},
  6078  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
  6079  				Generation: 2,
  6080  			}},
  6081  		}}, want: false},
  6082  		{name: "DifferentApplicationSpec", args: args{e: event.UpdateEvent{
  6083  			ObjectOld: &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Project: "default"}},
  6084  			ObjectNew: &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Project: "not-default"}},
  6085  		}}, want: true},
  6086  		{name: "DifferentApplicationLabels", args: args{e: event.UpdateEvent{
  6087  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}},
  6088  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}},
  6089  		}}, want: true},
  6090  		{name: "DifferentApplicationAnnotations", args: args{e: event.UpdateEvent{
  6091  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"foo": "bar"}}},
  6092  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"bar": "foo"}}},
  6093  		}}, want: true},
  6094  		{name: "DifferentApplicationFinalizers", args: args{e: event.UpdateEvent{
  6095  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"argo"}}},
  6096  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"none"}}},
  6097  		}}, want: true},
  6098  		{name: "NotAnAppOld", args: args{e: event.UpdateEvent{
  6099  			ObjectOld: &v1alpha1.AppProject{},
  6100  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}},
  6101  		}}, want: false},
  6102  		{name: "NotAnAppNew", args: args{e: event.UpdateEvent{
  6103  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}},
  6104  			ObjectNew: &v1alpha1.AppProject{},
  6105  		}}, want: false},
  6106  	}
  6107  	for _, tt := range tests {
  6108  		t.Run(tt.name, func(t *testing.T) {
  6109  			ownsHandler = getOwnsHandlerPredicates(tt.args.enableProgressiveSyncs)
  6110  			assert.Equalf(t, tt.want, ownsHandler.UpdateFunc(tt.args.e), "UpdateFunc(%v)", tt.args.e)
  6111  		})
  6112  	}
  6113  }