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

     1  package applicationset
     2  
     3  import (
     4  	"sort"
     5  	"testing"
     6  
     7  	"sigs.k8s.io/controller-runtime/pkg/client"
     8  	cr_fake "sigs.k8s.io/controller-runtime/pkg/client/fake"
     9  
    10  	"github.com/argoproj/gitops-engine/pkg/health"
    11  	"github.com/argoproj/pkg/v2/sync"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	corev1 "k8s.io/api/core/v1"
    15  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/client-go/kubernetes/fake"
    19  	k8scache "k8s.io/client-go/tools/cache"
    20  
    21  	"github.com/argoproj/argo-cd/v3/common"
    22  	"github.com/argoproj/argo-cd/v3/pkg/apiclient/applicationset"
    23  	appsv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    24  	apps "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake"
    25  	appinformer "github.com/argoproj/argo-cd/v3/pkg/client/informers/externalversions"
    26  	"github.com/argoproj/argo-cd/v3/server/rbacpolicy"
    27  	"github.com/argoproj/argo-cd/v3/util/argo"
    28  	"github.com/argoproj/argo-cd/v3/util/assets"
    29  	"github.com/argoproj/argo-cd/v3/util/db"
    30  	"github.com/argoproj/argo-cd/v3/util/rbac"
    31  	"github.com/argoproj/argo-cd/v3/util/settings"
    32  )
    33  
    34  const (
    35  	testNamespace = "default"
    36  	fakeRepoURL   = "https://git.com/repo.git"
    37  )
    38  
    39  var testEnableEventList []string = argo.DefaultEnableEventList()
    40  
    41  func fakeRepo() *appsv1.Repository {
    42  	return &appsv1.Repository{
    43  		Repo: fakeRepoURL,
    44  	}
    45  }
    46  
    47  func fakeCluster() *appsv1.Cluster {
    48  	return &appsv1.Cluster{
    49  		Server: "https://cluster-api.example.com",
    50  		Name:   "fake-cluster",
    51  		Config: appsv1.ClusterConfig{},
    52  	}
    53  }
    54  
    55  // return an ApplicationServiceServer which returns fake data
    56  func newTestAppSetServer(t *testing.T, objects ...client.Object) *Server {
    57  	t.Helper()
    58  	f := func(enf *rbac.Enforcer) {
    59  		_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
    60  		enf.SetDefaultRole("role:admin")
    61  	}
    62  	scopedNamespaces := ""
    63  	return newTestAppSetServerWithEnforcerConfigure(t, f, scopedNamespaces, objects...)
    64  }
    65  
    66  // return an ApplicationServiceServer which returns fake data
    67  func newTestNamespacedAppSetServer(t *testing.T, objects ...client.Object) *Server {
    68  	t.Helper()
    69  	f := func(enf *rbac.Enforcer) {
    70  		_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
    71  		enf.SetDefaultRole("role:admin")
    72  	}
    73  	scopedNamespaces := "argocd"
    74  	return newTestAppSetServerWithEnforcerConfigure(t, f, scopedNamespaces, objects...)
    75  }
    76  
    77  func newTestAppSetServerWithEnforcerConfigure(t *testing.T, f func(*rbac.Enforcer), namespace string, objects ...client.Object) *Server {
    78  	t.Helper()
    79  	kubeclientset := fake.NewClientset(&corev1.ConfigMap{
    80  		ObjectMeta: metav1.ObjectMeta{
    81  			Namespace: testNamespace,
    82  			Name:      "argocd-cm",
    83  			Labels: map[string]string{
    84  				"app.kubernetes.io/part-of": "argocd",
    85  			},
    86  		},
    87  	}, &corev1.Secret{
    88  		ObjectMeta: metav1.ObjectMeta{
    89  			Name:      "argocd-secret",
    90  			Namespace: testNamespace,
    91  		},
    92  		Data: map[string][]byte{
    93  			"admin.password":   []byte("test"),
    94  			"server.secretkey": []byte("test"),
    95  		},
    96  	})
    97  	ctx := t.Context()
    98  	db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset)
    99  	_, err := db.CreateRepository(ctx, fakeRepo())
   100  	require.NoError(t, err)
   101  	_, err = db.CreateCluster(ctx, fakeCluster())
   102  	require.NoError(t, err)
   103  
   104  	defaultProj := &appsv1.AppProject{
   105  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"},
   106  		Spec: appsv1.AppProjectSpec{
   107  			SourceRepos:  []string{"*"},
   108  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   109  		},
   110  	}
   111  	myProj := &appsv1.AppProject{
   112  		ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"},
   113  		Spec: appsv1.AppProjectSpec{
   114  			SourceRepos:  []string{"*"},
   115  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   116  		},
   117  	}
   118  
   119  	objects = append(objects, defaultProj, myProj)
   120  
   121  	runtimeObjects := make([]runtime.Object, len(objects))
   122  	for i := range objects {
   123  		runtimeObjects[i] = objects[i]
   124  	}
   125  	fakeAppsClientset := apps.NewSimpleClientset(runtimeObjects...)
   126  	factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(namespace), appinformer.WithTweakListOptions(func(_ *metav1.ListOptions) {}))
   127  	fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace)
   128  
   129  	enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
   130  	f(enforcer)
   131  	enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims)
   132  
   133  	// populate the app informer with the fake objects
   134  	appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
   135  	// TODO(jessesuen): probably should return cancel function so tests can stop background informer
   136  	// ctx, cancel := context.WithCancel(context.Background())
   137  	go appInformer.Run(ctx.Done())
   138  	if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) {
   139  		panic("Timed out waiting for caches to sync")
   140  	}
   141  	// populate the appset informer with the fake objects
   142  	appsetInformer := factory.Argoproj().V1alpha1().ApplicationSets().Informer()
   143  	go appsetInformer.Run(ctx.Done())
   144  	if !k8scache.WaitForCacheSync(ctx.Done(), appsetInformer.HasSynced) {
   145  		panic("Timed out waiting for caches to sync")
   146  	}
   147  
   148  	scheme := runtime.NewScheme()
   149  	err = appsv1.AddToScheme(scheme)
   150  	require.NoError(t, err)
   151  	err = corev1.AddToScheme(scheme)
   152  	require.NoError(t, err)
   153  	crClient := cr_fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()
   154  
   155  	projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
   156  	go projInformer.Run(ctx.Done())
   157  	if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) {
   158  		panic("Timed out waiting for caches to sync")
   159  	}
   160  
   161  	server := NewServer(
   162  		db,
   163  		kubeclientset,
   164  		nil,
   165  		crClient,
   166  		enforcer,
   167  		nil,
   168  		fakeAppsClientset,
   169  		appInformer,
   170  		factory.Argoproj().V1alpha1().ApplicationSets().Lister(),
   171  		testNamespace,
   172  		sync.NewKeyLock(),
   173  		[]string{testNamespace, "external-namespace"},
   174  		true,
   175  		true,
   176  		"",
   177  		[]string{},
   178  		true,
   179  		true,
   180  		testEnableEventList,
   181  	)
   182  	return server.(*Server)
   183  }
   184  
   185  func newTestAppSet(opts ...func(appset *appsv1.ApplicationSet)) *appsv1.ApplicationSet {
   186  	appset := appsv1.ApplicationSet{
   187  		ObjectMeta: metav1.ObjectMeta{
   188  			Namespace: testNamespace,
   189  		},
   190  		Spec: appsv1.ApplicationSetSpec{
   191  			Template: appsv1.ApplicationSetTemplate{
   192  				Spec: appsv1.ApplicationSpec{
   193  					Project: "default",
   194  				},
   195  			},
   196  		},
   197  	}
   198  	for i := range opts {
   199  		opts[i](&appset)
   200  	}
   201  	return &appset
   202  }
   203  
   204  func testListAppsetsWithLabels(t *testing.T, appsetQuery applicationset.ApplicationSetListQuery, appServer *Server) {
   205  	t.Helper()
   206  	validTests := []struct {
   207  		testName       string
   208  		label          string
   209  		expectedResult []string
   210  	}{
   211  		{
   212  			testName:       "Equality based filtering using '=' operator",
   213  			label:          "key1=value1",
   214  			expectedResult: []string{"AppSet1"},
   215  		},
   216  		{
   217  			testName:       "Equality based filtering using '==' operator",
   218  			label:          "key1==value1",
   219  			expectedResult: []string{"AppSet1"},
   220  		},
   221  		{
   222  			testName:       "Equality based filtering using '!=' operator",
   223  			label:          "key1!=value1",
   224  			expectedResult: []string{"AppSet2", "AppSet3"},
   225  		},
   226  		{
   227  			testName:       "Set based filtering using 'in' operator",
   228  			label:          "key1 in (value1, value3)",
   229  			expectedResult: []string{"AppSet1", "AppSet3"},
   230  		},
   231  		{
   232  			testName:       "Set based filtering using 'notin' operator",
   233  			label:          "key1 notin (value1, value3)",
   234  			expectedResult: []string{"AppSet2"},
   235  		},
   236  		{
   237  			testName:       "Set based filtering using 'exists' operator",
   238  			label:          "key1",
   239  			expectedResult: []string{"AppSet1", "AppSet2", "AppSet3"},
   240  		},
   241  		{
   242  			testName:       "Set based filtering using 'not exists' operator",
   243  			label:          "!key2",
   244  			expectedResult: []string{"AppSet2", "AppSet3"},
   245  		},
   246  	}
   247  	// test valid scenarios
   248  	for _, validTest := range validTests {
   249  		t.Run(validTest.testName, func(t *testing.T) {
   250  			appsetQuery.Selector = validTest.label
   251  			res, err := appServer.List(t.Context(), &appsetQuery)
   252  			require.NoError(t, err)
   253  			apps := []string{}
   254  			for i := range res.Items {
   255  				apps = append(apps, res.Items[i].Name)
   256  			}
   257  			assert.Equal(t, validTest.expectedResult, apps)
   258  		})
   259  	}
   260  
   261  	invalidTests := []struct {
   262  		testName    string
   263  		label       string
   264  		errorMesage string
   265  	}{
   266  		{
   267  			testName:    "Set based filtering using '>' operator",
   268  			label:       "key1>value1",
   269  			errorMesage: "error parsing the selector",
   270  		},
   271  		{
   272  			testName:    "Set based filtering using '<' operator",
   273  			label:       "key1<value1",
   274  			errorMesage: "error parsing the selector",
   275  		},
   276  	}
   277  	// test invalid scenarios
   278  	for _, invalidTest := range invalidTests {
   279  		t.Run(invalidTest.testName, func(t *testing.T) {
   280  			appsetQuery.Selector = invalidTest.label
   281  			_, err := appServer.List(t.Context(), &appsetQuery)
   282  			assert.ErrorContains(t, err, invalidTest.errorMesage)
   283  		})
   284  	}
   285  }
   286  
   287  func TestListAppSetsInNamespaceWithLabels(t *testing.T) {
   288  	testNamespace := "test-namespace"
   289  	appSetServer := newTestAppSetServer(t, newTestAppSet(func(appset *appsv1.ApplicationSet) {
   290  		appset.Name = "AppSet1"
   291  		appset.Namespace = testNamespace
   292  		appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
   293  	}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
   294  		appset.Name = "AppSet2"
   295  		appset.Namespace = testNamespace
   296  		appset.SetLabels(map[string]string{"key1": "value2"})
   297  	}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
   298  		appset.Name = "AppSet3"
   299  		appset.Namespace = testNamespace
   300  		appset.SetLabels(map[string]string{"key1": "value3"})
   301  	}))
   302  	appSetServer.enabledNamespaces = []string{testNamespace}
   303  	appsetQuery := applicationset.ApplicationSetListQuery{AppsetNamespace: testNamespace}
   304  
   305  	testListAppsetsWithLabels(t, appsetQuery, appSetServer)
   306  }
   307  
   308  func TestListAppSetsInDefaultNSWithLabels(t *testing.T) {
   309  	appSetServer := newTestAppSetServer(t, newTestAppSet(func(appset *appsv1.ApplicationSet) {
   310  		appset.Name = "AppSet1"
   311  		appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
   312  	}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
   313  		appset.Name = "AppSet2"
   314  		appset.SetLabels(map[string]string{"key1": "value2"})
   315  	}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
   316  		appset.Name = "AppSet3"
   317  		appset.SetLabels(map[string]string{"key1": "value3"})
   318  	}))
   319  	appsetQuery := applicationset.ApplicationSetListQuery{}
   320  
   321  	testListAppsetsWithLabels(t, appsetQuery, appSetServer)
   322  }
   323  
   324  // This test covers https://github.com/argoproj/argo-cd/issues/15429
   325  // If the namespace isn't provided during listing action, argocd's
   326  // default namespace must be used and not all the namespaces
   327  func TestListAppSetsWithoutNamespace(t *testing.T) {
   328  	testNamespace := "test-namespace"
   329  	appSetServer := newTestNamespacedAppSetServer(t, newTestAppSet(func(appset *appsv1.ApplicationSet) {
   330  		appset.Name = "AppSet1"
   331  		appset.Namespace = testNamespace
   332  		appset.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
   333  	}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
   334  		appset.Name = "AppSet2"
   335  		appset.Namespace = testNamespace
   336  		appset.SetLabels(map[string]string{"key1": "value2"})
   337  	}), newTestAppSet(func(appset *appsv1.ApplicationSet) {
   338  		appset.Name = "AppSet3"
   339  		appset.Namespace = testNamespace
   340  		appset.SetLabels(map[string]string{"key1": "value3"})
   341  	}))
   342  	appSetServer.enabledNamespaces = []string{testNamespace}
   343  	appsetQuery := applicationset.ApplicationSetListQuery{}
   344  
   345  	res, err := appSetServer.List(t.Context(), &appsetQuery)
   346  	require.NoError(t, err)
   347  	assert.Empty(t, res.Items)
   348  }
   349  
   350  func TestCreateAppSet(t *testing.T) {
   351  	testAppSet := newTestAppSet()
   352  	appServer := newTestAppSetServer(t)
   353  	testAppSet.Spec.Generators = []appsv1.ApplicationSetGenerator{
   354  		{
   355  			List: &appsv1.ListGenerator{},
   356  		},
   357  	}
   358  	createReq := applicationset.ApplicationSetCreateRequest{
   359  		Applicationset: testAppSet,
   360  	}
   361  	_, err := appServer.Create(t.Context(), &createReq)
   362  	require.NoError(t, err)
   363  }
   364  
   365  func TestCreateAppSetTemplatedProject(t *testing.T) {
   366  	testAppSet := newTestAppSet()
   367  	appServer := newTestAppSetServer(t)
   368  	testAppSet.Spec.Template.Spec.Project = "{{ .project }}"
   369  	createReq := applicationset.ApplicationSetCreateRequest{
   370  		Applicationset: testAppSet,
   371  	}
   372  	_, err := appServer.Create(t.Context(), &createReq)
   373  	assert.EqualError(t, err, "error validating ApplicationSets: the Argo CD API does not currently support creating ApplicationSets with templated `project` fields")
   374  }
   375  
   376  func TestCreateAppSetWrongNamespace(t *testing.T) {
   377  	testAppSet := newTestAppSet()
   378  	appServer := newTestAppSetServer(t)
   379  	testAppSet.Namespace = "NOT-ALLOWED"
   380  	createReq := applicationset.ApplicationSetCreateRequest{
   381  		Applicationset: testAppSet,
   382  	}
   383  	_, err := appServer.Create(t.Context(), &createReq)
   384  
   385  	assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted")
   386  }
   387  
   388  func TestCreateAppSetDryRun(t *testing.T) {
   389  	testAppSet := newTestAppSet()
   390  	appServer := newTestAppSetServer(t)
   391  	testAppSet.Spec.Template.Name = "{{name}}"
   392  	testAppSet.Spec.Generators = []appsv1.ApplicationSetGenerator{
   393  		{
   394  			List: &appsv1.ListGenerator{
   395  				Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"name": "a"}`)}, {Raw: []byte(`{"name": "b"}`)}},
   396  			},
   397  		},
   398  	}
   399  	createReq := applicationset.ApplicationSetCreateRequest{
   400  		Applicationset: testAppSet,
   401  		DryRun:         true,
   402  	}
   403  	result, err := appServer.Create(t.Context(), &createReq)
   404  
   405  	require.NoError(t, err)
   406  	assert.Len(t, result.Status.Resources, 2)
   407  
   408  	// Sort resulting application by name
   409  	sort.Slice(result.Status.Resources, func(i, j int) bool {
   410  		return result.Status.Resources[i].Name < result.Status.Resources[j].Name
   411  	})
   412  
   413  	assert.Equal(t, "a", result.Status.Resources[0].Name)
   414  	assert.Equal(t, testAppSet.Namespace, result.Status.Resources[0].Namespace)
   415  	assert.Equal(t, "b", result.Status.Resources[1].Name)
   416  	assert.Equal(t, testAppSet.Namespace, result.Status.Resources[1].Namespace)
   417  }
   418  
   419  func TestCreateAppSetDryRunWithDuplicate(t *testing.T) {
   420  	testAppSet := newTestAppSet()
   421  	appServer := newTestAppSetServer(t)
   422  	testAppSet.Spec.Template.Name = "{{name}}"
   423  	testAppSet.Spec.Generators = []appsv1.ApplicationSetGenerator{
   424  		{
   425  			List: &appsv1.ListGenerator{
   426  				Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"name": "a"}`)}, {Raw: []byte(`{"name": "a"}`)}},
   427  			},
   428  		},
   429  	}
   430  	createReq := applicationset.ApplicationSetCreateRequest{
   431  		Applicationset: testAppSet,
   432  		DryRun:         true,
   433  	}
   434  	result, err := appServer.Create(t.Context(), &createReq)
   435  
   436  	require.NoError(t, err)
   437  	assert.Len(t, result.Status.Resources, 1)
   438  	assert.Equal(t, "a", result.Status.Resources[0].Name)
   439  	assert.Equal(t, testAppSet.Namespace, result.Status.Resources[0].Namespace)
   440  }
   441  
   442  func TestGetAppSet(t *testing.T) {
   443  	appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   444  		appset.Name = "AppSet1"
   445  	})
   446  
   447  	appSet2 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   448  		appset.Name = "AppSet2"
   449  	})
   450  
   451  	appSet3 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   452  		appset.Name = "AppSet3"
   453  	})
   454  
   455  	t.Run("Get in default namespace", func(t *testing.T) {
   456  		appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3)
   457  
   458  		appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1"}
   459  
   460  		res, err := appSetServer.Get(t.Context(), &appsetQuery)
   461  		require.NoError(t, err)
   462  		assert.Equal(t, "AppSet1", res.Name)
   463  	})
   464  
   465  	t.Run("Get in named namespace", func(t *testing.T) {
   466  		appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3)
   467  
   468  		appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1", AppsetNamespace: testNamespace}
   469  
   470  		res, err := appSetServer.Get(t.Context(), &appsetQuery)
   471  		require.NoError(t, err)
   472  		assert.Equal(t, "AppSet1", res.Name)
   473  	})
   474  
   475  	t.Run("Get in not allowed namespace", func(t *testing.T) {
   476  		appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3)
   477  
   478  		appsetQuery := applicationset.ApplicationSetGetQuery{Name: "AppSet1", AppsetNamespace: "NOT-ALLOWED"}
   479  
   480  		_, err := appSetServer.Get(t.Context(), &appsetQuery)
   481  		assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted")
   482  	})
   483  }
   484  
   485  func TestDeleteAppSet(t *testing.T) {
   486  	appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   487  		appset.Name = "AppSet1"
   488  	})
   489  
   490  	appSet2 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   491  		appset.Name = "AppSet2"
   492  	})
   493  
   494  	appSet3 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   495  		appset.Name = "AppSet3"
   496  	})
   497  
   498  	t.Run("Delete in default namespace", func(t *testing.T) {
   499  		appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3)
   500  
   501  		appsetQuery := applicationset.ApplicationSetDeleteRequest{Name: "AppSet1"}
   502  
   503  		res, err := appSetServer.Delete(t.Context(), &appsetQuery)
   504  		require.NoError(t, err)
   505  		assert.Equal(t, &applicationset.ApplicationSetResponse{}, res)
   506  	})
   507  
   508  	t.Run("Delete in named namespace", func(t *testing.T) {
   509  		appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3)
   510  
   511  		appsetQuery := applicationset.ApplicationSetDeleteRequest{Name: "AppSet1", AppsetNamespace: testNamespace}
   512  
   513  		res, err := appSetServer.Delete(t.Context(), &appsetQuery)
   514  		require.NoError(t, err)
   515  		assert.Equal(t, &applicationset.ApplicationSetResponse{}, res)
   516  	})
   517  }
   518  
   519  func TestUpdateAppSet(t *testing.T) {
   520  	appSet := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   521  		appset.Annotations = map[string]string{
   522  			"annotation-key1": "annotation-value1",
   523  			"annotation-key2": "annotation-value2",
   524  		}
   525  		appset.Labels = map[string]string{
   526  			"label-key1": "label-value1",
   527  			"label-key2": "label-value2",
   528  		}
   529  		appset.Finalizers = []string{"finalizer"}
   530  	})
   531  
   532  	newAppSet := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   533  		appset.Annotations = map[string]string{
   534  			"annotation-key1": "annotation-value1-updated",
   535  		}
   536  		appset.Labels = map[string]string{
   537  			"label-key1": "label-value1-updated",
   538  		}
   539  		appset.Finalizers = []string{"finalizer-updated"}
   540  	})
   541  
   542  	t.Run("Update merge", func(t *testing.T) {
   543  		appServer := newTestAppSetServer(t, appSet)
   544  
   545  		updated, err := appServer.updateAppSet(t.Context(), appSet, newAppSet, true)
   546  
   547  		require.NoError(t, err)
   548  		assert.Equal(t, map[string]string{
   549  			"annotation-key1": "annotation-value1-updated",
   550  			"annotation-key2": "annotation-value2",
   551  		}, updated.Annotations)
   552  		assert.Equal(t, map[string]string{
   553  			"label-key1": "label-value1-updated",
   554  			"label-key2": "label-value2",
   555  		}, updated.Labels)
   556  		assert.Equal(t, []string{"finalizer-updated"}, updated.Finalizers)
   557  	})
   558  
   559  	t.Run("Update no merge", func(t *testing.T) {
   560  		appServer := newTestAppSetServer(t, appSet)
   561  
   562  		updated, err := appServer.updateAppSet(t.Context(), appSet, newAppSet, false)
   563  
   564  		require.NoError(t, err)
   565  		assert.Equal(t, map[string]string{
   566  			"annotation-key1": "annotation-value1-updated",
   567  		}, updated.Annotations)
   568  		assert.Equal(t, map[string]string{
   569  			"label-key1": "label-value1-updated",
   570  		}, updated.Labels)
   571  	})
   572  }
   573  
   574  func TestUpsertAppSet(t *testing.T) {
   575  	name := "test"
   576  	ns := "external-namespace"
   577  	appSet := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   578  		appset.Annotations = map[string]string{
   579  			"annotation-key1": "annotation-value1",
   580  			"annotation-key2": "annotation-value2",
   581  		}
   582  		appset.Labels = map[string]string{
   583  			"label-key1": "label-value1",
   584  			"label-key2": "label-value2",
   585  		}
   586  		appset.Name = name
   587  		appset.Namespace = ns
   588  		appset.Finalizers = []string{"finalizer"}
   589  	})
   590  
   591  	updatedAppSet := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   592  		appset.Annotations = map[string]string{
   593  			"annotation-key1": "annotation-value1-updated",
   594  		}
   595  		appset.Labels = map[string]string{
   596  			"label-key1": "label-value1-updated",
   597  		}
   598  		appset.Name = name
   599  		appset.Namespace = ns
   600  		appset.Finalizers = []string{"finalizer-updated"}
   601  	})
   602  
   603  	t.Run("Upsert", func(t *testing.T) {
   604  		appServer := newTestAppSetServer(t, appSet)
   605  
   606  		updated, err := appServer.Create(t.Context(), &applicationset.ApplicationSetCreateRequest{
   607  			Applicationset: updatedAppSet,
   608  			Upsert:         true,
   609  		})
   610  
   611  		require.NoError(t, err)
   612  		assert.Equal(t, map[string]string{
   613  			"annotation-key1": "annotation-value1-updated",
   614  			"annotation-key2": "annotation-value2",
   615  		}, updated.Annotations)
   616  		assert.Equal(t, map[string]string{
   617  			"label-key1": "label-value1-updated",
   618  			"label-key2": "label-value2",
   619  		}, updated.Labels)
   620  		assert.Equal(t, []string{"finalizer-updated"}, updated.Finalizers)
   621  	})
   622  }
   623  
   624  func TestResourceTree(t *testing.T) {
   625  	appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   626  		appset.Name = "AppSet1"
   627  		appset.Status.Resources = []appsv1.ResourceStatus{
   628  			{
   629  				Name:      "app1",
   630  				Kind:      "Application",
   631  				Group:     "argoproj.io",
   632  				Version:   "v1alpha1",
   633  				Namespace: "default",
   634  				Health: &appsv1.HealthStatus{
   635  					Status:  health.HealthStatusHealthy,
   636  					Message: "OK",
   637  				},
   638  				Status: appsv1.SyncStatusCodeSynced,
   639  			},
   640  		}
   641  	})
   642  
   643  	appSet2 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   644  		appset.Name = "AppSet2"
   645  	})
   646  
   647  	appSet3 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   648  		appset.Name = "AppSet3"
   649  	})
   650  
   651  	expectedTree := &appsv1.ApplicationSetTree{
   652  		Nodes: []appsv1.ResourceNode{
   653  			{
   654  				ResourceRef: appsv1.ResourceRef{
   655  					Kind:      "Application",
   656  					Group:     "argoproj.io",
   657  					Version:   "v1alpha1",
   658  					Namespace: "default",
   659  					Name:      "app1",
   660  				},
   661  				ParentRefs: []appsv1.ResourceRef{
   662  					{
   663  						Kind:      "ApplicationSet",
   664  						Group:     "argoproj.io",
   665  						Version:   "v1alpha1",
   666  						Namespace: "default",
   667  						Name:      "AppSet1",
   668  					},
   669  				},
   670  				Health: &appsv1.HealthStatus{
   671  					Status:  health.HealthStatusHealthy,
   672  					Message: "OK",
   673  				},
   674  			},
   675  		},
   676  	}
   677  
   678  	t.Run("ResourceTree in default namespace", func(t *testing.T) {
   679  		appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3)
   680  
   681  		appsetQuery := applicationset.ApplicationSetTreeQuery{Name: "AppSet1"}
   682  
   683  		res, err := appSetServer.ResourceTree(t.Context(), &appsetQuery)
   684  		require.NoError(t, err)
   685  		assert.Equal(t, expectedTree, res)
   686  	})
   687  
   688  	t.Run("ResourceTree in named namespace", func(t *testing.T) {
   689  		appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3)
   690  
   691  		appsetQuery := applicationset.ApplicationSetTreeQuery{Name: "AppSet1", AppsetNamespace: testNamespace}
   692  
   693  		res, err := appSetServer.ResourceTree(t.Context(), &appsetQuery)
   694  		require.NoError(t, err)
   695  		assert.Equal(t, expectedTree, res)
   696  	})
   697  
   698  	t.Run("ResourceTree in not allowed namespace", func(t *testing.T) {
   699  		appSetServer := newTestAppSetServer(t, appSet1, appSet2, appSet3)
   700  
   701  		appsetQuery := applicationset.ApplicationSetTreeQuery{Name: "AppSet1", AppsetNamespace: "NOT-ALLOWED"}
   702  
   703  		_, err := appSetServer.ResourceTree(t.Context(), &appsetQuery)
   704  		assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted")
   705  	})
   706  }
   707  
   708  func TestAppSet_Generate_Cluster(t *testing.T) {
   709  	appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) {
   710  		appset.Name = "AppSet1"
   711  		appset.Spec.Template.Name = "{{name}}"
   712  		appset.Spec.Generators = []appsv1.ApplicationSetGenerator{
   713  			{
   714  				Clusters: &appsv1.ClusterGenerator{},
   715  			},
   716  		}
   717  	})
   718  
   719  	t.Run("Generate in default namespace", func(t *testing.T) {
   720  		appSetServer := newTestAppSetServer(t, appSet1)
   721  		appsetQuery := applicationset.ApplicationSetGenerateRequest{
   722  			ApplicationSet: appSet1,
   723  		}
   724  
   725  		res, err := appSetServer.Generate(t.Context(), &appsetQuery)
   726  		require.NoError(t, err)
   727  		require.Len(t, res.Applications, 2)
   728  		assert.Equal(t, "fake-cluster", res.Applications[0].Name)
   729  		assert.Equal(t, "in-cluster", res.Applications[1].Name)
   730  	})
   731  
   732  	t.Run("Generate in different namespace", func(t *testing.T) {
   733  		appSetServer := newTestAppSetServer(t, appSet1)
   734  
   735  		appSet1Ns := appSet1.DeepCopy()
   736  		appSet1Ns.Namespace = "external-namespace"
   737  		appsetQuery := applicationset.ApplicationSetGenerateRequest{ApplicationSet: appSet1Ns}
   738  
   739  		res, err := appSetServer.Generate(t.Context(), &appsetQuery)
   740  		require.NoError(t, err)
   741  		require.Len(t, res.Applications, 2)
   742  		assert.Equal(t, "fake-cluster", res.Applications[0].Name)
   743  		assert.Equal(t, "in-cluster", res.Applications[1].Name)
   744  	})
   745  
   746  	t.Run("Generate in not allowed namespace", func(t *testing.T) {
   747  		appSetServer := newTestAppSetServer(t, appSet1)
   748  
   749  		appSet1Ns := appSet1.DeepCopy()
   750  		appSet1Ns.Namespace = "NOT-ALLOWED"
   751  
   752  		appsetQuery := applicationset.ApplicationSetGenerateRequest{ApplicationSet: appSet1Ns}
   753  
   754  		_, err := appSetServer.Generate(t.Context(), &appsetQuery)
   755  		assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted")
   756  	})
   757  }