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

     1  package controllers
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"strconv"
     8  	"testing"
     9  	"time"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/require"
    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  	"k8s.io/client-go/tools/record"
    23  	ctrl "sigs.k8s.io/controller-runtime"
    24  	crtclient "sigs.k8s.io/controller-runtime/pkg/client"
    25  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    26  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    27  	"sigs.k8s.io/controller-runtime/pkg/event"
    28  
    29  	"github.com/argoproj/gitops-engine/pkg/health"
    30  	"github.com/argoproj/gitops-engine/pkg/sync/common"
    31  
    32  	"github.com/argoproj/argo-cd/v3/applicationset/generators"
    33  	"github.com/argoproj/argo-cd/v3/applicationset/generators/mocks"
    34  	appsetmetrics "github.com/argoproj/argo-cd/v3/applicationset/metrics"
    35  	"github.com/argoproj/argo-cd/v3/applicationset/utils"
    36  	argocommon "github.com/argoproj/argo-cd/v3/common"
    37  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    38  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    39  	applog "github.com/argoproj/argo-cd/v3/util/app/log"
    40  	"github.com/argoproj/argo-cd/v3/util/db"
    41  	"github.com/argoproj/argo-cd/v3/util/settings"
    42  )
    43  
    44  // getDefaultTestClientSet creates a Clientset with the default argo objects
    45  // and objects specified in parameters
    46  func getDefaultTestClientSet(obj ...runtime.Object) *kubefake.Clientset {
    47  	argoCDSecret := &corev1.Secret{
    48  		ObjectMeta: metav1.ObjectMeta{
    49  			Name:      argocommon.ArgoCDSecretName,
    50  			Namespace: "argocd",
    51  			Labels: map[string]string{
    52  				"app.kubernetes.io/part-of": "argocd",
    53  			},
    54  		},
    55  		Data: map[string][]byte{
    56  			"admin.password":   nil,
    57  			"server.secretkey": nil,
    58  		},
    59  	}
    60  
    61  	emptyArgoCDConfigMap := &corev1.ConfigMap{
    62  		ObjectMeta: metav1.ObjectMeta{
    63  			Name:      argocommon.ArgoCDConfigMapName,
    64  			Namespace: "argocd",
    65  			Labels: map[string]string{
    66  				"app.kubernetes.io/part-of": "argocd",
    67  			},
    68  		},
    69  		Data: map[string]string{},
    70  	}
    71  
    72  	objects := append(obj, emptyArgoCDConfigMap, argoCDSecret)
    73  	kubeclientset := kubefake.NewClientset(objects...)
    74  	return kubeclientset
    75  }
    76  
    77  func TestCreateOrUpdateInCluster(t *testing.T) {
    78  	scheme := runtime.NewScheme()
    79  	err := v1alpha1.AddToScheme(scheme)
    80  	require.NoError(t, err)
    81  
    82  	for _, c := range []struct {
    83  		// name is human-readable test name
    84  		name string
    85  		// appSet is the ApplicationSet we are generating resources for
    86  		appSet v1alpha1.ApplicationSet
    87  		// existingApps are the apps that already exist on the cluster
    88  		existingApps []v1alpha1.Application
    89  		// desiredApps are the generated apps to create/update
    90  		desiredApps []v1alpha1.Application
    91  		// expected is what we expect the cluster Applications to look like, after createOrUpdateInCluster
    92  		expected []v1alpha1.Application
    93  	}{
    94  		{
    95  			name: "Create an app that doesn't exist",
    96  			appSet: v1alpha1.ApplicationSet{
    97  				ObjectMeta: metav1.ObjectMeta{
    98  					Name:      "name",
    99  					Namespace: "namespace",
   100  				},
   101  			},
   102  			existingApps: nil,
   103  			desiredApps: []v1alpha1.Application{
   104  				{
   105  					ObjectMeta: metav1.ObjectMeta{
   106  						Name:      "app1",
   107  						Namespace: "namespace",
   108  					},
   109  					Spec: v1alpha1.ApplicationSpec{Project: "default"},
   110  				},
   111  			},
   112  			expected: []v1alpha1.Application{
   113  				{
   114  					TypeMeta: metav1.TypeMeta{
   115  						Kind:       application.ApplicationKind,
   116  						APIVersion: "argoproj.io/v1alpha1",
   117  					},
   118  					ObjectMeta: metav1.ObjectMeta{
   119  						Name:            "app1",
   120  						Namespace:       "namespace",
   121  						ResourceVersion: "1",
   122  					},
   123  					Spec: v1alpha1.ApplicationSpec{Project: "default"},
   124  				},
   125  			},
   126  		},
   127  		{
   128  			name: "Update an existing app with a different project name",
   129  			appSet: v1alpha1.ApplicationSet{
   130  				ObjectMeta: metav1.ObjectMeta{
   131  					Name:      "name",
   132  					Namespace: "namespace",
   133  				},
   134  				Spec: v1alpha1.ApplicationSetSpec{
   135  					Template: v1alpha1.ApplicationSetTemplate{
   136  						Spec: v1alpha1.ApplicationSpec{
   137  							Project: "project",
   138  						},
   139  					},
   140  				},
   141  			},
   142  			existingApps: []v1alpha1.Application{
   143  				{
   144  					TypeMeta: metav1.TypeMeta{
   145  						Kind:       application.ApplicationKind,
   146  						APIVersion: "argoproj.io/v1alpha1",
   147  					},
   148  					ObjectMeta: metav1.ObjectMeta{
   149  						Name:            "app1",
   150  						Namespace:       "namespace",
   151  						ResourceVersion: "2",
   152  					},
   153  					Spec: v1alpha1.ApplicationSpec{
   154  						Project: "test",
   155  					},
   156  				},
   157  			},
   158  			desiredApps: []v1alpha1.Application{
   159  				{
   160  					ObjectMeta: metav1.ObjectMeta{
   161  						Name:      "app1",
   162  						Namespace: "namespace",
   163  					},
   164  					Spec: v1alpha1.ApplicationSpec{
   165  						Project: "project",
   166  					},
   167  				},
   168  			},
   169  			expected: []v1alpha1.Application{
   170  				{
   171  					TypeMeta: metav1.TypeMeta{
   172  						Kind:       application.ApplicationKind,
   173  						APIVersion: "argoproj.io/v1alpha1",
   174  					},
   175  					ObjectMeta: metav1.ObjectMeta{
   176  						Name:            "app1",
   177  						Namespace:       "namespace",
   178  						ResourceVersion: "3",
   179  					},
   180  					Spec: v1alpha1.ApplicationSpec{
   181  						Project: "project",
   182  					},
   183  				},
   184  			},
   185  		},
   186  		{
   187  			name: "Create a new app and check it doesn't replace the existing app",
   188  			appSet: v1alpha1.ApplicationSet{
   189  				ObjectMeta: metav1.ObjectMeta{
   190  					Name:      "name",
   191  					Namespace: "namespace",
   192  				},
   193  				Spec: v1alpha1.ApplicationSetSpec{
   194  					Template: v1alpha1.ApplicationSetTemplate{
   195  						Spec: v1alpha1.ApplicationSpec{
   196  							Project: "project",
   197  						},
   198  					},
   199  				},
   200  			},
   201  			existingApps: []v1alpha1.Application{
   202  				{
   203  					TypeMeta: metav1.TypeMeta{
   204  						Kind:       application.ApplicationKind,
   205  						APIVersion: "argoproj.io/v1alpha1",
   206  					},
   207  					ObjectMeta: metav1.ObjectMeta{
   208  						Name:            "app1",
   209  						Namespace:       "namespace",
   210  						ResourceVersion: "2",
   211  					},
   212  					Spec: v1alpha1.ApplicationSpec{
   213  						Project: "test",
   214  					},
   215  				},
   216  			},
   217  			desiredApps: []v1alpha1.Application{
   218  				{
   219  					ObjectMeta: metav1.ObjectMeta{
   220  						Name:      "app2",
   221  						Namespace: "namespace",
   222  					},
   223  					Spec: v1alpha1.ApplicationSpec{
   224  						Project: "project",
   225  					},
   226  				},
   227  			},
   228  			expected: []v1alpha1.Application{
   229  				{
   230  					TypeMeta: metav1.TypeMeta{
   231  						Kind:       application.ApplicationKind,
   232  						APIVersion: "argoproj.io/v1alpha1",
   233  					},
   234  					ObjectMeta: metav1.ObjectMeta{
   235  						Name:            "app2",
   236  						Namespace:       "namespace",
   237  						ResourceVersion: "1",
   238  					},
   239  					Spec: v1alpha1.ApplicationSpec{
   240  						Project: "project",
   241  					},
   242  				},
   243  			},
   244  		},
   245  		{
   246  			name: "Ensure that labels and annotations are added (via update) into an exiting application",
   247  			appSet: v1alpha1.ApplicationSet{
   248  				ObjectMeta: metav1.ObjectMeta{
   249  					Name:      "name",
   250  					Namespace: "namespace",
   251  				},
   252  				Spec: v1alpha1.ApplicationSetSpec{
   253  					Template: v1alpha1.ApplicationSetTemplate{
   254  						Spec: v1alpha1.ApplicationSpec{
   255  							Project: "project",
   256  						},
   257  					},
   258  				},
   259  			},
   260  			existingApps: []v1alpha1.Application{
   261  				{
   262  					TypeMeta: metav1.TypeMeta{
   263  						Kind:       application.ApplicationKind,
   264  						APIVersion: "argoproj.io/v1alpha1",
   265  					},
   266  					ObjectMeta: metav1.ObjectMeta{
   267  						Name:            "app1",
   268  						Namespace:       "namespace",
   269  						ResourceVersion: "2",
   270  					},
   271  					Spec: v1alpha1.ApplicationSpec{
   272  						Project: "project",
   273  					},
   274  				},
   275  			},
   276  			desiredApps: []v1alpha1.Application{
   277  				{
   278  					ObjectMeta: metav1.ObjectMeta{
   279  						Name:        "app1",
   280  						Namespace:   "namespace",
   281  						Labels:      map[string]string{"label-key": "label-value"},
   282  						Annotations: map[string]string{"annot-key": "annot-value"},
   283  					},
   284  					Spec: v1alpha1.ApplicationSpec{
   285  						Project: "project",
   286  					},
   287  				},
   288  			},
   289  			expected: []v1alpha1.Application{
   290  				{
   291  					TypeMeta: metav1.TypeMeta{
   292  						Kind:       application.ApplicationKind,
   293  						APIVersion: "argoproj.io/v1alpha1",
   294  					},
   295  					ObjectMeta: metav1.ObjectMeta{
   296  						Name:            "app1",
   297  						Namespace:       "namespace",
   298  						Labels:          map[string]string{"label-key": "label-value"},
   299  						Annotations:     map[string]string{"annot-key": "annot-value"},
   300  						ResourceVersion: "3",
   301  					},
   302  					Spec: v1alpha1.ApplicationSpec{
   303  						Project: "project",
   304  					},
   305  				},
   306  			},
   307  		},
   308  		{
   309  			name: "Ensure that labels and annotations are removed from an existing app",
   310  			appSet: v1alpha1.ApplicationSet{
   311  				ObjectMeta: metav1.ObjectMeta{
   312  					Name:      "name",
   313  					Namespace: "namespace",
   314  				},
   315  				Spec: v1alpha1.ApplicationSetSpec{
   316  					Template: v1alpha1.ApplicationSetTemplate{
   317  						Spec: v1alpha1.ApplicationSpec{
   318  							Project: "project",
   319  						},
   320  					},
   321  				},
   322  			},
   323  			existingApps: []v1alpha1.Application{
   324  				{
   325  					TypeMeta: metav1.TypeMeta{
   326  						Kind:       application.ApplicationKind,
   327  						APIVersion: "argoproj.io/v1alpha1",
   328  					},
   329  					ObjectMeta: metav1.ObjectMeta{
   330  						Name:            "app1",
   331  						Namespace:       "namespace",
   332  						ResourceVersion: "2",
   333  						Labels:          map[string]string{"label-key": "label-value"},
   334  						Annotations:     map[string]string{"annot-key": "annot-value"},
   335  					},
   336  					Spec: v1alpha1.ApplicationSpec{
   337  						Project: "project",
   338  					},
   339  				},
   340  			},
   341  			desiredApps: []v1alpha1.Application{
   342  				{
   343  					ObjectMeta: metav1.ObjectMeta{
   344  						Name:      "app1",
   345  						Namespace: "namespace",
   346  					},
   347  					Spec: v1alpha1.ApplicationSpec{
   348  						Project: "project",
   349  					},
   350  				},
   351  			},
   352  			expected: []v1alpha1.Application{
   353  				{
   354  					TypeMeta: metav1.TypeMeta{
   355  						Kind:       application.ApplicationKind,
   356  						APIVersion: "argoproj.io/v1alpha1",
   357  					},
   358  					ObjectMeta: metav1.ObjectMeta{
   359  						Name:            "app1",
   360  						Namespace:       "namespace",
   361  						ResourceVersion: "3",
   362  					},
   363  					Spec: v1alpha1.ApplicationSpec{
   364  						Project: "project",
   365  					},
   366  				},
   367  			},
   368  		},
   369  		{
   370  			name: "Ensure that status and operation fields are not overridden by an update, when removing labels/annotations",
   371  			appSet: v1alpha1.ApplicationSet{
   372  				ObjectMeta: metav1.ObjectMeta{
   373  					Name:      "name",
   374  					Namespace: "namespace",
   375  				},
   376  				Spec: v1alpha1.ApplicationSetSpec{
   377  					Template: v1alpha1.ApplicationSetTemplate{
   378  						Spec: v1alpha1.ApplicationSpec{
   379  							Project: "project",
   380  						},
   381  					},
   382  				},
   383  			},
   384  			existingApps: []v1alpha1.Application{
   385  				{
   386  					TypeMeta: metav1.TypeMeta{
   387  						Kind:       application.ApplicationKind,
   388  						APIVersion: "argoproj.io/v1alpha1",
   389  					},
   390  					ObjectMeta: metav1.ObjectMeta{
   391  						Name:            "app1",
   392  						Namespace:       "namespace",
   393  						ResourceVersion: "2",
   394  						Labels:          map[string]string{"label-key": "label-value"},
   395  						Annotations:     map[string]string{"annot-key": "annot-value"},
   396  					},
   397  					Spec: v1alpha1.ApplicationSpec{
   398  						Project: "project",
   399  					},
   400  					Status: v1alpha1.ApplicationStatus{
   401  						Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}},
   402  					},
   403  					Operation: &v1alpha1.Operation{
   404  						Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"},
   405  					},
   406  				},
   407  			},
   408  			desiredApps: []v1alpha1.Application{
   409  				{
   410  					ObjectMeta: metav1.ObjectMeta{
   411  						Name:      "app1",
   412  						Namespace: "namespace",
   413  					},
   414  					Spec: v1alpha1.ApplicationSpec{
   415  						Project: "project",
   416  					},
   417  				},
   418  			},
   419  			expected: []v1alpha1.Application{
   420  				{
   421  					TypeMeta: metav1.TypeMeta{
   422  						Kind:       application.ApplicationKind,
   423  						APIVersion: "argoproj.io/v1alpha1",
   424  					},
   425  					ObjectMeta: metav1.ObjectMeta{
   426  						Name:            "app1",
   427  						Namespace:       "namespace",
   428  						ResourceVersion: "3",
   429  					},
   430  					Spec: v1alpha1.ApplicationSpec{
   431  						Project: "project",
   432  					},
   433  					Status: v1alpha1.ApplicationStatus{
   434  						Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}},
   435  					},
   436  					Operation: &v1alpha1.Operation{
   437  						Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"},
   438  					},
   439  				},
   440  			},
   441  		},
   442  		{
   443  			name: "Ensure that status and operation fields are not overridden by an update, when removing labels/annotations and adding other fields",
   444  			appSet: v1alpha1.ApplicationSet{
   445  				ObjectMeta: metav1.ObjectMeta{
   446  					Name:      "name",
   447  					Namespace: "namespace",
   448  				},
   449  				Spec: v1alpha1.ApplicationSetSpec{
   450  					Template: v1alpha1.ApplicationSetTemplate{
   451  						Spec: v1alpha1.ApplicationSpec{
   452  							Project:     "project",
   453  							Source:      &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
   454  							Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"},
   455  						},
   456  					},
   457  				},
   458  			},
   459  			existingApps: []v1alpha1.Application{
   460  				{
   461  					TypeMeta: metav1.TypeMeta{
   462  						Kind:       application.ApplicationKind,
   463  						APIVersion: "argoproj.io/v1alpha1",
   464  					},
   465  					ObjectMeta: metav1.ObjectMeta{
   466  						Name:            "app1",
   467  						Namespace:       "namespace",
   468  						ResourceVersion: "2",
   469  					},
   470  					Spec: v1alpha1.ApplicationSpec{
   471  						Project: "project",
   472  					},
   473  					Status: v1alpha1.ApplicationStatus{
   474  						Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}},
   475  					},
   476  					Operation: &v1alpha1.Operation{
   477  						Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"},
   478  					},
   479  				},
   480  			},
   481  			desiredApps: []v1alpha1.Application{
   482  				{
   483  					ObjectMeta: metav1.ObjectMeta{
   484  						Name:        "app1",
   485  						Namespace:   "namespace",
   486  						Labels:      map[string]string{"label-key": "label-value"},
   487  						Annotations: map[string]string{"annot-key": "annot-value"},
   488  					},
   489  					Spec: v1alpha1.ApplicationSpec{
   490  						Project:     "project",
   491  						Source:      &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
   492  						Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"},
   493  					},
   494  				},
   495  			},
   496  			expected: []v1alpha1.Application{
   497  				{
   498  					TypeMeta: metav1.TypeMeta{
   499  						Kind:       application.ApplicationKind,
   500  						APIVersion: "argoproj.io/v1alpha1",
   501  					},
   502  					ObjectMeta: metav1.ObjectMeta{
   503  						Name:            "app1",
   504  						Namespace:       "namespace",
   505  						Labels:          map[string]string{"label-key": "label-value"},
   506  						Annotations:     map[string]string{"annot-key": "annot-value"},
   507  						ResourceVersion: "3",
   508  					},
   509  					Spec: v1alpha1.ApplicationSpec{
   510  						Project:     "project",
   511  						Source:      &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
   512  						Destination: v1alpha1.ApplicationDestination{Server: "server", Namespace: "namespace"},
   513  					},
   514  					Status: v1alpha1.ApplicationStatus{
   515  						Resources: []v1alpha1.ResourceStatus{{Name: "sample-name"}},
   516  					},
   517  					Operation: &v1alpha1.Operation{
   518  						Sync: &v1alpha1.SyncOperation{Revision: "sample-revision"},
   519  					},
   520  				},
   521  			},
   522  		},
   523  		{
   524  			name: "Ensure that argocd notifications state and refresh annotation is preserved from an existing app",
   525  			appSet: v1alpha1.ApplicationSet{
   526  				ObjectMeta: metav1.ObjectMeta{
   527  					Name:      "name",
   528  					Namespace: "namespace",
   529  				},
   530  				Spec: v1alpha1.ApplicationSetSpec{
   531  					Template: v1alpha1.ApplicationSetTemplate{
   532  						Spec: v1alpha1.ApplicationSpec{
   533  							Project: "project",
   534  						},
   535  					},
   536  				},
   537  			},
   538  			existingApps: []v1alpha1.Application{
   539  				{
   540  					TypeMeta: metav1.TypeMeta{
   541  						Kind:       application.ApplicationKind,
   542  						APIVersion: "argoproj.io/v1alpha1",
   543  					},
   544  					ObjectMeta: metav1.ObjectMeta{
   545  						Name:            "app1",
   546  						Namespace:       "namespace",
   547  						ResourceVersion: "2",
   548  						Labels:          map[string]string{"label-key": "label-value"},
   549  						Annotations: map[string]string{
   550  							"annot-key":                   "annot-value",
   551  							NotifiedAnnotationKey:         `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`,
   552  							v1alpha1.AnnotationKeyRefresh: string(v1alpha1.RefreshTypeNormal),
   553  						},
   554  					},
   555  					Spec: v1alpha1.ApplicationSpec{
   556  						Project: "project",
   557  					},
   558  				},
   559  			},
   560  			desiredApps: []v1alpha1.Application{
   561  				{
   562  					ObjectMeta: metav1.ObjectMeta{
   563  						Name:      "app1",
   564  						Namespace: "namespace",
   565  					},
   566  					Spec: v1alpha1.ApplicationSpec{
   567  						Project: "project",
   568  					},
   569  				},
   570  			},
   571  			expected: []v1alpha1.Application{
   572  				{
   573  					TypeMeta: metav1.TypeMeta{
   574  						Kind:       application.ApplicationKind,
   575  						APIVersion: "argoproj.io/v1alpha1",
   576  					},
   577  					ObjectMeta: metav1.ObjectMeta{
   578  						Name:            "app1",
   579  						Namespace:       "namespace",
   580  						ResourceVersion: "3",
   581  						Annotations: map[string]string{
   582  							NotifiedAnnotationKey:         `{"b620d4600c771a6f4cxxxxxxx:on-deployed:[0].y7b5sbwa2Q329JYHxxxxxx-fBs:slack:slack-test":1617144614}`,
   583  							v1alpha1.AnnotationKeyRefresh: string(v1alpha1.RefreshTypeNormal),
   584  						},
   585  					},
   586  					Spec: v1alpha1.ApplicationSpec{
   587  						Project: "project",
   588  					},
   589  				},
   590  			},
   591  		},
   592  		{
   593  			name: "Ensure that configured preserved annotations are preserved from an existing app",
   594  			appSet: v1alpha1.ApplicationSet{
   595  				ObjectMeta: metav1.ObjectMeta{
   596  					Name:      "name",
   597  					Namespace: "namespace",
   598  				},
   599  				Spec: v1alpha1.ApplicationSetSpec{
   600  					Template: v1alpha1.ApplicationSetTemplate{
   601  						Spec: v1alpha1.ApplicationSpec{
   602  							Project: "project",
   603  						},
   604  					},
   605  					PreservedFields: &v1alpha1.ApplicationPreservedFields{
   606  						Annotations: []string{"preserved-annot-key"},
   607  					},
   608  				},
   609  			},
   610  			existingApps: []v1alpha1.Application{
   611  				{
   612  					TypeMeta: metav1.TypeMeta{
   613  						Kind:       "Application",
   614  						APIVersion: "argoproj.io/v1alpha1",
   615  					},
   616  					ObjectMeta: metav1.ObjectMeta{
   617  						Name:            "app1",
   618  						Namespace:       "namespace",
   619  						ResourceVersion: "2",
   620  						Annotations: map[string]string{
   621  							"annot-key":           "annot-value",
   622  							"preserved-annot-key": "preserved-annot-value",
   623  						},
   624  					},
   625  					Spec: v1alpha1.ApplicationSpec{
   626  						Project: "project",
   627  					},
   628  				},
   629  			},
   630  			desiredApps: []v1alpha1.Application{
   631  				{
   632  					ObjectMeta: metav1.ObjectMeta{
   633  						Name:      "app1",
   634  						Namespace: "namespace",
   635  					},
   636  					Spec: v1alpha1.ApplicationSpec{
   637  						Project: "project",
   638  					},
   639  				},
   640  			},
   641  			expected: []v1alpha1.Application{
   642  				{
   643  					TypeMeta: metav1.TypeMeta{
   644  						Kind:       "Application",
   645  						APIVersion: "argoproj.io/v1alpha1",
   646  					},
   647  					ObjectMeta: metav1.ObjectMeta{
   648  						Name:            "app1",
   649  						Namespace:       "namespace",
   650  						ResourceVersion: "3",
   651  						Annotations: map[string]string{
   652  							"preserved-annot-key": "preserved-annot-value",
   653  						},
   654  					},
   655  					Spec: v1alpha1.ApplicationSpec{
   656  						Project: "project",
   657  					},
   658  				},
   659  			},
   660  		},
   661  		{
   662  			name: "Ensure that the app spec is normalized before applying",
   663  			appSet: v1alpha1.ApplicationSet{
   664  				ObjectMeta: metav1.ObjectMeta{
   665  					Name:      "name",
   666  					Namespace: "namespace",
   667  				},
   668  				Spec: v1alpha1.ApplicationSetSpec{
   669  					Template: v1alpha1.ApplicationSetTemplate{
   670  						Spec: v1alpha1.ApplicationSpec{
   671  							Project: "project",
   672  							Source: &v1alpha1.ApplicationSource{
   673  								Directory: &v1alpha1.ApplicationSourceDirectory{
   674  									Jsonnet: v1alpha1.ApplicationSourceJsonnet{},
   675  								},
   676  							},
   677  						},
   678  					},
   679  				},
   680  			},
   681  			desiredApps: []v1alpha1.Application{
   682  				{
   683  					ObjectMeta: metav1.ObjectMeta{
   684  						Name:      "app1",
   685  						Namespace: "namespace",
   686  					},
   687  					Spec: v1alpha1.ApplicationSpec{
   688  						Project: "project",
   689  						Source: &v1alpha1.ApplicationSource{
   690  							Directory: &v1alpha1.ApplicationSourceDirectory{
   691  								Jsonnet: v1alpha1.ApplicationSourceJsonnet{},
   692  							},
   693  						},
   694  					},
   695  				},
   696  			},
   697  			expected: []v1alpha1.Application{
   698  				{
   699  					TypeMeta: metav1.TypeMeta{
   700  						Kind:       "Application",
   701  						APIVersion: "argoproj.io/v1alpha1",
   702  					},
   703  					ObjectMeta: metav1.ObjectMeta{
   704  						Name:            "app1",
   705  						Namespace:       "namespace",
   706  						ResourceVersion: "1",
   707  					},
   708  					Spec: v1alpha1.ApplicationSpec{
   709  						Project: "project",
   710  						Source:  &v1alpha1.ApplicationSource{
   711  							// Directory and jsonnet block are removed
   712  						},
   713  					},
   714  				},
   715  			},
   716  		},
   717  		{
   718  			// For this use case: https://github.com/argoproj/argo-cd/issues/9101#issuecomment-1191138278
   719  			name: "Ensure that ignored targetRevision difference doesn't cause an update, even if another field changes",
   720  			appSet: v1alpha1.ApplicationSet{
   721  				ObjectMeta: metav1.ObjectMeta{
   722  					Name:      "name",
   723  					Namespace: "namespace",
   724  				},
   725  				Spec: v1alpha1.ApplicationSetSpec{
   726  					IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
   727  						{JQPathExpressions: []string{".spec.source.targetRevision"}},
   728  					},
   729  					Template: v1alpha1.ApplicationSetTemplate{
   730  						Spec: v1alpha1.ApplicationSpec{
   731  							Project: "project",
   732  							Source: &v1alpha1.ApplicationSource{
   733  								RepoURL:        "https://git.example.com/test-org/test-repo.git",
   734  								TargetRevision: "foo",
   735  							},
   736  						},
   737  					},
   738  				},
   739  			},
   740  			existingApps: []v1alpha1.Application{
   741  				{
   742  					TypeMeta: metav1.TypeMeta{
   743  						Kind:       "Application",
   744  						APIVersion: "argoproj.io/v1alpha1",
   745  					},
   746  					ObjectMeta: metav1.ObjectMeta{
   747  						Name:            "app1",
   748  						Namespace:       "namespace",
   749  						ResourceVersion: "2",
   750  					},
   751  					Spec: v1alpha1.ApplicationSpec{
   752  						Project: "project",
   753  						Source: &v1alpha1.ApplicationSource{
   754  							RepoURL:        "https://git.example.com/test-org/test-repo.git",
   755  							TargetRevision: "bar",
   756  						},
   757  					},
   758  				},
   759  			},
   760  			desiredApps: []v1alpha1.Application{
   761  				{
   762  					ObjectMeta: metav1.ObjectMeta{
   763  						Name:      "app1",
   764  						Namespace: "namespace",
   765  					},
   766  					Spec: v1alpha1.ApplicationSpec{
   767  						Project: "project",
   768  						Source: &v1alpha1.ApplicationSource{
   769  							RepoURL: "https://git.example.com/test-org/test-repo.git",
   770  							// The targetRevision is ignored, so this should not be updated.
   771  							TargetRevision: "foo",
   772  							// This should be updated.
   773  							Helm: &v1alpha1.ApplicationSourceHelm{
   774  								Parameters: []v1alpha1.HelmParameter{
   775  									{Name: "hi", Value: "there"},
   776  								},
   777  							},
   778  						},
   779  					},
   780  				},
   781  			},
   782  			expected: []v1alpha1.Application{
   783  				{
   784  					TypeMeta: metav1.TypeMeta{
   785  						Kind:       "Application",
   786  						APIVersion: "argoproj.io/v1alpha1",
   787  					},
   788  					ObjectMeta: metav1.ObjectMeta{
   789  						Name:            "app1",
   790  						Namespace:       "namespace",
   791  						ResourceVersion: "3",
   792  					},
   793  					Spec: v1alpha1.ApplicationSpec{
   794  						Project: "project",
   795  						Source: &v1alpha1.ApplicationSource{
   796  							RepoURL: "https://git.example.com/test-org/test-repo.git",
   797  							// This is the existing value from the cluster, which should not be updated because the field is ignored.
   798  							TargetRevision: "bar",
   799  							// This was missing on the cluster, so it should be added.
   800  							Helm: &v1alpha1.ApplicationSourceHelm{
   801  								Parameters: []v1alpha1.HelmParameter{
   802  									{Name: "hi", Value: "there"},
   803  								},
   804  							},
   805  						},
   806  					},
   807  				},
   808  			},
   809  		},
   810  		{
   811  			// For this use case: https://github.com/argoproj/argo-cd/pull/14743#issuecomment-1761954799
   812  			name: "ignore parameters added to a multi-source app in the cluster",
   813  			appSet: v1alpha1.ApplicationSet{
   814  				ObjectMeta: metav1.ObjectMeta{
   815  					Name:      "name",
   816  					Namespace: "namespace",
   817  				},
   818  				Spec: v1alpha1.ApplicationSetSpec{
   819  					IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
   820  						{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
   821  					},
   822  					Template: v1alpha1.ApplicationSetTemplate{
   823  						Spec: v1alpha1.ApplicationSpec{
   824  							Project: "project",
   825  							Sources: []v1alpha1.ApplicationSource{
   826  								{
   827  									RepoURL: "https://git.example.com/test-org/test-repo.git",
   828  									Helm: &v1alpha1.ApplicationSourceHelm{
   829  										Values: "foo: bar",
   830  									},
   831  								},
   832  							},
   833  						},
   834  					},
   835  				},
   836  			},
   837  			existingApps: []v1alpha1.Application{
   838  				{
   839  					TypeMeta: metav1.TypeMeta{
   840  						Kind:       "Application",
   841  						APIVersion: "argoproj.io/v1alpha1",
   842  					},
   843  					ObjectMeta: metav1.ObjectMeta{
   844  						Name:            "app1",
   845  						Namespace:       "namespace",
   846  						ResourceVersion: "2",
   847  					},
   848  					Spec: v1alpha1.ApplicationSpec{
   849  						Project: "project",
   850  						Sources: []v1alpha1.ApplicationSource{
   851  							{
   852  								RepoURL: "https://git.example.com/test-org/test-repo.git",
   853  								Helm: &v1alpha1.ApplicationSourceHelm{
   854  									Values: "foo: bar",
   855  									Parameters: []v1alpha1.HelmParameter{
   856  										{Name: "hi", Value: "there"},
   857  									},
   858  								},
   859  							},
   860  						},
   861  					},
   862  				},
   863  			},
   864  			desiredApps: []v1alpha1.Application{
   865  				{
   866  					ObjectMeta: metav1.ObjectMeta{
   867  						Name:      "app1",
   868  						Namespace: "namespace",
   869  					},
   870  					Spec: v1alpha1.ApplicationSpec{
   871  						Project: "project",
   872  						Sources: []v1alpha1.ApplicationSource{
   873  							{
   874  								RepoURL: "https://git.example.com/test-org/test-repo.git",
   875  								Helm: &v1alpha1.ApplicationSourceHelm{
   876  									Values: "foo: bar",
   877  								},
   878  							},
   879  						},
   880  					},
   881  				},
   882  			},
   883  			expected: []v1alpha1.Application{
   884  				{
   885  					TypeMeta: metav1.TypeMeta{
   886  						Kind:       "Application",
   887  						APIVersion: "argoproj.io/v1alpha1",
   888  					},
   889  					ObjectMeta: metav1.ObjectMeta{
   890  						Name:      "app1",
   891  						Namespace: "namespace",
   892  						// This should not be updated, because reconciliation shouldn't modify the App.
   893  						ResourceVersion: "2",
   894  					},
   895  					Spec: v1alpha1.ApplicationSpec{
   896  						Project: "project",
   897  						Sources: []v1alpha1.ApplicationSource{
   898  							{
   899  								RepoURL: "https://git.example.com/test-org/test-repo.git",
   900  								Helm: &v1alpha1.ApplicationSourceHelm{
   901  									Values: "foo: bar",
   902  									Parameters: []v1alpha1.HelmParameter{
   903  										// This existed only in the cluster, but it shouldn't be removed, because the field is ignored.
   904  										{Name: "hi", Value: "there"},
   905  									},
   906  								},
   907  							},
   908  						},
   909  					},
   910  				},
   911  			},
   912  		},
   913  		{
   914  			name: "Demonstrate limitation of MergePatch", // Maybe we can fix this in Argo CD 3.0: https://github.com/argoproj/argo-cd/issues/15975
   915  			appSet: v1alpha1.ApplicationSet{
   916  				ObjectMeta: metav1.ObjectMeta{
   917  					Name:      "name",
   918  					Namespace: "namespace",
   919  				},
   920  				Spec: v1alpha1.ApplicationSetSpec{
   921  					IgnoreApplicationDifferences: v1alpha1.ApplicationSetIgnoreDifferences{
   922  						{JQPathExpressions: []string{`.spec.sources[] | select(.repoURL | contains("test-repo")).helm.parameters`}},
   923  					},
   924  					Template: v1alpha1.ApplicationSetTemplate{
   925  						Spec: v1alpha1.ApplicationSpec{
   926  							Project: "project",
   927  							Sources: []v1alpha1.ApplicationSource{
   928  								{
   929  									RepoURL: "https://git.example.com/test-org/test-repo.git",
   930  									Helm: &v1alpha1.ApplicationSourceHelm{
   931  										Values: "new: values",
   932  									},
   933  								},
   934  							},
   935  						},
   936  					},
   937  				},
   938  			},
   939  			existingApps: []v1alpha1.Application{
   940  				{
   941  					TypeMeta: metav1.TypeMeta{
   942  						Kind:       "Application",
   943  						APIVersion: "argoproj.io/v1alpha1",
   944  					},
   945  					ObjectMeta: metav1.ObjectMeta{
   946  						Name:            "app1",
   947  						Namespace:       "namespace",
   948  						ResourceVersion: "2",
   949  					},
   950  					Spec: v1alpha1.ApplicationSpec{
   951  						Project: "project",
   952  						Sources: []v1alpha1.ApplicationSource{
   953  							{
   954  								RepoURL: "https://git.example.com/test-org/test-repo.git",
   955  								Helm: &v1alpha1.ApplicationSourceHelm{
   956  									Values: "foo: bar",
   957  									Parameters: []v1alpha1.HelmParameter{
   958  										{Name: "hi", Value: "there"},
   959  									},
   960  								},
   961  							},
   962  						},
   963  					},
   964  				},
   965  			},
   966  			desiredApps: []v1alpha1.Application{
   967  				{
   968  					ObjectMeta: metav1.ObjectMeta{
   969  						Name:      "app1",
   970  						Namespace: "namespace",
   971  					},
   972  					Spec: v1alpha1.ApplicationSpec{
   973  						Project: "project",
   974  						Sources: []v1alpha1.ApplicationSource{
   975  							{
   976  								RepoURL: "https://git.example.com/test-org/test-repo.git",
   977  								Helm: &v1alpha1.ApplicationSourceHelm{
   978  									Values: "new: values",
   979  								},
   980  							},
   981  						},
   982  					},
   983  				},
   984  			},
   985  			expected: []v1alpha1.Application{
   986  				{
   987  					TypeMeta: metav1.TypeMeta{
   988  						Kind:       "Application",
   989  						APIVersion: "argoproj.io/v1alpha1",
   990  					},
   991  					ObjectMeta: metav1.ObjectMeta{
   992  						Name:            "app1",
   993  						Namespace:       "namespace",
   994  						ResourceVersion: "3",
   995  					},
   996  					Spec: v1alpha1.ApplicationSpec{
   997  						Project: "project",
   998  						Sources: []v1alpha1.ApplicationSource{
   999  							{
  1000  								RepoURL: "https://git.example.com/test-org/test-repo.git",
  1001  								Helm: &v1alpha1.ApplicationSourceHelm{
  1002  									Values: "new: values",
  1003  									// The Parameters field got blown away, because the values field changed. MergePatch
  1004  									// doesn't merge list items, it replaces the whole list if an item changes.
  1005  									// If we eventually add a `name` field to Sources, we can use StrategicMergePatch.
  1006  								},
  1007  							},
  1008  						},
  1009  					},
  1010  				},
  1011  			},
  1012  		},
  1013  		{
  1014  			name: "Ensure that argocd post-delete finalizers are preserved from an existing app",
  1015  			appSet: v1alpha1.ApplicationSet{
  1016  				ObjectMeta: metav1.ObjectMeta{
  1017  					Name:      "name",
  1018  					Namespace: "namespace",
  1019  				},
  1020  				Spec: v1alpha1.ApplicationSetSpec{
  1021  					Template: v1alpha1.ApplicationSetTemplate{
  1022  						Spec: v1alpha1.ApplicationSpec{
  1023  							Project: "project",
  1024  						},
  1025  					},
  1026  				},
  1027  			},
  1028  			existingApps: []v1alpha1.Application{
  1029  				{
  1030  					TypeMeta: metav1.TypeMeta{
  1031  						Kind:       application.ApplicationKind,
  1032  						APIVersion: "argoproj.io/v1alpha1",
  1033  					},
  1034  					ObjectMeta: metav1.ObjectMeta{
  1035  						Name:            "app1",
  1036  						Namespace:       "namespace",
  1037  						ResourceVersion: "2",
  1038  						Finalizers: []string{
  1039  							v1alpha1.PostDeleteFinalizerName,
  1040  							v1alpha1.PostDeleteFinalizerName + "/mystage",
  1041  						},
  1042  					},
  1043  					Spec: v1alpha1.ApplicationSpec{
  1044  						Project: "project",
  1045  					},
  1046  				},
  1047  			},
  1048  			desiredApps: []v1alpha1.Application{
  1049  				{
  1050  					ObjectMeta: metav1.ObjectMeta{
  1051  						Name:      "app1",
  1052  						Namespace: "namespace",
  1053  					},
  1054  					Spec: v1alpha1.ApplicationSpec{
  1055  						Project: "project",
  1056  					},
  1057  				},
  1058  			},
  1059  			expected: []v1alpha1.Application{
  1060  				{
  1061  					TypeMeta: metav1.TypeMeta{
  1062  						Kind:       application.ApplicationKind,
  1063  						APIVersion: "argoproj.io/v1alpha1",
  1064  					},
  1065  					ObjectMeta: metav1.ObjectMeta{
  1066  						Name:            "app1",
  1067  						Namespace:       "namespace",
  1068  						ResourceVersion: "2",
  1069  						Finalizers: []string{
  1070  							v1alpha1.PostDeleteFinalizerName,
  1071  							v1alpha1.PostDeleteFinalizerName + "/mystage",
  1072  						},
  1073  					},
  1074  					Spec: v1alpha1.ApplicationSpec{
  1075  						Project: "project",
  1076  					},
  1077  				},
  1078  			},
  1079  		},
  1080  	} {
  1081  		t.Run(c.name, func(t *testing.T) {
  1082  			initObjs := []crtclient.Object{&c.appSet}
  1083  
  1084  			for _, a := range c.existingApps {
  1085  				err = controllerutil.SetControllerReference(&c.appSet, &a, scheme)
  1086  				require.NoError(t, err)
  1087  				initObjs = append(initObjs, &a)
  1088  			}
  1089  
  1090  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1091  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  1092  
  1093  			r := ApplicationSetReconciler{
  1094  				Client:   client,
  1095  				Scheme:   scheme,
  1096  				Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
  1097  				Metrics:  metrics,
  1098  			}
  1099  
  1100  			err = r.createOrUpdateInCluster(t.Context(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps)
  1101  			require.NoError(t, err)
  1102  
  1103  			for _, obj := range c.expected {
  1104  				got := &v1alpha1.Application{}
  1105  				_ = client.Get(t.Context(), crtclient.ObjectKey{
  1106  					Namespace: obj.Namespace,
  1107  					Name:      obj.Name,
  1108  				}, got)
  1109  
  1110  				err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
  1111  				assert.Equal(t, obj, *got)
  1112  			}
  1113  		})
  1114  	}
  1115  }
  1116  
  1117  func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
  1118  	scheme := runtime.NewScheme()
  1119  	err := v1alpha1.AddToScheme(scheme)
  1120  	require.NoError(t, err)
  1121  
  1122  	for _, c := range []struct {
  1123  		// name is human-readable test name
  1124  		name               string
  1125  		existingFinalizers []string
  1126  		expectedFinalizers []string
  1127  	}{
  1128  		{
  1129  			name:               "no finalizers",
  1130  			existingFinalizers: []string{},
  1131  			expectedFinalizers: nil,
  1132  		},
  1133  		{
  1134  			name:               "contains only argo finalizer",
  1135  			existingFinalizers: []string{v1alpha1.ResourcesFinalizerName},
  1136  			expectedFinalizers: nil,
  1137  		},
  1138  		{
  1139  			name:               "contains only non-argo finalizer",
  1140  			existingFinalizers: []string{"non-argo-finalizer"},
  1141  			expectedFinalizers: []string{"non-argo-finalizer"},
  1142  		},
  1143  		{
  1144  			name:               "contains both argo and non-argo finalizer",
  1145  			existingFinalizers: []string{"non-argo-finalizer", v1alpha1.ResourcesFinalizerName},
  1146  			expectedFinalizers: []string{"non-argo-finalizer"},
  1147  		},
  1148  	} {
  1149  		t.Run(c.name, func(t *testing.T) {
  1150  			appSet := v1alpha1.ApplicationSet{
  1151  				ObjectMeta: metav1.ObjectMeta{
  1152  					Name:      "name",
  1153  					Namespace: "namespace",
  1154  				},
  1155  				Spec: v1alpha1.ApplicationSetSpec{
  1156  					Template: v1alpha1.ApplicationSetTemplate{
  1157  						Spec: v1alpha1.ApplicationSpec{
  1158  							Project: "project",
  1159  						},
  1160  					},
  1161  				},
  1162  			}
  1163  
  1164  			app := v1alpha1.Application{
  1165  				ObjectMeta: metav1.ObjectMeta{
  1166  					Name:       "app1",
  1167  					Finalizers: c.existingFinalizers,
  1168  				},
  1169  				Spec: v1alpha1.ApplicationSpec{
  1170  					Project: "project",
  1171  					Source:  &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
  1172  					// Destination is always invalid, for this test:
  1173  					Destination: v1alpha1.ApplicationDestination{Name: "my-cluster", Namespace: "namespace"},
  1174  				},
  1175  			}
  1176  
  1177  			initObjs := []crtclient.Object{&app, &appSet}
  1178  
  1179  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1180  			secret := &corev1.Secret{
  1181  				ObjectMeta: metav1.ObjectMeta{
  1182  					Name:      "my-secret",
  1183  					Namespace: "namespace",
  1184  					Labels: map[string]string{
  1185  						argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
  1186  					},
  1187  				},
  1188  				Data: map[string][]byte{
  1189  					// Since this test requires the cluster to be an invalid destination, we
  1190  					// always return a cluster named 'my-cluster2' (different from app 'my-cluster', above)
  1191  					"name":   []byte("mycluster2"),
  1192  					"server": []byte("https://kubernetes.default.svc"),
  1193  					"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
  1194  				},
  1195  			}
  1196  
  1197  			objects := append([]runtime.Object{}, secret)
  1198  			kubeclientset := kubefake.NewSimpleClientset(objects...)
  1199  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  1200  
  1201  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  1202  
  1203  			r := ApplicationSetReconciler{
  1204  				Client:        client,
  1205  				Scheme:        scheme,
  1206  				Recorder:      record.NewFakeRecorder(10),
  1207  				KubeClientset: kubeclientset,
  1208  				Metrics:       metrics,
  1209  				ArgoDB:        argodb,
  1210  			}
  1211  			clusterList, err := utils.ListClusters(t.Context(), kubeclientset, "namespace")
  1212  			require.NoError(t, err)
  1213  
  1214  			appLog := log.WithFields(applog.GetAppLogFields(&app)).WithField("appSet", "")
  1215  
  1216  			appInputParam := app.DeepCopy()
  1217  
  1218  			err = r.removeFinalizerOnInvalidDestination(t.Context(), appSet, appInputParam, clusterList, appLog)
  1219  			require.NoError(t, err)
  1220  
  1221  			retrievedApp := v1alpha1.Application{}
  1222  			err = client.Get(t.Context(), crtclient.ObjectKeyFromObject(&app), &retrievedApp)
  1223  			require.NoError(t, err)
  1224  
  1225  			// App on the cluster should have the expected finalizers
  1226  			assert.ElementsMatch(t, c.expectedFinalizers, retrievedApp.Finalizers)
  1227  
  1228  			// App object passed in as a parameter should have the expected finaliers
  1229  			assert.ElementsMatch(t, c.expectedFinalizers, appInputParam.Finalizers)
  1230  
  1231  			bytes, _ := json.MarshalIndent(retrievedApp, "", "  ")
  1232  			t.Log("Contents of app after call:", string(bytes))
  1233  		})
  1234  	}
  1235  }
  1236  
  1237  func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
  1238  	scheme := runtime.NewScheme()
  1239  	err := v1alpha1.AddToScheme(scheme)
  1240  	require.NoError(t, err)
  1241  
  1242  	for _, c := range []struct {
  1243  		// name is human-readable test name
  1244  		name                   string
  1245  		destinationField       v1alpha1.ApplicationDestination
  1246  		expectFinalizerRemoved bool
  1247  	}{
  1248  		{
  1249  			name: "invalid cluster: empty destination",
  1250  			destinationField: v1alpha1.ApplicationDestination{
  1251  				Namespace: "namespace",
  1252  			},
  1253  			expectFinalizerRemoved: true,
  1254  		},
  1255  		{
  1256  			name: "invalid cluster: invalid server url",
  1257  			destinationField: v1alpha1.ApplicationDestination{
  1258  				Namespace: "namespace",
  1259  				Server:    "https://1.2.3.4",
  1260  			},
  1261  			expectFinalizerRemoved: true,
  1262  		},
  1263  		{
  1264  			name: "invalid cluster: invalid cluster name",
  1265  			destinationField: v1alpha1.ApplicationDestination{
  1266  				Namespace: "namespace",
  1267  				Name:      "invalid-cluster",
  1268  			},
  1269  			expectFinalizerRemoved: true,
  1270  		},
  1271  		{
  1272  			name: "invalid cluster by both valid",
  1273  			destinationField: v1alpha1.ApplicationDestination{
  1274  				Namespace: "namespace",
  1275  				Name:      "mycluster2",
  1276  				Server:    "https://kubernetes.default.svc",
  1277  			},
  1278  			expectFinalizerRemoved: true,
  1279  		},
  1280  		{
  1281  			name: "invalid cluster by both invalid",
  1282  			destinationField: v1alpha1.ApplicationDestination{
  1283  				Namespace: "namespace",
  1284  				Name:      "mycluster3",
  1285  				Server:    "https://4.5.6.7",
  1286  			},
  1287  			expectFinalizerRemoved: true,
  1288  		},
  1289  		{
  1290  			name: "valid cluster by name",
  1291  			destinationField: v1alpha1.ApplicationDestination{
  1292  				Namespace: "namespace",
  1293  				Name:      "mycluster2",
  1294  			},
  1295  			expectFinalizerRemoved: false,
  1296  		},
  1297  		{
  1298  			name: "valid cluster by server",
  1299  			destinationField: v1alpha1.ApplicationDestination{
  1300  				Namespace: "namespace",
  1301  				Server:    "https://kubernetes.default.svc",
  1302  			},
  1303  			expectFinalizerRemoved: false,
  1304  		},
  1305  	} {
  1306  		t.Run(c.name, func(t *testing.T) {
  1307  			appSet := v1alpha1.ApplicationSet{
  1308  				ObjectMeta: metav1.ObjectMeta{
  1309  					Name:      "name",
  1310  					Namespace: "namespace",
  1311  				},
  1312  				Spec: v1alpha1.ApplicationSetSpec{
  1313  					Template: v1alpha1.ApplicationSetTemplate{
  1314  						Spec: v1alpha1.ApplicationSpec{
  1315  							Project: "project",
  1316  						},
  1317  					},
  1318  				},
  1319  			}
  1320  
  1321  			app := v1alpha1.Application{
  1322  				ObjectMeta: metav1.ObjectMeta{
  1323  					Name:       "app1",
  1324  					Finalizers: []string{v1alpha1.ResourcesFinalizerName},
  1325  				},
  1326  				Spec: v1alpha1.ApplicationSpec{
  1327  					Project:     "project",
  1328  					Source:      &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
  1329  					Destination: c.destinationField,
  1330  				},
  1331  			}
  1332  
  1333  			initObjs := []crtclient.Object{&app, &appSet}
  1334  
  1335  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1336  			secret := &corev1.Secret{
  1337  				ObjectMeta: metav1.ObjectMeta{
  1338  					Name:      "my-secret",
  1339  					Namespace: "argocd",
  1340  					Labels: map[string]string{
  1341  						argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
  1342  					},
  1343  				},
  1344  				Data: map[string][]byte{
  1345  					// Since this test requires the cluster to be an invalid destination, we
  1346  					// always return a cluster named 'my-cluster2' (different from app 'my-cluster', above)
  1347  					"name":   []byte("mycluster2"),
  1348  					"server": []byte("https://kubernetes.default.svc"),
  1349  					"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
  1350  				},
  1351  			}
  1352  
  1353  			kubeclientset := getDefaultTestClientSet(secret)
  1354  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  1355  
  1356  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  1357  
  1358  			r := ApplicationSetReconciler{
  1359  				Client:        client,
  1360  				Scheme:        scheme,
  1361  				Recorder:      record.NewFakeRecorder(10),
  1362  				KubeClientset: kubeclientset,
  1363  				Metrics:       metrics,
  1364  				ArgoDB:        argodb,
  1365  			}
  1366  
  1367  			clusterList, err := utils.ListClusters(t.Context(), kubeclientset, "argocd")
  1368  			require.NoError(t, err)
  1369  
  1370  			appLog := log.WithFields(applog.GetAppLogFields(&app)).WithField("appSet", "")
  1371  
  1372  			appInputParam := app.DeepCopy()
  1373  
  1374  			err = r.removeFinalizerOnInvalidDestination(t.Context(), appSet, appInputParam, clusterList, appLog)
  1375  			require.NoError(t, err)
  1376  
  1377  			retrievedApp := v1alpha1.Application{}
  1378  			err = client.Get(t.Context(), crtclient.ObjectKeyFromObject(&app), &retrievedApp)
  1379  			require.NoError(t, err)
  1380  
  1381  			finalizerRemoved := len(retrievedApp.Finalizers) == 0
  1382  
  1383  			assert.Equal(t, c.expectFinalizerRemoved, finalizerRemoved)
  1384  
  1385  			bytes, _ := json.MarshalIndent(retrievedApp, "", "  ")
  1386  			t.Log("Contents of app after call:", string(bytes))
  1387  		})
  1388  	}
  1389  }
  1390  
  1391  func TestRemoveOwnerReferencesOnDeleteAppSet(t *testing.T) {
  1392  	scheme := runtime.NewScheme()
  1393  	err := v1alpha1.AddToScheme(scheme)
  1394  	require.NoError(t, err)
  1395  
  1396  	for _, c := range []struct {
  1397  		// name is human-readable test name
  1398  		name string
  1399  	}{
  1400  		{
  1401  			name: "ownerReferences cleared",
  1402  		},
  1403  	} {
  1404  		t.Run(c.name, func(t *testing.T) {
  1405  			appSet := v1alpha1.ApplicationSet{
  1406  				ObjectMeta: metav1.ObjectMeta{
  1407  					Name:       "name",
  1408  					Namespace:  "namespace",
  1409  					Finalizers: []string{v1alpha1.ResourcesFinalizerName},
  1410  				},
  1411  				Spec: v1alpha1.ApplicationSetSpec{
  1412  					Template: v1alpha1.ApplicationSetTemplate{
  1413  						Spec: v1alpha1.ApplicationSpec{
  1414  							Project: "project",
  1415  						},
  1416  					},
  1417  				},
  1418  			}
  1419  
  1420  			app := v1alpha1.Application{
  1421  				ObjectMeta: metav1.ObjectMeta{
  1422  					Name:      "app1",
  1423  					Namespace: "namespace",
  1424  				},
  1425  				Spec: v1alpha1.ApplicationSpec{
  1426  					Project: "project",
  1427  					Source:  &v1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
  1428  					Destination: v1alpha1.ApplicationDestination{
  1429  						Namespace: "namespace",
  1430  						Server:    "https://kubernetes.default.svc",
  1431  					},
  1432  				},
  1433  			}
  1434  
  1435  			err := controllerutil.SetControllerReference(&appSet, &app, scheme)
  1436  			require.NoError(t, err)
  1437  
  1438  			initObjs := []crtclient.Object{&app, &appSet}
  1439  
  1440  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1441  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  1442  
  1443  			r := ApplicationSetReconciler{
  1444  				Client:        client,
  1445  				Scheme:        scheme,
  1446  				Recorder:      record.NewFakeRecorder(10),
  1447  				KubeClientset: nil,
  1448  				Metrics:       metrics,
  1449  			}
  1450  
  1451  			err = r.removeOwnerReferencesOnDeleteAppSet(t.Context(), appSet)
  1452  			require.NoError(t, err)
  1453  
  1454  			retrievedApp := v1alpha1.Application{}
  1455  			err = client.Get(t.Context(), crtclient.ObjectKeyFromObject(&app), &retrievedApp)
  1456  			require.NoError(t, err)
  1457  
  1458  			ownerReferencesRemoved := len(retrievedApp.OwnerReferences) == 0
  1459  			assert.True(t, ownerReferencesRemoved)
  1460  		})
  1461  	}
  1462  }
  1463  
  1464  func TestCreateApplications(t *testing.T) {
  1465  	scheme := runtime.NewScheme()
  1466  	err := v1alpha1.AddToScheme(scheme)
  1467  	require.NoError(t, err)
  1468  
  1469  	testCases := []struct {
  1470  		name       string
  1471  		appSet     v1alpha1.ApplicationSet
  1472  		existsApps []v1alpha1.Application
  1473  		apps       []v1alpha1.Application
  1474  		expected   []v1alpha1.Application
  1475  	}{
  1476  		{
  1477  			name: "no existing apps",
  1478  			appSet: v1alpha1.ApplicationSet{
  1479  				ObjectMeta: metav1.ObjectMeta{
  1480  					Name:      "name",
  1481  					Namespace: "namespace",
  1482  				},
  1483  			},
  1484  			existsApps: nil,
  1485  			apps: []v1alpha1.Application{
  1486  				{
  1487  					ObjectMeta: metav1.ObjectMeta{
  1488  						Name:      "app1",
  1489  						Namespace: "namespace",
  1490  					},
  1491  				},
  1492  			},
  1493  			expected: []v1alpha1.Application{
  1494  				{
  1495  					TypeMeta: metav1.TypeMeta{
  1496  						Kind:       application.ApplicationKind,
  1497  						APIVersion: "argoproj.io/v1alpha1",
  1498  					},
  1499  					ObjectMeta: metav1.ObjectMeta{
  1500  						Name:            "app1",
  1501  						Namespace:       "namespace",
  1502  						ResourceVersion: "1",
  1503  					},
  1504  					Spec: v1alpha1.ApplicationSpec{
  1505  						Project: "default",
  1506  					},
  1507  				},
  1508  			},
  1509  		},
  1510  		{
  1511  			name: "existing apps",
  1512  			appSet: v1alpha1.ApplicationSet{
  1513  				ObjectMeta: metav1.ObjectMeta{
  1514  					Name:      "name",
  1515  					Namespace: "namespace",
  1516  				},
  1517  				Spec: v1alpha1.ApplicationSetSpec{
  1518  					Template: v1alpha1.ApplicationSetTemplate{
  1519  						Spec: v1alpha1.ApplicationSpec{
  1520  							Project: "project",
  1521  						},
  1522  					},
  1523  				},
  1524  			},
  1525  			existsApps: []v1alpha1.Application{
  1526  				{
  1527  					TypeMeta: metav1.TypeMeta{
  1528  						Kind:       application.ApplicationKind,
  1529  						APIVersion: "argoproj.io/v1alpha1",
  1530  					},
  1531  					ObjectMeta: metav1.ObjectMeta{
  1532  						Name:            "app1",
  1533  						Namespace:       "namespace",
  1534  						ResourceVersion: "2",
  1535  					},
  1536  					Spec: v1alpha1.ApplicationSpec{
  1537  						Project: "test",
  1538  					},
  1539  				},
  1540  			},
  1541  			apps: []v1alpha1.Application{
  1542  				{
  1543  					ObjectMeta: metav1.ObjectMeta{
  1544  						Name:      "app1",
  1545  						Namespace: "namespace",
  1546  					},
  1547  					Spec: v1alpha1.ApplicationSpec{
  1548  						Project: "project",
  1549  					},
  1550  				},
  1551  			},
  1552  			expected: []v1alpha1.Application{
  1553  				{
  1554  					TypeMeta: metav1.TypeMeta{
  1555  						Kind:       application.ApplicationKind,
  1556  						APIVersion: "argoproj.io/v1alpha1",
  1557  					},
  1558  					ObjectMeta: metav1.ObjectMeta{
  1559  						Name:            "app1",
  1560  						Namespace:       "namespace",
  1561  						ResourceVersion: "2",
  1562  					},
  1563  					Spec: v1alpha1.ApplicationSpec{
  1564  						Project: "test",
  1565  					},
  1566  				},
  1567  			},
  1568  		},
  1569  		{
  1570  			name: "existing apps with different project",
  1571  			appSet: v1alpha1.ApplicationSet{
  1572  				ObjectMeta: metav1.ObjectMeta{
  1573  					Name:      "name",
  1574  					Namespace: "namespace",
  1575  				},
  1576  				Spec: v1alpha1.ApplicationSetSpec{
  1577  					Template: v1alpha1.ApplicationSetTemplate{
  1578  						Spec: v1alpha1.ApplicationSpec{
  1579  							Project: "project",
  1580  						},
  1581  					},
  1582  				},
  1583  			},
  1584  			existsApps: []v1alpha1.Application{
  1585  				{
  1586  					TypeMeta: metav1.TypeMeta{
  1587  						Kind:       application.ApplicationKind,
  1588  						APIVersion: "argoproj.io/v1alpha1",
  1589  					},
  1590  					ObjectMeta: metav1.ObjectMeta{
  1591  						Name:            "app1",
  1592  						Namespace:       "namespace",
  1593  						ResourceVersion: "2",
  1594  					},
  1595  					Spec: v1alpha1.ApplicationSpec{
  1596  						Project: "test",
  1597  					},
  1598  				},
  1599  			},
  1600  			apps: []v1alpha1.Application{
  1601  				{
  1602  					ObjectMeta: metav1.ObjectMeta{
  1603  						Name:      "app2",
  1604  						Namespace: "namespace",
  1605  					},
  1606  					Spec: v1alpha1.ApplicationSpec{
  1607  						Project: "project",
  1608  					},
  1609  				},
  1610  			},
  1611  			expected: []v1alpha1.Application{
  1612  				{
  1613  					TypeMeta: metav1.TypeMeta{
  1614  						Kind:       application.ApplicationKind,
  1615  						APIVersion: "argoproj.io/v1alpha1",
  1616  					},
  1617  					ObjectMeta: metav1.ObjectMeta{
  1618  						Name:            "app2",
  1619  						Namespace:       "namespace",
  1620  						ResourceVersion: "1",
  1621  					},
  1622  					Spec: v1alpha1.ApplicationSpec{
  1623  						Project: "project",
  1624  					},
  1625  				},
  1626  			},
  1627  		},
  1628  	}
  1629  
  1630  	for _, c := range testCases {
  1631  		t.Run(c.name, func(t *testing.T) {
  1632  			initObjs := []crtclient.Object{&c.appSet}
  1633  			for _, a := range c.existsApps {
  1634  				err = controllerutil.SetControllerReference(&c.appSet, &a, scheme)
  1635  				require.NoError(t, err)
  1636  				initObjs = append(initObjs, &a)
  1637  			}
  1638  
  1639  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1640  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  1641  
  1642  			r := ApplicationSetReconciler{
  1643  				Client:   client,
  1644  				Scheme:   scheme,
  1645  				Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
  1646  				Metrics:  metrics,
  1647  			}
  1648  
  1649  			err = r.createInCluster(t.Context(), log.NewEntry(log.StandardLogger()), c.appSet, c.apps)
  1650  			require.NoError(t, err)
  1651  
  1652  			for _, obj := range c.expected {
  1653  				got := &v1alpha1.Application{}
  1654  				_ = client.Get(t.Context(), crtclient.ObjectKey{
  1655  					Namespace: obj.Namespace,
  1656  					Name:      obj.Name,
  1657  				}, got)
  1658  
  1659  				err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
  1660  				require.NoError(t, err)
  1661  
  1662  				assert.Equal(t, obj, *got)
  1663  			}
  1664  		})
  1665  	}
  1666  }
  1667  
  1668  func TestDeleteInCluster(t *testing.T) {
  1669  	scheme := runtime.NewScheme()
  1670  	err := v1alpha1.AddToScheme(scheme)
  1671  	require.NoError(t, err)
  1672  
  1673  	for _, c := range []struct {
  1674  		// appSet is the application set on which the delete function is called
  1675  		appSet v1alpha1.ApplicationSet
  1676  		// existingApps is the current state of Applications on the cluster
  1677  		existingApps []v1alpha1.Application
  1678  		// desireApps is the apps generated by the generator that we wish to keep alive
  1679  		desiredApps []v1alpha1.Application
  1680  		// expected is the list of applications that we expect to exist after calling delete
  1681  		expected []v1alpha1.Application
  1682  		// notExpected is the list of applications that we expect not to exist after calling delete
  1683  		notExpected []v1alpha1.Application
  1684  	}{
  1685  		{
  1686  			appSet: v1alpha1.ApplicationSet{
  1687  				ObjectMeta: metav1.ObjectMeta{
  1688  					Name:      "name",
  1689  					Namespace: "namespace",
  1690  				},
  1691  				Spec: v1alpha1.ApplicationSetSpec{
  1692  					Template: v1alpha1.ApplicationSetTemplate{
  1693  						Spec: v1alpha1.ApplicationSpec{
  1694  							Project: "project",
  1695  						},
  1696  					},
  1697  				},
  1698  			},
  1699  			existingApps: []v1alpha1.Application{
  1700  				{
  1701  					TypeMeta: metav1.TypeMeta{
  1702  						Kind:       application.ApplicationKind,
  1703  						APIVersion: "argoproj.io/v1alpha1",
  1704  					},
  1705  					ObjectMeta: metav1.ObjectMeta{
  1706  						Name:            "delete",
  1707  						Namespace:       "namespace",
  1708  						ResourceVersion: "2",
  1709  					},
  1710  					Spec: v1alpha1.ApplicationSpec{
  1711  						Project: "project",
  1712  					},
  1713  				},
  1714  				{
  1715  					TypeMeta: metav1.TypeMeta{
  1716  						Kind:       application.ApplicationKind,
  1717  						APIVersion: "argoproj.io/v1alpha1",
  1718  					},
  1719  					ObjectMeta: metav1.ObjectMeta{
  1720  						Name:            "keep",
  1721  						Namespace:       "namespace",
  1722  						ResourceVersion: "2",
  1723  					},
  1724  					Spec: v1alpha1.ApplicationSpec{
  1725  						Project: "project",
  1726  					},
  1727  				},
  1728  			},
  1729  			desiredApps: []v1alpha1.Application{
  1730  				{
  1731  					ObjectMeta: metav1.ObjectMeta{
  1732  						Name: "keep",
  1733  					},
  1734  					Spec: v1alpha1.ApplicationSpec{
  1735  						Project: "project",
  1736  					},
  1737  				},
  1738  			},
  1739  			expected: []v1alpha1.Application{
  1740  				{
  1741  					TypeMeta: metav1.TypeMeta{
  1742  						Kind:       application.ApplicationKind,
  1743  						APIVersion: "argoproj.io/v1alpha1",
  1744  					},
  1745  					ObjectMeta: metav1.ObjectMeta{
  1746  						Name:            "keep",
  1747  						Namespace:       "namespace",
  1748  						ResourceVersion: "2",
  1749  					},
  1750  					Spec: v1alpha1.ApplicationSpec{
  1751  						Project: "project",
  1752  					},
  1753  				},
  1754  			},
  1755  			notExpected: []v1alpha1.Application{
  1756  				{
  1757  					TypeMeta: metav1.TypeMeta{
  1758  						Kind:       application.ApplicationKind,
  1759  						APIVersion: "argoproj.io/v1alpha1",
  1760  					},
  1761  					ObjectMeta: metav1.ObjectMeta{
  1762  						Name:            "delete",
  1763  						Namespace:       "namespace",
  1764  						ResourceVersion: "1",
  1765  					},
  1766  					Spec: v1alpha1.ApplicationSpec{
  1767  						Project: "project",
  1768  					},
  1769  				},
  1770  			},
  1771  		},
  1772  	} {
  1773  		initObjs := []crtclient.Object{&c.appSet}
  1774  		for _, a := range c.existingApps {
  1775  			temp := a
  1776  			err = controllerutil.SetControllerReference(&c.appSet, &temp, scheme)
  1777  			require.NoError(t, err)
  1778  			initObjs = append(initObjs, &temp)
  1779  		}
  1780  
  1781  		client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  1782  		metrics := appsetmetrics.NewFakeAppsetMetrics()
  1783  
  1784  		r := ApplicationSetReconciler{
  1785  			Client:        client,
  1786  			Scheme:        scheme,
  1787  			Recorder:      record.NewFakeRecorder(len(initObjs) + len(c.expected)),
  1788  			KubeClientset: kubefake.NewSimpleClientset(),
  1789  			Metrics:       metrics,
  1790  		}
  1791  
  1792  		err = r.deleteInCluster(t.Context(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps)
  1793  		require.NoError(t, err)
  1794  
  1795  		// For each of the expected objects, verify they exist on the cluster
  1796  		for _, obj := range c.expected {
  1797  			got := &v1alpha1.Application{}
  1798  			_ = client.Get(t.Context(), crtclient.ObjectKey{
  1799  				Namespace: obj.Namespace,
  1800  				Name:      obj.Name,
  1801  			}, got)
  1802  
  1803  			err = controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)
  1804  			require.NoError(t, err)
  1805  
  1806  			assert.Equal(t, obj, *got)
  1807  		}
  1808  
  1809  		// Verify each of the unexpected objs cannot be found
  1810  		for _, obj := range c.notExpected {
  1811  			got := &v1alpha1.Application{}
  1812  			err := client.Get(t.Context(), crtclient.ObjectKey{
  1813  				Namespace: obj.Namespace,
  1814  				Name:      obj.Name,
  1815  			}, got)
  1816  
  1817  			assert.EqualError(t, err, fmt.Sprintf("applications.argoproj.io %q not found", obj.Name))
  1818  		}
  1819  	}
  1820  }
  1821  
  1822  func TestGetMinRequeueAfter(t *testing.T) {
  1823  	scheme := runtime.NewScheme()
  1824  	err := v1alpha1.AddToScheme(scheme)
  1825  	require.NoError(t, err)
  1826  
  1827  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
  1828  	metrics := appsetmetrics.NewFakeAppsetMetrics()
  1829  
  1830  	generator := v1alpha1.ApplicationSetGenerator{
  1831  		List:     &v1alpha1.ListGenerator{},
  1832  		Git:      &v1alpha1.GitGenerator{},
  1833  		Clusters: &v1alpha1.ClusterGenerator{},
  1834  	}
  1835  
  1836  	generatorMock0 := mocks.Generator{}
  1837  	generatorMock0.On("GetRequeueAfter", &generator).
  1838  		Return(generators.NoRequeueAfter)
  1839  
  1840  	generatorMock1 := mocks.Generator{}
  1841  	generatorMock1.On("GetRequeueAfter", &generator).
  1842  		Return(time.Duration(1) * time.Second)
  1843  
  1844  	generatorMock10 := mocks.Generator{}
  1845  	generatorMock10.On("GetRequeueAfter", &generator).
  1846  		Return(time.Duration(10) * time.Second)
  1847  
  1848  	r := ApplicationSetReconciler{
  1849  		Client:   client,
  1850  		Scheme:   scheme,
  1851  		Recorder: record.NewFakeRecorder(0),
  1852  		Metrics:  metrics,
  1853  		Generators: map[string]generators.Generator{
  1854  			"List":     &generatorMock10,
  1855  			"Git":      &generatorMock1,
  1856  			"Clusters": &generatorMock1,
  1857  		},
  1858  	}
  1859  
  1860  	got := r.getMinRequeueAfter(&v1alpha1.ApplicationSet{
  1861  		Spec: v1alpha1.ApplicationSetSpec{
  1862  			Generators: []v1alpha1.ApplicationSetGenerator{generator},
  1863  		},
  1864  	})
  1865  
  1866  	assert.Equal(t, time.Duration(1)*time.Second, got)
  1867  }
  1868  
  1869  func TestRequeueGeneratorFails(t *testing.T) {
  1870  	scheme := runtime.NewScheme()
  1871  	err := v1alpha1.AddToScheme(scheme)
  1872  	require.NoError(t, err)
  1873  	err = v1alpha1.AddToScheme(scheme)
  1874  	require.NoError(t, err)
  1875  
  1876  	appSet := v1alpha1.ApplicationSet{
  1877  		ObjectMeta: metav1.ObjectMeta{
  1878  			Name:      "name",
  1879  			Namespace: "argocd",
  1880  		},
  1881  		Spec: v1alpha1.ApplicationSetSpec{
  1882  			Generators: []v1alpha1.ApplicationSetGenerator{{
  1883  				PullRequest: &v1alpha1.PullRequestGenerator{},
  1884  			}},
  1885  		},
  1886  	}
  1887  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build()
  1888  
  1889  	generator := v1alpha1.ApplicationSetGenerator{
  1890  		PullRequest: &v1alpha1.PullRequestGenerator{},
  1891  	}
  1892  
  1893  	generatorMock := mocks.Generator{}
  1894  	generatorMock.On("GetTemplate", &generator).
  1895  		Return(&v1alpha1.ApplicationSetTemplate{})
  1896  	generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
  1897  		Return([]map[string]any{}, errors.New("Simulated error generating params that could be related to an external service/API call"))
  1898  
  1899  	metrics := appsetmetrics.NewFakeAppsetMetrics()
  1900  
  1901  	r := ApplicationSetReconciler{
  1902  		Client:   client,
  1903  		Scheme:   scheme,
  1904  		Recorder: record.NewFakeRecorder(0),
  1905  		Generators: map[string]generators.Generator{
  1906  			"PullRequest": &generatorMock,
  1907  		},
  1908  		Metrics: metrics,
  1909  	}
  1910  
  1911  	req := ctrl.Request{
  1912  		NamespacedName: types.NamespacedName{
  1913  			Namespace: "argocd",
  1914  			Name:      "name",
  1915  		},
  1916  	}
  1917  
  1918  	res, err := r.Reconcile(t.Context(), req)
  1919  	require.NoError(t, err)
  1920  	assert.Equal(t, ReconcileRequeueOnValidationError, res.RequeueAfter)
  1921  }
  1922  
  1923  func TestValidateGeneratedApplications(t *testing.T) {
  1924  	t.Parallel()
  1925  
  1926  	scheme := runtime.NewScheme()
  1927  	err := v1alpha1.AddToScheme(scheme)
  1928  	require.NoError(t, err)
  1929  
  1930  	// Valid project
  1931  	myProject := &v1alpha1.AppProject{
  1932  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "namespace"},
  1933  		Spec: v1alpha1.AppProjectSpec{
  1934  			SourceRepos: []string{"*"},
  1935  			Destinations: []v1alpha1.ApplicationDestination{
  1936  				{
  1937  					Namespace: "*",
  1938  					Server:    "*",
  1939  				},
  1940  			},
  1941  			ClusterResourceWhitelist: []metav1.GroupKind{
  1942  				{
  1943  					Group: "*",
  1944  					Kind:  "*",
  1945  				},
  1946  			},
  1947  		},
  1948  	}
  1949  
  1950  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(myProject).Build()
  1951  	metrics := appsetmetrics.NewFakeAppsetMetrics()
  1952  
  1953  	// Test a subset of the validations that 'validateGeneratedApplications' performs
  1954  	for _, cc := range []struct {
  1955  		name             string
  1956  		apps             []v1alpha1.Application
  1957  		validationErrors map[string]error
  1958  	}{
  1959  		{
  1960  			name: "valid app should return true",
  1961  			apps: []v1alpha1.Application{
  1962  				{
  1963  					ObjectMeta: metav1.ObjectMeta{
  1964  						Name: "app",
  1965  					},
  1966  					Spec: v1alpha1.ApplicationSpec{
  1967  						Project: "default",
  1968  						Source: &v1alpha1.ApplicationSource{
  1969  							RepoURL:        "https://url",
  1970  							Path:           "/",
  1971  							TargetRevision: "HEAD",
  1972  						},
  1973  						Destination: v1alpha1.ApplicationDestination{
  1974  							Namespace: "namespace",
  1975  							Name:      "my-cluster",
  1976  						},
  1977  					},
  1978  				},
  1979  			},
  1980  			validationErrors: map[string]error{},
  1981  		},
  1982  		{
  1983  			name: "can't have both name and server defined",
  1984  			apps: []v1alpha1.Application{
  1985  				{
  1986  					ObjectMeta: metav1.ObjectMeta{
  1987  						Name: "app",
  1988  					},
  1989  					Spec: v1alpha1.ApplicationSpec{
  1990  						Project: "default",
  1991  						Source: &v1alpha1.ApplicationSource{
  1992  							RepoURL:        "https://url",
  1993  							Path:           "/",
  1994  							TargetRevision: "HEAD",
  1995  						},
  1996  						Destination: v1alpha1.ApplicationDestination{
  1997  							Namespace: "namespace",
  1998  							Server:    "my-server",
  1999  							Name:      "my-cluster",
  2000  						},
  2001  					},
  2002  				},
  2003  			},
  2004  			validationErrors: map[string]error{"app": errors.New("application destination spec is invalid: application destination can't have both name and server defined: my-cluster my-server")},
  2005  		},
  2006  		{
  2007  			name: "project mismatch should return error",
  2008  			apps: []v1alpha1.Application{
  2009  				{
  2010  					ObjectMeta: metav1.ObjectMeta{
  2011  						Name: "app",
  2012  					},
  2013  					Spec: v1alpha1.ApplicationSpec{
  2014  						Project: "DOES-NOT-EXIST",
  2015  						Source: &v1alpha1.ApplicationSource{
  2016  							RepoURL:        "https://url",
  2017  							Path:           "/",
  2018  							TargetRevision: "HEAD",
  2019  						},
  2020  						Destination: v1alpha1.ApplicationDestination{
  2021  							Namespace: "namespace",
  2022  							Name:      "my-cluster",
  2023  						},
  2024  					},
  2025  				},
  2026  			},
  2027  			validationErrors: map[string]error{"app": errors.New("application references project DOES-NOT-EXIST which does not exist")},
  2028  		},
  2029  		{
  2030  			name: "valid app should return true",
  2031  			apps: []v1alpha1.Application{
  2032  				{
  2033  					ObjectMeta: metav1.ObjectMeta{
  2034  						Name: "app",
  2035  					},
  2036  					Spec: v1alpha1.ApplicationSpec{
  2037  						Project: "default",
  2038  						Source: &v1alpha1.ApplicationSource{
  2039  							RepoURL:        "https://url",
  2040  							Path:           "/",
  2041  							TargetRevision: "HEAD",
  2042  						},
  2043  						Destination: v1alpha1.ApplicationDestination{
  2044  							Namespace: "namespace",
  2045  							Name:      "my-cluster",
  2046  						},
  2047  					},
  2048  				},
  2049  			},
  2050  			validationErrors: map[string]error{},
  2051  		},
  2052  		{
  2053  			name: "cluster should match",
  2054  			apps: []v1alpha1.Application{
  2055  				{
  2056  					ObjectMeta: metav1.ObjectMeta{
  2057  						Name: "app",
  2058  					},
  2059  					Spec: v1alpha1.ApplicationSpec{
  2060  						Project: "default",
  2061  						Source: &v1alpha1.ApplicationSource{
  2062  							RepoURL:        "https://url",
  2063  							Path:           "/",
  2064  							TargetRevision: "HEAD",
  2065  						},
  2066  						Destination: v1alpha1.ApplicationDestination{
  2067  							Namespace: "namespace",
  2068  							Name:      "nonexistent-cluster",
  2069  						},
  2070  					},
  2071  				},
  2072  			},
  2073  			validationErrors: map[string]error{"app": errors.New("application destination spec is invalid: there are no clusters with this name: nonexistent-cluster")},
  2074  		},
  2075  	} {
  2076  		t.Run(cc.name, func(t *testing.T) {
  2077  			t.Parallel()
  2078  
  2079  			secret := &corev1.Secret{
  2080  				ObjectMeta: metav1.ObjectMeta{
  2081  					Name:      "my-secret",
  2082  					Namespace: "argocd",
  2083  					Labels: map[string]string{
  2084  						argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
  2085  					},
  2086  				},
  2087  				Data: map[string][]byte{
  2088  					"name":   []byte("my-cluster"),
  2089  					"server": []byte("https://kubernetes.default.svc"),
  2090  					"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
  2091  				},
  2092  			}
  2093  
  2094  			kubeclientset := getDefaultTestClientSet(secret)
  2095  
  2096  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  2097  
  2098  			r := ApplicationSetReconciler{
  2099  				Client:          client,
  2100  				Scheme:          scheme,
  2101  				Recorder:        record.NewFakeRecorder(1),
  2102  				Generators:      map[string]generators.Generator{},
  2103  				ArgoDB:          argodb,
  2104  				ArgoCDNamespace: "namespace",
  2105  				KubeClientset:   kubeclientset,
  2106  				Metrics:         metrics,
  2107  			}
  2108  
  2109  			appSetInfo := v1alpha1.ApplicationSet{}
  2110  			validationErrors, _ := r.validateGeneratedApplications(t.Context(), cc.apps, appSetInfo)
  2111  			assert.Equal(t, cc.validationErrors, validationErrors)
  2112  		})
  2113  	}
  2114  }
  2115  
  2116  func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
  2117  	scheme := runtime.NewScheme()
  2118  	err := v1alpha1.AddToScheme(scheme)
  2119  	require.NoError(t, err)
  2120  
  2121  	project := v1alpha1.AppProject{
  2122  		ObjectMeta: metav1.ObjectMeta{Name: "good-project", Namespace: "argocd"},
  2123  	}
  2124  	appSet := v1alpha1.ApplicationSet{
  2125  		ObjectMeta: metav1.ObjectMeta{
  2126  			Name:      "name",
  2127  			Namespace: "argocd",
  2128  		},
  2129  		Spec: v1alpha1.ApplicationSetSpec{
  2130  			GoTemplate: true,
  2131  			Generators: []v1alpha1.ApplicationSetGenerator{
  2132  				{
  2133  					List: &v1alpha1.ListGenerator{
  2134  						Elements: []apiextensionsv1.JSON{{
  2135  							Raw: []byte(`{"project": "good-project"}`),
  2136  						}, {
  2137  							Raw: []byte(`{"project": "bad-project"}`),
  2138  						}},
  2139  					},
  2140  				},
  2141  			},
  2142  			Template: v1alpha1.ApplicationSetTemplate{
  2143  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  2144  					Name:      "{{.project}}",
  2145  					Namespace: "argocd",
  2146  				},
  2147  				Spec: v1alpha1.ApplicationSpec{
  2148  					Source:      &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
  2149  					Project:     "{{.project}}",
  2150  					Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"},
  2151  				},
  2152  			},
  2153  		},
  2154  	}
  2155  
  2156  	kubeclientset := getDefaultTestClientSet()
  2157  
  2158  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &project).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  2159  	metrics := appsetmetrics.NewFakeAppsetMetrics()
  2160  
  2161  	argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  2162  
  2163  	r := ApplicationSetReconciler{
  2164  		Client:   client,
  2165  		Scheme:   scheme,
  2166  		Renderer: &utils.Render{},
  2167  		Recorder: record.NewFakeRecorder(1),
  2168  		Generators: map[string]generators.Generator{
  2169  			"List": generators.NewListGenerator(),
  2170  		},
  2171  		ArgoDB:          argodb,
  2172  		KubeClientset:   kubeclientset,
  2173  		Policy:          v1alpha1.ApplicationsSyncPolicySync,
  2174  		ArgoCDNamespace: "argocd",
  2175  		Metrics:         metrics,
  2176  	}
  2177  
  2178  	req := ctrl.Request{
  2179  		NamespacedName: types.NamespacedName{
  2180  			Namespace: "argocd",
  2181  			Name:      "name",
  2182  		},
  2183  	}
  2184  
  2185  	// Verify that on validation error, no error is returned, but the object is requeued
  2186  	res, err := r.Reconcile(t.Context(), req)
  2187  	require.NoError(t, err)
  2188  	assert.Equal(t, ReconcileRequeueOnValidationError, res.RequeueAfter)
  2189  
  2190  	var app v1alpha1.Application
  2191  
  2192  	// make sure good app got created
  2193  	err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-project"}, &app)
  2194  	require.NoError(t, err)
  2195  	assert.Equal(t, "good-project", app.Name)
  2196  
  2197  	// make sure bad app was not created
  2198  	err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "bad-project"}, &app)
  2199  	require.Error(t, err)
  2200  }
  2201  
  2202  func TestSetApplicationSetStatusCondition(t *testing.T) {
  2203  	scheme := runtime.NewScheme()
  2204  	err := v1alpha1.AddToScheme(scheme)
  2205  	require.NoError(t, err)
  2206  	kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  2207  	someTime := &metav1.Time{Time: time.Now().Add(-5 * time.Minute)}
  2208  	existingParameterGeneratedCondition := getParametersGeneratedCondition(true, "")
  2209  	existingParameterGeneratedCondition.LastTransitionTime = someTime
  2210  
  2211  	for _, c := range []struct {
  2212  		name                string
  2213  		appset              v1alpha1.ApplicationSet
  2214  		condition           v1alpha1.ApplicationSetCondition
  2215  		parametersGenerated bool
  2216  		testfunc            func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition)
  2217  	}{
  2218  		{
  2219  			name: "has parameters generated condition when false",
  2220  			appset: v1alpha1.ApplicationSet{
  2221  				ObjectMeta: metav1.ObjectMeta{
  2222  					Name:      "name",
  2223  					Namespace: "argocd",
  2224  				},
  2225  				Spec: v1alpha1.ApplicationSetSpec{
  2226  					Generators: []v1alpha1.ApplicationSetGenerator{
  2227  						{List: &v1alpha1.ListGenerator{
  2228  							Elements: []apiextensionsv1.JSON{{
  2229  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2230  							}},
  2231  						}},
  2232  					},
  2233  					Template: v1alpha1.ApplicationSetTemplate{},
  2234  				},
  2235  			},
  2236  			condition: v1alpha1.ApplicationSetCondition{
  2237  				Type:    v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2238  				Message: "This is a message",
  2239  				Reason:  "test",
  2240  				Status:  v1alpha1.ApplicationSetConditionStatusFalse,
  2241  			},
  2242  			parametersGenerated: false,
  2243  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2244  				t.Helper()
  2245  				require.Len(t, conditions, 2)
  2246  
  2247  				// Conditions are ordered by type, so the order is deterministic
  2248  				assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[0].Type)
  2249  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[0].Status)
  2250  
  2251  				assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[1].Type)
  2252  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[1].Status)
  2253  				assert.Equal(t, "test", conditions[1].Reason)
  2254  			},
  2255  		},
  2256  		{
  2257  			name: "parameters generated condition is used when specified",
  2258  			appset: v1alpha1.ApplicationSet{
  2259  				ObjectMeta: metav1.ObjectMeta{
  2260  					Name:      "name",
  2261  					Namespace: "argocd",
  2262  				},
  2263  				Spec: v1alpha1.ApplicationSetSpec{
  2264  					Generators: []v1alpha1.ApplicationSetGenerator{
  2265  						{List: &v1alpha1.ListGenerator{
  2266  							Elements: []apiextensionsv1.JSON{{
  2267  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2268  							}},
  2269  						}},
  2270  					},
  2271  					Template: v1alpha1.ApplicationSetTemplate{},
  2272  				},
  2273  			},
  2274  			condition: v1alpha1.ApplicationSetCondition{
  2275  				Type:    v1alpha1.ApplicationSetConditionParametersGenerated,
  2276  				Message: "This is a message",
  2277  				Reason:  "test",
  2278  				Status:  v1alpha1.ApplicationSetConditionStatusFalse,
  2279  			},
  2280  			parametersGenerated: true,
  2281  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2282  				t.Helper()
  2283  				require.Len(t, conditions, 1)
  2284  
  2285  				assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[0].Type)
  2286  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[0].Status)
  2287  				assert.Equal(t, "test", conditions[0].Reason)
  2288  			},
  2289  		},
  2290  		{
  2291  			name: "has parameter conditions when true",
  2292  			appset: v1alpha1.ApplicationSet{
  2293  				ObjectMeta: metav1.ObjectMeta{
  2294  					Name:      "name",
  2295  					Namespace: "argocd",
  2296  				},
  2297  				Spec: v1alpha1.ApplicationSetSpec{
  2298  					Generators: []v1alpha1.ApplicationSetGenerator{
  2299  						{List: &v1alpha1.ListGenerator{
  2300  							Elements: []apiextensionsv1.JSON{{
  2301  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2302  							}},
  2303  						}},
  2304  					},
  2305  					Template: v1alpha1.ApplicationSetTemplate{},
  2306  				},
  2307  			},
  2308  			condition: v1alpha1.ApplicationSetCondition{
  2309  				Type:    v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2310  				Message: "This is a message",
  2311  				Reason:  "test",
  2312  				Status:  v1alpha1.ApplicationSetConditionStatusFalse,
  2313  			},
  2314  			parametersGenerated: true,
  2315  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2316  				t.Helper()
  2317  				require.Len(t, conditions, 2)
  2318  
  2319  				// Conditions are ordered by type, so the order is deterministic
  2320  				assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[0].Type)
  2321  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusTrue, conditions[0].Status)
  2322  
  2323  				assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[1].Type)
  2324  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[1].Status)
  2325  				assert.Equal(t, "test", conditions[1].Reason)
  2326  			},
  2327  		},
  2328  		{
  2329  			name: "resource up to date sets error condition to false",
  2330  			appset: v1alpha1.ApplicationSet{
  2331  				ObjectMeta: metav1.ObjectMeta{
  2332  					Name:      "name",
  2333  					Namespace: "argocd",
  2334  				},
  2335  				Spec: v1alpha1.ApplicationSetSpec{
  2336  					Generators: []v1alpha1.ApplicationSetGenerator{
  2337  						{List: &v1alpha1.ListGenerator{
  2338  							Elements: []apiextensionsv1.JSON{{
  2339  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2340  							}},
  2341  						}},
  2342  					},
  2343  					Template: v1alpha1.ApplicationSetTemplate{},
  2344  				},
  2345  			},
  2346  			condition: v1alpha1.ApplicationSetCondition{
  2347  				Type:    v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2348  				Message: "Completed",
  2349  				Reason:  "test",
  2350  				Status:  v1alpha1.ApplicationSetConditionStatusTrue,
  2351  			},
  2352  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2353  				t.Helper()
  2354  				require.Len(t, conditions, 3)
  2355  
  2356  				assert.Equal(t, v1alpha1.ApplicationSetConditionErrorOccurred, conditions[0].Type)
  2357  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[0].Status)
  2358  				assert.Equal(t, "test", conditions[0].Reason)
  2359  				assert.Equal(t, "Completed", conditions[0].Message)
  2360  
  2361  				assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[1].Type)
  2362  
  2363  				assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[2].Type)
  2364  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusTrue, conditions[2].Status)
  2365  				assert.Equal(t, "test", conditions[2].Reason)
  2366  				assert.Equal(t, "Completed", conditions[2].Message)
  2367  			},
  2368  		},
  2369  		{
  2370  			name: "error condition sets resource up to date to false",
  2371  			appset: v1alpha1.ApplicationSet{
  2372  				ObjectMeta: metav1.ObjectMeta{
  2373  					Name:      "name",
  2374  					Namespace: "argocd",
  2375  				},
  2376  				Spec: v1alpha1.ApplicationSetSpec{
  2377  					Generators: []v1alpha1.ApplicationSetGenerator{
  2378  						{List: &v1alpha1.ListGenerator{
  2379  							Elements: []apiextensionsv1.JSON{{
  2380  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2381  							}},
  2382  						}},
  2383  					},
  2384  					Template: v1alpha1.ApplicationSetTemplate{},
  2385  				},
  2386  			},
  2387  			condition: v1alpha1.ApplicationSetCondition{
  2388  				Type:    v1alpha1.ApplicationSetConditionErrorOccurred,
  2389  				Message: "Error",
  2390  				Reason:  "test",
  2391  				Status:  v1alpha1.ApplicationSetConditionStatusTrue,
  2392  			},
  2393  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2394  				t.Helper()
  2395  				require.Len(t, conditions, 3)
  2396  
  2397  				assert.Equal(t, v1alpha1.ApplicationSetConditionErrorOccurred, conditions[0].Type)
  2398  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusTrue, conditions[0].Status)
  2399  				assert.Equal(t, "test", conditions[0].Reason)
  2400  				assert.Equal(t, "Error", conditions[0].Message)
  2401  
  2402  				assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[1].Type)
  2403  
  2404  				assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[2].Type)
  2405  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[2].Status)
  2406  				assert.Equal(t, v1alpha1.ApplicationSetReasonErrorOccurred, conditions[2].Reason)
  2407  				assert.Equal(t, "Error", conditions[2].Message)
  2408  			},
  2409  		},
  2410  		{
  2411  			name: "updating an unchanged condition does not mutate existing conditions",
  2412  			appset: v1alpha1.ApplicationSet{
  2413  				ObjectMeta: metav1.ObjectMeta{
  2414  					Name:      "name",
  2415  					Namespace: "argocd",
  2416  				},
  2417  				Spec: v1alpha1.ApplicationSetSpec{
  2418  					Generators: []v1alpha1.ApplicationSetGenerator{
  2419  						{List: &v1alpha1.ListGenerator{
  2420  							Elements: []apiextensionsv1.JSON{{
  2421  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2422  							}},
  2423  						}},
  2424  					},
  2425  					Strategy: &v1alpha1.ApplicationSetStrategy{
  2426  						Type:        "RollingSync",
  2427  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  2428  					},
  2429  					Template: v1alpha1.ApplicationSetTemplate{},
  2430  				},
  2431  				Status: v1alpha1.ApplicationSetStatus{
  2432  					Conditions: []v1alpha1.ApplicationSetCondition{
  2433  						{
  2434  							Type:               v1alpha1.ApplicationSetConditionErrorOccurred,
  2435  							Message:            "existing",
  2436  							LastTransitionTime: someTime,
  2437  						},
  2438  						existingParameterGeneratedCondition,
  2439  						{
  2440  							Type:               v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2441  							Message:            "existing",
  2442  							Status:             v1alpha1.ApplicationSetConditionStatusFalse,
  2443  							LastTransitionTime: someTime,
  2444  						},
  2445  						{
  2446  							Type:               v1alpha1.ApplicationSetConditionRolloutProgressing,
  2447  							Message:            "existing",
  2448  							LastTransitionTime: someTime,
  2449  						},
  2450  					},
  2451  				},
  2452  			},
  2453  			condition: v1alpha1.ApplicationSetCondition{
  2454  				Type:    v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2455  				Message: "existing",
  2456  				Status:  v1alpha1.ApplicationSetConditionStatusFalse,
  2457  			},
  2458  			parametersGenerated: true,
  2459  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2460  				t.Helper()
  2461  				require.Len(t, conditions, 4)
  2462  
  2463  				assert.Equal(t, v1alpha1.ApplicationSetConditionErrorOccurred, conditions[0].Type)
  2464  				assert.Equal(t, someTime, conditions[0].LastTransitionTime)
  2465  
  2466  				assert.Equal(t, v1alpha1.ApplicationSetConditionParametersGenerated, conditions[1].Type)
  2467  				assert.Equal(t, someTime, conditions[1].LastTransitionTime)
  2468  
  2469  				assert.Equal(t, v1alpha1.ApplicationSetConditionResourcesUpToDate, conditions[2].Type)
  2470  				assert.Equal(t, someTime, conditions[2].LastTransitionTime)
  2471  
  2472  				assert.Equal(t, v1alpha1.ApplicationSetConditionRolloutProgressing, conditions[3].Type)
  2473  				assert.Equal(t, someTime, conditions[3].LastTransitionTime)
  2474  			},
  2475  		},
  2476  		{
  2477  			name: "progressing conditions is removed when AppSet is not configured",
  2478  			appset: v1alpha1.ApplicationSet{
  2479  				ObjectMeta: metav1.ObjectMeta{
  2480  					Name:      "name",
  2481  					Namespace: "argocd",
  2482  				},
  2483  				Spec: v1alpha1.ApplicationSetSpec{
  2484  					Generators: []v1alpha1.ApplicationSetGenerator{
  2485  						{List: &v1alpha1.ListGenerator{
  2486  							Elements: []apiextensionsv1.JSON{{
  2487  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2488  							}},
  2489  						}},
  2490  					},
  2491  					// Strategy removed
  2492  					// Strategy: &v1alpha1.ApplicationSetStrategy{
  2493  					// 	Type:        "RollingSync",
  2494  					// 	RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  2495  					// },
  2496  					Template: v1alpha1.ApplicationSetTemplate{},
  2497  				},
  2498  				Status: v1alpha1.ApplicationSetStatus{
  2499  					Conditions: []v1alpha1.ApplicationSetCondition{
  2500  						{
  2501  							Type:               v1alpha1.ApplicationSetConditionErrorOccurred,
  2502  							Message:            "existing",
  2503  							LastTransitionTime: someTime,
  2504  						},
  2505  						existingParameterGeneratedCondition,
  2506  						{
  2507  							Type:               v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2508  							Message:            "existing",
  2509  							Status:             v1alpha1.ApplicationSetConditionStatusFalse,
  2510  							LastTransitionTime: someTime,
  2511  						},
  2512  						{
  2513  							Type:               v1alpha1.ApplicationSetConditionRolloutProgressing,
  2514  							Message:            "existing",
  2515  							LastTransitionTime: someTime,
  2516  						},
  2517  					},
  2518  				},
  2519  			},
  2520  			condition: v1alpha1.ApplicationSetCondition{
  2521  				Type:    v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2522  				Message: "existing",
  2523  				Status:  v1alpha1.ApplicationSetConditionStatusFalse,
  2524  			},
  2525  			parametersGenerated: true,
  2526  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2527  				t.Helper()
  2528  				require.Len(t, conditions, 3)
  2529  				for _, c := range conditions {
  2530  					assert.NotEqual(t, v1alpha1.ApplicationSetConditionRolloutProgressing, c.Type)
  2531  				}
  2532  			},
  2533  		},
  2534  		{
  2535  			name: "progressing conditions is ignored when AppSet is not configured",
  2536  			appset: v1alpha1.ApplicationSet{
  2537  				ObjectMeta: metav1.ObjectMeta{
  2538  					Name:      "name",
  2539  					Namespace: "argocd",
  2540  				},
  2541  				Spec: v1alpha1.ApplicationSetSpec{
  2542  					Generators: []v1alpha1.ApplicationSetGenerator{
  2543  						{List: &v1alpha1.ListGenerator{
  2544  							Elements: []apiextensionsv1.JSON{{
  2545  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2546  							}},
  2547  						}},
  2548  					},
  2549  					// Strategy removed
  2550  					// Strategy: &v1alpha1.ApplicationSetStrategy{
  2551  					// 	Type:        "RollingSync",
  2552  					// 	RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  2553  					// },
  2554  					Template: v1alpha1.ApplicationSetTemplate{},
  2555  				},
  2556  				Status: v1alpha1.ApplicationSetStatus{
  2557  					Conditions: []v1alpha1.ApplicationSetCondition{
  2558  						{
  2559  							Type:               v1alpha1.ApplicationSetConditionErrorOccurred,
  2560  							Message:            "existing",
  2561  							LastTransitionTime: someTime,
  2562  						},
  2563  						existingParameterGeneratedCondition,
  2564  						{
  2565  							Type:               v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2566  							Message:            "existing",
  2567  							Status:             v1alpha1.ApplicationSetConditionStatusFalse,
  2568  							LastTransitionTime: someTime,
  2569  						},
  2570  					},
  2571  				},
  2572  			},
  2573  			condition: v1alpha1.ApplicationSetCondition{
  2574  				Type:    v1alpha1.ApplicationSetConditionRolloutProgressing,
  2575  				Message: "do not add me",
  2576  				Status:  v1alpha1.ApplicationSetConditionStatusTrue,
  2577  			},
  2578  			parametersGenerated: true,
  2579  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2580  				t.Helper()
  2581  				require.Len(t, conditions, 3)
  2582  				for _, c := range conditions {
  2583  					assert.NotEqual(t, v1alpha1.ApplicationSetConditionRolloutProgressing, c.Type)
  2584  				}
  2585  			},
  2586  		},
  2587  		{
  2588  			name: "progressing conditions is updated correctly when configured",
  2589  			appset: v1alpha1.ApplicationSet{
  2590  				ObjectMeta: metav1.ObjectMeta{
  2591  					Name:      "name",
  2592  					Namespace: "argocd",
  2593  				},
  2594  				Spec: v1alpha1.ApplicationSetSpec{
  2595  					Generators: []v1alpha1.ApplicationSetGenerator{
  2596  						{List: &v1alpha1.ListGenerator{
  2597  							Elements: []apiextensionsv1.JSON{{
  2598  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  2599  							}},
  2600  						}},
  2601  					},
  2602  					Strategy: &v1alpha1.ApplicationSetStrategy{
  2603  						Type:        "RollingSync",
  2604  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
  2605  					},
  2606  					Template: v1alpha1.ApplicationSetTemplate{},
  2607  				},
  2608  				Status: v1alpha1.ApplicationSetStatus{
  2609  					Conditions: []v1alpha1.ApplicationSetCondition{
  2610  						{
  2611  							Type:               v1alpha1.ApplicationSetConditionErrorOccurred,
  2612  							Message:            "existing",
  2613  							LastTransitionTime: someTime,
  2614  						},
  2615  						existingParameterGeneratedCondition,
  2616  						{
  2617  							Type:               v1alpha1.ApplicationSetConditionResourcesUpToDate,
  2618  							Message:            "existing",
  2619  							Status:             v1alpha1.ApplicationSetConditionStatusFalse,
  2620  							LastTransitionTime: someTime,
  2621  						},
  2622  						{
  2623  							Type:    v1alpha1.ApplicationSetConditionRolloutProgressing,
  2624  							Message: "old value",
  2625  							Status:  v1alpha1.ApplicationSetConditionStatusTrue,
  2626  						},
  2627  					},
  2628  				},
  2629  			},
  2630  			condition: v1alpha1.ApplicationSetCondition{
  2631  				Type:    v1alpha1.ApplicationSetConditionRolloutProgressing,
  2632  				Message: "new value",
  2633  				Status:  v1alpha1.ApplicationSetConditionStatusFalse,
  2634  			},
  2635  			parametersGenerated: true,
  2636  			testfunc: func(t *testing.T, conditions []v1alpha1.ApplicationSetCondition) {
  2637  				t.Helper()
  2638  				require.Len(t, conditions, 4)
  2639  
  2640  				assert.Equal(t, v1alpha1.ApplicationSetConditionRolloutProgressing, conditions[3].Type)
  2641  				assert.Equal(t, v1alpha1.ApplicationSetConditionStatusFalse, conditions[3].Status)
  2642  				assert.Equal(t, "new value", conditions[3].Message)
  2643  			},
  2644  		},
  2645  	} {
  2646  		t.Run(c.name, func(t *testing.T) {
  2647  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&c.appset).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).WithStatusSubresource(&c.appset).Build()
  2648  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  2649  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  2650  
  2651  			r := ApplicationSetReconciler{
  2652  				Client:   client,
  2653  				Scheme:   scheme,
  2654  				Renderer: &utils.Render{},
  2655  				Recorder: record.NewFakeRecorder(1),
  2656  				Generators: map[string]generators.Generator{
  2657  					"List": generators.NewListGenerator(),
  2658  				},
  2659  				ArgoDB:        argodb,
  2660  				KubeClientset: kubeclientset,
  2661  				Metrics:       metrics,
  2662  			}
  2663  
  2664  			err = r.setApplicationSetStatusCondition(t.Context(), &c.appset, c.condition, c.parametersGenerated)
  2665  			require.NoError(t, err)
  2666  
  2667  			c.testfunc(t, c.appset.Status.Conditions)
  2668  		})
  2669  	}
  2670  }
  2671  
  2672  func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.Application {
  2673  	t.Helper()
  2674  	scheme := runtime.NewScheme()
  2675  	err := v1alpha1.AddToScheme(scheme)
  2676  	require.NoError(t, err)
  2677  
  2678  	defaultProject := v1alpha1.AppProject{
  2679  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
  2680  		Spec:       v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}},
  2681  	}
  2682  	appSet := v1alpha1.ApplicationSet{
  2683  		ObjectMeta: metav1.ObjectMeta{
  2684  			Name:      "name",
  2685  			Namespace: "argocd",
  2686  		},
  2687  		Spec: v1alpha1.ApplicationSetSpec{
  2688  			Generators: []v1alpha1.ApplicationSetGenerator{
  2689  				{
  2690  					List: &v1alpha1.ListGenerator{
  2691  						Elements: []apiextensionsv1.JSON{{
  2692  							Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`),
  2693  						}},
  2694  					},
  2695  				},
  2696  			},
  2697  			SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{
  2698  				ApplicationsSync: &applicationsSyncPolicy,
  2699  			},
  2700  			Template: v1alpha1.ApplicationSetTemplate{
  2701  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  2702  					Name:      "{{cluster}}",
  2703  					Namespace: "argocd",
  2704  				},
  2705  				Spec: v1alpha1.ApplicationSpec{
  2706  					Source:      &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
  2707  					Project:     "default",
  2708  					Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"},
  2709  				},
  2710  			},
  2711  		},
  2712  	}
  2713  
  2714  	secret := &corev1.Secret{
  2715  		ObjectMeta: metav1.ObjectMeta{
  2716  			Name:      "my-cluster",
  2717  			Namespace: "argocd",
  2718  			Labels: map[string]string{
  2719  				argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
  2720  			},
  2721  		},
  2722  		Data: map[string][]byte{
  2723  			// Since this test requires the cluster to be an invalid destination, we
  2724  			// always return a cluster named 'my-cluster2' (different from app 'my-cluster', above)
  2725  			"name":   []byte("good-cluster"),
  2726  			"server": []byte("https://good-cluster"),
  2727  			"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
  2728  		},
  2729  	}
  2730  
  2731  	kubeclientset := getDefaultTestClientSet(secret)
  2732  
  2733  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &defaultProject).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  2734  	metrics := appsetmetrics.NewFakeAppsetMetrics()
  2735  
  2736  	argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  2737  
  2738  	r := ApplicationSetReconciler{
  2739  		Client:   client,
  2740  		Scheme:   scheme,
  2741  		Renderer: &utils.Render{},
  2742  		Recorder: record.NewFakeRecorder(recordBuffer),
  2743  		Generators: map[string]generators.Generator{
  2744  			"List": generators.NewListGenerator(),
  2745  		},
  2746  		ArgoDB:               argodb,
  2747  		ArgoCDNamespace:      "argocd",
  2748  		KubeClientset:        kubeclientset,
  2749  		Policy:               v1alpha1.ApplicationsSyncPolicySync,
  2750  		EnablePolicyOverride: allowPolicyOverride,
  2751  		Metrics:              metrics,
  2752  	}
  2753  
  2754  	req := ctrl.Request{
  2755  		NamespacedName: types.NamespacedName{
  2756  			Namespace: "argocd",
  2757  			Name:      "name",
  2758  		},
  2759  	}
  2760  
  2761  	// Verify that on validation error, no error is returned, but the object is requeued
  2762  	resCreate, err := r.Reconcile(t.Context(), req)
  2763  	require.NoErrorf(t, err, "Reconcile failed with error: %v", err)
  2764  	assert.Equal(t, time.Duration(0), resCreate.RequeueAfter)
  2765  
  2766  	var app v1alpha1.Application
  2767  
  2768  	// make sure good app got created
  2769  	err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
  2770  	require.NoError(t, err)
  2771  	assert.Equal(t, "good-cluster", app.Name)
  2772  
  2773  	// Update resource
  2774  	var retrievedApplicationSet v1alpha1.ApplicationSet
  2775  	err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet)
  2776  	require.NoError(t, err)
  2777  
  2778  	retrievedApplicationSet.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
  2779  	retrievedApplicationSet.Spec.Template.Labels = map[string]string{"label-key": "label-value"}
  2780  
  2781  	retrievedApplicationSet.Spec.Template.Spec.Source.Helm = &v1alpha1.ApplicationSourceHelm{
  2782  		Values: "global.test: test",
  2783  	}
  2784  
  2785  	err = r.Update(t.Context(), &retrievedApplicationSet)
  2786  	require.NoError(t, err)
  2787  
  2788  	resUpdate, err := r.Reconcile(t.Context(), req)
  2789  	require.NoError(t, err)
  2790  
  2791  	err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
  2792  	require.NoError(t, err)
  2793  	assert.Equal(t, time.Duration(0), resUpdate.RequeueAfter)
  2794  	assert.Equal(t, "good-cluster", app.Name)
  2795  
  2796  	return app
  2797  }
  2798  
  2799  func TestUpdateNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
  2800  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
  2801  
  2802  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
  2803  
  2804  	assert.Nil(t, app.Spec.Source.Helm)
  2805  	assert.Nil(t, app.Annotations)
  2806  }
  2807  
  2808  func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) {
  2809  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete
  2810  
  2811  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
  2812  
  2813  	assert.Nil(t, app.Spec.Source.Helm)
  2814  	assert.Nil(t, app.Annotations)
  2815  }
  2816  
  2817  func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) {
  2818  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate
  2819  
  2820  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
  2821  
  2822  	assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
  2823  	assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.Annotations)
  2824  	assert.Equal(t, map[string]string{"label-key": "label-value"}, app.Labels)
  2825  }
  2826  
  2827  func TestUpdatePerformedWithSyncPolicySync(t *testing.T) {
  2828  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync
  2829  
  2830  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
  2831  
  2832  	assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
  2833  	assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.Annotations)
  2834  	assert.Equal(t, map[string]string{"label-key": "label-value"}, app.Labels)
  2835  }
  2836  
  2837  func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
  2838  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
  2839  
  2840  	app := applicationsUpdateSyncPolicyTest(t, applicationsSyncPolicy, 2, false)
  2841  
  2842  	assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
  2843  	assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.Annotations)
  2844  	assert.Equal(t, map[string]string{"label-key": "label-value"}, app.Labels)
  2845  }
  2846  
  2847  func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.ApplicationList {
  2848  	t.Helper()
  2849  	scheme := runtime.NewScheme()
  2850  	err := v1alpha1.AddToScheme(scheme)
  2851  	require.NoError(t, err)
  2852  
  2853  	defaultProject := v1alpha1.AppProject{
  2854  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
  2855  		Spec:       v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://good-cluster"}}},
  2856  	}
  2857  	appSet := v1alpha1.ApplicationSet{
  2858  		ObjectMeta: metav1.ObjectMeta{
  2859  			Name:      "name",
  2860  			Namespace: "argocd",
  2861  		},
  2862  		Spec: v1alpha1.ApplicationSetSpec{
  2863  			Generators: []v1alpha1.ApplicationSetGenerator{
  2864  				{
  2865  					List: &v1alpha1.ListGenerator{
  2866  						Elements: []apiextensionsv1.JSON{{
  2867  							Raw: []byte(`{"cluster": "good-cluster","url": "https://good-cluster"}`),
  2868  						}},
  2869  					},
  2870  				},
  2871  			},
  2872  			SyncPolicy: &v1alpha1.ApplicationSetSyncPolicy{
  2873  				ApplicationsSync: &applicationsSyncPolicy,
  2874  			},
  2875  			Template: v1alpha1.ApplicationSetTemplate{
  2876  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  2877  					Name:      "{{cluster}}",
  2878  					Namespace: "argocd",
  2879  				},
  2880  				Spec: v1alpha1.ApplicationSpec{
  2881  					Source:      &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
  2882  					Project:     "default",
  2883  					Destination: v1alpha1.ApplicationDestination{Server: "{{url}}"},
  2884  				},
  2885  			},
  2886  		},
  2887  	}
  2888  
  2889  	secret := &corev1.Secret{
  2890  		ObjectMeta: metav1.ObjectMeta{
  2891  			Name:      "my-cluster",
  2892  			Namespace: "argocd",
  2893  			Labels: map[string]string{
  2894  				argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
  2895  			},
  2896  		},
  2897  		Data: map[string][]byte{
  2898  			// Since this test requires the cluster to be an invalid destination, we
  2899  			// always return a cluster named 'my-cluster2' (different from app 'my-cluster', above)
  2900  			"name":   []byte("good-cluster"),
  2901  			"server": []byte("https://good-cluster"),
  2902  			"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
  2903  		},
  2904  	}
  2905  
  2906  	kubeclientset := getDefaultTestClientSet(secret)
  2907  
  2908  	client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &defaultProject).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  2909  	metrics := appsetmetrics.NewFakeAppsetMetrics()
  2910  
  2911  	argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  2912  
  2913  	r := ApplicationSetReconciler{
  2914  		Client:   client,
  2915  		Scheme:   scheme,
  2916  		Renderer: &utils.Render{},
  2917  		Recorder: record.NewFakeRecorder(recordBuffer),
  2918  		Generators: map[string]generators.Generator{
  2919  			"List": generators.NewListGenerator(),
  2920  		},
  2921  		ArgoDB:               argodb,
  2922  		ArgoCDNamespace:      "argocd",
  2923  		KubeClientset:        kubeclientset,
  2924  		Policy:               v1alpha1.ApplicationsSyncPolicySync,
  2925  		EnablePolicyOverride: allowPolicyOverride,
  2926  		Metrics:              metrics,
  2927  	}
  2928  
  2929  	req := ctrl.Request{
  2930  		NamespacedName: types.NamespacedName{
  2931  			Namespace: "argocd",
  2932  			Name:      "name",
  2933  		},
  2934  	}
  2935  
  2936  	// Verify that on validation error, no error is returned, but the object is requeued
  2937  	resCreate, err := r.Reconcile(t.Context(), req)
  2938  	require.NoError(t, err)
  2939  	assert.Equal(t, time.Duration(0), resCreate.RequeueAfter)
  2940  
  2941  	var app v1alpha1.Application
  2942  
  2943  	// make sure good app got created
  2944  	err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "good-cluster"}, &app)
  2945  	require.NoError(t, err)
  2946  	assert.Equal(t, "good-cluster", app.Name)
  2947  
  2948  	// Update resource
  2949  	var retrievedApplicationSet v1alpha1.ApplicationSet
  2950  	err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &retrievedApplicationSet)
  2951  	require.NoError(t, err)
  2952  	retrievedApplicationSet.Spec.Generators = []v1alpha1.ApplicationSetGenerator{
  2953  		{
  2954  			List: &v1alpha1.ListGenerator{
  2955  				Elements: []apiextensionsv1.JSON{},
  2956  			},
  2957  		},
  2958  	}
  2959  
  2960  	err = r.Update(t.Context(), &retrievedApplicationSet)
  2961  	require.NoError(t, err)
  2962  
  2963  	resUpdate, err := r.Reconcile(t.Context(), req)
  2964  	require.NoError(t, err)
  2965  
  2966  	var apps v1alpha1.ApplicationList
  2967  
  2968  	err = r.List(t.Context(), &apps)
  2969  	require.NoError(t, err)
  2970  	assert.Equal(t, time.Duration(0), resUpdate.RequeueAfter)
  2971  
  2972  	return apps
  2973  }
  2974  
  2975  func TestDeleteNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
  2976  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
  2977  
  2978  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 1, true)
  2979  
  2980  	assert.Equal(t, "good-cluster", apps.Items[0].Name)
  2981  }
  2982  
  2983  func TestDeleteNotPerformedWithSyncPolicyCreateUpdate(t *testing.T) {
  2984  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateUpdate
  2985  
  2986  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 2, true)
  2987  
  2988  	assert.Equal(t, "good-cluster", apps.Items[0].Name)
  2989  }
  2990  
  2991  func TestDeletePerformedWithSyncPolicyCreateDelete(t *testing.T) {
  2992  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateDelete
  2993  
  2994  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true)
  2995  
  2996  	assert.NotNil(t, apps.Items[0].DeletionTimestamp)
  2997  }
  2998  
  2999  func TestDeletePerformedWithSyncPolicySync(t *testing.T) {
  3000  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicySync
  3001  
  3002  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true)
  3003  
  3004  	assert.NotNil(t, apps.Items[0].DeletionTimestamp)
  3005  }
  3006  
  3007  func TestDeletePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
  3008  	applicationsSyncPolicy := v1alpha1.ApplicationsSyncPolicyCreateOnly
  3009  
  3010  	apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, false)
  3011  
  3012  	assert.NotNil(t, apps.Items[0].DeletionTimestamp)
  3013  }
  3014  
  3015  func TestPolicies(t *testing.T) {
  3016  	scheme := runtime.NewScheme()
  3017  	err := v1alpha1.AddToScheme(scheme)
  3018  	require.NoError(t, err)
  3019  
  3020  	defaultProject := v1alpha1.AppProject{
  3021  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
  3022  		Spec:       v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://kubernetes.default.svc"}}},
  3023  	}
  3024  
  3025  	kubeclientset := getDefaultTestClientSet()
  3026  
  3027  	for _, c := range []struct {
  3028  		name          string
  3029  		policyName    string
  3030  		allowedUpdate bool
  3031  		allowedDelete bool
  3032  	}{
  3033  		{
  3034  			name:          "Apps are allowed to update and delete",
  3035  			policyName:    "sync",
  3036  			allowedUpdate: true,
  3037  			allowedDelete: true,
  3038  		},
  3039  		{
  3040  			name:          "Apps are not allowed to update and delete",
  3041  			policyName:    "create-only",
  3042  			allowedUpdate: false,
  3043  			allowedDelete: false,
  3044  		},
  3045  		{
  3046  			name:          "Apps are allowed to update, not allowed to delete",
  3047  			policyName:    "create-update",
  3048  			allowedUpdate: true,
  3049  			allowedDelete: false,
  3050  		},
  3051  		{
  3052  			name:          "Apps are allowed to delete, not allowed to update",
  3053  			policyName:    "create-delete",
  3054  			allowedUpdate: false,
  3055  			allowedDelete: true,
  3056  		},
  3057  	} {
  3058  		t.Run(c.name, func(t *testing.T) {
  3059  			policy := utils.Policies[c.policyName]
  3060  			assert.NotNil(t, policy)
  3061  
  3062  			appSet := v1alpha1.ApplicationSet{
  3063  				ObjectMeta: metav1.ObjectMeta{
  3064  					Name:      "name",
  3065  					Namespace: "argocd",
  3066  				},
  3067  				Spec: v1alpha1.ApplicationSetSpec{
  3068  					GoTemplate: true,
  3069  					Generators: []v1alpha1.ApplicationSetGenerator{
  3070  						{
  3071  							List: &v1alpha1.ListGenerator{
  3072  								Elements: []apiextensionsv1.JSON{
  3073  									{
  3074  										Raw: []byte(`{"name": "my-app"}`),
  3075  									},
  3076  								},
  3077  							},
  3078  						},
  3079  					},
  3080  					Template: v1alpha1.ApplicationSetTemplate{
  3081  						ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
  3082  							Name:      "{{.name}}",
  3083  							Namespace: "argocd",
  3084  							Annotations: map[string]string{
  3085  								"key": "value",
  3086  							},
  3087  						},
  3088  						Spec: v1alpha1.ApplicationSpec{
  3089  							Source:      &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
  3090  							Project:     "default",
  3091  							Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"},
  3092  						},
  3093  					},
  3094  				},
  3095  			}
  3096  
  3097  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &defaultProject).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  3098  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  3099  
  3100  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  3101  
  3102  			r := ApplicationSetReconciler{
  3103  				Client:   client,
  3104  				Scheme:   scheme,
  3105  				Renderer: &utils.Render{},
  3106  				Recorder: record.NewFakeRecorder(10),
  3107  				Generators: map[string]generators.Generator{
  3108  					"List": generators.NewListGenerator(),
  3109  				},
  3110  				ArgoDB:          argodb,
  3111  				ArgoCDNamespace: "argocd",
  3112  				KubeClientset:   kubeclientset,
  3113  				Policy:          policy,
  3114  				Metrics:         metrics,
  3115  			}
  3116  
  3117  			req := ctrl.Request{
  3118  				NamespacedName: types.NamespacedName{
  3119  					Namespace: "argocd",
  3120  					Name:      "name",
  3121  				},
  3122  			}
  3123  
  3124  			// Check if Application is created
  3125  			res, err := r.Reconcile(t.Context(), req)
  3126  			require.NoError(t, err)
  3127  			assert.Equal(t, time.Duration(0), res.RequeueAfter)
  3128  
  3129  			var app v1alpha1.Application
  3130  			err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app)
  3131  			require.NoError(t, err)
  3132  			assert.Equal(t, "value", app.Annotations["key"])
  3133  
  3134  			// Check if Application is updated
  3135  			app.Annotations["key"] = "edited"
  3136  			err = r.Update(t.Context(), &app)
  3137  			require.NoError(t, err)
  3138  
  3139  			res, err = r.Reconcile(t.Context(), req)
  3140  			require.NoError(t, err)
  3141  			assert.Equal(t, time.Duration(0), res.RequeueAfter)
  3142  
  3143  			err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app)
  3144  			require.NoError(t, err)
  3145  
  3146  			if c.allowedUpdate {
  3147  				assert.Equal(t, "value", app.Annotations["key"])
  3148  			} else {
  3149  				assert.Equal(t, "edited", app.Annotations["key"])
  3150  			}
  3151  
  3152  			// Check if Application is deleted
  3153  			err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "name"}, &appSet)
  3154  			require.NoError(t, err)
  3155  			appSet.Spec.Generators[0] = v1alpha1.ApplicationSetGenerator{
  3156  				List: &v1alpha1.ListGenerator{
  3157  					Elements: []apiextensionsv1.JSON{},
  3158  				},
  3159  			}
  3160  			err = r.Update(t.Context(), &appSet)
  3161  			require.NoError(t, err)
  3162  
  3163  			res, err = r.Reconcile(t.Context(), req)
  3164  			require.NoError(t, err)
  3165  			assert.Equal(t, time.Duration(0), res.RequeueAfter)
  3166  
  3167  			err = r.Get(t.Context(), crtclient.ObjectKey{Namespace: "argocd", Name: "my-app"}, &app)
  3168  			require.NoError(t, err)
  3169  			if c.allowedDelete {
  3170  				assert.NotNil(t, app.DeletionTimestamp)
  3171  			} else {
  3172  				assert.Nil(t, app.DeletionTimestamp)
  3173  			}
  3174  		})
  3175  	}
  3176  }
  3177  
  3178  func TestSetApplicationSetApplicationStatus(t *testing.T) {
  3179  	scheme := runtime.NewScheme()
  3180  	err := v1alpha1.AddToScheme(scheme)
  3181  	require.NoError(t, err)
  3182  
  3183  	kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  3184  
  3185  	for _, cc := range []struct {
  3186  		name                string
  3187  		appSet              v1alpha1.ApplicationSet
  3188  		appStatuses         []v1alpha1.ApplicationSetApplicationStatus
  3189  		expectedAppStatuses []v1alpha1.ApplicationSetApplicationStatus
  3190  	}{
  3191  		{
  3192  			name: "sets a single appstatus",
  3193  			appSet: v1alpha1.ApplicationSet{
  3194  				ObjectMeta: metav1.ObjectMeta{
  3195  					Name:      "name",
  3196  					Namespace: "argocd",
  3197  				},
  3198  				Spec: v1alpha1.ApplicationSetSpec{
  3199  					Generators: []v1alpha1.ApplicationSetGenerator{
  3200  						{List: &v1alpha1.ListGenerator{
  3201  							Elements: []apiextensionsv1.JSON{{
  3202  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  3203  							}},
  3204  						}},
  3205  					},
  3206  					Template: v1alpha1.ApplicationSetTemplate{},
  3207  				},
  3208  			},
  3209  			appStatuses: []v1alpha1.ApplicationSetApplicationStatus{
  3210  				{
  3211  					Application: "app1",
  3212  					Message:     "testing SetApplicationSetApplicationStatus to Healthy",
  3213  					Status:      "Healthy",
  3214  				},
  3215  			},
  3216  			expectedAppStatuses: []v1alpha1.ApplicationSetApplicationStatus{
  3217  				{
  3218  					Application: "app1",
  3219  					Message:     "testing SetApplicationSetApplicationStatus to Healthy",
  3220  					Status:      "Healthy",
  3221  				},
  3222  			},
  3223  		},
  3224  		{
  3225  			name: "removes an appstatus",
  3226  			appSet: v1alpha1.ApplicationSet{
  3227  				ObjectMeta: metav1.ObjectMeta{
  3228  					Name:      "name",
  3229  					Namespace: "argocd",
  3230  				},
  3231  				Spec: v1alpha1.ApplicationSetSpec{
  3232  					Generators: []v1alpha1.ApplicationSetGenerator{
  3233  						{List: &v1alpha1.ListGenerator{
  3234  							Elements: []apiextensionsv1.JSON{{
  3235  								Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
  3236  							}},
  3237  						}},
  3238  					},
  3239  					Template: v1alpha1.ApplicationSetTemplate{},
  3240  				},
  3241  				Status: v1alpha1.ApplicationSetStatus{
  3242  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  3243  						{
  3244  							Application: "app1",
  3245  							Message:     "testing SetApplicationSetApplicationStatus to Healthy",
  3246  							Status:      "Healthy",
  3247  						},
  3248  					},
  3249  				},
  3250  			},
  3251  			appStatuses:         []v1alpha1.ApplicationSetApplicationStatus{},
  3252  			expectedAppStatuses: nil,
  3253  		},
  3254  	} {
  3255  		t.Run(cc.name, func(t *testing.T) {
  3256  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).Build()
  3257  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  3258  
  3259  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  3260  
  3261  			r := ApplicationSetReconciler{
  3262  				Client:   client,
  3263  				Scheme:   scheme,
  3264  				Renderer: &utils.Render{},
  3265  				Recorder: record.NewFakeRecorder(1),
  3266  				Generators: map[string]generators.Generator{
  3267  					"List": generators.NewListGenerator(),
  3268  				},
  3269  				ArgoDB:        argodb,
  3270  				KubeClientset: kubeclientset,
  3271  				Metrics:       metrics,
  3272  			}
  3273  
  3274  			err = r.setAppSetApplicationStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appStatuses)
  3275  			require.NoError(t, err)
  3276  
  3277  			assert.Equal(t, cc.expectedAppStatuses, cc.appSet.Status.ApplicationStatus)
  3278  		})
  3279  	}
  3280  }
  3281  
  3282  func TestBuildAppDependencyList(t *testing.T) {
  3283  	scheme := runtime.NewScheme()
  3284  	err := v1alpha1.AddToScheme(scheme)
  3285  	require.NoError(t, err)
  3286  
  3287  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
  3288  	metrics := appsetmetrics.NewFakeAppsetMetrics()
  3289  
  3290  	for _, cc := range []struct {
  3291  		name            string
  3292  		appSet          v1alpha1.ApplicationSet
  3293  		apps            []v1alpha1.Application
  3294  		expectedList    [][]string
  3295  		expectedStepMap map[string]int
  3296  	}{
  3297  		{
  3298  			name: "handles an empty set of applications and no strategy",
  3299  			appSet: v1alpha1.ApplicationSet{
  3300  				ObjectMeta: metav1.ObjectMeta{
  3301  					Name:      "name",
  3302  					Namespace: "argocd",
  3303  				},
  3304  				Spec: v1alpha1.ApplicationSetSpec{},
  3305  			},
  3306  			apps:            []v1alpha1.Application{},
  3307  			expectedList:    [][]string{},
  3308  			expectedStepMap: map[string]int{},
  3309  		},
  3310  		{
  3311  			name: "handles an empty set of applications and ignores AllAtOnce strategy",
  3312  			appSet: v1alpha1.ApplicationSet{
  3313  				ObjectMeta: metav1.ObjectMeta{
  3314  					Name:      "name",
  3315  					Namespace: "argocd",
  3316  				},
  3317  				Spec: v1alpha1.ApplicationSetSpec{
  3318  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3319  						Type: "AllAtOnce",
  3320  					},
  3321  				},
  3322  			},
  3323  			apps:            []v1alpha1.Application{},
  3324  			expectedList:    [][]string{},
  3325  			expectedStepMap: map[string]int{},
  3326  		},
  3327  		{
  3328  			name: "handles an empty set of applications with good 'In' selectors",
  3329  			appSet: v1alpha1.ApplicationSet{
  3330  				ObjectMeta: metav1.ObjectMeta{
  3331  					Name:      "name",
  3332  					Namespace: "argocd",
  3333  				},
  3334  				Spec: v1alpha1.ApplicationSetSpec{
  3335  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3336  						Type: "RollingSync",
  3337  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3338  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3339  								{
  3340  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3341  										{
  3342  											Key:      "env",
  3343  											Operator: "In",
  3344  											Values: []string{
  3345  												"dev",
  3346  											},
  3347  										},
  3348  									},
  3349  								},
  3350  							},
  3351  						},
  3352  					},
  3353  				},
  3354  			},
  3355  			apps: []v1alpha1.Application{},
  3356  			expectedList: [][]string{
  3357  				{},
  3358  			},
  3359  			expectedStepMap: map[string]int{},
  3360  		},
  3361  		{
  3362  			name: "handles selecting 1 application with 1 'In' selector",
  3363  			appSet: v1alpha1.ApplicationSet{
  3364  				ObjectMeta: metav1.ObjectMeta{
  3365  					Name:      "name",
  3366  					Namespace: "argocd",
  3367  				},
  3368  				Spec: v1alpha1.ApplicationSetSpec{
  3369  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3370  						Type: "RollingSync",
  3371  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3372  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3373  								{
  3374  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3375  										{
  3376  											Key:      "env",
  3377  											Operator: "In",
  3378  											Values: []string{
  3379  												"dev",
  3380  											},
  3381  										},
  3382  									},
  3383  								},
  3384  							},
  3385  						},
  3386  					},
  3387  				},
  3388  			},
  3389  			apps: []v1alpha1.Application{
  3390  				{
  3391  					ObjectMeta: metav1.ObjectMeta{
  3392  						Name: "app-dev",
  3393  						Labels: map[string]string{
  3394  							"env": "dev",
  3395  						},
  3396  					},
  3397  				},
  3398  			},
  3399  			expectedList: [][]string{
  3400  				{"app-dev"},
  3401  			},
  3402  			expectedStepMap: map[string]int{
  3403  				"app-dev": 0,
  3404  			},
  3405  		},
  3406  		{
  3407  			name: "handles 'In' selectors that select no applications",
  3408  			appSet: v1alpha1.ApplicationSet{
  3409  				ObjectMeta: metav1.ObjectMeta{
  3410  					Name:      "name",
  3411  					Namespace: "argocd",
  3412  				},
  3413  				Spec: v1alpha1.ApplicationSetSpec{
  3414  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3415  						Type: "RollingSync",
  3416  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3417  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3418  								{
  3419  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3420  										{
  3421  											Key:      "env",
  3422  											Operator: "In",
  3423  											Values: []string{
  3424  												"dev",
  3425  											},
  3426  										},
  3427  									},
  3428  								},
  3429  								{
  3430  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3431  										{
  3432  											Key:      "env",
  3433  											Operator: "In",
  3434  											Values: []string{
  3435  												"qa",
  3436  											},
  3437  										},
  3438  									},
  3439  								},
  3440  								{
  3441  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3442  										{
  3443  											Key:      "env",
  3444  											Operator: "In",
  3445  											Values: []string{
  3446  												"prod",
  3447  											},
  3448  										},
  3449  									},
  3450  								},
  3451  							},
  3452  						},
  3453  					},
  3454  				},
  3455  			},
  3456  			apps: []v1alpha1.Application{
  3457  				{
  3458  					ObjectMeta: metav1.ObjectMeta{
  3459  						Name: "app-qa",
  3460  						Labels: map[string]string{
  3461  							"env": "qa",
  3462  						},
  3463  					},
  3464  				},
  3465  				{
  3466  					ObjectMeta: metav1.ObjectMeta{
  3467  						Name: "app-prod",
  3468  						Labels: map[string]string{
  3469  							"env": "prod",
  3470  						},
  3471  					},
  3472  				},
  3473  			},
  3474  			expectedList: [][]string{
  3475  				{},
  3476  				{"app-qa"},
  3477  				{"app-prod"},
  3478  			},
  3479  			expectedStepMap: map[string]int{
  3480  				"app-qa":   1,
  3481  				"app-prod": 2,
  3482  			},
  3483  		},
  3484  		{
  3485  			name: "multiple 'In' selectors in the same matchExpression only select Applications that match all selectors",
  3486  			appSet: v1alpha1.ApplicationSet{
  3487  				ObjectMeta: metav1.ObjectMeta{
  3488  					Name:      "name",
  3489  					Namespace: "argocd",
  3490  				},
  3491  				Spec: v1alpha1.ApplicationSetSpec{
  3492  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3493  						Type: "RollingSync",
  3494  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3495  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3496  								{
  3497  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3498  										{
  3499  											Key:      "region",
  3500  											Operator: "In",
  3501  											Values: []string{
  3502  												"us-east-2",
  3503  											},
  3504  										},
  3505  										{
  3506  											Key:      "env",
  3507  											Operator: "In",
  3508  											Values: []string{
  3509  												"qa",
  3510  											},
  3511  										},
  3512  									},
  3513  								},
  3514  							},
  3515  						},
  3516  					},
  3517  				},
  3518  			},
  3519  			apps: []v1alpha1.Application{
  3520  				{
  3521  					ObjectMeta: metav1.ObjectMeta{
  3522  						Name: "app-qa1",
  3523  						Labels: map[string]string{
  3524  							"env": "qa",
  3525  						},
  3526  					},
  3527  				},
  3528  				{
  3529  					ObjectMeta: metav1.ObjectMeta{
  3530  						Name: "app-qa2",
  3531  						Labels: map[string]string{
  3532  							"env":    "qa",
  3533  							"region": "us-east-2",
  3534  						},
  3535  					},
  3536  				},
  3537  			},
  3538  			expectedList: [][]string{
  3539  				{"app-qa2"},
  3540  			},
  3541  			expectedStepMap: map[string]int{
  3542  				"app-qa2": 0,
  3543  			},
  3544  		},
  3545  		{
  3546  			name: "multiple values in the same 'In' matchExpression can match on any value",
  3547  			appSet: v1alpha1.ApplicationSet{
  3548  				ObjectMeta: metav1.ObjectMeta{
  3549  					Name:      "name",
  3550  					Namespace: "argocd",
  3551  				},
  3552  				Spec: v1alpha1.ApplicationSetSpec{
  3553  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3554  						Type: "RollingSync",
  3555  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3556  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3557  								{
  3558  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3559  										{
  3560  											Key:      "env",
  3561  											Operator: "In",
  3562  											Values: []string{
  3563  												"qa",
  3564  												"prod",
  3565  											},
  3566  										},
  3567  									},
  3568  								},
  3569  							},
  3570  						},
  3571  					},
  3572  				},
  3573  			},
  3574  			apps: []v1alpha1.Application{
  3575  				{
  3576  					ObjectMeta: metav1.ObjectMeta{
  3577  						Name: "app-dev",
  3578  						Labels: map[string]string{
  3579  							"env": "dev",
  3580  						},
  3581  					},
  3582  				},
  3583  				{
  3584  					ObjectMeta: metav1.ObjectMeta{
  3585  						Name: "app-qa",
  3586  						Labels: map[string]string{
  3587  							"env": "qa",
  3588  						},
  3589  					},
  3590  				},
  3591  				{
  3592  					ObjectMeta: metav1.ObjectMeta{
  3593  						Name: "app-prod",
  3594  						Labels: map[string]string{
  3595  							"env":    "prod",
  3596  							"region": "us-east-2",
  3597  						},
  3598  					},
  3599  				},
  3600  			},
  3601  			expectedList: [][]string{
  3602  				{"app-qa", "app-prod"},
  3603  			},
  3604  			expectedStepMap: map[string]int{
  3605  				"app-qa":   0,
  3606  				"app-prod": 0,
  3607  			},
  3608  		},
  3609  		{
  3610  			name: "handles an empty set of applications with good 'NotIn' selectors",
  3611  			appSet: v1alpha1.ApplicationSet{
  3612  				ObjectMeta: metav1.ObjectMeta{
  3613  					Name:      "name",
  3614  					Namespace: "argocd",
  3615  				},
  3616  				Spec: v1alpha1.ApplicationSetSpec{
  3617  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3618  						Type: "RollingSync",
  3619  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3620  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3621  								{
  3622  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3623  										{
  3624  											Key:      "env",
  3625  											Operator: "In",
  3626  											Values: []string{
  3627  												"dev",
  3628  											},
  3629  										},
  3630  									},
  3631  								},
  3632  							},
  3633  						},
  3634  					},
  3635  				},
  3636  			},
  3637  			apps: []v1alpha1.Application{},
  3638  			expectedList: [][]string{
  3639  				{},
  3640  			},
  3641  			expectedStepMap: map[string]int{},
  3642  		},
  3643  		{
  3644  			name: "selects 1 application with 1 'NotIn' selector",
  3645  			appSet: v1alpha1.ApplicationSet{
  3646  				ObjectMeta: metav1.ObjectMeta{
  3647  					Name:      "name",
  3648  					Namespace: "argocd",
  3649  				},
  3650  				Spec: v1alpha1.ApplicationSetSpec{
  3651  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3652  						Type: "RollingSync",
  3653  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3654  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3655  								{
  3656  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3657  										{
  3658  											Key:      "env",
  3659  											Operator: "NotIn",
  3660  											Values: []string{
  3661  												"qa",
  3662  											},
  3663  										},
  3664  									},
  3665  								},
  3666  							},
  3667  						},
  3668  					},
  3669  				},
  3670  			},
  3671  			apps: []v1alpha1.Application{
  3672  				{
  3673  					ObjectMeta: metav1.ObjectMeta{
  3674  						Name: "app-dev",
  3675  						Labels: map[string]string{
  3676  							"env": "dev",
  3677  						},
  3678  					},
  3679  				},
  3680  			},
  3681  			expectedList: [][]string{
  3682  				{"app-dev"},
  3683  			},
  3684  			expectedStepMap: map[string]int{
  3685  				"app-dev": 0,
  3686  			},
  3687  		},
  3688  		{
  3689  			name: "'NotIn' selectors that select no applications",
  3690  			appSet: v1alpha1.ApplicationSet{
  3691  				ObjectMeta: metav1.ObjectMeta{
  3692  					Name:      "name",
  3693  					Namespace: "argocd",
  3694  				},
  3695  				Spec: v1alpha1.ApplicationSetSpec{
  3696  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3697  						Type: "RollingSync",
  3698  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3699  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3700  								{
  3701  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3702  										{
  3703  											Key:      "env",
  3704  											Operator: "NotIn",
  3705  											Values: []string{
  3706  												"dev",
  3707  											},
  3708  										},
  3709  									},
  3710  								},
  3711  							},
  3712  						},
  3713  					},
  3714  				},
  3715  			},
  3716  			apps: []v1alpha1.Application{
  3717  				{
  3718  					ObjectMeta: metav1.ObjectMeta{
  3719  						Name: "app-qa",
  3720  						Labels: map[string]string{
  3721  							"env": "qa",
  3722  						},
  3723  					},
  3724  				},
  3725  				{
  3726  					ObjectMeta: metav1.ObjectMeta{
  3727  						Name: "app-prod",
  3728  						Labels: map[string]string{
  3729  							"env": "prod",
  3730  						},
  3731  					},
  3732  				},
  3733  			},
  3734  			expectedList: [][]string{
  3735  				{"app-qa", "app-prod"},
  3736  			},
  3737  			expectedStepMap: map[string]int{
  3738  				"app-qa":   0,
  3739  				"app-prod": 0,
  3740  			},
  3741  		},
  3742  		{
  3743  			name: "multiple 'NotIn' selectors remove Applications with mising labels on any match",
  3744  			appSet: v1alpha1.ApplicationSet{
  3745  				ObjectMeta: metav1.ObjectMeta{
  3746  					Name:      "name",
  3747  					Namespace: "argocd",
  3748  				},
  3749  				Spec: v1alpha1.ApplicationSetSpec{
  3750  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3751  						Type: "RollingSync",
  3752  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3753  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3754  								{
  3755  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3756  										{
  3757  											Key:      "region",
  3758  											Operator: "NotIn",
  3759  											Values: []string{
  3760  												"us-east-2",
  3761  											},
  3762  										},
  3763  										{
  3764  											Key:      "env",
  3765  											Operator: "NotIn",
  3766  											Values: []string{
  3767  												"qa",
  3768  											},
  3769  										},
  3770  									},
  3771  								},
  3772  							},
  3773  						},
  3774  					},
  3775  				},
  3776  			},
  3777  			apps: []v1alpha1.Application{
  3778  				{
  3779  					ObjectMeta: metav1.ObjectMeta{
  3780  						Name: "app-qa1",
  3781  						Labels: map[string]string{
  3782  							"env": "qa",
  3783  						},
  3784  					},
  3785  				},
  3786  				{
  3787  					ObjectMeta: metav1.ObjectMeta{
  3788  						Name: "app-qa2",
  3789  						Labels: map[string]string{
  3790  							"env":    "qa",
  3791  							"region": "us-east-2",
  3792  						},
  3793  					},
  3794  				},
  3795  			},
  3796  			expectedList: [][]string{
  3797  				{},
  3798  			},
  3799  			expectedStepMap: map[string]int{},
  3800  		},
  3801  		{
  3802  			name: "multiple 'NotIn' selectors filter all matching Applications",
  3803  			appSet: v1alpha1.ApplicationSet{
  3804  				ObjectMeta: metav1.ObjectMeta{
  3805  					Name:      "name",
  3806  					Namespace: "argocd",
  3807  				},
  3808  				Spec: v1alpha1.ApplicationSetSpec{
  3809  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3810  						Type: "RollingSync",
  3811  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3812  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3813  								{
  3814  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3815  										{
  3816  											Key:      "region",
  3817  											Operator: "NotIn",
  3818  											Values: []string{
  3819  												"us-east-2",
  3820  											},
  3821  										},
  3822  										{
  3823  											Key:      "env",
  3824  											Operator: "NotIn",
  3825  											Values: []string{
  3826  												"qa",
  3827  											},
  3828  										},
  3829  									},
  3830  								},
  3831  							},
  3832  						},
  3833  					},
  3834  				},
  3835  			},
  3836  			apps: []v1alpha1.Application{
  3837  				{
  3838  					ObjectMeta: metav1.ObjectMeta{
  3839  						Name: "app-qa1",
  3840  						Labels: map[string]string{
  3841  							"env":    "qa",
  3842  							"region": "us-east-1",
  3843  						},
  3844  					},
  3845  				},
  3846  				{
  3847  					ObjectMeta: metav1.ObjectMeta{
  3848  						Name: "app-qa2",
  3849  						Labels: map[string]string{
  3850  							"env":    "qa",
  3851  							"region": "us-east-2",
  3852  						},
  3853  					},
  3854  				},
  3855  				{
  3856  					ObjectMeta: metav1.ObjectMeta{
  3857  						Name: "app-prod1",
  3858  						Labels: map[string]string{
  3859  							"env":    "prod",
  3860  							"region": "us-east-1",
  3861  						},
  3862  					},
  3863  				},
  3864  				{
  3865  					ObjectMeta: metav1.ObjectMeta{
  3866  						Name: "app-prod2",
  3867  						Labels: map[string]string{
  3868  							"env":    "prod",
  3869  							"region": "us-east-2",
  3870  						},
  3871  					},
  3872  				},
  3873  			},
  3874  			expectedList: [][]string{
  3875  				{"app-prod1"},
  3876  			},
  3877  			expectedStepMap: map[string]int{
  3878  				"app-prod1": 0,
  3879  			},
  3880  		},
  3881  		{
  3882  			name: "multiple values in the same 'NotIn' matchExpression exclude a match from any value",
  3883  			appSet: v1alpha1.ApplicationSet{
  3884  				ObjectMeta: metav1.ObjectMeta{
  3885  					Name:      "name",
  3886  					Namespace: "argocd",
  3887  				},
  3888  				Spec: v1alpha1.ApplicationSetSpec{
  3889  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3890  						Type: "RollingSync",
  3891  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3892  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3893  								{
  3894  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3895  										{
  3896  											Key:      "env",
  3897  											Operator: "NotIn",
  3898  											Values: []string{
  3899  												"qa",
  3900  												"prod",
  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-qa",
  3922  						Labels: map[string]string{
  3923  							"env": "qa",
  3924  						},
  3925  					},
  3926  				},
  3927  				{
  3928  					ObjectMeta: metav1.ObjectMeta{
  3929  						Name: "app-prod",
  3930  						Labels: map[string]string{
  3931  							"env":    "prod",
  3932  							"region": "us-east-2",
  3933  						},
  3934  					},
  3935  				},
  3936  			},
  3937  			expectedList: [][]string{
  3938  				{"app-dev"},
  3939  			},
  3940  			expectedStepMap: map[string]int{
  3941  				"app-dev": 0,
  3942  			},
  3943  		},
  3944  		{
  3945  			name: "in a mix of 'In' and 'NotIn' selectors, 'NotIn' takes precedence",
  3946  			appSet: v1alpha1.ApplicationSet{
  3947  				ObjectMeta: metav1.ObjectMeta{
  3948  					Name:      "name",
  3949  					Namespace: "argocd",
  3950  				},
  3951  				Spec: v1alpha1.ApplicationSetSpec{
  3952  					Strategy: &v1alpha1.ApplicationSetStrategy{
  3953  						Type: "RollingSync",
  3954  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  3955  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  3956  								{
  3957  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{
  3958  										{
  3959  											Key:      "env",
  3960  											Operator: "In",
  3961  											Values: []string{
  3962  												"qa",
  3963  												"prod",
  3964  											},
  3965  										},
  3966  										{
  3967  											Key:      "region",
  3968  											Operator: "NotIn",
  3969  											Values: []string{
  3970  												"us-west-2",
  3971  											},
  3972  										},
  3973  									},
  3974  								},
  3975  							},
  3976  						},
  3977  					},
  3978  				},
  3979  			},
  3980  			apps: []v1alpha1.Application{
  3981  				{
  3982  					ObjectMeta: metav1.ObjectMeta{
  3983  						Name: "app-dev",
  3984  						Labels: map[string]string{
  3985  							"env": "dev",
  3986  						},
  3987  					},
  3988  				},
  3989  				{
  3990  					ObjectMeta: metav1.ObjectMeta{
  3991  						Name: "app-qa1",
  3992  						Labels: map[string]string{
  3993  							"env":    "qa",
  3994  							"region": "us-west-2",
  3995  						},
  3996  					},
  3997  				},
  3998  				{
  3999  					ObjectMeta: metav1.ObjectMeta{
  4000  						Name: "app-qa2",
  4001  						Labels: map[string]string{
  4002  							"env":    "qa",
  4003  							"region": "us-east-2",
  4004  						},
  4005  					},
  4006  				},
  4007  			},
  4008  			expectedList: [][]string{
  4009  				{"app-qa2"},
  4010  			},
  4011  			expectedStepMap: map[string]int{
  4012  				"app-qa2": 0,
  4013  			},
  4014  		},
  4015  	} {
  4016  		t.Run(cc.name, func(t *testing.T) {
  4017  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  4018  
  4019  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  4020  
  4021  			r := ApplicationSetReconciler{
  4022  				Client:        client,
  4023  				Scheme:        scheme,
  4024  				Recorder:      record.NewFakeRecorder(1),
  4025  				Generators:    map[string]generators.Generator{},
  4026  				ArgoDB:        argodb,
  4027  				KubeClientset: kubeclientset,
  4028  				Metrics:       metrics,
  4029  			}
  4030  
  4031  			appDependencyList, appStepMap := r.buildAppDependencyList(log.NewEntry(log.StandardLogger()), cc.appSet, cc.apps)
  4032  			assert.Equal(t, cc.expectedList, appDependencyList, "expected appDependencyList did not match actual")
  4033  			assert.Equal(t, cc.expectedStepMap, appStepMap, "expected appStepMap did not match actual")
  4034  		})
  4035  	}
  4036  }
  4037  
  4038  func TestBuildAppSyncMap(t *testing.T) {
  4039  	scheme := runtime.NewScheme()
  4040  	err := v1alpha1.AddToScheme(scheme)
  4041  	require.NoError(t, err)
  4042  
  4043  	client := fake.NewClientBuilder().WithScheme(scheme).Build()
  4044  	metrics := appsetmetrics.NewFakeAppsetMetrics()
  4045  
  4046  	for _, cc := range []struct {
  4047  		name              string
  4048  		appSet            v1alpha1.ApplicationSet
  4049  		appMap            map[string]v1alpha1.Application
  4050  		appDependencyList [][]string
  4051  		expectedMap       map[string]bool
  4052  	}{
  4053  		{
  4054  			name: "handles an empty app dependency list",
  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  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4065  								{
  4066  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4067  								},
  4068  								{
  4069  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4070  								},
  4071  							},
  4072  						},
  4073  					},
  4074  				},
  4075  			},
  4076  			appDependencyList: [][]string{},
  4077  			expectedMap:       map[string]bool{},
  4078  		},
  4079  		{
  4080  			name: "handles two applications with no statuses",
  4081  			appSet: v1alpha1.ApplicationSet{
  4082  				ObjectMeta: metav1.ObjectMeta{
  4083  					Name:      "name",
  4084  					Namespace: "argocd",
  4085  				},
  4086  				Spec: v1alpha1.ApplicationSetSpec{
  4087  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4088  						Type: "RollingSync",
  4089  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4090  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4091  								{
  4092  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4093  								},
  4094  								{
  4095  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4096  								},
  4097  							},
  4098  						},
  4099  					},
  4100  				},
  4101  			},
  4102  			appDependencyList: [][]string{
  4103  				{"app1"},
  4104  				{"app2"},
  4105  			},
  4106  			expectedMap: map[string]bool{
  4107  				"app1": true,
  4108  				"app2": false,
  4109  			},
  4110  		},
  4111  		{
  4112  			name: "handles applications after an empty selection",
  4113  			appSet: v1alpha1.ApplicationSet{
  4114  				ObjectMeta: metav1.ObjectMeta{
  4115  					Name:      "name",
  4116  					Namespace: "argocd",
  4117  				},
  4118  				Spec: v1alpha1.ApplicationSetSpec{
  4119  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4120  						Type: "RollingSync",
  4121  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4122  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4123  								{
  4124  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4125  								},
  4126  								{
  4127  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4128  								},
  4129  							},
  4130  						},
  4131  					},
  4132  				},
  4133  			},
  4134  			appDependencyList: [][]string{
  4135  				{},
  4136  				{"app1", "app2"},
  4137  			},
  4138  			expectedMap: map[string]bool{
  4139  				"app1": true,
  4140  				"app2": true,
  4141  			},
  4142  		},
  4143  		{
  4144  			name: "handles RollingSync applications that are healthy and have no changes",
  4145  			appSet: v1alpha1.ApplicationSet{
  4146  				ObjectMeta: metav1.ObjectMeta{
  4147  					Name:      "name",
  4148  					Namespace: "argocd",
  4149  				},
  4150  				Spec: v1alpha1.ApplicationSetSpec{
  4151  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4152  						Type: "RollingSync",
  4153  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4154  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4155  								{
  4156  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4157  								},
  4158  								{
  4159  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4160  								},
  4161  							},
  4162  						},
  4163  					},
  4164  				},
  4165  				Status: v1alpha1.ApplicationSetStatus{
  4166  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4167  						{
  4168  							Application: "app1",
  4169  							Status:      "Healthy",
  4170  						},
  4171  						{
  4172  							Application: "app2",
  4173  							Status:      "Healthy",
  4174  						},
  4175  					},
  4176  				},
  4177  			},
  4178  			appMap: map[string]v1alpha1.Application{
  4179  				"app1": {
  4180  					ObjectMeta: metav1.ObjectMeta{
  4181  						Name: "app1",
  4182  					},
  4183  					Status: v1alpha1.ApplicationStatus{
  4184  						Health: v1alpha1.AppHealthStatus{
  4185  							Status: health.HealthStatusHealthy,
  4186  						},
  4187  						OperationState: &v1alpha1.OperationState{
  4188  							Phase: common.OperationSucceeded,
  4189  						},
  4190  						Sync: v1alpha1.SyncStatus{
  4191  							Status: v1alpha1.SyncStatusCodeSynced,
  4192  						},
  4193  					},
  4194  				},
  4195  				"app2": {
  4196  					ObjectMeta: metav1.ObjectMeta{
  4197  						Name: "app2",
  4198  					},
  4199  					Status: v1alpha1.ApplicationStatus{
  4200  						Health: v1alpha1.AppHealthStatus{
  4201  							Status: health.HealthStatusHealthy,
  4202  						},
  4203  						OperationState: &v1alpha1.OperationState{
  4204  							Phase: common.OperationSucceeded,
  4205  						},
  4206  						Sync: v1alpha1.SyncStatus{
  4207  							Status: v1alpha1.SyncStatusCodeSynced,
  4208  						},
  4209  					},
  4210  				},
  4211  			},
  4212  			appDependencyList: [][]string{
  4213  				{"app1"},
  4214  				{"app2"},
  4215  			},
  4216  			expectedMap: map[string]bool{
  4217  				"app1": true,
  4218  				"app2": true,
  4219  			},
  4220  		},
  4221  		{
  4222  			name: "blocks RollingSync applications that are healthy and have no changes, but are still pending",
  4223  			appSet: v1alpha1.ApplicationSet{
  4224  				ObjectMeta: metav1.ObjectMeta{
  4225  					Name:      "name",
  4226  					Namespace: "argocd",
  4227  				},
  4228  				Spec: v1alpha1.ApplicationSetSpec{
  4229  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4230  						Type: "RollingSync",
  4231  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4232  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4233  								{
  4234  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4235  								},
  4236  								{
  4237  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4238  								},
  4239  							},
  4240  						},
  4241  					},
  4242  				},
  4243  				Status: v1alpha1.ApplicationSetStatus{
  4244  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4245  						{
  4246  							Application: "app1",
  4247  							Status:      "Pending",
  4248  						},
  4249  						{
  4250  							Application: "app2",
  4251  							Status:      "Healthy",
  4252  						},
  4253  					},
  4254  				},
  4255  			},
  4256  			appMap: map[string]v1alpha1.Application{
  4257  				"app1": {
  4258  					ObjectMeta: metav1.ObjectMeta{
  4259  						Name: "app1",
  4260  					},
  4261  					Status: v1alpha1.ApplicationStatus{
  4262  						Health: v1alpha1.AppHealthStatus{
  4263  							Status: health.HealthStatusHealthy,
  4264  						},
  4265  						OperationState: &v1alpha1.OperationState{
  4266  							Phase: common.OperationSucceeded,
  4267  						},
  4268  						Sync: v1alpha1.SyncStatus{
  4269  							Status: v1alpha1.SyncStatusCodeSynced,
  4270  						},
  4271  					},
  4272  				},
  4273  				"app2": {
  4274  					ObjectMeta: metav1.ObjectMeta{
  4275  						Name: "app2",
  4276  					},
  4277  					Status: v1alpha1.ApplicationStatus{
  4278  						Health: v1alpha1.AppHealthStatus{
  4279  							Status: health.HealthStatusHealthy,
  4280  						},
  4281  						OperationState: &v1alpha1.OperationState{
  4282  							Phase: common.OperationSucceeded,
  4283  						},
  4284  						Sync: v1alpha1.SyncStatus{
  4285  							Status: v1alpha1.SyncStatusCodeSynced,
  4286  						},
  4287  					},
  4288  				},
  4289  			},
  4290  			appDependencyList: [][]string{
  4291  				{"app1"},
  4292  				{"app2"},
  4293  			},
  4294  			expectedMap: map[string]bool{
  4295  				"app1": true,
  4296  				"app2": false,
  4297  			},
  4298  		},
  4299  		{
  4300  			name: "handles RollingSync applications that are up to date and healthy, but still syncing",
  4301  			appSet: v1alpha1.ApplicationSet{
  4302  				ObjectMeta: metav1.ObjectMeta{
  4303  					Name:      "name",
  4304  					Namespace: "argocd",
  4305  				},
  4306  				Spec: v1alpha1.ApplicationSetSpec{
  4307  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4308  						Type: "RollingSync",
  4309  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4310  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4311  								{
  4312  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4313  								},
  4314  								{
  4315  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4316  								},
  4317  							},
  4318  						},
  4319  					},
  4320  				},
  4321  				Status: v1alpha1.ApplicationSetStatus{
  4322  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4323  						{
  4324  							Application: "app1",
  4325  							Status:      "Progressing",
  4326  						},
  4327  						{
  4328  							Application: "app2",
  4329  							Status:      "Progressing",
  4330  						},
  4331  					},
  4332  				},
  4333  			},
  4334  			appMap: map[string]v1alpha1.Application{
  4335  				"app1": {
  4336  					ObjectMeta: metav1.ObjectMeta{
  4337  						Name: "app1",
  4338  					},
  4339  					Status: v1alpha1.ApplicationStatus{
  4340  						Health: v1alpha1.AppHealthStatus{
  4341  							Status: health.HealthStatusHealthy,
  4342  						},
  4343  						OperationState: &v1alpha1.OperationState{
  4344  							Phase: common.OperationRunning,
  4345  						},
  4346  						Sync: v1alpha1.SyncStatus{
  4347  							Status: v1alpha1.SyncStatusCodeSynced,
  4348  						},
  4349  					},
  4350  				},
  4351  				"app2": {
  4352  					ObjectMeta: metav1.ObjectMeta{
  4353  						Name: "app2",
  4354  					},
  4355  					Status: v1alpha1.ApplicationStatus{
  4356  						Health: v1alpha1.AppHealthStatus{
  4357  							Status: health.HealthStatusHealthy,
  4358  						},
  4359  						OperationState: &v1alpha1.OperationState{
  4360  							Phase: common.OperationRunning,
  4361  						},
  4362  						Sync: v1alpha1.SyncStatus{
  4363  							Status: v1alpha1.SyncStatusCodeSynced,
  4364  						},
  4365  					},
  4366  				},
  4367  			},
  4368  			appDependencyList: [][]string{
  4369  				{"app1"},
  4370  				{"app2"},
  4371  			},
  4372  			expectedMap: map[string]bool{
  4373  				"app1": true,
  4374  				"app2": false,
  4375  			},
  4376  		},
  4377  		{
  4378  			name: "handles RollingSync applications that are up to date and synced, but degraded",
  4379  			appSet: v1alpha1.ApplicationSet{
  4380  				ObjectMeta: metav1.ObjectMeta{
  4381  					Name:      "name",
  4382  					Namespace: "argocd",
  4383  				},
  4384  				Spec: v1alpha1.ApplicationSetSpec{
  4385  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4386  						Type: "RollingSync",
  4387  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4388  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4389  								{
  4390  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4391  								},
  4392  								{
  4393  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4394  								},
  4395  							},
  4396  						},
  4397  					},
  4398  				},
  4399  				Status: v1alpha1.ApplicationSetStatus{
  4400  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4401  						{
  4402  							Application: "app1",
  4403  							Status:      "Progressing",
  4404  						},
  4405  						{
  4406  							Application: "app2",
  4407  							Status:      "Progressing",
  4408  						},
  4409  					},
  4410  				},
  4411  			},
  4412  			appMap: map[string]v1alpha1.Application{
  4413  				"app1": {
  4414  					ObjectMeta: metav1.ObjectMeta{
  4415  						Name: "app1",
  4416  					},
  4417  					Status: v1alpha1.ApplicationStatus{
  4418  						Health: v1alpha1.AppHealthStatus{
  4419  							Status: health.HealthStatusDegraded,
  4420  						},
  4421  						OperationState: &v1alpha1.OperationState{
  4422  							Phase: common.OperationRunning,
  4423  						},
  4424  						Sync: v1alpha1.SyncStatus{
  4425  							Status: v1alpha1.SyncStatusCodeSynced,
  4426  						},
  4427  					},
  4428  				},
  4429  				"app2": {
  4430  					ObjectMeta: metav1.ObjectMeta{
  4431  						Name: "app2",
  4432  					},
  4433  					Status: v1alpha1.ApplicationStatus{
  4434  						Health: v1alpha1.AppHealthStatus{
  4435  							Status: health.HealthStatusDegraded,
  4436  						},
  4437  						OperationState: &v1alpha1.OperationState{
  4438  							Phase: common.OperationRunning,
  4439  						},
  4440  						Sync: v1alpha1.SyncStatus{
  4441  							Status: v1alpha1.SyncStatusCodeSynced,
  4442  						},
  4443  					},
  4444  				},
  4445  			},
  4446  			appDependencyList: [][]string{
  4447  				{"app1"},
  4448  				{"app2"},
  4449  			},
  4450  			expectedMap: map[string]bool{
  4451  				"app1": true,
  4452  				"app2": false,
  4453  			},
  4454  		},
  4455  		{
  4456  			name: "handles RollingSync applications that are OutOfSync and healthy",
  4457  			appSet: v1alpha1.ApplicationSet{
  4458  				ObjectMeta: metav1.ObjectMeta{
  4459  					Name:      "name",
  4460  					Namespace: "argocd",
  4461  				},
  4462  				Spec: v1alpha1.ApplicationSetSpec{
  4463  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4464  						Type: "RollingSync",
  4465  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4466  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4467  								{
  4468  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4469  								},
  4470  								{
  4471  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4472  								},
  4473  							},
  4474  						},
  4475  					},
  4476  				},
  4477  				Status: v1alpha1.ApplicationSetStatus{
  4478  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4479  						{
  4480  							Application: "app1",
  4481  							Status:      "Healthy",
  4482  						},
  4483  						{
  4484  							Application: "app2",
  4485  							Status:      "Healthy",
  4486  						},
  4487  					},
  4488  				},
  4489  			},
  4490  			appDependencyList: [][]string{
  4491  				{"app1"},
  4492  				{"app2"},
  4493  			},
  4494  			appMap: map[string]v1alpha1.Application{
  4495  				"app1": {
  4496  					ObjectMeta: metav1.ObjectMeta{
  4497  						Name: "app1",
  4498  					},
  4499  					Status: v1alpha1.ApplicationStatus{
  4500  						Health: v1alpha1.AppHealthStatus{
  4501  							Status: health.HealthStatusHealthy,
  4502  						},
  4503  						OperationState: &v1alpha1.OperationState{
  4504  							Phase: common.OperationSucceeded,
  4505  						},
  4506  						Sync: v1alpha1.SyncStatus{
  4507  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  4508  						},
  4509  					},
  4510  				},
  4511  				"app2": {
  4512  					ObjectMeta: metav1.ObjectMeta{
  4513  						Name: "app2",
  4514  					},
  4515  					Status: v1alpha1.ApplicationStatus{
  4516  						Health: v1alpha1.AppHealthStatus{
  4517  							Status: health.HealthStatusHealthy,
  4518  						},
  4519  						OperationState: &v1alpha1.OperationState{
  4520  							Phase: common.OperationSucceeded,
  4521  						},
  4522  						Sync: v1alpha1.SyncStatus{
  4523  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  4524  						},
  4525  					},
  4526  				},
  4527  			},
  4528  			expectedMap: map[string]bool{
  4529  				"app1": true,
  4530  				"app2": false,
  4531  			},
  4532  		},
  4533  		{
  4534  			name: "handles a lot of applications",
  4535  			appSet: v1alpha1.ApplicationSet{
  4536  				ObjectMeta: metav1.ObjectMeta{
  4537  					Name:      "name",
  4538  					Namespace: "argocd",
  4539  				},
  4540  				Spec: v1alpha1.ApplicationSetSpec{
  4541  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4542  						Type: "RollingSync",
  4543  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4544  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4545  								{
  4546  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4547  								},
  4548  								{
  4549  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4550  								},
  4551  							},
  4552  						},
  4553  					},
  4554  				},
  4555  				Status: v1alpha1.ApplicationSetStatus{
  4556  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4557  						{
  4558  							Application: "app1",
  4559  							Status:      "Healthy",
  4560  						},
  4561  						{
  4562  							Application: "app2",
  4563  							Status:      "Healthy",
  4564  						},
  4565  						{
  4566  							Application: "app3",
  4567  							Status:      "Healthy",
  4568  						},
  4569  						{
  4570  							Application: "app4",
  4571  							Status:      "Healthy",
  4572  						},
  4573  						{
  4574  							Application: "app5",
  4575  							Status:      "Healthy",
  4576  						},
  4577  						{
  4578  							Application: "app7",
  4579  							Status:      "Healthy",
  4580  						},
  4581  					},
  4582  				},
  4583  			},
  4584  			appMap: map[string]v1alpha1.Application{
  4585  				"app1": {
  4586  					ObjectMeta: metav1.ObjectMeta{
  4587  						Name: "app1",
  4588  					},
  4589  					Status: v1alpha1.ApplicationStatus{
  4590  						Health: v1alpha1.AppHealthStatus{
  4591  							Status: health.HealthStatusHealthy,
  4592  						},
  4593  						OperationState: &v1alpha1.OperationState{
  4594  							Phase: common.OperationSucceeded,
  4595  						},
  4596  						Sync: v1alpha1.SyncStatus{
  4597  							Status: v1alpha1.SyncStatusCodeSynced,
  4598  						},
  4599  					},
  4600  				},
  4601  				"app2": {
  4602  					ObjectMeta: metav1.ObjectMeta{
  4603  						Name: "app2",
  4604  					},
  4605  					Status: v1alpha1.ApplicationStatus{
  4606  						Health: v1alpha1.AppHealthStatus{
  4607  							Status: health.HealthStatusHealthy,
  4608  						},
  4609  						OperationState: &v1alpha1.OperationState{
  4610  							Phase: common.OperationSucceeded,
  4611  						},
  4612  						Sync: v1alpha1.SyncStatus{
  4613  							Status: v1alpha1.SyncStatusCodeSynced,
  4614  						},
  4615  					},
  4616  				},
  4617  				"app3": {
  4618  					ObjectMeta: metav1.ObjectMeta{
  4619  						Name: "app3",
  4620  					},
  4621  					Status: v1alpha1.ApplicationStatus{
  4622  						Health: v1alpha1.AppHealthStatus{
  4623  							Status: health.HealthStatusHealthy,
  4624  						},
  4625  						OperationState: &v1alpha1.OperationState{
  4626  							Phase: common.OperationSucceeded,
  4627  						},
  4628  						Sync: v1alpha1.SyncStatus{
  4629  							Status: v1alpha1.SyncStatusCodeSynced,
  4630  						},
  4631  					},
  4632  				},
  4633  				"app5": {
  4634  					ObjectMeta: metav1.ObjectMeta{
  4635  						Name: "app5",
  4636  					},
  4637  					Status: v1alpha1.ApplicationStatus{
  4638  						Health: v1alpha1.AppHealthStatus{
  4639  							Status: health.HealthStatusHealthy,
  4640  						},
  4641  						OperationState: &v1alpha1.OperationState{
  4642  							Phase: common.OperationSucceeded,
  4643  						},
  4644  						Sync: v1alpha1.SyncStatus{
  4645  							Status: v1alpha1.SyncStatusCodeSynced,
  4646  						},
  4647  					},
  4648  				},
  4649  				"app6": {
  4650  					ObjectMeta: metav1.ObjectMeta{
  4651  						Name: "app6",
  4652  					},
  4653  					Status: v1alpha1.ApplicationStatus{
  4654  						Health: v1alpha1.AppHealthStatus{
  4655  							Status: health.HealthStatusDegraded,
  4656  						},
  4657  						OperationState: &v1alpha1.OperationState{
  4658  							Phase: common.OperationSucceeded,
  4659  						},
  4660  						Sync: v1alpha1.SyncStatus{
  4661  							Status: v1alpha1.SyncStatusCodeSynced,
  4662  						},
  4663  					},
  4664  				},
  4665  			},
  4666  			appDependencyList: [][]string{
  4667  				{"app1", "app2", "app3"},
  4668  				{"app4", "app5", "app6"},
  4669  				{"app7", "app8", "app9"},
  4670  			},
  4671  			expectedMap: map[string]bool{
  4672  				"app1": true,
  4673  				"app2": true,
  4674  				"app3": true,
  4675  				"app4": true,
  4676  				"app5": true,
  4677  				"app6": true,
  4678  				"app7": false,
  4679  				"app8": false,
  4680  				"app9": false,
  4681  			},
  4682  		},
  4683  	} {
  4684  		t.Run(cc.name, func(t *testing.T) {
  4685  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  4686  
  4687  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  4688  
  4689  			r := ApplicationSetReconciler{
  4690  				Client:        client,
  4691  				Scheme:        scheme,
  4692  				Recorder:      record.NewFakeRecorder(1),
  4693  				Generators:    map[string]generators.Generator{},
  4694  				ArgoDB:        argodb,
  4695  				KubeClientset: kubeclientset,
  4696  				Metrics:       metrics,
  4697  			}
  4698  
  4699  			appSyncMap := r.buildAppSyncMap(cc.appSet, cc.appDependencyList, cc.appMap)
  4700  			assert.Equal(t, cc.expectedMap, appSyncMap, "expected appSyncMap did not match actual")
  4701  		})
  4702  	}
  4703  }
  4704  
  4705  func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
  4706  	scheme := runtime.NewScheme()
  4707  	err := v1alpha1.AddToScheme(scheme)
  4708  	require.NoError(t, err)
  4709  
  4710  	for _, cc := range []struct {
  4711  		name              string
  4712  		appSet            v1alpha1.ApplicationSet
  4713  		apps              []v1alpha1.Application
  4714  		appStepMap        map[string]int
  4715  		expectedAppStatus []v1alpha1.ApplicationSetApplicationStatus
  4716  	}{
  4717  		{
  4718  			name: "handles a nil list of statuses and no applications",
  4719  			appSet: v1alpha1.ApplicationSet{
  4720  				ObjectMeta: metav1.ObjectMeta{
  4721  					Name:      "name",
  4722  					Namespace: "argocd",
  4723  				},
  4724  				Spec: v1alpha1.ApplicationSetSpec{
  4725  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4726  						Type: "RollingSync",
  4727  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4728  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4729  								{
  4730  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4731  								},
  4732  								{
  4733  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4734  								},
  4735  							},
  4736  						},
  4737  					},
  4738  				},
  4739  			},
  4740  			apps:              []v1alpha1.Application{},
  4741  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  4742  		},
  4743  		{
  4744  			name: "handles a nil list of statuses with a healthy application",
  4745  			appSet: v1alpha1.ApplicationSet{
  4746  				ObjectMeta: metav1.ObjectMeta{
  4747  					Name:      "name",
  4748  					Namespace: "argocd",
  4749  				},
  4750  				Spec: v1alpha1.ApplicationSetSpec{
  4751  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4752  						Type: "RollingSync",
  4753  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4754  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4755  								{
  4756  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4757  								},
  4758  								{
  4759  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4760  								},
  4761  							},
  4762  						},
  4763  					},
  4764  				},
  4765  			},
  4766  			apps: []v1alpha1.Application{
  4767  				{
  4768  					ObjectMeta: metav1.ObjectMeta{
  4769  						Name: "app1",
  4770  					},
  4771  					Status: v1alpha1.ApplicationStatus{
  4772  						Health: v1alpha1.AppHealthStatus{
  4773  							Status: health.HealthStatusHealthy,
  4774  						},
  4775  						OperationState: &v1alpha1.OperationState{
  4776  							Phase: common.OperationSucceeded,
  4777  						},
  4778  						Sync: v1alpha1.SyncStatus{
  4779  							Status: v1alpha1.SyncStatusCodeSynced,
  4780  						},
  4781  					},
  4782  				},
  4783  			},
  4784  			appStepMap: map[string]int{
  4785  				"app1": 0,
  4786  			},
  4787  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4788  				{
  4789  					Application:     "app1",
  4790  					Message:         "Application resource is already Healthy, updating status from Waiting to Healthy.",
  4791  					Status:          "Healthy",
  4792  					Step:            "1",
  4793  					TargetRevisions: []string{},
  4794  				},
  4795  			},
  4796  		},
  4797  		{
  4798  			name: "handles an empty list of statuses with a healthy application",
  4799  			appSet: v1alpha1.ApplicationSet{
  4800  				ObjectMeta: metav1.ObjectMeta{
  4801  					Name:      "name",
  4802  					Namespace: "argocd",
  4803  				},
  4804  				Spec: v1alpha1.ApplicationSetSpec{
  4805  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4806  						Type: "RollingSync",
  4807  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4808  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4809  								{
  4810  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4811  								},
  4812  								{
  4813  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4814  								},
  4815  							},
  4816  						},
  4817  					},
  4818  				},
  4819  				Status: v1alpha1.ApplicationSetStatus{},
  4820  			},
  4821  			apps: []v1alpha1.Application{
  4822  				{
  4823  					ObjectMeta: metav1.ObjectMeta{
  4824  						Name: "app1",
  4825  					},
  4826  					Status: v1alpha1.ApplicationStatus{
  4827  						Health: v1alpha1.AppHealthStatus{
  4828  							Status: health.HealthStatusHealthy,
  4829  						},
  4830  						OperationState: &v1alpha1.OperationState{
  4831  							Phase: common.OperationSucceeded,
  4832  						},
  4833  						Sync: v1alpha1.SyncStatus{
  4834  							Status: v1alpha1.SyncStatusCodeSynced,
  4835  						},
  4836  					},
  4837  				},
  4838  			},
  4839  			appStepMap: map[string]int{
  4840  				"app1": 0,
  4841  			},
  4842  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4843  				{
  4844  					Application:     "app1",
  4845  					Message:         "Application resource is already Healthy, updating status from Waiting to Healthy.",
  4846  					Status:          "Healthy",
  4847  					Step:            "1",
  4848  					TargetRevisions: []string{},
  4849  				},
  4850  			},
  4851  		},
  4852  		{
  4853  			name: "handles an outdated list of statuses with a healthy application, setting required variables",
  4854  			appSet: v1alpha1.ApplicationSet{
  4855  				ObjectMeta: metav1.ObjectMeta{
  4856  					Name:      "name",
  4857  					Namespace: "argocd",
  4858  				},
  4859  				Spec: v1alpha1.ApplicationSetSpec{
  4860  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4861  						Type: "RollingSync",
  4862  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4863  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4864  								{
  4865  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4866  								},
  4867  								{
  4868  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4869  								},
  4870  							},
  4871  						},
  4872  					},
  4873  				},
  4874  				Status: v1alpha1.ApplicationSetStatus{
  4875  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4876  						{
  4877  							Application: "app1",
  4878  							Message:     "Application resource is already Healthy, updating status from Waiting to Healthy.",
  4879  							Status:      "Healthy",
  4880  							Step:        "1",
  4881  						},
  4882  					},
  4883  				},
  4884  			},
  4885  			apps: []v1alpha1.Application{
  4886  				{
  4887  					ObjectMeta: metav1.ObjectMeta{
  4888  						Name: "app1",
  4889  					},
  4890  					Status: v1alpha1.ApplicationStatus{
  4891  						Health: v1alpha1.AppHealthStatus{
  4892  							Status: health.HealthStatusHealthy,
  4893  						},
  4894  						OperationState: &v1alpha1.OperationState{
  4895  							Phase: common.OperationSucceeded,
  4896  						},
  4897  						Sync: v1alpha1.SyncStatus{
  4898  							Status: v1alpha1.SyncStatusCodeSynced,
  4899  						},
  4900  					},
  4901  				},
  4902  			},
  4903  			appStepMap: map[string]int{
  4904  				"app1": 0,
  4905  			},
  4906  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4907  				{
  4908  					Application:     "app1",
  4909  					Message:         "Application resource is already Healthy, updating status from Waiting to Healthy.",
  4910  					Status:          "Healthy",
  4911  					Step:            "1",
  4912  					TargetRevisions: []string{},
  4913  				},
  4914  			},
  4915  		},
  4916  		{
  4917  			name: "progresses an OutOfSync RollingSync application to waiting",
  4918  			appSet: v1alpha1.ApplicationSet{
  4919  				ObjectMeta: metav1.ObjectMeta{
  4920  					Name:      "name",
  4921  					Namespace: "argocd",
  4922  				},
  4923  				Spec: v1alpha1.ApplicationSetSpec{
  4924  					Strategy: &v1alpha1.ApplicationSetStrategy{
  4925  						Type: "RollingSync",
  4926  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  4927  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  4928  								{
  4929  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4930  								},
  4931  								{
  4932  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  4933  								},
  4934  							},
  4935  						},
  4936  					},
  4937  				},
  4938  				Status: v1alpha1.ApplicationSetStatus{
  4939  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4940  						{
  4941  							Application:     "app1",
  4942  							Message:         "",
  4943  							Status:          "Healthy",
  4944  							Step:            "1",
  4945  							TargetRevisions: []string{"Previous"},
  4946  						},
  4947  						{
  4948  							Application:     "app2-multisource",
  4949  							Message:         "",
  4950  							Status:          "Healthy",
  4951  							Step:            "1",
  4952  							TargetRevisions: []string{"Previous", "OtherPrevious"},
  4953  						},
  4954  					},
  4955  				},
  4956  			},
  4957  			apps: []v1alpha1.Application{
  4958  				{
  4959  					ObjectMeta: metav1.ObjectMeta{
  4960  						Name: "app1",
  4961  					},
  4962  					Status: v1alpha1.ApplicationStatus{
  4963  						Sync: v1alpha1.SyncStatus{
  4964  							Status:   v1alpha1.SyncStatusCodeOutOfSync,
  4965  							Revision: "Next",
  4966  						},
  4967  					},
  4968  				},
  4969  				{
  4970  					ObjectMeta: metav1.ObjectMeta{
  4971  						Name: "app2-multisource",
  4972  					},
  4973  					Status: v1alpha1.ApplicationStatus{
  4974  						Sync: v1alpha1.SyncStatus{
  4975  							Status:    v1alpha1.SyncStatusCodeOutOfSync,
  4976  							Revisions: []string{"Next", "OtherNext"},
  4977  						},
  4978  					},
  4979  				},
  4980  			},
  4981  			appStepMap: map[string]int{
  4982  				"app1":             0,
  4983  				"app2-multisource": 0,
  4984  			},
  4985  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  4986  				{
  4987  					Application:     "app1",
  4988  					Message:         "Application has pending changes, setting status to Waiting.",
  4989  					Status:          "Waiting",
  4990  					Step:            "1",
  4991  					TargetRevisions: []string{"Next"},
  4992  				},
  4993  				{
  4994  					Application:     "app2-multisource",
  4995  					Message:         "Application has pending changes, setting status to Waiting.",
  4996  					Status:          "Waiting",
  4997  					Step:            "1",
  4998  					TargetRevisions: []string{"Next", "OtherNext"},
  4999  				},
  5000  			},
  5001  		},
  5002  		{
  5003  			name: "progresses a pending progressing application to progressing",
  5004  			appSet: v1alpha1.ApplicationSet{
  5005  				ObjectMeta: metav1.ObjectMeta{
  5006  					Name:      "name",
  5007  					Namespace: "argocd",
  5008  				},
  5009  				Spec: v1alpha1.ApplicationSetSpec{
  5010  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5011  						Type: "RollingSync",
  5012  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5013  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5014  								{
  5015  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5016  								},
  5017  								{
  5018  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5019  								},
  5020  							},
  5021  						},
  5022  					},
  5023  				},
  5024  				Status: v1alpha1.ApplicationSetStatus{
  5025  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5026  						{
  5027  							Application:     "app1",
  5028  							Message:         "",
  5029  							Status:          "Pending",
  5030  							Step:            "1",
  5031  							TargetRevisions: []string{"Next"},
  5032  						},
  5033  					},
  5034  				},
  5035  			},
  5036  			apps: []v1alpha1.Application{
  5037  				{
  5038  					ObjectMeta: metav1.ObjectMeta{
  5039  						Name: "app1",
  5040  					},
  5041  					Status: v1alpha1.ApplicationStatus{
  5042  						Health: v1alpha1.AppHealthStatus{
  5043  							Status: health.HealthStatusProgressing,
  5044  						},
  5045  						Sync: v1alpha1.SyncStatus{
  5046  							Revision: "Next",
  5047  						},
  5048  					},
  5049  				},
  5050  			},
  5051  			appStepMap: map[string]int{
  5052  				"app1": 0,
  5053  			},
  5054  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5055  				{
  5056  					Application:     "app1",
  5057  					Message:         "Application resource became Progressing, updating status from Pending to Progressing.",
  5058  					Status:          "Progressing",
  5059  					Step:            "1",
  5060  					TargetRevisions: []string{"Next"},
  5061  				},
  5062  			},
  5063  		},
  5064  		{
  5065  			name: "progresses a pending synced application to progressing",
  5066  			appSet: v1alpha1.ApplicationSet{
  5067  				ObjectMeta: metav1.ObjectMeta{
  5068  					Name:      "name",
  5069  					Namespace: "argocd",
  5070  				},
  5071  				Spec: v1alpha1.ApplicationSetSpec{
  5072  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5073  						Type: "RollingSync",
  5074  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5075  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5076  								{
  5077  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5078  								},
  5079  								{
  5080  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5081  								},
  5082  							},
  5083  						},
  5084  					},
  5085  				},
  5086  				Status: v1alpha1.ApplicationSetStatus{
  5087  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5088  						{
  5089  							Application:     "app1",
  5090  							Message:         "",
  5091  							Status:          "Pending",
  5092  							Step:            "1",
  5093  							TargetRevisions: []string{"Current"},
  5094  						},
  5095  					},
  5096  				},
  5097  			},
  5098  			apps: []v1alpha1.Application{
  5099  				{
  5100  					ObjectMeta: metav1.ObjectMeta{
  5101  						Name: "app1",
  5102  					},
  5103  					Status: v1alpha1.ApplicationStatus{
  5104  						Health: v1alpha1.AppHealthStatus{
  5105  							Status: health.HealthStatusHealthy,
  5106  						},
  5107  						OperationState: &v1alpha1.OperationState{
  5108  							Phase: common.OperationRunning,
  5109  						},
  5110  						Sync: v1alpha1.SyncStatus{
  5111  							Status:   v1alpha1.SyncStatusCodeSynced,
  5112  							Revision: "Current",
  5113  						},
  5114  					},
  5115  				},
  5116  			},
  5117  			appStepMap: map[string]int{
  5118  				"app1": 0,
  5119  			},
  5120  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5121  				{
  5122  					Application:     "app1",
  5123  					Message:         "Application resource became Progressing, updating status from Pending to Progressing.",
  5124  					Status:          "Progressing",
  5125  					Step:            "1",
  5126  					TargetRevisions: []string{"Current"},
  5127  				},
  5128  			},
  5129  		},
  5130  		{
  5131  			name: "progresses a progressing application to healthy",
  5132  			appSet: v1alpha1.ApplicationSet{
  5133  				ObjectMeta: metav1.ObjectMeta{
  5134  					Name:      "name",
  5135  					Namespace: "argocd",
  5136  				},
  5137  				Spec: v1alpha1.ApplicationSetSpec{
  5138  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5139  						Type: "RollingSync",
  5140  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5141  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5142  								{
  5143  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5144  								},
  5145  								{
  5146  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5147  								},
  5148  							},
  5149  						},
  5150  					},
  5151  				},
  5152  				Status: v1alpha1.ApplicationSetStatus{
  5153  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5154  						{
  5155  							Application:     "app1",
  5156  							Message:         "",
  5157  							Status:          "Progressing",
  5158  							Step:            "1",
  5159  							TargetRevisions: []string{"Next"},
  5160  						},
  5161  					},
  5162  				},
  5163  			},
  5164  			apps: []v1alpha1.Application{
  5165  				{
  5166  					ObjectMeta: metav1.ObjectMeta{
  5167  						Name: "app1",
  5168  					},
  5169  					Status: v1alpha1.ApplicationStatus{
  5170  						Health: v1alpha1.AppHealthStatus{
  5171  							Status: health.HealthStatusHealthy,
  5172  						},
  5173  						OperationState: &v1alpha1.OperationState{
  5174  							Phase: common.OperationSucceeded,
  5175  						},
  5176  						Sync: v1alpha1.SyncStatus{
  5177  							Status:   v1alpha1.SyncStatusCodeSynced,
  5178  							Revision: "Next",
  5179  						},
  5180  					},
  5181  				},
  5182  			},
  5183  			appStepMap: map[string]int{
  5184  				"app1": 0,
  5185  			},
  5186  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5187  				{
  5188  					Application:     "app1",
  5189  					Message:         "Application resource became Healthy, updating status from Progressing to Healthy.",
  5190  					Status:          "Healthy",
  5191  					Step:            "1",
  5192  					TargetRevisions: []string{"Next"},
  5193  				},
  5194  			},
  5195  		},
  5196  		{
  5197  			name: "progresses a waiting healthy application to healthy",
  5198  			appSet: v1alpha1.ApplicationSet{
  5199  				ObjectMeta: metav1.ObjectMeta{
  5200  					Name:      "name",
  5201  					Namespace: "argocd",
  5202  				},
  5203  				Spec: v1alpha1.ApplicationSetSpec{
  5204  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5205  						Type: "RollingSync",
  5206  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5207  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5208  								{
  5209  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5210  								},
  5211  								{
  5212  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5213  								},
  5214  							},
  5215  						},
  5216  					},
  5217  				},
  5218  				Status: v1alpha1.ApplicationSetStatus{
  5219  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5220  						{
  5221  							Application:     "app1",
  5222  							Message:         "",
  5223  							Status:          "Waiting",
  5224  							Step:            "1",
  5225  							TargetRevisions: []string{"Current"},
  5226  						},
  5227  					},
  5228  				},
  5229  			},
  5230  			apps: []v1alpha1.Application{
  5231  				{
  5232  					ObjectMeta: metav1.ObjectMeta{
  5233  						Name: "app1",
  5234  					},
  5235  					Status: v1alpha1.ApplicationStatus{
  5236  						Health: v1alpha1.AppHealthStatus{
  5237  							Status: health.HealthStatusHealthy,
  5238  						},
  5239  						OperationState: &v1alpha1.OperationState{
  5240  							Phase: common.OperationSucceeded,
  5241  						},
  5242  						Sync: v1alpha1.SyncStatus{
  5243  							Revision: "Current",
  5244  							Status:   v1alpha1.SyncStatusCodeSynced,
  5245  						},
  5246  					},
  5247  				},
  5248  			},
  5249  			appStepMap: map[string]int{
  5250  				"app1": 0,
  5251  			},
  5252  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5253  				{
  5254  					Application:     "app1",
  5255  					Message:         "Application resource is already Healthy, updating status from Waiting to Healthy.",
  5256  					Status:          "Healthy",
  5257  					Step:            "1",
  5258  					TargetRevisions: []string{"Current"},
  5259  				},
  5260  			},
  5261  		},
  5262  		{
  5263  			name: "progresses a new outofsync application in a later step to waiting",
  5264  			appSet: v1alpha1.ApplicationSet{
  5265  				ObjectMeta: metav1.ObjectMeta{
  5266  					Name:      "name",
  5267  					Namespace: "argocd",
  5268  				},
  5269  				Spec: v1alpha1.ApplicationSetSpec{
  5270  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5271  						Type: "RollingSync",
  5272  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5273  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5274  								{
  5275  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5276  								},
  5277  								{
  5278  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5279  								},
  5280  							},
  5281  						},
  5282  					},
  5283  				},
  5284  			},
  5285  			apps: []v1alpha1.Application{
  5286  				{
  5287  					ObjectMeta: metav1.ObjectMeta{
  5288  						Name: "app1",
  5289  					},
  5290  					Status: v1alpha1.ApplicationStatus{
  5291  						Health: v1alpha1.AppHealthStatus{
  5292  							Status: health.HealthStatusHealthy,
  5293  						},
  5294  						OperationState: &v1alpha1.OperationState{
  5295  							Phase: common.OperationSucceeded,
  5296  							SyncResult: &v1alpha1.SyncOperationResult{
  5297  								Revision: "Previous",
  5298  							},
  5299  						},
  5300  						Sync: v1alpha1.SyncStatus{
  5301  							Status:   v1alpha1.SyncStatusCodeOutOfSync,
  5302  							Revision: "Next",
  5303  						},
  5304  					},
  5305  				},
  5306  			},
  5307  			appStepMap: map[string]int{
  5308  				"app1": 1,
  5309  				"app2": 0,
  5310  			},
  5311  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5312  				{
  5313  					Application:     "app1",
  5314  					Message:         "No Application status found, defaulting status to Waiting.",
  5315  					Status:          "Waiting",
  5316  					Step:            "2",
  5317  					TargetRevisions: []string{"Next"},
  5318  				},
  5319  			},
  5320  		},
  5321  		{
  5322  			name: "progresses a pending application with a successful sync triggered by controller to progressing",
  5323  			appSet: v1alpha1.ApplicationSet{
  5324  				ObjectMeta: metav1.ObjectMeta{
  5325  					Name:      "name",
  5326  					Namespace: "argocd",
  5327  				},
  5328  				Spec: v1alpha1.ApplicationSetSpec{
  5329  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5330  						Type: "RollingSync",
  5331  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5332  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5333  								{
  5334  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5335  								},
  5336  								{
  5337  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5338  								},
  5339  							},
  5340  						},
  5341  					},
  5342  				},
  5343  				Status: v1alpha1.ApplicationSetStatus{
  5344  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5345  						{
  5346  							Application: "app1",
  5347  							LastTransitionTime: &metav1.Time{
  5348  								Time: time.Now().Add(time.Duration(-1) * time.Minute),
  5349  							},
  5350  							Message:         "",
  5351  							Status:          "Pending",
  5352  							Step:            "1",
  5353  							TargetRevisions: []string{"Next"},
  5354  						},
  5355  					},
  5356  				},
  5357  			},
  5358  			apps: []v1alpha1.Application{
  5359  				{
  5360  					ObjectMeta: metav1.ObjectMeta{
  5361  						Name: "app1",
  5362  					},
  5363  					Status: v1alpha1.ApplicationStatus{
  5364  						Health: v1alpha1.AppHealthStatus{
  5365  							Status: health.HealthStatusDegraded,
  5366  						},
  5367  						OperationState: &v1alpha1.OperationState{
  5368  							Phase: common.OperationSucceeded,
  5369  							StartedAt: metav1.Time{
  5370  								Time: time.Now(),
  5371  							},
  5372  							Operation: v1alpha1.Operation{
  5373  								InitiatedBy: v1alpha1.OperationInitiator{
  5374  									Username:  "applicationset-controller",
  5375  									Automated: true,
  5376  								},
  5377  							},
  5378  							SyncResult: &v1alpha1.SyncOperationResult{
  5379  								Revision: "Next",
  5380  							},
  5381  						},
  5382  						Sync: v1alpha1.SyncStatus{
  5383  							Status:   v1alpha1.SyncStatusCodeSynced,
  5384  							Revision: "Next",
  5385  						},
  5386  					},
  5387  				},
  5388  			},
  5389  			appStepMap: map[string]int{
  5390  				"app1": 0,
  5391  			},
  5392  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5393  				{
  5394  					Application:     "app1",
  5395  					Message:         "Application resource completed a sync successfully, updating status from Pending to Progressing.",
  5396  					Status:          "Progressing",
  5397  					Step:            "1",
  5398  					TargetRevisions: []string{"Next"},
  5399  				},
  5400  			},
  5401  		},
  5402  		{
  5403  			name: "progresses a pending application with a successful sync trigger by applicationset-controller <1s ago to progressing",
  5404  			appSet: v1alpha1.ApplicationSet{
  5405  				ObjectMeta: metav1.ObjectMeta{
  5406  					Name:      "name",
  5407  					Namespace: "argocd",
  5408  				},
  5409  				Spec: v1alpha1.ApplicationSetSpec{
  5410  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5411  						Type: "RollingSync",
  5412  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5413  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5414  								{
  5415  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5416  								},
  5417  								{
  5418  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5419  								},
  5420  							},
  5421  						},
  5422  					},
  5423  				},
  5424  				Status: v1alpha1.ApplicationSetStatus{
  5425  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5426  						{
  5427  							Application: "app1",
  5428  							LastTransitionTime: &metav1.Time{
  5429  								Time: time.Now(),
  5430  							},
  5431  							Message:         "",
  5432  							Status:          "Pending",
  5433  							Step:            "1",
  5434  							TargetRevisions: []string{"Next"},
  5435  						},
  5436  					},
  5437  				},
  5438  			},
  5439  			apps: []v1alpha1.Application{
  5440  				{
  5441  					ObjectMeta: metav1.ObjectMeta{
  5442  						Name: "app1",
  5443  					},
  5444  					Status: v1alpha1.ApplicationStatus{
  5445  						Health: v1alpha1.AppHealthStatus{
  5446  							Status: health.HealthStatusDegraded,
  5447  						},
  5448  						OperationState: &v1alpha1.OperationState{
  5449  							Phase: common.OperationSucceeded,
  5450  							StartedAt: metav1.Time{
  5451  								Time: time.Now().Add(time.Duration(-1) * time.Second),
  5452  							},
  5453  							Operation: v1alpha1.Operation{
  5454  								InitiatedBy: v1alpha1.OperationInitiator{
  5455  									Username:  "applicationset-controller",
  5456  									Automated: true,
  5457  								},
  5458  							},
  5459  							SyncResult: &v1alpha1.SyncOperationResult{
  5460  								Revision: "Next",
  5461  							},
  5462  						},
  5463  						Sync: v1alpha1.SyncStatus{
  5464  							Status:   v1alpha1.SyncStatusCodeSynced,
  5465  							Revision: "Next",
  5466  						},
  5467  					},
  5468  				},
  5469  			},
  5470  			appStepMap: map[string]int{
  5471  				"app1": 0,
  5472  			},
  5473  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5474  				{
  5475  					Application:     "app1",
  5476  					Message:         "Application resource completed a sync successfully, updating status from Pending to Progressing.",
  5477  					Status:          "Progressing",
  5478  					Step:            "1",
  5479  					TargetRevisions: []string{"Next"},
  5480  				},
  5481  			},
  5482  		},
  5483  		{
  5484  			name: "removes the appStatus for applications that no longer exist",
  5485  			appSet: v1alpha1.ApplicationSet{
  5486  				ObjectMeta: metav1.ObjectMeta{
  5487  					Name:      "name",
  5488  					Namespace: "argocd",
  5489  				},
  5490  				Spec: v1alpha1.ApplicationSetSpec{
  5491  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5492  						Type: "RollingSync",
  5493  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5494  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5495  								{
  5496  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  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 has pending changes, setting status to Waiting.",
  5510  							Status:          "Waiting",
  5511  							Step:            "1",
  5512  							TargetRevisions: []string{"Current"},
  5513  						},
  5514  						{
  5515  							Application:     "app2",
  5516  							Message:         "Application has pending changes, setting status to Waiting.",
  5517  							Status:          "Waiting",
  5518  							Step:            "1",
  5519  							TargetRevisions: []string{"Current"},
  5520  						},
  5521  					},
  5522  				},
  5523  			},
  5524  			apps: []v1alpha1.Application{
  5525  				{
  5526  					ObjectMeta: metav1.ObjectMeta{
  5527  						Name: "app1",
  5528  					},
  5529  					Status: v1alpha1.ApplicationStatus{
  5530  						Health: v1alpha1.AppHealthStatus{
  5531  							Status: health.HealthStatusHealthy,
  5532  						},
  5533  						OperationState: &v1alpha1.OperationState{
  5534  							Phase: common.OperationSucceeded,
  5535  						},
  5536  						Sync: v1alpha1.SyncStatus{
  5537  							Status:   v1alpha1.SyncStatusCodeSynced,
  5538  							Revision: "Current",
  5539  						},
  5540  					},
  5541  				},
  5542  			},
  5543  			appStepMap: map[string]int{
  5544  				"app1": 0,
  5545  			},
  5546  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5547  				{
  5548  					Application:     "app1",
  5549  					Message:         "Application resource is already Healthy, updating status from Waiting to Healthy.",
  5550  					Status:          "Healthy",
  5551  					Step:            "1",
  5552  					TargetRevisions: []string{"Current"},
  5553  				},
  5554  			},
  5555  		},
  5556  		{
  5557  			name: "progresses a pending synced application with an old revision to progressing with the Current one",
  5558  			appSet: v1alpha1.ApplicationSet{
  5559  				ObjectMeta: metav1.ObjectMeta{
  5560  					Name:      "name",
  5561  					Namespace: "argocd",
  5562  				},
  5563  				Spec: v1alpha1.ApplicationSetSpec{
  5564  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5565  						Type: "RollingSync",
  5566  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5567  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5568  								{
  5569  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5570  								},
  5571  								{
  5572  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5573  								},
  5574  							},
  5575  						},
  5576  					},
  5577  				},
  5578  				Status: v1alpha1.ApplicationSetStatus{
  5579  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5580  						{
  5581  							Application:     "app1",
  5582  							Message:         "",
  5583  							Status:          "Pending",
  5584  							Step:            "1",
  5585  							TargetRevisions: []string{"Old"},
  5586  						},
  5587  					},
  5588  				},
  5589  			},
  5590  			apps: []v1alpha1.Application{
  5591  				{
  5592  					ObjectMeta: metav1.ObjectMeta{
  5593  						Name: "app1",
  5594  					},
  5595  					Status: v1alpha1.ApplicationStatus{
  5596  						Health: v1alpha1.AppHealthStatus{
  5597  							Status: health.HealthStatusHealthy,
  5598  						},
  5599  						OperationState: &v1alpha1.OperationState{
  5600  							Phase: common.OperationSucceeded,
  5601  							SyncResult: &v1alpha1.SyncOperationResult{
  5602  								Revision: "Current",
  5603  							},
  5604  						},
  5605  						Sync: v1alpha1.SyncStatus{
  5606  							Status:    v1alpha1.SyncStatusCodeSynced,
  5607  							Revisions: []string{"Current"},
  5608  						},
  5609  					},
  5610  				},
  5611  			},
  5612  			appStepMap: map[string]int{
  5613  				"app1": 0,
  5614  			},
  5615  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5616  				{
  5617  					Application:     "app1",
  5618  					Message:         "Application resource is already Healthy, updating status from Waiting to Healthy.",
  5619  					Status:          "Healthy",
  5620  					Step:            "1",
  5621  					TargetRevisions: []string{"Current"},
  5622  				},
  5623  			},
  5624  		},
  5625  	} {
  5626  		t.Run(cc.name, func(t *testing.T) {
  5627  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  5628  
  5629  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).Build()
  5630  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  5631  
  5632  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  5633  
  5634  			r := ApplicationSetReconciler{
  5635  				Client:        client,
  5636  				Scheme:        scheme,
  5637  				Recorder:      record.NewFakeRecorder(1),
  5638  				Generators:    map[string]generators.Generator{},
  5639  				ArgoDB:        argodb,
  5640  				KubeClientset: kubeclientset,
  5641  				Metrics:       metrics,
  5642  			}
  5643  
  5644  			appStatuses, err := r.updateApplicationSetApplicationStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps, cc.appStepMap)
  5645  
  5646  			// opt out of testing the LastTransitionTime is accurate
  5647  			for i := range appStatuses {
  5648  				appStatuses[i].LastTransitionTime = nil
  5649  			}
  5650  
  5651  			require.NoError(t, err, "expected no errors, but errors occurred")
  5652  			assert.Equal(t, cc.expectedAppStatus, appStatuses, "expected appStatuses did not match actual")
  5653  		})
  5654  	}
  5655  }
  5656  
  5657  func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
  5658  	scheme := runtime.NewScheme()
  5659  	err := v1alpha1.AddToScheme(scheme)
  5660  	require.NoError(t, err)
  5661  
  5662  	for _, cc := range []struct {
  5663  		name              string
  5664  		appSet            v1alpha1.ApplicationSet
  5665  		appSyncMap        map[string]bool
  5666  		appStepMap        map[string]int
  5667  		appMap            map[string]v1alpha1.Application
  5668  		expectedAppStatus []v1alpha1.ApplicationSetApplicationStatus
  5669  	}{
  5670  		{
  5671  			name: "handles an empty appSync and appStepMap",
  5672  			appSet: v1alpha1.ApplicationSet{
  5673  				ObjectMeta: metav1.ObjectMeta{
  5674  					Name:      "name",
  5675  					Namespace: "argocd",
  5676  				},
  5677  				Spec: v1alpha1.ApplicationSetSpec{
  5678  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5679  						Type: "RollingSync",
  5680  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5681  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5682  								{
  5683  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5684  								},
  5685  								{
  5686  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5687  								},
  5688  							},
  5689  						},
  5690  					},
  5691  				},
  5692  				Status: v1alpha1.ApplicationSetStatus{
  5693  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5694  				},
  5695  			},
  5696  			appSyncMap:        map[string]bool{},
  5697  			appStepMap:        map[string]int{},
  5698  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5699  		},
  5700  		{
  5701  			name: "handles an empty strategy",
  5702  			appSet: v1alpha1.ApplicationSet{
  5703  				ObjectMeta: metav1.ObjectMeta{
  5704  					Name:      "name",
  5705  					Namespace: "argocd",
  5706  				},
  5707  				Spec: v1alpha1.ApplicationSetSpec{},
  5708  				Status: v1alpha1.ApplicationSetStatus{
  5709  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5710  				},
  5711  			},
  5712  			appSyncMap:        map[string]bool{},
  5713  			appStepMap:        map[string]int{},
  5714  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5715  		},
  5716  		{
  5717  			name: "handles an empty applicationset strategy",
  5718  			appSet: v1alpha1.ApplicationSet{
  5719  				ObjectMeta: metav1.ObjectMeta{
  5720  					Name:      "name",
  5721  					Namespace: "argocd",
  5722  				},
  5723  				Spec: v1alpha1.ApplicationSetSpec{
  5724  					Strategy: &v1alpha1.ApplicationSetStrategy{},
  5725  				},
  5726  				Status: v1alpha1.ApplicationSetStatus{
  5727  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5728  				},
  5729  			},
  5730  			appSyncMap:        map[string]bool{},
  5731  			appStepMap:        map[string]int{},
  5732  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5733  		},
  5734  		{
  5735  			name: "handles an appSyncMap with no existing statuses",
  5736  			appSet: v1alpha1.ApplicationSet{
  5737  				ObjectMeta: metav1.ObjectMeta{
  5738  					Name:      "name",
  5739  					Namespace: "argocd",
  5740  				},
  5741  				Status: v1alpha1.ApplicationSetStatus{
  5742  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5743  				},
  5744  			},
  5745  			appSyncMap: map[string]bool{
  5746  				"app1": true,
  5747  				"app2": false,
  5748  			},
  5749  			appStepMap: map[string]int{
  5750  				"app1": 0,
  5751  				"app2": 1,
  5752  			},
  5753  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  5754  		},
  5755  		{
  5756  			name: "handles updating a RollingSync status from Waiting to Pending",
  5757  			appSet: v1alpha1.ApplicationSet{
  5758  				ObjectMeta: metav1.ObjectMeta{
  5759  					Name:      "name",
  5760  					Namespace: "argocd",
  5761  				},
  5762  				Spec: v1alpha1.ApplicationSetSpec{
  5763  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5764  						Type: "RollingSync",
  5765  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5766  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5767  								{
  5768  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5769  								},
  5770  								{
  5771  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5772  								},
  5773  							},
  5774  						},
  5775  					},
  5776  				},
  5777  				Status: v1alpha1.ApplicationSetStatus{
  5778  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5779  						{
  5780  							Application:     "app1",
  5781  							Message:         "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5782  							Status:          "Waiting",
  5783  							TargetRevisions: []string{"Next"},
  5784  						},
  5785  					},
  5786  				},
  5787  			},
  5788  			appSyncMap: map[string]bool{
  5789  				"app1": true,
  5790  			},
  5791  			appStepMap: map[string]int{
  5792  				"app1": 0,
  5793  			},
  5794  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5795  				{
  5796  					Application:        "app1",
  5797  					LastTransitionTime: nil,
  5798  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5799  					Status:             "Pending",
  5800  					Step:               "1",
  5801  					TargetRevisions:    []string{"Next"},
  5802  				},
  5803  			},
  5804  		},
  5805  		{
  5806  			name: "does not update a RollingSync status if appSyncMap is false",
  5807  			appSet: v1alpha1.ApplicationSet{
  5808  				ObjectMeta: metav1.ObjectMeta{
  5809  					Name:      "name",
  5810  					Namespace: "argocd",
  5811  				},
  5812  				Spec: v1alpha1.ApplicationSetSpec{
  5813  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5814  						Type: "RollingSync",
  5815  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5816  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5817  								{
  5818  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5819  								},
  5820  								{
  5821  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5822  								},
  5823  							},
  5824  						},
  5825  					},
  5826  				},
  5827  				Status: v1alpha1.ApplicationSetStatus{
  5828  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5829  						{
  5830  							Application: "app1",
  5831  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5832  							Status:      "Waiting",
  5833  							Step:        "1",
  5834  						},
  5835  					},
  5836  				},
  5837  			},
  5838  			appSyncMap: map[string]bool{
  5839  				"app1": false,
  5840  			},
  5841  			appStepMap: map[string]int{
  5842  				"app1": 0,
  5843  			},
  5844  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5845  				{
  5846  					Application:        "app1",
  5847  					LastTransitionTime: nil,
  5848  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5849  					Status:             "Waiting",
  5850  					Step:               "1",
  5851  				},
  5852  			},
  5853  		},
  5854  		{
  5855  			name: "does not update a status if status is not pending",
  5856  			appSet: v1alpha1.ApplicationSet{
  5857  				ObjectMeta: metav1.ObjectMeta{
  5858  					Name:      "name",
  5859  					Namespace: "argocd",
  5860  				},
  5861  				Spec: v1alpha1.ApplicationSetSpec{
  5862  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5863  						Type: "RollingSync",
  5864  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5865  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5866  								{
  5867  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5868  								},
  5869  								{
  5870  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5871  								},
  5872  							},
  5873  						},
  5874  					},
  5875  				},
  5876  				Status: v1alpha1.ApplicationSetStatus{
  5877  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5878  						{
  5879  							Application: "app1",
  5880  							Message:     "Application Pending status timed out while waiting to become Progressing, reset status to Healthy.",
  5881  							Status:      "Healthy",
  5882  							Step:        "1",
  5883  						},
  5884  					},
  5885  				},
  5886  			},
  5887  			appSyncMap: map[string]bool{
  5888  				"app1": true,
  5889  			},
  5890  			appStepMap: map[string]int{
  5891  				"app1": 0,
  5892  			},
  5893  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5894  				{
  5895  					Application:        "app1",
  5896  					LastTransitionTime: nil,
  5897  					Message:            "Application Pending status timed out while waiting to become Progressing, reset status to Healthy.",
  5898  					Status:             "Healthy",
  5899  					Step:               "1",
  5900  				},
  5901  			},
  5902  		},
  5903  		{
  5904  			name: "does not update a status if maxUpdate has already been reached with RollingSync",
  5905  			appSet: v1alpha1.ApplicationSet{
  5906  				ObjectMeta: metav1.ObjectMeta{
  5907  					Name:      "name",
  5908  					Namespace: "argocd",
  5909  				},
  5910  				Spec: v1alpha1.ApplicationSetSpec{
  5911  					Strategy: &v1alpha1.ApplicationSetStrategy{
  5912  						Type: "RollingSync",
  5913  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  5914  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  5915  								{
  5916  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5917  									MaxUpdate: &intstr.IntOrString{
  5918  										Type:   intstr.Int,
  5919  										IntVal: 3,
  5920  									},
  5921  								},
  5922  								{
  5923  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  5924  								},
  5925  							},
  5926  						},
  5927  					},
  5928  				},
  5929  				Status: v1alpha1.ApplicationSetStatus{
  5930  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  5931  						{
  5932  							Application: "app1",
  5933  							Message:     "Application resource became Progressing, updating status from Pending to Progressing.",
  5934  							Status:      "Progressing",
  5935  							Step:        "1",
  5936  						},
  5937  						{
  5938  							Application: "app2",
  5939  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  5940  							Status:      "Waiting",
  5941  							Step:        "1",
  5942  						},
  5943  						{
  5944  							Application: "app3",
  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  							Application: "app4",
  5951  							Message:     "Application moved to Pending status, watching for the Application resource to start Progressing.",
  5952  							Status:      "Pending",
  5953  							Step:        "1",
  5954  						},
  5955  					},
  5956  				},
  5957  			},
  5958  			appSyncMap: map[string]bool{
  5959  				"app1": true,
  5960  				"app2": true,
  5961  				"app3": true,
  5962  				"app4": true,
  5963  			},
  5964  			appStepMap: map[string]int{
  5965  				"app1": 0,
  5966  				"app2": 0,
  5967  				"app3": 0,
  5968  				"app4": 0,
  5969  			},
  5970  			appMap: map[string]v1alpha1.Application{
  5971  				"app1": {
  5972  					ObjectMeta: metav1.ObjectMeta{
  5973  						Name: "app1",
  5974  					},
  5975  					Status: v1alpha1.ApplicationStatus{
  5976  						Sync: v1alpha1.SyncStatus{
  5977  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  5978  						},
  5979  					},
  5980  				},
  5981  				"app2": {
  5982  					ObjectMeta: metav1.ObjectMeta{
  5983  						Name: "app2",
  5984  					},
  5985  					Status: v1alpha1.ApplicationStatus{
  5986  						Sync: v1alpha1.SyncStatus{
  5987  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  5988  						},
  5989  					},
  5990  				},
  5991  				"app3": {
  5992  					ObjectMeta: metav1.ObjectMeta{
  5993  						Name: "app3",
  5994  					},
  5995  					Status: v1alpha1.ApplicationStatus{
  5996  						Sync: v1alpha1.SyncStatus{
  5997  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  5998  						},
  5999  					},
  6000  				},
  6001  				"app4": {
  6002  					ObjectMeta: metav1.ObjectMeta{
  6003  						Name: "app4",
  6004  					},
  6005  					Status: v1alpha1.ApplicationStatus{
  6006  						Sync: v1alpha1.SyncStatus{
  6007  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  6008  						},
  6009  					},
  6010  				},
  6011  			},
  6012  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6013  				{
  6014  					Application:        "app1",
  6015  					LastTransitionTime: nil,
  6016  					Message:            "Application resource became Progressing, updating status from Pending to Progressing.",
  6017  					Status:             "Progressing",
  6018  					Step:               "1",
  6019  				},
  6020  				{
  6021  					Application:        "app2",
  6022  					LastTransitionTime: nil,
  6023  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  6024  					Status:             "Pending",
  6025  					Step:               "1",
  6026  				},
  6027  				{
  6028  					Application:        "app3",
  6029  					LastTransitionTime: nil,
  6030  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6031  					Status:             "Waiting",
  6032  					Step:               "1",
  6033  				},
  6034  				{
  6035  					Application:        "app4",
  6036  					LastTransitionTime: nil,
  6037  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  6038  					Status:             "Pending",
  6039  					Step:               "1",
  6040  				},
  6041  			},
  6042  		},
  6043  		{
  6044  			name: "rounds down for maxUpdate set to percentage string",
  6045  			appSet: v1alpha1.ApplicationSet{
  6046  				ObjectMeta: metav1.ObjectMeta{
  6047  					Name:      "name",
  6048  					Namespace: "argocd",
  6049  				},
  6050  				Spec: v1alpha1.ApplicationSetSpec{
  6051  					Strategy: &v1alpha1.ApplicationSetStrategy{
  6052  						Type: "RollingSync",
  6053  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  6054  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  6055  								{
  6056  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  6057  									MaxUpdate: &intstr.IntOrString{
  6058  										Type:   intstr.String,
  6059  										StrVal: "50%",
  6060  									},
  6061  								},
  6062  								{
  6063  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  6064  								},
  6065  							},
  6066  						},
  6067  					},
  6068  				},
  6069  				Status: v1alpha1.ApplicationSetStatus{
  6070  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6071  						{
  6072  							Application: "app1",
  6073  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6074  							Status:      "Waiting",
  6075  							Step:        "1",
  6076  						},
  6077  						{
  6078  							Application: "app2",
  6079  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6080  							Status:      "Waiting",
  6081  							Step:        "1",
  6082  						},
  6083  						{
  6084  							Application: "app3",
  6085  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6086  							Status:      "Waiting",
  6087  							Step:        "1",
  6088  						},
  6089  					},
  6090  				},
  6091  			},
  6092  			appSyncMap: map[string]bool{
  6093  				"app1": true,
  6094  				"app2": true,
  6095  				"app3": true,
  6096  			},
  6097  			appStepMap: map[string]int{
  6098  				"app1": 0,
  6099  				"app2": 0,
  6100  				"app3": 0,
  6101  			},
  6102  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6103  				{
  6104  					Application:        "app1",
  6105  					LastTransitionTime: nil,
  6106  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  6107  					Status:             "Pending",
  6108  					Step:               "1",
  6109  				},
  6110  				{
  6111  					Application:        "app2",
  6112  					LastTransitionTime: nil,
  6113  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6114  					Status:             "Waiting",
  6115  					Step:               "1",
  6116  				},
  6117  				{
  6118  					Application:        "app3",
  6119  					LastTransitionTime: nil,
  6120  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6121  					Status:             "Waiting",
  6122  					Step:               "1",
  6123  				},
  6124  			},
  6125  		},
  6126  		{
  6127  			name: "does not update any applications with maxUpdate set to 0",
  6128  			appSet: v1alpha1.ApplicationSet{
  6129  				ObjectMeta: metav1.ObjectMeta{
  6130  					Name:      "name",
  6131  					Namespace: "argocd",
  6132  				},
  6133  				Spec: v1alpha1.ApplicationSetSpec{
  6134  					Strategy: &v1alpha1.ApplicationSetStrategy{
  6135  						Type: "RollingSync",
  6136  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  6137  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  6138  								{
  6139  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  6140  									MaxUpdate: &intstr.IntOrString{
  6141  										Type:   intstr.Int,
  6142  										IntVal: 0,
  6143  									},
  6144  								},
  6145  								{
  6146  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  6147  								},
  6148  							},
  6149  						},
  6150  					},
  6151  				},
  6152  				Status: v1alpha1.ApplicationSetStatus{
  6153  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6154  						{
  6155  							Application: "app1",
  6156  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6157  							Status:      "Waiting",
  6158  							Step:        "1",
  6159  						},
  6160  						{
  6161  							Application: "app2",
  6162  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6163  							Status:      "Waiting",
  6164  							Step:        "1",
  6165  						},
  6166  						{
  6167  							Application: "app3",
  6168  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6169  							Status:      "Waiting",
  6170  							Step:        "1",
  6171  						},
  6172  					},
  6173  				},
  6174  			},
  6175  			appSyncMap: map[string]bool{
  6176  				"app1": true,
  6177  				"app2": true,
  6178  				"app3": true,
  6179  			},
  6180  			appStepMap: map[string]int{
  6181  				"app1": 0,
  6182  				"app2": 0,
  6183  				"app3": 0,
  6184  			},
  6185  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6186  				{
  6187  					Application:        "app1",
  6188  					LastTransitionTime: nil,
  6189  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6190  					Status:             "Waiting",
  6191  					Step:               "1",
  6192  				},
  6193  				{
  6194  					Application:        "app2",
  6195  					LastTransitionTime: nil,
  6196  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6197  					Status:             "Waiting",
  6198  					Step:               "1",
  6199  				},
  6200  				{
  6201  					Application:        "app3",
  6202  					LastTransitionTime: nil,
  6203  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6204  					Status:             "Waiting",
  6205  					Step:               "1",
  6206  				},
  6207  			},
  6208  		},
  6209  		{
  6210  			name: "updates all applications with maxUpdate set to 100%",
  6211  			appSet: v1alpha1.ApplicationSet{
  6212  				ObjectMeta: metav1.ObjectMeta{
  6213  					Name:      "name",
  6214  					Namespace: "argocd",
  6215  				},
  6216  				Spec: v1alpha1.ApplicationSetSpec{
  6217  					Strategy: &v1alpha1.ApplicationSetStrategy{
  6218  						Type: "RollingSync",
  6219  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  6220  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  6221  								{
  6222  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  6223  									MaxUpdate: &intstr.IntOrString{
  6224  										Type:   intstr.String,
  6225  										StrVal: "100%",
  6226  									},
  6227  								},
  6228  								{
  6229  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  6230  								},
  6231  							},
  6232  						},
  6233  					},
  6234  				},
  6235  				Status: v1alpha1.ApplicationSetStatus{
  6236  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6237  						{
  6238  							Application: "app1",
  6239  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6240  							Status:      "Waiting",
  6241  							Step:        "1",
  6242  						},
  6243  						{
  6244  							Application: "app2",
  6245  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6246  							Status:      "Waiting",
  6247  							Step:        "1",
  6248  						},
  6249  						{
  6250  							Application: "app3",
  6251  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6252  							Status:      "Waiting",
  6253  							Step:        "1",
  6254  						},
  6255  					},
  6256  				},
  6257  			},
  6258  			appSyncMap: map[string]bool{
  6259  				"app1": true,
  6260  				"app2": true,
  6261  				"app3": true,
  6262  			},
  6263  			appStepMap: map[string]int{
  6264  				"app1": 0,
  6265  				"app2": 0,
  6266  				"app3": 0,
  6267  			},
  6268  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6269  				{
  6270  					Application:        "app1",
  6271  					LastTransitionTime: nil,
  6272  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  6273  					Status:             "Pending",
  6274  					Step:               "1",
  6275  				},
  6276  				{
  6277  					Application:        "app2",
  6278  					LastTransitionTime: nil,
  6279  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  6280  					Status:             "Pending",
  6281  					Step:               "1",
  6282  				},
  6283  				{
  6284  					Application:        "app3",
  6285  					LastTransitionTime: nil,
  6286  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  6287  					Status:             "Pending",
  6288  					Step:               "1",
  6289  				},
  6290  			},
  6291  		},
  6292  		{
  6293  			name: "updates at least 1 application with maxUpdate >0%",
  6294  			appSet: v1alpha1.ApplicationSet{
  6295  				ObjectMeta: metav1.ObjectMeta{
  6296  					Name:      "name",
  6297  					Namespace: "argocd",
  6298  				},
  6299  				Spec: v1alpha1.ApplicationSetSpec{
  6300  					Strategy: &v1alpha1.ApplicationSetStrategy{
  6301  						Type: "RollingSync",
  6302  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  6303  							Steps: []v1alpha1.ApplicationSetRolloutStep{
  6304  								{
  6305  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  6306  									MaxUpdate: &intstr.IntOrString{
  6307  										Type:   intstr.String,
  6308  										StrVal: "1%",
  6309  									},
  6310  								},
  6311  								{
  6312  									MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
  6313  								},
  6314  							},
  6315  						},
  6316  					},
  6317  				},
  6318  				Status: v1alpha1.ApplicationSetStatus{
  6319  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6320  						{
  6321  							Application: "app1",
  6322  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6323  							Status:      "Waiting",
  6324  							Step:        "1",
  6325  						},
  6326  						{
  6327  							Application: "app2",
  6328  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6329  							Status:      "Waiting",
  6330  							Step:        "1",
  6331  						},
  6332  						{
  6333  							Application: "app3",
  6334  							Message:     "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6335  							Status:      "Waiting",
  6336  							Step:        "1",
  6337  						},
  6338  					},
  6339  				},
  6340  			},
  6341  			appSyncMap: map[string]bool{
  6342  				"app1": true,
  6343  				"app2": true,
  6344  				"app3": true,
  6345  			},
  6346  			appStepMap: map[string]int{
  6347  				"app1": 0,
  6348  				"app2": 0,
  6349  				"app3": 0,
  6350  			},
  6351  			expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6352  				{
  6353  					Application:        "app1",
  6354  					LastTransitionTime: nil,
  6355  					Message:            "Application moved to Pending status, watching for the Application resource to start Progressing.",
  6356  					Status:             "Pending",
  6357  					Step:               "1",
  6358  				},
  6359  				{
  6360  					Application:        "app2",
  6361  					LastTransitionTime: nil,
  6362  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6363  					Status:             "Waiting",
  6364  					Step:               "1",
  6365  				},
  6366  				{
  6367  					Application:        "app3",
  6368  					LastTransitionTime: nil,
  6369  					Message:            "Application is out of date with the current AppSet generation, setting status to Waiting.",
  6370  					Status:             "Waiting",
  6371  					Step:               "1",
  6372  				},
  6373  			},
  6374  		},
  6375  	} {
  6376  		t.Run(cc.name, func(t *testing.T) {
  6377  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  6378  
  6379  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).Build()
  6380  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  6381  
  6382  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  6383  
  6384  			r := ApplicationSetReconciler{
  6385  				Client:        client,
  6386  				Scheme:        scheme,
  6387  				Recorder:      record.NewFakeRecorder(1),
  6388  				Generators:    map[string]generators.Generator{},
  6389  				ArgoDB:        argodb,
  6390  				KubeClientset: kubeclientset,
  6391  				Metrics:       metrics,
  6392  			}
  6393  
  6394  			appStatuses, err := r.updateApplicationSetApplicationStatusProgress(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.appSyncMap, cc.appStepMap)
  6395  
  6396  			// opt out of testing the LastTransitionTime is accurate
  6397  			for i := range appStatuses {
  6398  				appStatuses[i].LastTransitionTime = nil
  6399  			}
  6400  
  6401  			require.NoError(t, err, "expected no errors, but errors occurred")
  6402  			assert.Equal(t, cc.expectedAppStatus, appStatuses, "expected appStatuses did not match actual")
  6403  		})
  6404  	}
  6405  }
  6406  
  6407  func TestUpdateResourceStatus(t *testing.T) {
  6408  	scheme := runtime.NewScheme()
  6409  	err := v1alpha1.AddToScheme(scheme)
  6410  	require.NoError(t, err)
  6411  
  6412  	for _, cc := range []struct {
  6413  		name                    string
  6414  		appSet                  v1alpha1.ApplicationSet
  6415  		apps                    []v1alpha1.Application
  6416  		expectedResources       []v1alpha1.ResourceStatus
  6417  		maxResourcesStatusCount int
  6418  	}{
  6419  		{
  6420  			name: "handles an empty application list",
  6421  			appSet: v1alpha1.ApplicationSet{
  6422  				ObjectMeta: metav1.ObjectMeta{
  6423  					Name:      "name",
  6424  					Namespace: "argocd",
  6425  				},
  6426  				Status: v1alpha1.ApplicationSetStatus{
  6427  					Resources: []v1alpha1.ResourceStatus{},
  6428  				},
  6429  			},
  6430  			apps:              []v1alpha1.Application{},
  6431  			expectedResources: nil,
  6432  		},
  6433  		{
  6434  			name: "adds status if no existing statuses",
  6435  			appSet: v1alpha1.ApplicationSet{
  6436  				ObjectMeta: metav1.ObjectMeta{
  6437  					Name:      "name",
  6438  					Namespace: "argocd",
  6439  				},
  6440  				Status: v1alpha1.ApplicationSetStatus{
  6441  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{},
  6442  				},
  6443  			},
  6444  			apps: []v1alpha1.Application{
  6445  				{
  6446  					ObjectMeta: metav1.ObjectMeta{
  6447  						Name: "app1",
  6448  					},
  6449  					Status: v1alpha1.ApplicationStatus{
  6450  						Sync: v1alpha1.SyncStatus{
  6451  							Status: v1alpha1.SyncStatusCodeSynced,
  6452  						},
  6453  						Health: v1alpha1.AppHealthStatus{
  6454  							Status: health.HealthStatusHealthy,
  6455  						},
  6456  					},
  6457  				},
  6458  			},
  6459  			expectedResources: []v1alpha1.ResourceStatus{
  6460  				{
  6461  					Name:   "app1",
  6462  					Status: v1alpha1.SyncStatusCodeSynced,
  6463  					Health: &v1alpha1.HealthStatus{
  6464  						Status: health.HealthStatusHealthy,
  6465  					},
  6466  				},
  6467  			},
  6468  		},
  6469  		{
  6470  			name: "handles an applicationset with existing and up-to-date status",
  6471  			appSet: v1alpha1.ApplicationSet{
  6472  				ObjectMeta: metav1.ObjectMeta{
  6473  					Name:      "name",
  6474  					Namespace: "argocd",
  6475  				},
  6476  				Status: v1alpha1.ApplicationSetStatus{
  6477  					Resources: []v1alpha1.ResourceStatus{
  6478  						{
  6479  							Name:   "app1",
  6480  							Status: v1alpha1.SyncStatusCodeSynced,
  6481  							Health: &v1alpha1.HealthStatus{
  6482  								Status: health.HealthStatusHealthy,
  6483  							},
  6484  						},
  6485  					},
  6486  				},
  6487  			},
  6488  			apps: []v1alpha1.Application{
  6489  				{
  6490  					ObjectMeta: metav1.ObjectMeta{
  6491  						Name: "app1",
  6492  					},
  6493  					Status: v1alpha1.ApplicationStatus{
  6494  						Sync: v1alpha1.SyncStatus{
  6495  							Status: v1alpha1.SyncStatusCodeSynced,
  6496  						},
  6497  						Health: v1alpha1.AppHealthStatus{
  6498  							Status: health.HealthStatusHealthy,
  6499  						},
  6500  					},
  6501  				},
  6502  			},
  6503  			expectedResources: []v1alpha1.ResourceStatus{
  6504  				{
  6505  					Name:   "app1",
  6506  					Status: v1alpha1.SyncStatusCodeSynced,
  6507  					Health: &v1alpha1.HealthStatus{
  6508  						Status: health.HealthStatusHealthy,
  6509  					},
  6510  				},
  6511  			},
  6512  		},
  6513  		{
  6514  			name: "updates an applicationset with existing and out of date status",
  6515  			appSet: v1alpha1.ApplicationSet{
  6516  				ObjectMeta: metav1.ObjectMeta{
  6517  					Name:      "name",
  6518  					Namespace: "argocd",
  6519  				},
  6520  				Status: v1alpha1.ApplicationSetStatus{
  6521  					Resources: []v1alpha1.ResourceStatus{
  6522  						{
  6523  							Name:   "app1",
  6524  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  6525  							Health: &v1alpha1.HealthStatus{
  6526  								Status:  health.HealthStatusProgressing,
  6527  								Message: "Progressing",
  6528  							},
  6529  						},
  6530  					},
  6531  				},
  6532  			},
  6533  			apps: []v1alpha1.Application{
  6534  				{
  6535  					ObjectMeta: metav1.ObjectMeta{
  6536  						Name: "app1",
  6537  					},
  6538  					Status: v1alpha1.ApplicationStatus{
  6539  						Sync: v1alpha1.SyncStatus{
  6540  							Status: v1alpha1.SyncStatusCodeSynced,
  6541  						},
  6542  						Health: v1alpha1.AppHealthStatus{
  6543  							Status: health.HealthStatusHealthy,
  6544  						},
  6545  					},
  6546  				},
  6547  			},
  6548  			expectedResources: []v1alpha1.ResourceStatus{
  6549  				{
  6550  					Name:   "app1",
  6551  					Status: v1alpha1.SyncStatusCodeSynced,
  6552  					Health: &v1alpha1.HealthStatus{
  6553  						Status: health.HealthStatusHealthy,
  6554  					},
  6555  				},
  6556  			},
  6557  		},
  6558  		{
  6559  			name: "deletes an applicationset status if the application no longer exists",
  6560  			appSet: v1alpha1.ApplicationSet{
  6561  				ObjectMeta: metav1.ObjectMeta{
  6562  					Name:      "name",
  6563  					Namespace: "argocd",
  6564  				},
  6565  				Status: v1alpha1.ApplicationSetStatus{
  6566  					Resources: []v1alpha1.ResourceStatus{
  6567  						{
  6568  							Name:   "app1",
  6569  							Status: v1alpha1.SyncStatusCodeSynced,
  6570  							Health: &v1alpha1.HealthStatus{
  6571  								Status:  health.HealthStatusHealthy,
  6572  								Message: "OK",
  6573  							},
  6574  						},
  6575  					},
  6576  				},
  6577  			},
  6578  			apps:              []v1alpha1.Application{},
  6579  			expectedResources: nil,
  6580  		},
  6581  		{
  6582  			name: "truncates resources status list to",
  6583  			appSet: v1alpha1.ApplicationSet{
  6584  				ObjectMeta: metav1.ObjectMeta{
  6585  					Name:      "name",
  6586  					Namespace: "argocd",
  6587  				},
  6588  				Status: v1alpha1.ApplicationSetStatus{
  6589  					Resources: []v1alpha1.ResourceStatus{
  6590  						{
  6591  							Name:   "app1",
  6592  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  6593  							Health: &v1alpha1.HealthStatus{
  6594  								Status:  health.HealthStatusProgressing,
  6595  								Message: "this is progressing",
  6596  							},
  6597  						},
  6598  						{
  6599  							Name:   "app2",
  6600  							Status: v1alpha1.SyncStatusCodeOutOfSync,
  6601  							Health: &v1alpha1.HealthStatus{
  6602  								Status:  health.HealthStatusProgressing,
  6603  								Message: "this is progressing",
  6604  							},
  6605  						},
  6606  					},
  6607  				},
  6608  			},
  6609  			apps: []v1alpha1.Application{
  6610  				{
  6611  					ObjectMeta: metav1.ObjectMeta{
  6612  						Name: "app1",
  6613  					},
  6614  					Status: v1alpha1.ApplicationStatus{
  6615  						Sync: v1alpha1.SyncStatus{
  6616  							Status: v1alpha1.SyncStatusCodeSynced,
  6617  						},
  6618  						Health: v1alpha1.AppHealthStatus{
  6619  							Status: health.HealthStatusHealthy,
  6620  						},
  6621  					},
  6622  				},
  6623  				{
  6624  					ObjectMeta: metav1.ObjectMeta{
  6625  						Name: "app2",
  6626  					},
  6627  					Status: v1alpha1.ApplicationStatus{
  6628  						Sync: v1alpha1.SyncStatus{
  6629  							Status: v1alpha1.SyncStatusCodeSynced,
  6630  						},
  6631  						Health: v1alpha1.AppHealthStatus{
  6632  							Status: health.HealthStatusHealthy,
  6633  						},
  6634  					},
  6635  				},
  6636  			},
  6637  			expectedResources: []v1alpha1.ResourceStatus{
  6638  				{
  6639  					Name:   "app1",
  6640  					Status: v1alpha1.SyncStatusCodeSynced,
  6641  					Health: &v1alpha1.HealthStatus{
  6642  						Status: health.HealthStatusHealthy,
  6643  					},
  6644  				},
  6645  			},
  6646  			maxResourcesStatusCount: 1,
  6647  		},
  6648  	} {
  6649  		t.Run(cc.name, func(t *testing.T) {
  6650  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  6651  
  6652  			client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&cc.appSet).WithObjects(&cc.appSet).Build()
  6653  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  6654  
  6655  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  6656  
  6657  			r := ApplicationSetReconciler{
  6658  				Client:                  client,
  6659  				Scheme:                  scheme,
  6660  				Recorder:                record.NewFakeRecorder(1),
  6661  				Generators:              map[string]generators.Generator{},
  6662  				ArgoDB:                  argodb,
  6663  				KubeClientset:           kubeclientset,
  6664  				Metrics:                 metrics,
  6665  				MaxResourcesStatusCount: cc.maxResourcesStatusCount,
  6666  			}
  6667  
  6668  			err := r.updateResourcesStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
  6669  
  6670  			require.NoError(t, err, "expected no errors, but errors occurred")
  6671  			assert.Equal(t, cc.expectedResources, cc.appSet.Status.Resources, "expected resources did not match actual")
  6672  		})
  6673  	}
  6674  }
  6675  
  6676  func generateNAppResourceStatuses(n int) []v1alpha1.ResourceStatus {
  6677  	var r []v1alpha1.ResourceStatus
  6678  	for i := 0; i < n; i++ {
  6679  		r = append(r, v1alpha1.ResourceStatus{
  6680  			Name:   "app" + strconv.Itoa(i),
  6681  			Status: v1alpha1.SyncStatusCodeSynced,
  6682  			Health: &v1alpha1.HealthStatus{
  6683  				Status: health.HealthStatusHealthy,
  6684  			},
  6685  		},
  6686  		)
  6687  	}
  6688  	return r
  6689  }
  6690  
  6691  func generateNHealthyApps(n int) []v1alpha1.Application {
  6692  	var r []v1alpha1.Application
  6693  	for i := 0; i < n; i++ {
  6694  		r = append(r, v1alpha1.Application{
  6695  			ObjectMeta: metav1.ObjectMeta{
  6696  				Name: "app" + strconv.Itoa(i),
  6697  			},
  6698  			Status: v1alpha1.ApplicationStatus{
  6699  				Sync: v1alpha1.SyncStatus{
  6700  					Status: v1alpha1.SyncStatusCodeSynced,
  6701  				},
  6702  				Health: v1alpha1.AppHealthStatus{
  6703  					Status: health.HealthStatusHealthy,
  6704  				},
  6705  			},
  6706  		})
  6707  	}
  6708  	return r
  6709  }
  6710  
  6711  func TestResourceStatusAreOrdered(t *testing.T) {
  6712  	scheme := runtime.NewScheme()
  6713  	err := v1alpha1.AddToScheme(scheme)
  6714  	require.NoError(t, err)
  6715  
  6716  	err = v1alpha1.AddToScheme(scheme)
  6717  	require.NoError(t, err)
  6718  	for _, cc := range []struct {
  6719  		name              string
  6720  		appSet            v1alpha1.ApplicationSet
  6721  		apps              []v1alpha1.Application
  6722  		expectedResources []v1alpha1.ResourceStatus
  6723  	}{
  6724  		{
  6725  			name: "Ensures AppSet is always ordered",
  6726  			appSet: v1alpha1.ApplicationSet{
  6727  				ObjectMeta: metav1.ObjectMeta{
  6728  					Name:      "name",
  6729  					Namespace: "argocd",
  6730  				},
  6731  				Status: v1alpha1.ApplicationSetStatus{
  6732  					Resources: []v1alpha1.ResourceStatus{},
  6733  				},
  6734  			},
  6735  			apps:              generateNHealthyApps(10),
  6736  			expectedResources: generateNAppResourceStatuses(10),
  6737  		},
  6738  	} {
  6739  		t.Run(cc.name, func(t *testing.T) {
  6740  			kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  6741  
  6742  			client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&cc.appSet).WithObjects(&cc.appSet).Build()
  6743  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  6744  
  6745  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  6746  
  6747  			r := ApplicationSetReconciler{
  6748  				Client:        client,
  6749  				Scheme:        scheme,
  6750  				Recorder:      record.NewFakeRecorder(1),
  6751  				Generators:    map[string]generators.Generator{},
  6752  				ArgoDB:        argodb,
  6753  				KubeClientset: kubeclientset,
  6754  				Metrics:       metrics,
  6755  			}
  6756  
  6757  			err := r.updateResourcesStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
  6758  			require.NoError(t, err, "expected no errors, but errors occurred")
  6759  
  6760  			err = r.updateResourcesStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
  6761  			require.NoError(t, err, "expected no errors, but errors occurred")
  6762  
  6763  			err = r.updateResourcesStatus(t.Context(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
  6764  			require.NoError(t, err, "expected no errors, but errors occurred")
  6765  
  6766  			assert.Equal(t, cc.expectedResources, cc.appSet.Status.Resources, "expected resources did not match actual")
  6767  		})
  6768  	}
  6769  }
  6770  
  6771  func TestApplicationOwnsHandler(t *testing.T) {
  6772  	// progressive syncs do not affect create, delete, or generic
  6773  	ownsHandler := getApplicationOwnsHandler(true)
  6774  	assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{}))
  6775  	assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{}))
  6776  	assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{}))
  6777  	ownsHandler = getApplicationOwnsHandler(false)
  6778  	assert.False(t, ownsHandler.CreateFunc(event.CreateEvent{}))
  6779  	assert.True(t, ownsHandler.DeleteFunc(event.DeleteEvent{}))
  6780  	assert.True(t, ownsHandler.GenericFunc(event.GenericEvent{}))
  6781  
  6782  	now := metav1.Now()
  6783  	type args struct {
  6784  		e                      event.UpdateEvent
  6785  		enableProgressiveSyncs bool
  6786  	}
  6787  	tests := []struct {
  6788  		name string
  6789  		args args
  6790  		want bool
  6791  	}{
  6792  		{name: "SameApplicationReconciledAtDiff", args: args{e: event.UpdateEvent{
  6793  			ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ReconciledAt: &now}},
  6794  			ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{ReconciledAt: &now}},
  6795  		}}, want: false},
  6796  		{name: "SameApplicationResourceVersionDiff", args: args{e: event.UpdateEvent{
  6797  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
  6798  				ResourceVersion: "foo",
  6799  			}},
  6800  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
  6801  				ResourceVersion: "bar",
  6802  			}},
  6803  		}}, want: false},
  6804  		{name: "ApplicationHealthStatusDiff", args: args{
  6805  			e: event.UpdateEvent{
  6806  				ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6807  					Health: v1alpha1.AppHealthStatus{
  6808  						Status: "Unknown",
  6809  					},
  6810  				}},
  6811  				ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6812  					Health: v1alpha1.AppHealthStatus{
  6813  						Status: "Healthy",
  6814  					},
  6815  				}},
  6816  			},
  6817  			enableProgressiveSyncs: true,
  6818  		}, want: true},
  6819  		{name: "ApplicationSyncStatusDiff", args: args{
  6820  			e: event.UpdateEvent{
  6821  				ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6822  					Sync: v1alpha1.SyncStatus{
  6823  						Status: "OutOfSync",
  6824  					},
  6825  				}},
  6826  				ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6827  					Sync: v1alpha1.SyncStatus{
  6828  						Status: "Synced",
  6829  					},
  6830  				}},
  6831  			},
  6832  			enableProgressiveSyncs: true,
  6833  		}, want: true},
  6834  		{name: "ApplicationOperationStateDiff", args: args{
  6835  			e: event.UpdateEvent{
  6836  				ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6837  					OperationState: &v1alpha1.OperationState{
  6838  						Phase: "foo",
  6839  					},
  6840  				}},
  6841  				ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6842  					OperationState: &v1alpha1.OperationState{
  6843  						Phase: "bar",
  6844  					},
  6845  				}},
  6846  			},
  6847  			enableProgressiveSyncs: true,
  6848  		}, want: true},
  6849  		{name: "ApplicationOperationStartedAtDiff", args: args{
  6850  			e: event.UpdateEvent{
  6851  				ObjectOld: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6852  					OperationState: &v1alpha1.OperationState{
  6853  						StartedAt: now,
  6854  					},
  6855  				}},
  6856  				ObjectNew: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
  6857  					OperationState: &v1alpha1.OperationState{
  6858  						StartedAt: metav1.NewTime(now.Add(time.Minute * 1)),
  6859  					},
  6860  				}},
  6861  			},
  6862  			enableProgressiveSyncs: true,
  6863  		}, want: true},
  6864  		{name: "SameApplicationGeneration", args: args{e: event.UpdateEvent{
  6865  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
  6866  				Generation: 1,
  6867  			}},
  6868  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{
  6869  				Generation: 2,
  6870  			}},
  6871  		}}, want: false},
  6872  		{name: "DifferentApplicationSpec", args: args{e: event.UpdateEvent{
  6873  			ObjectOld: &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Project: "default"}},
  6874  			ObjectNew: &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Project: "not-default"}},
  6875  		}}, want: true},
  6876  		{name: "DifferentApplicationLabels", args: args{e: event.UpdateEvent{
  6877  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}},
  6878  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}},
  6879  		}}, want: true},
  6880  		{name: "DifferentApplicationLabelsNil", args: args{e: event.UpdateEvent{
  6881  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{}}},
  6882  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: nil}},
  6883  		}}, want: false},
  6884  		{name: "DifferentApplicationAnnotations", args: args{e: event.UpdateEvent{
  6885  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"foo": "bar"}}},
  6886  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{"bar": "foo"}}},
  6887  		}}, want: true},
  6888  		{name: "DifferentApplicationAnnotationsNil", args: args{e: event.UpdateEvent{
  6889  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}},
  6890  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: nil}},
  6891  		}}, want: false},
  6892  		{name: "DifferentApplicationFinalizers", args: args{e: event.UpdateEvent{
  6893  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"argo"}}},
  6894  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{"none"}}},
  6895  		}}, want: true},
  6896  		{name: "DifferentApplicationFinalizersNil", args: args{e: event.UpdateEvent{
  6897  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{}}},
  6898  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Finalizers: nil}},
  6899  		}}, want: false},
  6900  		{name: "ApplicationDestinationSame", args: args{
  6901  			e: event.UpdateEvent{
  6902  				ObjectOld: &v1alpha1.Application{
  6903  					Spec: v1alpha1.ApplicationSpec{
  6904  						Destination: v1alpha1.ApplicationDestination{
  6905  							Server:    "server",
  6906  							Namespace: "ns",
  6907  							Name:      "name",
  6908  						},
  6909  					},
  6910  				},
  6911  				ObjectNew: &v1alpha1.Application{
  6912  					Spec: v1alpha1.ApplicationSpec{
  6913  						Destination: v1alpha1.ApplicationDestination{
  6914  							Server:    "server",
  6915  							Namespace: "ns",
  6916  							Name:      "name",
  6917  						},
  6918  					},
  6919  				},
  6920  			},
  6921  			enableProgressiveSyncs: true,
  6922  		}, want: false},
  6923  		{name: "ApplicationDestinationDiff", args: args{
  6924  			e: event.UpdateEvent{
  6925  				ObjectOld: &v1alpha1.Application{
  6926  					Spec: v1alpha1.ApplicationSpec{
  6927  						Destination: v1alpha1.ApplicationDestination{
  6928  							Server:    "server",
  6929  							Namespace: "ns",
  6930  							Name:      "name",
  6931  						},
  6932  					},
  6933  				},
  6934  				ObjectNew: &v1alpha1.Application{
  6935  					Spec: v1alpha1.ApplicationSpec{
  6936  						Destination: v1alpha1.ApplicationDestination{
  6937  							Server:    "notSameServer",
  6938  							Namespace: "ns",
  6939  							Name:      "name",
  6940  						},
  6941  					},
  6942  				},
  6943  			},
  6944  			enableProgressiveSyncs: true,
  6945  		}, want: true},
  6946  		{name: "NotAnAppOld", args: args{e: event.UpdateEvent{
  6947  			ObjectOld: &v1alpha1.AppProject{},
  6948  			ObjectNew: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"bar": "foo"}}},
  6949  		}}, want: false},
  6950  		{name: "NotAnAppNew", args: args{e: event.UpdateEvent{
  6951  			ObjectOld: &v1alpha1.Application{ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}},
  6952  			ObjectNew: &v1alpha1.AppProject{},
  6953  		}}, want: false},
  6954  	}
  6955  	for _, tt := range tests {
  6956  		t.Run(tt.name, func(t *testing.T) {
  6957  			ownsHandler = getApplicationOwnsHandler(tt.args.enableProgressiveSyncs)
  6958  			assert.Equalf(t, tt.want, ownsHandler.UpdateFunc(tt.args.e), "UpdateFunc(%v)", tt.args.e)
  6959  		})
  6960  	}
  6961  }
  6962  
  6963  func TestMigrateStatus(t *testing.T) {
  6964  	scheme := runtime.NewScheme()
  6965  	err := v1alpha1.AddToScheme(scheme)
  6966  	require.NoError(t, err)
  6967  
  6968  	err = v1alpha1.AddToScheme(scheme)
  6969  	require.NoError(t, err)
  6970  
  6971  	for _, tc := range []struct {
  6972  		name           string
  6973  		appset         v1alpha1.ApplicationSet
  6974  		expectedStatus v1alpha1.ApplicationSetStatus
  6975  	}{
  6976  		{
  6977  			name: "status without applicationstatus target revisions set will default to empty list",
  6978  			appset: v1alpha1.ApplicationSet{
  6979  				ObjectMeta: metav1.ObjectMeta{
  6980  					Name:      "test",
  6981  					Namespace: "test",
  6982  				},
  6983  				Status: v1alpha1.ApplicationSetStatus{
  6984  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6985  						{},
  6986  					},
  6987  				},
  6988  			},
  6989  			expectedStatus: v1alpha1.ApplicationSetStatus{
  6990  				ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  6991  					{
  6992  						TargetRevisions: []string{},
  6993  					},
  6994  				},
  6995  			},
  6996  		},
  6997  		{
  6998  			name: "status with applicationstatus target revisions set will do nothing",
  6999  			appset: v1alpha1.ApplicationSet{
  7000  				ObjectMeta: metav1.ObjectMeta{
  7001  					Name:      "test",
  7002  					Namespace: "test",
  7003  				},
  7004  				Status: v1alpha1.ApplicationSetStatus{
  7005  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  7006  						{
  7007  							TargetRevisions: []string{"Current"},
  7008  						},
  7009  					},
  7010  				},
  7011  			},
  7012  			expectedStatus: v1alpha1.ApplicationSetStatus{
  7013  				ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  7014  					{
  7015  						TargetRevisions: []string{"Current"},
  7016  					},
  7017  				},
  7018  			},
  7019  		},
  7020  	} {
  7021  		t.Run(tc.name, func(t *testing.T) {
  7022  			client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&tc.appset).WithObjects(&tc.appset).Build()
  7023  			r := ApplicationSetReconciler{
  7024  				Client: client,
  7025  			}
  7026  
  7027  			err := r.migrateStatus(t.Context(), &tc.appset)
  7028  			require.NoError(t, err)
  7029  			assert.Equal(t, tc.expectedStatus, tc.appset.Status)
  7030  		})
  7031  	}
  7032  }
  7033  
  7034  func TestApplicationSetOwnsHandlerUpdate(t *testing.T) {
  7035  	buildAppSet := func(annotations map[string]string) *v1alpha1.ApplicationSet {
  7036  		return &v1alpha1.ApplicationSet{
  7037  			ObjectMeta: metav1.ObjectMeta{
  7038  				Annotations: annotations,
  7039  			},
  7040  		}
  7041  	}
  7042  
  7043  	tests := []struct {
  7044  		name                   string
  7045  		appSetOld              crtclient.Object
  7046  		appSetNew              crtclient.Object
  7047  		enableProgressiveSyncs bool
  7048  		want                   bool
  7049  	}{
  7050  		{
  7051  			name: "Different Spec",
  7052  			appSetOld: &v1alpha1.ApplicationSet{
  7053  				Spec: v1alpha1.ApplicationSetSpec{
  7054  					Generators: []v1alpha1.ApplicationSetGenerator{
  7055  						{List: &v1alpha1.ListGenerator{}},
  7056  					},
  7057  				},
  7058  			},
  7059  			appSetNew: &v1alpha1.ApplicationSet{
  7060  				Spec: v1alpha1.ApplicationSetSpec{
  7061  					Generators: []v1alpha1.ApplicationSetGenerator{
  7062  						{Git: &v1alpha1.GitGenerator{}},
  7063  					},
  7064  				},
  7065  			},
  7066  			enableProgressiveSyncs: false,
  7067  			want:                   true,
  7068  		},
  7069  		{
  7070  			name:                   "Different Annotations",
  7071  			appSetOld:              buildAppSet(map[string]string{"key1": "value1"}),
  7072  			appSetNew:              buildAppSet(map[string]string{"key1": "value2"}),
  7073  			enableProgressiveSyncs: false,
  7074  			want:                   true,
  7075  		},
  7076  		{
  7077  			name: "Different Labels",
  7078  			appSetOld: &v1alpha1.ApplicationSet{
  7079  				ObjectMeta: metav1.ObjectMeta{
  7080  					Labels: map[string]string{"key1": "value1"},
  7081  				},
  7082  			},
  7083  			appSetNew: &v1alpha1.ApplicationSet{
  7084  				ObjectMeta: metav1.ObjectMeta{
  7085  					Labels: map[string]string{"key1": "value2"},
  7086  				},
  7087  			},
  7088  			enableProgressiveSyncs: false,
  7089  			want:                   true,
  7090  		},
  7091  		{
  7092  			name: "Different Finalizers",
  7093  			appSetOld: &v1alpha1.ApplicationSet{
  7094  				ObjectMeta: metav1.ObjectMeta{
  7095  					Finalizers: []string{"finalizer1"},
  7096  				},
  7097  			},
  7098  			appSetNew: &v1alpha1.ApplicationSet{
  7099  				ObjectMeta: metav1.ObjectMeta{
  7100  					Finalizers: []string{"finalizer2"},
  7101  				},
  7102  			},
  7103  			enableProgressiveSyncs: false,
  7104  			want:                   true,
  7105  		},
  7106  		{
  7107  			name: "No Changes",
  7108  			appSetOld: &v1alpha1.ApplicationSet{
  7109  				Spec: v1alpha1.ApplicationSetSpec{
  7110  					Generators: []v1alpha1.ApplicationSetGenerator{
  7111  						{List: &v1alpha1.ListGenerator{}},
  7112  					},
  7113  				},
  7114  				ObjectMeta: metav1.ObjectMeta{
  7115  					Annotations: map[string]string{"key1": "value1"},
  7116  					Labels:      map[string]string{"key1": "value1"},
  7117  					Finalizers:  []string{"finalizer1"},
  7118  				},
  7119  			},
  7120  			appSetNew: &v1alpha1.ApplicationSet{
  7121  				Spec: v1alpha1.ApplicationSetSpec{
  7122  					Generators: []v1alpha1.ApplicationSetGenerator{
  7123  						{List: &v1alpha1.ListGenerator{}},
  7124  					},
  7125  				},
  7126  				ObjectMeta: metav1.ObjectMeta{
  7127  					Annotations: map[string]string{"key1": "value1"},
  7128  					Labels:      map[string]string{"key1": "value1"},
  7129  					Finalizers:  []string{"finalizer1"},
  7130  				},
  7131  			},
  7132  			enableProgressiveSyncs: false,
  7133  			want:                   false,
  7134  		},
  7135  		{
  7136  			name: "annotation removed",
  7137  			appSetOld: buildAppSet(map[string]string{
  7138  				argocommon.AnnotationApplicationSetRefresh: "true",
  7139  			}),
  7140  			appSetNew:              buildAppSet(map[string]string{}),
  7141  			enableProgressiveSyncs: false,
  7142  			want:                   false,
  7143  		},
  7144  		{
  7145  			name: "annotation not removed",
  7146  			appSetOld: buildAppSet(map[string]string{
  7147  				argocommon.AnnotationApplicationSetRefresh: "true",
  7148  			}),
  7149  			appSetNew: buildAppSet(map[string]string{
  7150  				argocommon.AnnotationApplicationSetRefresh: "true",
  7151  			}),
  7152  			enableProgressiveSyncs: false,
  7153  			want:                   false,
  7154  		},
  7155  		{
  7156  			name:      "annotation added",
  7157  			appSetOld: buildAppSet(map[string]string{}),
  7158  			appSetNew: buildAppSet(map[string]string{
  7159  				argocommon.AnnotationApplicationSetRefresh: "true",
  7160  			}),
  7161  			enableProgressiveSyncs: false,
  7162  			want:                   true,
  7163  		},
  7164  		{
  7165  			name:                   "old object is not an appset",
  7166  			appSetOld:              &v1alpha1.Application{},
  7167  			appSetNew:              buildAppSet(map[string]string{}),
  7168  			enableProgressiveSyncs: false,
  7169  			want:                   false,
  7170  		},
  7171  		{
  7172  			name:                   "new object is not an appset",
  7173  			appSetOld:              buildAppSet(map[string]string{}),
  7174  			appSetNew:              &v1alpha1.Application{},
  7175  			enableProgressiveSyncs: false,
  7176  			want:                   false,
  7177  		},
  7178  		{
  7179  			name:      "deletionTimestamp present when progressive sync enabled",
  7180  			appSetOld: buildAppSet(map[string]string{}),
  7181  			appSetNew: &v1alpha1.ApplicationSet{
  7182  				ObjectMeta: metav1.ObjectMeta{
  7183  					DeletionTimestamp: &metav1.Time{Time: time.Now()},
  7184  				},
  7185  			},
  7186  			enableProgressiveSyncs: true,
  7187  			want:                   true,
  7188  		},
  7189  		{
  7190  			name:      "deletionTimestamp present when progressive sync disabled",
  7191  			appSetOld: buildAppSet(map[string]string{}),
  7192  			appSetNew: &v1alpha1.ApplicationSet{
  7193  				ObjectMeta: metav1.ObjectMeta{
  7194  					DeletionTimestamp: &metav1.Time{Time: time.Now()},
  7195  				},
  7196  			},
  7197  			enableProgressiveSyncs: false,
  7198  			want:                   true,
  7199  		},
  7200  	}
  7201  
  7202  	for _, tt := range tests {
  7203  		t.Run(tt.name, func(t *testing.T) {
  7204  			ownsHandler := getApplicationSetOwnsHandler(tt.enableProgressiveSyncs)
  7205  			requeue := ownsHandler.UpdateFunc(event.UpdateEvent{
  7206  				ObjectOld: tt.appSetOld,
  7207  				ObjectNew: tt.appSetNew,
  7208  			})
  7209  			assert.Equalf(t, tt.want, requeue, "ownsHandler.UpdateFunc(%v, %v, %t)", tt.appSetOld, tt.appSetNew, tt.enableProgressiveSyncs)
  7210  		})
  7211  	}
  7212  }
  7213  
  7214  func TestApplicationSetOwnsHandlerGeneric(t *testing.T) {
  7215  	ownsHandler := getApplicationSetOwnsHandler(false)
  7216  	tests := []struct {
  7217  		name string
  7218  		obj  crtclient.Object
  7219  		want bool
  7220  	}{
  7221  		{
  7222  			name: "Object is ApplicationSet",
  7223  			obj:  &v1alpha1.ApplicationSet{},
  7224  			want: true,
  7225  		},
  7226  		{
  7227  			name: "Object is not ApplicationSet",
  7228  			obj:  &v1alpha1.Application{},
  7229  			want: false,
  7230  		},
  7231  	}
  7232  
  7233  	for _, tt := range tests {
  7234  		t.Run(tt.name, func(t *testing.T) {
  7235  			requeue := ownsHandler.GenericFunc(event.GenericEvent{
  7236  				Object: tt.obj,
  7237  			})
  7238  			assert.Equalf(t, tt.want, requeue, "ownsHandler.GenericFunc(%v)", tt.obj)
  7239  		})
  7240  	}
  7241  }
  7242  
  7243  func TestApplicationSetOwnsHandlerCreate(t *testing.T) {
  7244  	ownsHandler := getApplicationSetOwnsHandler(false)
  7245  	tests := []struct {
  7246  		name string
  7247  		obj  crtclient.Object
  7248  		want bool
  7249  	}{
  7250  		{
  7251  			name: "Object is ApplicationSet",
  7252  			obj:  &v1alpha1.ApplicationSet{},
  7253  			want: true,
  7254  		},
  7255  		{
  7256  			name: "Object is not ApplicationSet",
  7257  			obj:  &v1alpha1.Application{},
  7258  			want: false,
  7259  		},
  7260  	}
  7261  
  7262  	for _, tt := range tests {
  7263  		t.Run(tt.name, func(t *testing.T) {
  7264  			requeue := ownsHandler.CreateFunc(event.CreateEvent{
  7265  				Object: tt.obj,
  7266  			})
  7267  			assert.Equalf(t, tt.want, requeue, "ownsHandler.CreateFunc(%v)", tt.obj)
  7268  		})
  7269  	}
  7270  }
  7271  
  7272  func TestApplicationSetOwnsHandlerDelete(t *testing.T) {
  7273  	ownsHandler := getApplicationSetOwnsHandler(false)
  7274  	tests := []struct {
  7275  		name string
  7276  		obj  crtclient.Object
  7277  		want bool
  7278  	}{
  7279  		{
  7280  			name: "Object is ApplicationSet",
  7281  			obj:  &v1alpha1.ApplicationSet{},
  7282  			want: true,
  7283  		},
  7284  		{
  7285  			name: "Object is not ApplicationSet",
  7286  			obj:  &v1alpha1.Application{},
  7287  			want: false,
  7288  		},
  7289  	}
  7290  
  7291  	for _, tt := range tests {
  7292  		t.Run(tt.name, func(t *testing.T) {
  7293  			requeue := ownsHandler.DeleteFunc(event.DeleteEvent{
  7294  				Object: tt.obj,
  7295  			})
  7296  			assert.Equalf(t, tt.want, requeue, "ownsHandler.DeleteFunc(%v)", tt.obj)
  7297  		})
  7298  	}
  7299  }
  7300  
  7301  func TestShouldRequeueForApplicationSet(t *testing.T) {
  7302  	type args struct {
  7303  		appSetOld              *v1alpha1.ApplicationSet
  7304  		appSetNew              *v1alpha1.ApplicationSet
  7305  		enableProgressiveSyncs bool
  7306  	}
  7307  	tests := []struct {
  7308  		name string
  7309  		args args
  7310  		want bool
  7311  	}{
  7312  		{
  7313  			name: "NilAppSet",
  7314  			args: args{
  7315  				appSetNew:              &v1alpha1.ApplicationSet{},
  7316  				appSetOld:              nil,
  7317  				enableProgressiveSyncs: false,
  7318  			},
  7319  			want: false,
  7320  		},
  7321  		{
  7322  			name: "ApplicationSetApplicationStatusChanged",
  7323  			args: args{
  7324  				appSetOld: &v1alpha1.ApplicationSet{
  7325  					Status: v1alpha1.ApplicationSetStatus{
  7326  						ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  7327  							{
  7328  								Application: "app1",
  7329  								Status:      "Healthy",
  7330  							},
  7331  						},
  7332  					},
  7333  				},
  7334  				appSetNew: &v1alpha1.ApplicationSet{
  7335  					Status: v1alpha1.ApplicationSetStatus{
  7336  						ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  7337  							{
  7338  								Application: "app1",
  7339  								Status:      "Waiting",
  7340  							},
  7341  						},
  7342  					},
  7343  				},
  7344  				enableProgressiveSyncs: true,
  7345  			},
  7346  			want: true,
  7347  		},
  7348  		{
  7349  			name: "ApplicationSetWithDeletionTimestamp",
  7350  			args: args{
  7351  				appSetOld: &v1alpha1.ApplicationSet{
  7352  					Status: v1alpha1.ApplicationSetStatus{
  7353  						ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  7354  							{
  7355  								Application: "app1",
  7356  								Status:      "Healthy",
  7357  							},
  7358  						},
  7359  					},
  7360  				},
  7361  				appSetNew: &v1alpha1.ApplicationSet{
  7362  					ObjectMeta: metav1.ObjectMeta{
  7363  						DeletionTimestamp: &metav1.Time{Time: time.Now()},
  7364  					},
  7365  					Status: v1alpha1.ApplicationSetStatus{
  7366  						ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  7367  							{
  7368  								Application: "app1",
  7369  								Status:      "Waiting",
  7370  							},
  7371  						},
  7372  					},
  7373  				},
  7374  				enableProgressiveSyncs: false,
  7375  			},
  7376  			want: true,
  7377  		},
  7378  	}
  7379  	for _, tt := range tests {
  7380  		t.Run(tt.name, func(t *testing.T) {
  7381  			assert.Equalf(t, tt.want, shouldRequeueForApplicationSet(tt.args.appSetOld, tt.args.appSetNew, tt.args.enableProgressiveSyncs), "shouldRequeueForApplicationSet(%v, %v)", tt.args.appSetOld, tt.args.appSetNew)
  7382  		})
  7383  	}
  7384  }
  7385  
  7386  func TestIgnoreNotAllowedNamespaces(t *testing.T) {
  7387  	tests := []struct {
  7388  		name       string
  7389  		namespaces []string
  7390  		objectNS   string
  7391  		expected   bool
  7392  	}{
  7393  		{
  7394  			name:       "Namespace allowed",
  7395  			namespaces: []string{"allowed-namespace"},
  7396  			objectNS:   "allowed-namespace",
  7397  			expected:   true,
  7398  		},
  7399  		{
  7400  			name:       "Namespace not allowed",
  7401  			namespaces: []string{"allowed-namespace"},
  7402  			objectNS:   "not-allowed-namespace",
  7403  			expected:   false,
  7404  		},
  7405  		{
  7406  			name:       "Empty allowed namespaces",
  7407  			namespaces: []string{},
  7408  			objectNS:   "any-namespace",
  7409  			expected:   false,
  7410  		},
  7411  		{
  7412  			name:       "Multiple allowed namespaces",
  7413  			namespaces: []string{"allowed-namespace-1", "allowed-namespace-2"},
  7414  			objectNS:   "allowed-namespace-2",
  7415  			expected:   true,
  7416  		},
  7417  		{
  7418  			name:       "Namespace not in multiple allowed namespaces",
  7419  			namespaces: []string{"allowed-namespace-1", "allowed-namespace-2"},
  7420  			objectNS:   "not-allowed-namespace",
  7421  			expected:   false,
  7422  		},
  7423  		{
  7424  			name:       "Namespace matched by glob pattern",
  7425  			namespaces: []string{"allowed-namespace-*"},
  7426  			objectNS:   "allowed-namespace-1",
  7427  			expected:   true,
  7428  		},
  7429  		{
  7430  			name:       "Namespace matched by regex pattern",
  7431  			namespaces: []string{"/^allowed-namespace-[^-]+$/"},
  7432  			objectNS:   "allowed-namespace-1",
  7433  			expected:   true,
  7434  		},
  7435  	}
  7436  
  7437  	for _, tt := range tests {
  7438  		t.Run(tt.name, func(t *testing.T) {
  7439  			predicate := ignoreNotAllowedNamespaces(tt.namespaces)
  7440  			object := &v1alpha1.ApplicationSet{
  7441  				ObjectMeta: metav1.ObjectMeta{
  7442  					Namespace: tt.objectNS,
  7443  				},
  7444  			}
  7445  
  7446  			t.Run(tt.name+":Create", func(t *testing.T) {
  7447  				result := predicate.Create(event.CreateEvent{Object: object})
  7448  				assert.Equal(t, tt.expected, result)
  7449  			})
  7450  
  7451  			t.Run(tt.name+":Update", func(t *testing.T) {
  7452  				result := predicate.Update(event.UpdateEvent{ObjectNew: object})
  7453  				assert.Equal(t, tt.expected, result)
  7454  			})
  7455  
  7456  			t.Run(tt.name+":Delete", func(t *testing.T) {
  7457  				result := predicate.Delete(event.DeleteEvent{Object: object})
  7458  				assert.Equal(t, tt.expected, result)
  7459  			})
  7460  
  7461  			t.Run(tt.name+":Generic", func(t *testing.T) {
  7462  				result := predicate.Generic(event.GenericEvent{Object: object})
  7463  				assert.Equal(t, tt.expected, result)
  7464  			})
  7465  		})
  7466  	}
  7467  }
  7468  
  7469  func TestIsRollingSyncStrategy(t *testing.T) {
  7470  	tests := []struct {
  7471  		name     string
  7472  		appset   *v1alpha1.ApplicationSet
  7473  		expected bool
  7474  	}{
  7475  		{
  7476  			name: "RollingSync strategy is explicitly set",
  7477  			appset: &v1alpha1.ApplicationSet{
  7478  				Spec: v1alpha1.ApplicationSetSpec{
  7479  					Strategy: &v1alpha1.ApplicationSetStrategy{
  7480  						Type: "RollingSync",
  7481  						RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
  7482  							Steps: []v1alpha1.ApplicationSetRolloutStep{},
  7483  						},
  7484  					},
  7485  				},
  7486  			},
  7487  			expected: true,
  7488  		},
  7489  		{
  7490  			name: "AllAtOnce strategy is explicitly set",
  7491  			appset: &v1alpha1.ApplicationSet{
  7492  				Spec: v1alpha1.ApplicationSetSpec{
  7493  					Strategy: &v1alpha1.ApplicationSetStrategy{
  7494  						Type: "AllAtOnce",
  7495  					},
  7496  				},
  7497  			},
  7498  			expected: false,
  7499  		},
  7500  		{
  7501  			name: "Strategy is empty",
  7502  			appset: &v1alpha1.ApplicationSet{
  7503  				Spec: v1alpha1.ApplicationSetSpec{
  7504  					Strategy: &v1alpha1.ApplicationSetStrategy{},
  7505  				},
  7506  			},
  7507  			expected: false,
  7508  		},
  7509  		{
  7510  			name: "Strategy is nil",
  7511  			appset: &v1alpha1.ApplicationSet{
  7512  				Spec: v1alpha1.ApplicationSetSpec{
  7513  					Strategy: nil,
  7514  				},
  7515  			},
  7516  			expected: false,
  7517  		},
  7518  	}
  7519  
  7520  	for _, tt := range tests {
  7521  		t.Run(tt.name, func(t *testing.T) {
  7522  			result := isRollingSyncStrategy(tt.appset)
  7523  			assert.Equal(t, tt.expected, result)
  7524  		})
  7525  	}
  7526  }
  7527  
  7528  func TestSyncApplication(t *testing.T) {
  7529  	tests := []struct {
  7530  		name     string
  7531  		input    v1alpha1.Application
  7532  		prune    bool
  7533  		expected v1alpha1.Application
  7534  	}{
  7535  		{
  7536  			name: "Default retry limit with no SyncPolicy",
  7537  			input: v1alpha1.Application{
  7538  				Spec: v1alpha1.ApplicationSpec{},
  7539  			},
  7540  			prune: false,
  7541  			expected: v1alpha1.Application{
  7542  				Spec: v1alpha1.ApplicationSpec{},
  7543  				Operation: &v1alpha1.Operation{
  7544  					InitiatedBy: v1alpha1.OperationInitiator{
  7545  						Username:  "applicationset-controller",
  7546  						Automated: true,
  7547  					},
  7548  					Info: []*v1alpha1.Info{
  7549  						{
  7550  							Name:  "Reason",
  7551  							Value: "ApplicationSet RollingSync triggered a sync of this Application resource",
  7552  						},
  7553  					},
  7554  					Sync: &v1alpha1.SyncOperation{
  7555  						Prune: false,
  7556  					},
  7557  					Retry: v1alpha1.RetryStrategy{
  7558  						Limit: 5,
  7559  					},
  7560  				},
  7561  			},
  7562  		},
  7563  		{
  7564  			name: "Retry and SyncOptions from SyncPolicy are applied",
  7565  			input: v1alpha1.Application{
  7566  				Spec: v1alpha1.ApplicationSpec{
  7567  					SyncPolicy: &v1alpha1.SyncPolicy{
  7568  						Retry: &v1alpha1.RetryStrategy{
  7569  							Limit: 10,
  7570  						},
  7571  						SyncOptions: []string{"CreateNamespace=true"},
  7572  					},
  7573  				},
  7574  			},
  7575  			prune: true,
  7576  			expected: v1alpha1.Application{
  7577  				Spec: v1alpha1.ApplicationSpec{
  7578  					SyncPolicy: &v1alpha1.SyncPolicy{
  7579  						Retry: &v1alpha1.RetryStrategy{
  7580  							Limit: 10,
  7581  						},
  7582  						SyncOptions: []string{"CreateNamespace=true"},
  7583  					},
  7584  				},
  7585  				Operation: &v1alpha1.Operation{
  7586  					InitiatedBy: v1alpha1.OperationInitiator{
  7587  						Username:  "applicationset-controller",
  7588  						Automated: true,
  7589  					},
  7590  					Info: []*v1alpha1.Info{
  7591  						{
  7592  							Name:  "Reason",
  7593  							Value: "ApplicationSet RollingSync triggered a sync of this Application resource",
  7594  						},
  7595  					},
  7596  					Sync: &v1alpha1.SyncOperation{
  7597  						SyncOptions: []string{"CreateNamespace=true"},
  7598  						Prune:       true,
  7599  					},
  7600  					Retry: v1alpha1.RetryStrategy{
  7601  						Limit: 10,
  7602  					},
  7603  				},
  7604  			},
  7605  		},
  7606  	}
  7607  
  7608  	for _, tt := range tests {
  7609  		t.Run(tt.name, func(t *testing.T) {
  7610  			result := syncApplication(tt.input, tt.prune)
  7611  			assert.Equal(t, tt.expected, result)
  7612  		})
  7613  	}
  7614  }
  7615  
  7616  func TestReconcileProgressiveSyncDisabled(t *testing.T) {
  7617  	scheme := runtime.NewScheme()
  7618  	err := v1alpha1.AddToScheme(scheme)
  7619  	require.NoError(t, err)
  7620  
  7621  	kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
  7622  
  7623  	for _, cc := range []struct {
  7624  		name                   string
  7625  		appSet                 v1alpha1.ApplicationSet
  7626  		enableProgressiveSyncs bool
  7627  		expectedAppStatuses    []v1alpha1.ApplicationSetApplicationStatus
  7628  	}{
  7629  		{
  7630  			name: "clears applicationStatus when Progressive Sync is disabled",
  7631  			appSet: v1alpha1.ApplicationSet{
  7632  				ObjectMeta: metav1.ObjectMeta{
  7633  					Name:      "test-appset",
  7634  					Namespace: "argocd",
  7635  				},
  7636  				Spec: v1alpha1.ApplicationSetSpec{
  7637  					Generators: []v1alpha1.ApplicationSetGenerator{},
  7638  					Template:   v1alpha1.ApplicationSetTemplate{},
  7639  				},
  7640  				Status: v1alpha1.ApplicationSetStatus{
  7641  					ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
  7642  						{
  7643  							Application: "test-appset-guestbook",
  7644  							Message:     "Application resource became Healthy, updating status from Progressing to Healthy.",
  7645  							Status:      "Healthy",
  7646  							Step:        "1",
  7647  						},
  7648  					},
  7649  				},
  7650  			},
  7651  			enableProgressiveSyncs: false,
  7652  			expectedAppStatuses:    nil,
  7653  		},
  7654  	} {
  7655  		t.Run(cc.name, func(t *testing.T) {
  7656  			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
  7657  			metrics := appsetmetrics.NewFakeAppsetMetrics()
  7658  
  7659  			argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset)
  7660  
  7661  			r := ApplicationSetReconciler{
  7662  				Client:                 client,
  7663  				Scheme:                 scheme,
  7664  				Renderer:               &utils.Render{},
  7665  				Recorder:               record.NewFakeRecorder(1),
  7666  				Generators:             map[string]generators.Generator{},
  7667  				ArgoDB:                 argodb,
  7668  				KubeClientset:          kubeclientset,
  7669  				Metrics:                metrics,
  7670  				EnableProgressiveSyncs: cc.enableProgressiveSyncs,
  7671  			}
  7672  
  7673  			req := ctrl.Request{
  7674  				NamespacedName: types.NamespacedName{
  7675  					Namespace: cc.appSet.Namespace,
  7676  					Name:      cc.appSet.Name,
  7677  				},
  7678  			}
  7679  
  7680  			// Run reconciliation
  7681  			_, err = r.Reconcile(t.Context(), req)
  7682  			require.NoError(t, err)
  7683  
  7684  			// Fetch the updated ApplicationSet
  7685  			var updatedAppSet v1alpha1.ApplicationSet
  7686  			err = r.Get(t.Context(), req.NamespacedName, &updatedAppSet)
  7687  			require.NoError(t, err)
  7688  
  7689  			// Verify the applicationStatus field
  7690  			assert.Equal(t, cc.expectedAppStatuses, updatedAppSet.Status.ApplicationStatus, "applicationStatus should match expected value")
  7691  		})
  7692  	}
  7693  }