github.com/argoproj/argo-cd/v3@v3.2.1/util/argo/argo_test.go (about)

     1  package argo
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    11  	"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/require"
    15  	"google.golang.org/grpc/codes"
    16  	"google.golang.org/grpc/status"
    17  	corev1 "k8s.io/api/core/v1"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    20  	"k8s.io/apimachinery/pkg/runtime/schema"
    21  	"k8s.io/client-go/kubernetes/fake"
    22  	"k8s.io/client-go/tools/cache"
    23  
    24  	"github.com/argoproj/gitops-engine/pkg/sync/common"
    25  
    26  	argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    27  	appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake"
    28  	"github.com/argoproj/argo-cd/v3/pkg/client/informers/externalversions/application/v1alpha1"
    29  	applisters "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
    30  	"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
    31  	"github.com/argoproj/argo-cd/v3/reposerver/apiclient/mocks"
    32  	"github.com/argoproj/argo-cd/v3/test"
    33  	"github.com/argoproj/argo-cd/v3/util/db"
    34  	dbmocks "github.com/argoproj/argo-cd/v3/util/db/mocks"
    35  	"github.com/argoproj/argo-cd/v3/util/settings"
    36  )
    37  
    38  func TestRefreshApp(t *testing.T) {
    39  	var testApp argoappv1.Application
    40  	testApp.Name = "test-app"
    41  	testApp.Namespace = "default"
    42  	appClientset := appclientset.NewSimpleClientset(&testApp)
    43  	appIf := appClientset.ArgoprojV1alpha1().Applications("default")
    44  	_, err := RefreshApp(appIf, "test-app", argoappv1.RefreshTypeNormal, true)
    45  	require.NoError(t, err)
    46  	// For some reason, the fake Application interface doesn't reflect the patch status after Patch(),
    47  	// so can't verify it was set in unit tests.
    48  	// _, ok := newApp.Annotations[common.AnnotationKeyRefresh]
    49  	// assert.True(t, ok)
    50  }
    51  
    52  func TestGetAppProjectWithNoProjDefined(t *testing.T) {
    53  	projName := "default"
    54  	namespace := "default"
    55  
    56  	cm := corev1.ConfigMap{
    57  		ObjectMeta: metav1.ObjectMeta{
    58  			Name:      "argocd-cm",
    59  			Namespace: test.FakeArgoCDNamespace,
    60  			Labels: map[string]string{
    61  				"app.kubernetes.io/part-of": "argocd",
    62  			},
    63  		},
    64  	}
    65  
    66  	testProj := &argoappv1.AppProject{
    67  		ObjectMeta: metav1.ObjectMeta{Name: projName, Namespace: namespace},
    68  	}
    69  
    70  	var testApp argoappv1.Application
    71  	testApp.Name = "test-app"
    72  	testApp.Namespace = namespace
    73  	appClientset := appclientset.NewSimpleClientset(testProj)
    74  	ctx, cancel := context.WithCancel(t.Context())
    75  	defer cancel()
    76  	indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
    77  	informer := v1alpha1.NewAppProjectInformer(appClientset, namespace, 0, indexers)
    78  	go informer.Run(ctx.Done())
    79  	cache.WaitForCacheSync(ctx.Done(), informer.HasSynced)
    80  
    81  	kubeClient := fake.NewClientset(&cm)
    82  	settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
    83  	argoDB := db.NewDB("default", settingsMgr, kubeClient)
    84  	proj, err := GetAppProject(ctx, &testApp, applisters.NewAppProjectLister(informer.GetIndexer()), namespace, settingsMgr, argoDB)
    85  	require.NoError(t, err)
    86  	assert.Equal(t, proj.Name, projName)
    87  }
    88  
    89  func TestIncludeResource(t *testing.T) {
    90  	// Resource filters format - GROUP:KIND:NAMESPACE/NAME or GROUP:KIND:NAME
    91  	var (
    92  		blankValues = argoappv1.SyncOperationResource{Group: "", Kind: "", Name: "", Namespace: "", Exclude: false}
    93  		// *:*:*
    94  		includeAllResources = argoappv1.SyncOperationResource{Group: "*", Kind: "*", Name: "*", Namespace: "", Exclude: false}
    95  		// !*:*:*
    96  		excludeAllResources = argoappv1.SyncOperationResource{Group: "*", Kind: "*", Name: "*", Namespace: "", Exclude: true}
    97  		// *:Service:*
    98  		includeAllServiceResources = argoappv1.SyncOperationResource{Group: "*", Kind: "Service", Name: "*", Namespace: "", Exclude: false}
    99  		// !*:Service:*
   100  		excludeAllServiceResources = argoappv1.SyncOperationResource{Group: "*", Kind: "Service", Name: "*", Namespace: "", Exclude: true}
   101  		// apps:ReplicaSet:backend
   102  		includeAllReplicaSetResource = argoappv1.SyncOperationResource{Group: "apps", Kind: "ReplicaSet", Name: "*", Namespace: "", Exclude: false}
   103  		// apps:ReplicaSet:backend
   104  		includeReplicaSetResource = argoappv1.SyncOperationResource{Group: "apps", Kind: "ReplicaSet", Name: "backend", Namespace: "", Exclude: false}
   105  		// !apps:ReplicaSet:backend
   106  		excludeReplicaSetResource = argoappv1.SyncOperationResource{Group: "apps", Kind: "ReplicaSet", Name: "backend", Namespace: "", Exclude: true}
   107  	)
   108  	tests := []struct {
   109  		testName              string
   110  		name                  string
   111  		namespace             string
   112  		gvk                   schema.GroupVersionKind
   113  		syncOperationResource []*argoappv1.SyncOperationResource
   114  		expectedResult        bool
   115  	}{
   116  		//--resource apps:ReplicaSet:backend --resource *:Service:*
   117  		{
   118  			testName:              "Include ReplicaSet backend resource and all service resources",
   119  			name:                  "backend",
   120  			namespace:             "default",
   121  			gvk:                   schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
   122  			syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllServiceResources, &includeReplicaSetResource},
   123  			expectedResult:        true,
   124  		},
   125  		//--resource apps:ReplicaSet:backend --resource *:Service:*
   126  		{
   127  			testName:              "Include ReplicaSet backend resource and all service resources",
   128  			name:                  "main-page-down",
   129  			namespace:             "default",
   130  			gvk:                   schema.GroupVersionKind{Group: "batch", Kind: "Job"},
   131  			syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllServiceResources, &includeReplicaSetResource},
   132  			expectedResult:        false,
   133  		},
   134  		//--resource apps:ReplicaSet:backend --resource !*:Service:*
   135  		{
   136  			testName:              "Include ReplicaSet backend resource and exclude all service resources",
   137  			name:                  "main-page-down",
   138  			namespace:             "default",
   139  			gvk:                   schema.GroupVersionKind{Group: "batch", Kind: "Job"},
   140  			syncOperationResource: []*argoappv1.SyncOperationResource{&excludeAllServiceResources, &includeReplicaSetResource},
   141  			expectedResult:        false,
   142  		},
   143  		// --resource !apps:ReplicaSet:backend --resource !*:Service:*
   144  		{
   145  			testName:              "Exclude ReplicaSet backend resource and all service resources",
   146  			name:                  "main-page-down",
   147  			namespace:             "default",
   148  			gvk:                   schema.GroupVersionKind{Group: "batch", Kind: "Job"},
   149  			syncOperationResource: []*argoappv1.SyncOperationResource{&excludeReplicaSetResource, &excludeAllServiceResources},
   150  			expectedResult:        true,
   151  		},
   152  		// --resource !apps:ReplicaSet:backend
   153  		{
   154  			testName:              "Exclude ReplicaSet backend resource",
   155  			name:                  "backend",
   156  			namespace:             "default",
   157  			gvk:                   schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
   158  			syncOperationResource: []*argoappv1.SyncOperationResource{&excludeReplicaSetResource},
   159  			expectedResult:        false,
   160  		},
   161  		// --resource !apps:ReplicaSet:backend --resource !*:Service:*
   162  		{
   163  			testName:              "Exclude ReplicaSet backend resource and all service resources(dummy condition)",
   164  			name:                  "backend",
   165  			namespace:             "default",
   166  			gvk:                   schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
   167  			syncOperationResource: []*argoappv1.SyncOperationResource{&excludeReplicaSetResource, &excludeAllServiceResources},
   168  			expectedResult:        false,
   169  		},
   170  		// --resource apps:ReplicaSet:backend
   171  		{
   172  			testName:              "Include ReplicaSet backend resource",
   173  			name:                  "backend",
   174  			namespace:             "default",
   175  			gvk:                   schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
   176  			syncOperationResource: []*argoappv1.SyncOperationResource{&includeReplicaSetResource},
   177  			expectedResult:        true,
   178  		},
   179  		// --resource !*:Service:*
   180  		{
   181  			testName:              "Exclude Service resources",
   182  			name:                  "backend",
   183  			namespace:             "default",
   184  			gvk:                   schema.GroupVersionKind{Group: "", Kind: "Service"},
   185  			syncOperationResource: []*argoappv1.SyncOperationResource{&excludeAllServiceResources},
   186  			expectedResult:        false,
   187  		},
   188  		// --resource *:Service:*
   189  		{
   190  			testName:              "Include Service resources",
   191  			name:                  "backend",
   192  			namespace:             "default",
   193  			gvk:                   schema.GroupVersionKind{Group: "", Kind: "Service"},
   194  			syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllServiceResources},
   195  			expectedResult:        true,
   196  		},
   197  		// --resource apps:ReplicaSet:* --resource !apps:ReplicaSet:backend
   198  		{
   199  			testName:              "Include & Exclude ReplicaSet resources",
   200  			name:                  "backend",
   201  			namespace:             "default",
   202  			gvk:                   schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
   203  			syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllReplicaSetResource, &excludeReplicaSetResource},
   204  			expectedResult:        false,
   205  		},
   206  		// --resource !*:*:*
   207  		{
   208  			testName:              "Exclude all resources",
   209  			name:                  "backend",
   210  			namespace:             "default",
   211  			gvk:                   schema.GroupVersionKind{Group: "", Kind: "Service"},
   212  			syncOperationResource: []*argoappv1.SyncOperationResource{&excludeAllResources},
   213  			expectedResult:        false,
   214  		},
   215  		// --resource *:*:*
   216  		{
   217  			testName:              "Include all resources",
   218  			name:                  "backend",
   219  			namespace:             "default",
   220  			gvk:                   schema.GroupVersionKind{Group: "", Kind: "Service"},
   221  			syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllResources},
   222  			expectedResult:        true,
   223  		},
   224  		{
   225  			testName:              "No Filters",
   226  			name:                  "backend",
   227  			namespace:             "default",
   228  			gvk:                   schema.GroupVersionKind{Group: "", Kind: "Service"},
   229  			syncOperationResource: []*argoappv1.SyncOperationResource{&blankValues},
   230  			expectedResult:        false,
   231  		},
   232  		{
   233  			testName:       "Default values",
   234  			expectedResult: true,
   235  		},
   236  	}
   237  
   238  	for _, test := range tests {
   239  		t.Run(test.testName, func(t *testing.T) {
   240  			isResourceIncluded := IncludeResource(test.name, test.namespace, test.gvk, test.syncOperationResource)
   241  			assert.Equal(t, test.expectedResult, isResourceIncluded)
   242  		})
   243  	}
   244  }
   245  
   246  func TestContainsSyncResource(t *testing.T) {
   247  	var (
   248  		blankUnstructured unstructured.Unstructured
   249  		blankResource     argoappv1.SyncOperationResource
   250  		helloResource     = argoappv1.SyncOperationResource{Name: "hello"}
   251  	)
   252  	tables := []struct {
   253  		u        *unstructured.Unstructured
   254  		rr       []argoappv1.SyncOperationResource
   255  		expected bool
   256  	}{
   257  		{&blankUnstructured, []argoappv1.SyncOperationResource{}, false},
   258  		{&blankUnstructured, []argoappv1.SyncOperationResource{blankResource}, true},
   259  		{&blankUnstructured, []argoappv1.SyncOperationResource{helloResource}, false},
   260  	}
   261  
   262  	for _, table := range tables {
   263  		out := ContainsSyncResource(table.u.GetName(), table.u.GetNamespace(), table.u.GroupVersionKind(), table.rr)
   264  		assert.Equal(t, table.expected, out, "Expected %t for slice %+v contains resource %+v; instead got %t", table.expected, table.rr, table.u, out)
   265  	}
   266  }
   267  
   268  // TestNilOutZerValueAppSources verifies we will nil out app source specs when they are their zero-value
   269  func TestNilOutZerValueAppSources(t *testing.T) {
   270  	var spec *argoappv1.ApplicationSpec
   271  	spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Kustomize: &argoappv1.ApplicationSourceKustomize{NamePrefix: "foo"}}})
   272  	assert.NotNil(t, spec.GetSource().Kustomize)
   273  	spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Kustomize: &argoappv1.ApplicationSourceKustomize{NamePrefix: ""}}})
   274  	source := spec.GetSource()
   275  	assert.Nil(t, source.Kustomize)
   276  
   277  	spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Kustomize: &argoappv1.ApplicationSourceKustomize{NameSuffix: "foo"}}})
   278  	assert.NotNil(t, spec.GetSource().Kustomize)
   279  	spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Kustomize: &argoappv1.ApplicationSourceKustomize{NameSuffix: ""}}})
   280  	source = spec.GetSource()
   281  	assert.Nil(t, source.Kustomize)
   282  
   283  	spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{"values.yaml"}}}})
   284  	assert.NotNil(t, spec.GetSource().Helm)
   285  	spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{}}}})
   286  	assert.Nil(t, spec.GetSource().Helm)
   287  
   288  	spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Directory: &argoappv1.ApplicationSourceDirectory{Recurse: true}}})
   289  	assert.NotNil(t, spec.GetSource().Directory)
   290  	spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Directory: &argoappv1.ApplicationSourceDirectory{Recurse: false}}})
   291  	assert.Nil(t, spec.GetSource().Directory)
   292  }
   293  
   294  func TestValidatePermissionsEmptyDestination(t *testing.T) {
   295  	conditions, err := ValidatePermissions(t.Context(), &argoappv1.ApplicationSpec{
   296  		Source: &argoappv1.ApplicationSource{RepoURL: "https://github.com/argoproj/argo-cd", Path: "."},
   297  	}, &argoappv1.AppProject{
   298  		Spec: argoappv1.AppProjectSpec{
   299  			SourceRepos:  []string{"*"},
   300  			Destinations: []argoappv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   301  		},
   302  	}, nil)
   303  	require.NoError(t, err)
   304  	assert.ElementsMatch(t, conditions, []argoappv1.ApplicationCondition{{Type: argoappv1.ApplicationConditionInvalidSpecError, Message: "Destination server missing from app spec"}})
   305  }
   306  
   307  func TestValidateChartWithoutRevision(t *testing.T) {
   308  	appSpec := &argoappv1.ApplicationSpec{
   309  		Source: &argoappv1.ApplicationSource{RepoURL: "https://charts.helm.sh/incubator/", Chart: "myChart", TargetRevision: ""},
   310  		Destination: argoappv1.ApplicationDestination{
   311  			Server: "https://kubernetes.default.svc", Namespace: "default",
   312  		},
   313  	}
   314  	cluster := &argoappv1.Cluster{Server: "https://kubernetes.default.svc"}
   315  	db := &dbmocks.ArgoDB{}
   316  	ctx := t.Context()
   317  	db.On("GetCluster", ctx, appSpec.Destination.Server).Return(cluster, nil)
   318  
   319  	conditions, err := ValidatePermissions(ctx, appSpec, &argoappv1.AppProject{
   320  		Spec: argoappv1.AppProjectSpec{
   321  			SourceRepos:  []string{"*"},
   322  			Destinations: []argoappv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   323  		},
   324  	}, db)
   325  	require.NoError(t, err)
   326  	assert.Len(t, conditions, 1)
   327  	assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
   328  	assert.Equal(t, "spec.source.targetRevision is required if the manifest source is a helm chart", conditions[0].Message)
   329  }
   330  
   331  func TestAPIResourcesToStrings(t *testing.T) {
   332  	resources := []kube.APIResourceInfo{{
   333  		GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta1"},
   334  		GroupKind:            schema.GroupKind{Kind: "Deployment"},
   335  	}, {
   336  		GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta2"},
   337  		GroupKind:            schema.GroupKind{Kind: "Deployment"},
   338  	}, {
   339  		GroupVersionResource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1"},
   340  		GroupKind:            schema.GroupKind{Kind: "Deployment"},
   341  	}}
   342  
   343  	assert.ElementsMatch(t, []string{"apps/v1beta1", "apps/v1beta2", "extensions/v1beta1"}, APIResourcesToStrings(resources, false))
   344  	assert.ElementsMatch(t, []string{
   345  		"apps/v1beta1", "apps/v1beta1/Deployment", "apps/v1beta2", "apps/v1beta2/Deployment", "extensions/v1beta1", "extensions/v1beta1/Deployment",
   346  	},
   347  		APIResourcesToStrings(resources, true))
   348  }
   349  
   350  func TestValidateRepo(t *testing.T) {
   351  	repoPath, err := filepath.Abs("./../..")
   352  	require.NoError(t, err)
   353  
   354  	apiResources := []kube.APIResourceInfo{{
   355  		GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta1"},
   356  		GroupKind:            schema.GroupKind{Kind: "Deployment"},
   357  	}, {
   358  		GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta2"},
   359  		GroupKind:            schema.GroupKind{Kind: "Deployment"},
   360  	}}
   361  	kubeVersion := "v1.16"
   362  	kustomizeOptions := &argoappv1.KustomizeOptions{BuildOptions: ""}
   363  	repo := &argoappv1.Repository{Repo: "file://" + repoPath}
   364  	cluster := &argoappv1.Cluster{Server: "sample server"}
   365  	app := &argoappv1.Application{
   366  		Spec: argoappv1.ApplicationSpec{
   367  			Source: &argoappv1.ApplicationSource{
   368  				RepoURL: repo.Repo,
   369  			},
   370  			Destination: argoappv1.ApplicationDestination{
   371  				Server:    cluster.Server,
   372  				Namespace: "default",
   373  			},
   374  		},
   375  	}
   376  
   377  	proj := &argoappv1.AppProject{
   378  		Spec: argoappv1.AppProjectSpec{
   379  			SourceRepos: []string{"*"},
   380  		},
   381  	}
   382  
   383  	helmRepos := []*argoappv1.Repository{{Repo: "sample helm repo"}}
   384  
   385  	repoClient := &mocks.RepoServerServiceClient{}
   386  	source := app.Spec.GetSource()
   387  	repoClient.On("GetAppDetails", t.Context(), &apiclient.RepoServerAppDetailsQuery{
   388  		Repo:             repo,
   389  		Source:           &source,
   390  		Repos:            helmRepos,
   391  		KustomizeOptions: kustomizeOptions,
   392  		HelmOptions:      &argoappv1.HelmOptions{ValuesFileSchemes: []string{"https", "http"}},
   393  		NoRevisionCache:  true,
   394  	}).Return(&apiclient.RepoAppDetailsResponse{}, nil)
   395  
   396  	repo.Type = "git"
   397  	repoClient.On("TestRepository", t.Context(), &apiclient.TestRepositoryRequest{
   398  		Repo: repo,
   399  	}).Return(&apiclient.TestRepositoryResponse{
   400  		VerifiedRepository: true,
   401  	}, nil)
   402  
   403  	repoClientSet := &mocks.Clientset{RepoServerServiceClient: repoClient}
   404  
   405  	db := &dbmocks.ArgoDB{}
   406  
   407  	db.On("GetRepository", t.Context(), app.Spec.Source.RepoURL, "").Return(repo, nil)
   408  	db.On("ListHelmRepositories", t.Context()).Return(helmRepos, nil)
   409  	db.On("ListOCIRepositories", t.Context()).Return([]*argoappv1.Repository{}, nil)
   410  	db.On("GetCluster", t.Context(), app.Spec.Destination.Server).Return(cluster, nil)
   411  	db.On("GetAllHelmRepositoryCredentials", t.Context()).Return(nil, nil)
   412  	db.On("GetAllOCIRepositoryCredentials", t.Context()).Return([]*argoappv1.RepoCreds{}, nil)
   413  
   414  	var receivedRequest *apiclient.ManifestRequest
   415  
   416  	repoClient.On("GenerateManifest", t.Context(), mock.MatchedBy(func(req *apiclient.ManifestRequest) bool {
   417  		receivedRequest = req
   418  		return true
   419  	})).Return(nil, nil)
   420  
   421  	cm := corev1.ConfigMap{
   422  		ObjectMeta: metav1.ObjectMeta{
   423  			Name:      "argocd-cm",
   424  			Namespace: test.FakeArgoCDNamespace,
   425  			Labels: map[string]string{
   426  				"app.kubernetes.io/part-of": "argocd",
   427  			},
   428  		},
   429  		Data: map[string]string{
   430  			"globalProjects": `
   431   - projectName: default-x
   432     labelSelector:
   433       matchExpressions:
   434        - key: is-x
   435          operator: Exists
   436   - projectName: default-non-x
   437     labelSelector:
   438       matchExpressions:
   439        - key: is-x
   440          operator: DoesNotExist
   441  `,
   442  		},
   443  	}
   444  
   445  	kubeClient := fake.NewClientset(&cm)
   446  	settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
   447  
   448  	conditions, err := ValidateRepo(t.Context(), app, repoClientSet, db, &kubetest.MockKubectlCmd{Version: kubeVersion, APIResources: apiResources}, proj, settingsMgr)
   449  
   450  	require.NoError(t, err)
   451  	assert.Empty(t, conditions)
   452  	assert.ElementsMatch(t, []string{"apps/v1beta1", "apps/v1beta1/Deployment", "apps/v1beta2", "apps/v1beta2/Deployment"}, receivedRequest.ApiVersions)
   453  	assert.Equal(t, kubeVersion, receivedRequest.KubeVersion)
   454  	assert.Equal(t, app.Spec.Destination.Namespace, receivedRequest.Namespace)
   455  	assert.Equal(t, &source, receivedRequest.ApplicationSource)
   456  	assert.Equal(t, kustomizeOptions, receivedRequest.KustomizeOptions)
   457  }
   458  
   459  func TestFormatAppConditions(t *testing.T) {
   460  	conditions := []argoappv1.ApplicationCondition{
   461  		{
   462  			Type:    EventReasonOperationCompleted,
   463  			Message: "Foo",
   464  		},
   465  		{
   466  			Type:    EventReasonResourceCreated,
   467  			Message: "Bar",
   468  		},
   469  	}
   470  
   471  	t.Run("Single Condition", func(t *testing.T) {
   472  		res := FormatAppConditions(conditions[0:1])
   473  		assert.NotEmpty(t, res)
   474  		assert.Equal(t, EventReasonOperationCompleted+": Foo", res)
   475  	})
   476  
   477  	t.Run("Multiple Conditions", func(t *testing.T) {
   478  		res := FormatAppConditions(conditions)
   479  		assert.NotEmpty(t, res)
   480  		assert.Equal(t, fmt.Sprintf("%s: Foo;%s: Bar", EventReasonOperationCompleted, EventReasonResourceCreated), res)
   481  	})
   482  
   483  	t.Run("Empty Conditions", func(t *testing.T) {
   484  		res := FormatAppConditions([]argoappv1.ApplicationCondition{})
   485  		assert.Empty(t, res)
   486  	})
   487  }
   488  
   489  func TestFilterByProjects(t *testing.T) {
   490  	apps := []argoappv1.Application{
   491  		{
   492  			Spec: argoappv1.ApplicationSpec{
   493  				Project: "fooproj",
   494  			},
   495  		},
   496  		{
   497  			Spec: argoappv1.ApplicationSpec{
   498  				Project: "barproj",
   499  			},
   500  		},
   501  	}
   502  
   503  	t.Run("No apps in single project", func(t *testing.T) {
   504  		res := FilterByProjects(apps, []string{"foobarproj"})
   505  		assert.Empty(t, res)
   506  	})
   507  
   508  	t.Run("Single app in single project", func(t *testing.T) {
   509  		res := FilterByProjects(apps, []string{"fooproj"})
   510  		assert.Len(t, res, 1)
   511  	})
   512  
   513  	t.Run("Single app in multiple project", func(t *testing.T) {
   514  		res := FilterByProjects(apps, []string{"fooproj", "foobarproj"})
   515  		assert.Len(t, res, 1)
   516  	})
   517  
   518  	t.Run("Multiple apps in multiple project", func(t *testing.T) {
   519  		res := FilterByProjects(apps, []string{"fooproj", "barproj"})
   520  		assert.Len(t, res, 2)
   521  	})
   522  }
   523  
   524  func TestFilterByProjectsP(t *testing.T) {
   525  	apps := []*argoappv1.Application{
   526  		{
   527  			Spec: argoappv1.ApplicationSpec{
   528  				Project: "fooproj",
   529  			},
   530  		},
   531  		{
   532  			Spec: argoappv1.ApplicationSpec{
   533  				Project: "barproj",
   534  			},
   535  		},
   536  	}
   537  
   538  	t.Run("No apps in single project", func(t *testing.T) {
   539  		res := FilterByProjectsP(apps, []string{"foobarproj"})
   540  		assert.Empty(t, res)
   541  	})
   542  
   543  	t.Run("Single app in single project", func(t *testing.T) {
   544  		res := FilterByProjectsP(apps, []string{"fooproj"})
   545  		assert.Len(t, res, 1)
   546  	})
   547  
   548  	t.Run("Single app in multiple project", func(t *testing.T) {
   549  		res := FilterByProjectsP(apps, []string{"fooproj", "foobarproj"})
   550  		assert.Len(t, res, 1)
   551  	})
   552  
   553  	t.Run("Multiple apps in multiple project", func(t *testing.T) {
   554  		res := FilterByProjectsP(apps, []string{"fooproj", "barproj"})
   555  		assert.Len(t, res, 2)
   556  	})
   557  }
   558  
   559  func TestFilterByRepo(t *testing.T) {
   560  	apps := []argoappv1.Application{
   561  		{
   562  			Spec: argoappv1.ApplicationSpec{
   563  				Source: &argoappv1.ApplicationSource{
   564  					RepoURL: "git@github.com:owner/repo.git",
   565  				},
   566  			},
   567  		},
   568  		{
   569  			Spec: argoappv1.ApplicationSpec{
   570  				Source: &argoappv1.ApplicationSource{
   571  					RepoURL: "git@github.com:owner/otherrepo.git",
   572  				},
   573  			},
   574  		},
   575  	}
   576  
   577  	t.Run("Empty filter", func(t *testing.T) {
   578  		res := FilterByRepo(apps, "")
   579  		assert.Len(t, res, 2)
   580  	})
   581  
   582  	t.Run("Match", func(t *testing.T) {
   583  		res := FilterByRepo(apps, "git@github.com:owner/repo.git")
   584  		assert.Len(t, res, 1)
   585  	})
   586  
   587  	t.Run("No match", func(t *testing.T) {
   588  		res := FilterByRepo(apps, "git@github.com:owner/willnotmatch.git")
   589  		assert.Empty(t, res)
   590  	})
   591  }
   592  
   593  func TestFilterByRepoP(t *testing.T) {
   594  	apps := []*argoappv1.Application{
   595  		{
   596  			Spec: argoappv1.ApplicationSpec{
   597  				Source: &argoappv1.ApplicationSource{
   598  					RepoURL: "git@github.com:owner/repo.git",
   599  				},
   600  			},
   601  		},
   602  		{
   603  			Spec: argoappv1.ApplicationSpec{
   604  				Source: &argoappv1.ApplicationSource{
   605  					RepoURL: "git@github.com:owner/otherrepo.git",
   606  				},
   607  			},
   608  		},
   609  	}
   610  
   611  	t.Run("Empty filter", func(t *testing.T) {
   612  		res := FilterByRepoP(apps, "")
   613  		assert.Len(t, res, 2)
   614  	})
   615  
   616  	t.Run("Match", func(t *testing.T) {
   617  		res := FilterByRepoP(apps, "git@github.com:owner/repo.git")
   618  		assert.Len(t, res, 1)
   619  	})
   620  
   621  	t.Run("No match", func(t *testing.T) {
   622  		res := FilterByRepoP(apps, "git@github.com:owner/willnotmatch.git")
   623  		assert.Empty(t, res)
   624  	})
   625  }
   626  
   627  func TestValidatePermissions(t *testing.T) {
   628  	t.Run("Empty Repo URL result in condition", func(t *testing.T) {
   629  		spec := argoappv1.ApplicationSpec{
   630  			Source: &argoappv1.ApplicationSource{
   631  				RepoURL: "",
   632  			},
   633  		}
   634  		proj := argoappv1.AppProject{}
   635  		db := &dbmocks.ArgoDB{}
   636  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   637  		require.NoError(t, err)
   638  		assert.Len(t, conditions, 1)
   639  		assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
   640  		assert.Contains(t, conditions[0].Message, "are required")
   641  	})
   642  
   643  	t.Run("Incomplete Path/Chart combo result in condition", func(t *testing.T) {
   644  		spec := argoappv1.ApplicationSpec{
   645  			Source: &argoappv1.ApplicationSource{
   646  				RepoURL: "http://some/where",
   647  				Path:    "",
   648  				Chart:   "",
   649  			},
   650  		}
   651  		proj := argoappv1.AppProject{}
   652  		db := &dbmocks.ArgoDB{}
   653  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   654  		require.NoError(t, err)
   655  		assert.Len(t, conditions, 1)
   656  		assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
   657  		assert.Contains(t, conditions[0].Message, "are required")
   658  	})
   659  
   660  	t.Run("Helm chart requires targetRevision", func(t *testing.T) {
   661  		spec := argoappv1.ApplicationSpec{
   662  			Source: &argoappv1.ApplicationSource{
   663  				RepoURL: "http://some/where",
   664  				Path:    "",
   665  				Chart:   "somechart",
   666  			},
   667  		}
   668  		proj := argoappv1.AppProject{}
   669  		db := &dbmocks.ArgoDB{}
   670  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   671  		require.NoError(t, err)
   672  		assert.Len(t, conditions, 1)
   673  		assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
   674  		assert.Contains(t, conditions[0].Message, "is required if the manifest source is a helm chart")
   675  	})
   676  
   677  	t.Run("Application source is not permitted in project", func(t *testing.T) {
   678  		spec := argoappv1.ApplicationSpec{
   679  			Source: &argoappv1.ApplicationSource{
   680  				RepoURL:        "http://some/where",
   681  				Path:           "",
   682  				Chart:          "somechart",
   683  				TargetRevision: "1.4.1",
   684  			},
   685  			Destination: argoappv1.ApplicationDestination{
   686  				Server:    "https://127.0.0.1:6443",
   687  				Namespace: "testns",
   688  			},
   689  		}
   690  		proj := argoappv1.AppProject{
   691  			Spec: argoappv1.AppProjectSpec{
   692  				Destinations: []argoappv1.ApplicationDestination{
   693  					{
   694  						Server:    "*",
   695  						Namespace: "*",
   696  					},
   697  				},
   698  				SourceRepos: []string{"http://some/where/else"},
   699  			},
   700  		}
   701  		cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"}
   702  		db := &dbmocks.ArgoDB{}
   703  		db.On("GetCluster", t.Context(), spec.Destination.Server).Return(cluster, nil)
   704  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   705  		require.NoError(t, err)
   706  		assert.Len(t, conditions, 1)
   707  		assert.Contains(t, conditions[0].Message, "application repo http://some/where is not permitted")
   708  	})
   709  
   710  	t.Run("Application destination is not permitted in project", func(t *testing.T) {
   711  		spec := argoappv1.ApplicationSpec{
   712  			Source: &argoappv1.ApplicationSource{
   713  				RepoURL:        "http://some/where",
   714  				Path:           "",
   715  				Chart:          "somechart",
   716  				TargetRevision: "1.4.1",
   717  			},
   718  			Destination: argoappv1.ApplicationDestination{
   719  				Server:    "https://127.0.0.1:6443",
   720  				Namespace: "testns",
   721  			},
   722  		}
   723  		proj := argoappv1.AppProject{
   724  			Spec: argoappv1.AppProjectSpec{
   725  				Destinations: []argoappv1.ApplicationDestination{
   726  					{
   727  						Server:    "*",
   728  						Namespace: "default",
   729  					},
   730  				},
   731  				SourceRepos: []string{"http://some/where"},
   732  			},
   733  		}
   734  		cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"}
   735  		db := &dbmocks.ArgoDB{}
   736  		db.On("GetCluster", t.Context(), spec.Destination.Server).Return(cluster, nil)
   737  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   738  		require.NoError(t, err)
   739  		assert.Len(t, conditions, 1)
   740  		assert.Contains(t, conditions[0].Message, "application destination")
   741  	})
   742  
   743  	t.Run("Destination cluster does not exist", func(t *testing.T) {
   744  		spec := argoappv1.ApplicationSpec{
   745  			Source: &argoappv1.ApplicationSource{
   746  				RepoURL:        "http://some/where",
   747  				Path:           "",
   748  				Chart:          "somechart",
   749  				TargetRevision: "1.4.1",
   750  			},
   751  			Destination: argoappv1.ApplicationDestination{
   752  				Server:    "https://127.0.0.1:6443",
   753  				Namespace: "default",
   754  			},
   755  		}
   756  		proj := argoappv1.AppProject{
   757  			Spec: argoappv1.AppProjectSpec{
   758  				Destinations: []argoappv1.ApplicationDestination{
   759  					{
   760  						Server:    "*",
   761  						Namespace: "default",
   762  					},
   763  				},
   764  				SourceRepos: []string{"http://some/where"},
   765  			},
   766  		}
   767  		db := &dbmocks.ArgoDB{}
   768  		db.On("GetCluster", t.Context(), spec.Destination.Server).Return(nil, status.Errorf(codes.NotFound, "Cluster does not exist"))
   769  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   770  		require.NoError(t, err)
   771  		assert.Len(t, conditions, 1)
   772  		assert.Contains(t, conditions[0].Message, "Cluster does not exist")
   773  	})
   774  
   775  	t.Run("Destination cluster name does not exist", func(t *testing.T) {
   776  		spec := argoappv1.ApplicationSpec{
   777  			Source: &argoappv1.ApplicationSource{
   778  				RepoURL:        "http://some/where",
   779  				Path:           "",
   780  				Chart:          "somechart",
   781  				TargetRevision: "1.4.1",
   782  			},
   783  			Destination: argoappv1.ApplicationDestination{
   784  				Name:      "does-not-exist",
   785  				Namespace: "default",
   786  			},
   787  		}
   788  		proj := argoappv1.AppProject{
   789  			Spec: argoappv1.AppProjectSpec{
   790  				Destinations: []argoappv1.ApplicationDestination{
   791  					{
   792  						Server:    "*",
   793  						Namespace: "default",
   794  					},
   795  				},
   796  				SourceRepos: []string{"http://some/where"},
   797  			},
   798  		}
   799  		db := &dbmocks.ArgoDB{}
   800  		db.On("GetClusterServersByName", t.Context(), "does-not-exist").Return(nil, nil)
   801  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   802  		require.NoError(t, err)
   803  		assert.Len(t, conditions, 1)
   804  		assert.Contains(t, conditions[0].Message, "there are no clusters with this name: does-not-exist")
   805  	})
   806  
   807  	t.Run("Cannot get cluster info from DB", func(t *testing.T) {
   808  		spec := argoappv1.ApplicationSpec{
   809  			Source: &argoappv1.ApplicationSource{
   810  				RepoURL:        "http://some/where",
   811  				Path:           "",
   812  				Chart:          "somechart",
   813  				TargetRevision: "1.4.1",
   814  			},
   815  			Destination: argoappv1.ApplicationDestination{
   816  				Server:    "https://127.0.0.1:6443",
   817  				Namespace: "default",
   818  			},
   819  		}
   820  		proj := argoappv1.AppProject{
   821  			Spec: argoappv1.AppProjectSpec{
   822  				Destinations: []argoappv1.ApplicationDestination{
   823  					{
   824  						Server:    "*",
   825  						Namespace: "default",
   826  					},
   827  				},
   828  				SourceRepos: []string{"http://some/where"},
   829  			},
   830  		}
   831  		db := &dbmocks.ArgoDB{}
   832  		db.On("GetCluster", t.Context(), spec.Destination.Server).Return(nil, errors.New("Unknown error occurred"))
   833  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   834  		require.NoError(t, err)
   835  		assert.Len(t, conditions, 1)
   836  		assert.Contains(t, conditions[0].Message, "Unknown error occurred")
   837  	})
   838  
   839  	t.Run("Destination cluster name resolves to valid server", func(t *testing.T) {
   840  		spec := argoappv1.ApplicationSpec{
   841  			Source: &argoappv1.ApplicationSource{
   842  				RepoURL:        "http://some/where",
   843  				Path:           "",
   844  				Chart:          "somechart",
   845  				TargetRevision: "1.4.1",
   846  			},
   847  			Destination: argoappv1.ApplicationDestination{
   848  				Name:      "does-exist",
   849  				Namespace: "default",
   850  			},
   851  		}
   852  		proj := argoappv1.AppProject{
   853  			Spec: argoappv1.AppProjectSpec{
   854  				Destinations: []argoappv1.ApplicationDestination{
   855  					{
   856  						Server:    "*",
   857  						Namespace: "default",
   858  					},
   859  				},
   860  				SourceRepos: []string{"http://some/where"},
   861  			},
   862  		}
   863  		db := &dbmocks.ArgoDB{}
   864  		cluster := argoappv1.Cluster{
   865  			Name:   "does-exist",
   866  			Server: "https://127.0.0.1:6443",
   867  		}
   868  		db.On("GetClusterServersByName", t.Context(), "does-exist").Return([]string{"https://127.0.0.1:6443"}, nil)
   869  		db.On("GetCluster", t.Context(), "https://127.0.0.1:6443").Return(&cluster, nil)
   870  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
   871  		require.NoError(t, err)
   872  		assert.Empty(t, conditions)
   873  	})
   874  }
   875  
   876  func TestSetAppOperations(t *testing.T) {
   877  	t.Run("Application not existing", func(t *testing.T) {
   878  		appIf := appclientset.NewSimpleClientset().ArgoprojV1alpha1().Applications("default")
   879  		app, err := SetAppOperation(appIf, "someapp", &argoappv1.Operation{Sync: &argoappv1.SyncOperation{Revision: "aaa"}})
   880  		require.Error(t, err)
   881  		assert.Nil(t, app)
   882  	})
   883  
   884  	t.Run("Operation already in progress", func(t *testing.T) {
   885  		a := argoappv1.Application{
   886  			ObjectMeta: metav1.ObjectMeta{
   887  				Name:      "someapp",
   888  				Namespace: "default",
   889  			},
   890  			Operation: &argoappv1.Operation{Sync: &argoappv1.SyncOperation{Revision: "aaa"}},
   891  		}
   892  		appIf := appclientset.NewSimpleClientset(&a).ArgoprojV1alpha1().Applications("default")
   893  		app, err := SetAppOperation(appIf, "someapp", &argoappv1.Operation{Sync: &argoappv1.SyncOperation{Revision: "aaa"}})
   894  		require.ErrorContains(t, err, "operation is already in progress")
   895  		assert.Nil(t, app)
   896  	})
   897  
   898  	t.Run("Operation unspecified", func(t *testing.T) {
   899  		a := argoappv1.Application{
   900  			ObjectMeta: metav1.ObjectMeta{
   901  				Name:      "someapp",
   902  				Namespace: "default",
   903  			},
   904  		}
   905  		appIf := appclientset.NewSimpleClientset(&a).ArgoprojV1alpha1().Applications("default")
   906  		app, err := SetAppOperation(appIf, "someapp", &argoappv1.Operation{Sync: nil})
   907  		require.ErrorContains(t, err, "Operation unspecified")
   908  		assert.Nil(t, app)
   909  	})
   910  
   911  	t.Run("Success", func(t *testing.T) {
   912  		a := argoappv1.Application{
   913  			ObjectMeta: metav1.ObjectMeta{
   914  				Name:      "someapp",
   915  				Namespace: "default",
   916  			},
   917  		}
   918  		appIf := appclientset.NewSimpleClientset(&a).ArgoprojV1alpha1().Applications("default")
   919  		app, err := SetAppOperation(appIf, "someapp", &argoappv1.Operation{Sync: &argoappv1.SyncOperation{Revision: "aaa"}})
   920  		require.NoError(t, err)
   921  		assert.NotNil(t, app)
   922  	})
   923  }
   924  
   925  func TestGetDestinationCluster(t *testing.T) {
   926  	t.Run("Validate destination with server url", func(t *testing.T) {
   927  		dest := argoappv1.ApplicationDestination{
   928  			Server:    "https://127.0.0.1:6443",
   929  			Namespace: "default",
   930  		}
   931  
   932  		expectedCluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443"}
   933  		db := &dbmocks.ArgoDB{}
   934  		db.On("GetCluster", t.Context(), "https://127.0.0.1:6443").Return(expectedCluster, nil)
   935  
   936  		destCluster, err := GetDestinationCluster(t.Context(), dest, db)
   937  		require.NoError(t, err)
   938  		require.NotNil(t, expectedCluster)
   939  		assert.Equal(t, expectedCluster, destCluster)
   940  	})
   941  
   942  	t.Run("Validate destination with server name", func(t *testing.T) {
   943  		dest := argoappv1.ApplicationDestination{
   944  			Name: "minikube",
   945  		}
   946  
   947  		db := &dbmocks.ArgoDB{}
   948  		db.On("GetClusterServersByName", t.Context(), "minikube").Return([]string{"https://127.0.0.1:6443"}, nil)
   949  		db.On("GetCluster", t.Context(), "https://127.0.0.1:6443").Return(&argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "minikube"}, nil)
   950  
   951  		destCluster, err := GetDestinationCluster(t.Context(), dest, db)
   952  		require.NoError(t, err)
   953  		assert.Equal(t, "https://127.0.0.1:6443", destCluster.Server)
   954  	})
   955  
   956  	t.Run("Error when having both server url and name", func(t *testing.T) {
   957  		dest := argoappv1.ApplicationDestination{
   958  			Server:    "https://127.0.0.1:6443",
   959  			Name:      "minikube",
   960  			Namespace: "default",
   961  		}
   962  
   963  		_, err := GetDestinationCluster(t.Context(), dest, nil)
   964  		assert.EqualError(t, err, "application destination can't have both name and server defined: minikube https://127.0.0.1:6443")
   965  	})
   966  
   967  	t.Run("GetClusterServersByName fails", func(t *testing.T) {
   968  		dest := argoappv1.ApplicationDestination{
   969  			Name: "minikube",
   970  		}
   971  
   972  		db := &dbmocks.ArgoDB{}
   973  		db.On("GetClusterServersByName", t.Context(), mock.Anything).Return(nil, errors.New("an error occurred"))
   974  
   975  		_, err := GetDestinationCluster(t.Context(), dest, db)
   976  		require.ErrorContains(t, err, "an error occurred")
   977  	})
   978  
   979  	t.Run("Destination cluster does not exist", func(t *testing.T) {
   980  		dest := argoappv1.ApplicationDestination{
   981  			Name: "minikube",
   982  		}
   983  
   984  		db := &dbmocks.ArgoDB{}
   985  		db.On("GetClusterServersByName", t.Context(), "minikube").Return(nil, nil)
   986  
   987  		_, err := GetDestinationCluster(t.Context(), dest, db)
   988  		assert.EqualError(t, err, "there are no clusters with this name: minikube")
   989  	})
   990  
   991  	t.Run("Validate too many clusters with the same name", func(t *testing.T) {
   992  		dest := argoappv1.ApplicationDestination{
   993  			Name: "dind",
   994  		}
   995  
   996  		db := &dbmocks.ArgoDB{}
   997  		db.On("GetClusterServersByName", t.Context(), "dind").Return([]string{"https://127.0.0.1:2443", "https://127.0.0.1:8443"}, nil)
   998  
   999  		_, err := GetDestinationCluster(t.Context(), dest, db)
  1000  		assert.EqualError(t, err, "there are 2 clusters with the same name: [https://127.0.0.1:2443 https://127.0.0.1:8443]")
  1001  	})
  1002  }
  1003  
  1004  func TestFilterByName(t *testing.T) {
  1005  	apps := []argoappv1.Application{
  1006  		{
  1007  			ObjectMeta: metav1.ObjectMeta{
  1008  				Name: "foo",
  1009  			},
  1010  			Spec: argoappv1.ApplicationSpec{
  1011  				Project: "fooproj",
  1012  			},
  1013  		},
  1014  		{
  1015  			ObjectMeta: metav1.ObjectMeta{
  1016  				Name: "bar",
  1017  			},
  1018  			Spec: argoappv1.ApplicationSpec{
  1019  				Project: "barproj",
  1020  			},
  1021  		},
  1022  	}
  1023  
  1024  	t.Run("Name is empty string", func(t *testing.T) {
  1025  		res, err := FilterByName(apps, "")
  1026  		require.NoError(t, err)
  1027  		assert.Len(t, res, 2)
  1028  	})
  1029  
  1030  	t.Run("Single app by name", func(t *testing.T) {
  1031  		res, err := FilterByName(apps, "foo")
  1032  		require.NoError(t, err)
  1033  		assert.Len(t, res, 1)
  1034  	})
  1035  
  1036  	t.Run("No such app", func(t *testing.T) {
  1037  		res, err := FilterByName(apps, "foobar")
  1038  		require.Error(t, err)
  1039  		assert.Empty(t, res)
  1040  	})
  1041  }
  1042  
  1043  func TestFilterByNameP(t *testing.T) {
  1044  	apps := []*argoappv1.Application{
  1045  		{
  1046  			ObjectMeta: metav1.ObjectMeta{
  1047  				Name: "foo",
  1048  			},
  1049  			Spec: argoappv1.ApplicationSpec{
  1050  				Project: "fooproj",
  1051  			},
  1052  		},
  1053  		{
  1054  			ObjectMeta: metav1.ObjectMeta{
  1055  				Name: "bar",
  1056  			},
  1057  			Spec: argoappv1.ApplicationSpec{
  1058  				Project: "barproj",
  1059  			},
  1060  		},
  1061  	}
  1062  
  1063  	t.Run("Name is empty string", func(t *testing.T) {
  1064  		res := FilterByNameP(apps, "")
  1065  		assert.Len(t, res, 2)
  1066  	})
  1067  
  1068  	t.Run("Single app by name", func(t *testing.T) {
  1069  		res := FilterByNameP(apps, "foo")
  1070  		assert.Len(t, res, 1)
  1071  	})
  1072  
  1073  	t.Run("No such app", func(t *testing.T) {
  1074  		res := FilterByNameP(apps, "foobar")
  1075  		assert.Empty(t, res)
  1076  	})
  1077  }
  1078  
  1079  func TestGetGlobalProjects(t *testing.T) {
  1080  	t.Run("Multiple global projects", func(t *testing.T) {
  1081  		namespace := "default"
  1082  
  1083  		cm := corev1.ConfigMap{
  1084  			ObjectMeta: metav1.ObjectMeta{
  1085  				Name:      "argocd-cm",
  1086  				Namespace: test.FakeArgoCDNamespace,
  1087  				Labels: map[string]string{
  1088  					"app.kubernetes.io/part-of": "argocd",
  1089  				},
  1090  			},
  1091  			Data: map[string]string{
  1092  				"globalProjects": `
  1093   - projectName: default-x
  1094     labelSelector:
  1095       matchExpressions:
  1096        - key: is-x
  1097          operator: Exists
  1098   - projectName: default-non-x
  1099     labelSelector:
  1100       matchExpressions:
  1101        - key: is-x
  1102          operator: DoesNotExist
  1103  `,
  1104  			},
  1105  		}
  1106  
  1107  		defaultX := &argoappv1.AppProject{
  1108  			ObjectMeta: metav1.ObjectMeta{Name: "default-x", Namespace: namespace},
  1109  			Spec: argoappv1.AppProjectSpec{
  1110  				ClusterResourceWhitelist: []metav1.GroupKind{
  1111  					{Group: "*", Kind: "*"},
  1112  				},
  1113  				ClusterResourceBlacklist: []metav1.GroupKind{
  1114  					{Kind: "Volume"},
  1115  				},
  1116  			},
  1117  		}
  1118  
  1119  		defaultNonX := &argoappv1.AppProject{
  1120  			ObjectMeta: metav1.ObjectMeta{Name: "default-non-x", Namespace: namespace},
  1121  			Spec: argoappv1.AppProjectSpec{
  1122  				ClusterResourceBlacklist: []metav1.GroupKind{
  1123  					{Group: "*", Kind: "*"},
  1124  				},
  1125  			},
  1126  		}
  1127  
  1128  		isX := &argoappv1.AppProject{
  1129  			ObjectMeta: metav1.ObjectMeta{
  1130  				Name:      "is-x",
  1131  				Namespace: namespace,
  1132  				Labels: map[string]string{
  1133  					"is-x": "yep",
  1134  				},
  1135  			},
  1136  		}
  1137  
  1138  		isNoX := &argoappv1.AppProject{
  1139  			ObjectMeta: metav1.ObjectMeta{Name: "is-no-x", Namespace: namespace},
  1140  		}
  1141  
  1142  		projClientset := appclientset.NewSimpleClientset(defaultX, defaultNonX, isX, isNoX)
  1143  		ctx, cancel := context.WithCancel(t.Context())
  1144  		defer cancel()
  1145  		indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
  1146  		informer := v1alpha1.NewAppProjectInformer(projClientset, namespace, 0, indexers)
  1147  		go informer.Run(ctx.Done())
  1148  		cache.WaitForCacheSync(ctx.Done(), informer.HasSynced)
  1149  
  1150  		kubeClient := fake.NewSimpleClientset(&cm)
  1151  		settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
  1152  
  1153  		projLister := applisters.NewAppProjectLister(informer.GetIndexer())
  1154  
  1155  		xGlobalProjects := GetGlobalProjects(isX, projLister, settingsMgr)
  1156  		assert.Len(t, xGlobalProjects, 1)
  1157  		assert.Equal(t, "default-x", xGlobalProjects[0].Name)
  1158  
  1159  		nonXGlobalProjects := GetGlobalProjects(isNoX, projLister, settingsMgr)
  1160  		assert.Len(t, nonXGlobalProjects, 1)
  1161  		assert.Equal(t, "default-non-x", nonXGlobalProjects[0].Name)
  1162  	})
  1163  }
  1164  
  1165  func Test_GetDifferentPathsBetweenStructs(t *testing.T) {
  1166  	r1 := argoappv1.Repository{}
  1167  	r2 := argoappv1.Repository{
  1168  		Name: "SomeName",
  1169  	}
  1170  
  1171  	difference, _ := GetDifferentPathsBetweenStructs(r1, r2)
  1172  	assert.Equal(t, []string{"Name"}, difference)
  1173  }
  1174  
  1175  func Test_GenerateSpecIsDifferentErrorMessageWithNoDiff(t *testing.T) {
  1176  	r1 := argoappv1.Repository{}
  1177  	r2 := argoappv1.Repository{}
  1178  
  1179  	msg := GenerateSpecIsDifferentErrorMessage("application", r1, r2)
  1180  	assert.Equal(t, "existing application spec is different; use upsert flag to force update", msg)
  1181  }
  1182  
  1183  func Test_GenerateSpecIsDifferentErrorMessageWithDiff(t *testing.T) {
  1184  	r1 := argoappv1.Repository{}
  1185  	r2 := argoappv1.Repository{
  1186  		Name: "test",
  1187  	}
  1188  
  1189  	msg := GenerateSpecIsDifferentErrorMessage("repo", r1, r2)
  1190  	assert.Equal(t, "existing repo spec is different; use upsert flag to force update; difference in keys \"Name\"", msg)
  1191  }
  1192  
  1193  func Test_ParseAppQualifiedName(t *testing.T) {
  1194  	testcases := []struct {
  1195  		name       string
  1196  		input      string
  1197  		implicitNs string
  1198  		appName    string
  1199  		appNs      string
  1200  	}{
  1201  		{"Full qualified without implicit NS", "namespace/name", "", "name", "namespace"},
  1202  		{"Non qualified without implicit NS", "name", "", "name", ""},
  1203  		{"Full qualified with implicit NS", "namespace/name", "namespace2", "name", "namespace"},
  1204  		{"Non qualified with implicit NS", "name", "namespace2", "name", "namespace2"},
  1205  		{"Invalid without implicit NS", "namespace_name", "", "namespace_name", ""},
  1206  	}
  1207  
  1208  	for _, tt := range testcases {
  1209  		t.Run(tt.name, func(t *testing.T) {
  1210  			appName, appNs := ParseFromQualifiedName(tt.input, tt.implicitNs)
  1211  			assert.Equal(t, tt.appName, appName)
  1212  			assert.Equal(t, tt.appNs, appNs)
  1213  		})
  1214  	}
  1215  }
  1216  
  1217  func Test_ParseAppInstanceName(t *testing.T) {
  1218  	testcases := []struct {
  1219  		name       string
  1220  		input      string
  1221  		implicitNs string
  1222  		appName    string
  1223  		appNs      string
  1224  	}{
  1225  		{"Full qualified without implicit NS", "namespace_name", "", "name", "namespace"},
  1226  		{"Non qualified without implicit NS", "name", "", "name", ""},
  1227  		{"Full qualified with implicit NS", "namespace_name", "namespace2", "name", "namespace"},
  1228  		{"Non qualified with implicit NS", "name", "namespace2", "name", "namespace2"},
  1229  		{"Invalid without implicit NS", "namespace/name", "", "namespace/name", ""},
  1230  	}
  1231  
  1232  	for _, tt := range testcases {
  1233  		t.Run(tt.name, func(t *testing.T) {
  1234  			appName, appNs := ParseInstanceName(tt.input, tt.implicitNs)
  1235  			assert.Equal(t, tt.appName, appName)
  1236  			assert.Equal(t, tt.appNs, appNs)
  1237  		})
  1238  	}
  1239  }
  1240  
  1241  func Test_AppInstanceName(t *testing.T) {
  1242  	testcases := []struct {
  1243  		name         string
  1244  		appName      string
  1245  		appNamespace string
  1246  		defaultNs    string
  1247  		result       string
  1248  	}{
  1249  		{"defaultns different as appns", "appname", "appns", "defaultns", "appns_appname"},
  1250  		{"defaultns same as appns", "appname", "appns", "appns", "appname"},
  1251  		{"defaultns set and appns not given", "appname", "", "appns", "appname"},
  1252  		{"neither defaultns nor appns set", "appname", "", "appns", "appname"},
  1253  	}
  1254  
  1255  	for _, tt := range testcases {
  1256  		t.Run(tt.name, func(t *testing.T) {
  1257  			result := AppInstanceName(tt.appName, tt.appNamespace, tt.defaultNs)
  1258  			assert.Equal(t, tt.result, result)
  1259  		})
  1260  	}
  1261  }
  1262  
  1263  func Test_AppInstanceNameFromQualified(t *testing.T) {
  1264  	testcases := []struct {
  1265  		name      string
  1266  		appName   string
  1267  		defaultNs string
  1268  		result    string
  1269  	}{
  1270  		{"Qualified name with namespace not being defaultns", "appns/appname", "defaultns", "appns_appname"},
  1271  		{"Qualified name with namespace being defaultns", "defaultns/appname", "defaultns", "appname"},
  1272  		{"Qualified name without namespace", "appname", "defaultns", "appname"},
  1273  		{"Qualified name without namespace and defaultns", "appname", "", "appname"},
  1274  	}
  1275  
  1276  	for _, tt := range testcases {
  1277  		t.Run(tt.name, func(t *testing.T) {
  1278  			result := InstanceNameFromQualified(tt.appName, tt.defaultNs)
  1279  			assert.Equal(t, tt.result, result)
  1280  		})
  1281  	}
  1282  }
  1283  
  1284  func Test_GetRefSources(t *testing.T) {
  1285  	repoPath, err := filepath.Abs("./../..")
  1286  	require.NoError(t, err)
  1287  
  1288  	getMultiSourceAppSpec := func(sources argoappv1.ApplicationSources) *argoappv1.ApplicationSpec {
  1289  		return &argoappv1.ApplicationSpec{
  1290  			Sources: sources,
  1291  		}
  1292  	}
  1293  
  1294  	repo := &argoappv1.Repository{Repo: "file://" + repoPath}
  1295  
  1296  	t.Run("target ref exists", func(t *testing.T) {
  1297  		argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{
  1298  			{RepoURL: "file://" + repoPath, Ref: "source-1_2"},
  1299  			{RepoURL: "file://" + repoPath},
  1300  		})
  1301  
  1302  		refSources, err := GetRefSources(t.Context(), argoSpec.Sources, argoSpec.Project, func(_ context.Context, _ string, _ string) (*argoappv1.Repository, error) {
  1303  			return repo, nil
  1304  		}, []string{})
  1305  
  1306  		expectedRefSource := argoappv1.RefTargetRevisionMapping{
  1307  			"$source-1_2": &argoappv1.RefTarget{
  1308  				Repo: *repo,
  1309  			},
  1310  		}
  1311  		require.NoError(t, err)
  1312  		assert.Len(t, refSources, 1)
  1313  		assert.Equal(t, expectedRefSource, refSources)
  1314  	})
  1315  
  1316  	t.Run("target ref does not exist", func(t *testing.T) {
  1317  		argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{
  1318  			{RepoURL: "file://does-not-exist", Ref: "source1"},
  1319  			{RepoURL: "file://" + repoPath},
  1320  		})
  1321  
  1322  		refSources, err := GetRefSources(t.Context(), argoSpec.Sources, argoSpec.Project, func(_ context.Context, _ string, _ string) (*argoappv1.Repository, error) {
  1323  			return nil, errors.New("repo does not exist")
  1324  		}, []string{})
  1325  
  1326  		require.Error(t, err)
  1327  		assert.Empty(t, refSources)
  1328  	})
  1329  
  1330  	t.Run("invalid ref", func(t *testing.T) {
  1331  		argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{
  1332  			{RepoURL: "file://does-not-exist", Ref: "%invalid-name%"},
  1333  			{RepoURL: "file://" + repoPath},
  1334  		})
  1335  
  1336  		refSources, err := GetRefSources(t.Context(), argoSpec.Sources, argoSpec.Project, func(_ context.Context, _ string, _ string) (*argoappv1.Repository, error) {
  1337  			return nil, err
  1338  		}, []string{})
  1339  
  1340  		require.Error(t, err)
  1341  		assert.Empty(t, refSources)
  1342  	})
  1343  
  1344  	t.Run("duplicate ref keys", func(t *testing.T) {
  1345  		argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{
  1346  			{RepoURL: "file://does-not-exist", Ref: "source1"},
  1347  			{RepoURL: "file://does-not-exist", Ref: "source1"},
  1348  		})
  1349  
  1350  		refSources, err := GetRefSources(t.Context(), argoSpec.Sources, argoSpec.Project, func(_ context.Context, _ string, _ string) (*argoappv1.Repository, error) {
  1351  			return nil, err
  1352  		}, []string{})
  1353  
  1354  		require.Error(t, err)
  1355  		assert.Empty(t, refSources)
  1356  	})
  1357  }
  1358  
  1359  func TestValidatePermissionsMultipleSources(t *testing.T) {
  1360  	t.Run("Empty Repo URL result in condition", func(t *testing.T) {
  1361  		spec := argoappv1.ApplicationSpec{
  1362  			Sources: argoappv1.ApplicationSources{
  1363  				{RepoURL: ""},
  1364  			},
  1365  		}
  1366  
  1367  		proj := argoappv1.AppProject{}
  1368  		db := &dbmocks.ArgoDB{}
  1369  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
  1370  		require.NoError(t, err)
  1371  		assert.Len(t, conditions, 1)
  1372  		assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
  1373  		assert.Contains(t, conditions[0].Message, "are required")
  1374  	})
  1375  
  1376  	t.Run("Incomplete Path/Chart/Ref combo result in condition", func(t *testing.T) {
  1377  		spec := argoappv1.ApplicationSpec{
  1378  			Sources: argoappv1.ApplicationSources{
  1379  				{
  1380  					RepoURL: "http://some/where",
  1381  					Path:    "",
  1382  					Chart:   "",
  1383  					Ref:     "",
  1384  				},
  1385  			},
  1386  		}
  1387  		proj := argoappv1.AppProject{}
  1388  		db := &dbmocks.ArgoDB{}
  1389  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
  1390  		require.NoError(t, err)
  1391  		assert.Len(t, conditions, 1)
  1392  		assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
  1393  		assert.Contains(t, conditions[0].Message, "are required")
  1394  	})
  1395  
  1396  	t.Run("One of the Application sources is not permitted in project", func(t *testing.T) {
  1397  		spec := argoappv1.ApplicationSpec{
  1398  			Sources: argoappv1.ApplicationSources{
  1399  				{
  1400  					RepoURL:        "http://some/where",
  1401  					Path:           "",
  1402  					Chart:          "somechart",
  1403  					TargetRevision: "1.4.1",
  1404  				},
  1405  			},
  1406  			Destination: argoappv1.ApplicationDestination{
  1407  				Server:    "https://127.0.0.1:6443",
  1408  				Namespace: "testns",
  1409  			},
  1410  		}
  1411  		proj := argoappv1.AppProject{
  1412  			Spec: argoappv1.AppProjectSpec{
  1413  				Destinations: []argoappv1.ApplicationDestination{
  1414  					{
  1415  						Server:    "*",
  1416  						Namespace: "*",
  1417  					},
  1418  				},
  1419  				SourceRepos: []string{"http://some/where/else"},
  1420  			},
  1421  		}
  1422  		cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"}
  1423  		db := &dbmocks.ArgoDB{}
  1424  		db.On("GetCluster", t.Context(), spec.Destination.Server).Return(cluster, nil)
  1425  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
  1426  		require.NoError(t, err)
  1427  		assert.Len(t, conditions, 1)
  1428  		assert.Contains(t, conditions[0].Message, "application repo http://some/where is not permitted")
  1429  	})
  1430  
  1431  	t.Run("Source with a Ref field and missing Path/Chart field", func(t *testing.T) {
  1432  		spec := argoappv1.ApplicationSpec{
  1433  			Sources: argoappv1.ApplicationSources{
  1434  				{
  1435  					RepoURL: "http://some/where",
  1436  					Path:    "",
  1437  					Chart:   "",
  1438  					Ref:     "somechart",
  1439  				},
  1440  			},
  1441  			Destination: argoappv1.ApplicationDestination{
  1442  				Name:      "does-exist",
  1443  				Namespace: "default",
  1444  			},
  1445  		}
  1446  		proj := argoappv1.AppProject{
  1447  			Spec: argoappv1.AppProjectSpec{
  1448  				Destinations: []argoappv1.ApplicationDestination{
  1449  					{
  1450  						Server:    "*",
  1451  						Namespace: "default",
  1452  					},
  1453  				},
  1454  				SourceRepos: []string{"http://some/where"},
  1455  			},
  1456  		}
  1457  		db := &dbmocks.ArgoDB{}
  1458  		cluster := argoappv1.Cluster{
  1459  			Name:   "does-exist",
  1460  			Server: "https://127.0.0.1:6443",
  1461  		}
  1462  		db.On("GetClusterServersByName", t.Context(), "does-exist").Return([]string{"https://127.0.0.1:6443"}, nil)
  1463  		db.On("GetCluster", t.Context(), "https://127.0.0.1:6443").Return(&cluster, nil)
  1464  		conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
  1465  		require.NoError(t, err)
  1466  		assert.Empty(t, conditions)
  1467  	})
  1468  }
  1469  
  1470  func TestAugmentSyncMsg(t *testing.T) {
  1471  	mockAPIResourcesFn := func() ([]kube.APIResourceInfo, error) {
  1472  		return []kube.APIResourceInfo{
  1473  			{
  1474  				GroupKind: schema.GroupKind{
  1475  					Group: "apps",
  1476  					Kind:  "Deployment",
  1477  				},
  1478  				GroupVersionResource: schema.GroupVersionResource{
  1479  					Group:   "apps",
  1480  					Version: "v1",
  1481  				},
  1482  			},
  1483  			{
  1484  				GroupKind: schema.GroupKind{
  1485  					Group: "networking.k8s.io",
  1486  					Kind:  "Ingress",
  1487  				},
  1488  				GroupVersionResource: schema.GroupVersionResource{
  1489  					Group:   "networking.k8s.io",
  1490  					Version: "v1",
  1491  				},
  1492  			},
  1493  		}, nil
  1494  	}
  1495  
  1496  	testcases := []struct {
  1497  		name            string
  1498  		msg             string
  1499  		expectedMessage string
  1500  		res             common.ResourceSyncResult
  1501  		mockFn          func() ([]kube.APIResourceInfo, error)
  1502  		errMsg          string
  1503  	}{
  1504  		{
  1505  			name: "match specific k8s error",
  1506  			msg:  "the server could not find the requested resource",
  1507  			res: common.ResourceSyncResult{
  1508  				ResourceKey: kube.ResourceKey{
  1509  					Name:      "deployment-resource",
  1510  					Namespace: "test-namespace",
  1511  					Kind:      "Deployment",
  1512  					Group:     "apps",
  1513  				},
  1514  				Version: "v1beta1",
  1515  			},
  1516  			expectedMessage: "The Kubernetes API could not find version \"v1beta1\" of apps/Deployment for requested resource test-namespace/deployment-resource. Version \"v1\" of apps/Deployment is installed on the destination cluster.",
  1517  			mockFn:          mockAPIResourcesFn,
  1518  		},
  1519  		{
  1520  			name: "any random k8s msg",
  1521  			msg:  "random message from k8s",
  1522  			res: common.ResourceSyncResult{
  1523  				ResourceKey: kube.ResourceKey{
  1524  					Name:      "deployment-resource",
  1525  					Namespace: "test-namespace",
  1526  					Kind:      "Deployment",
  1527  					Group:     "apps",
  1528  				},
  1529  				Version: "v1beta1",
  1530  			},
  1531  			expectedMessage: "random message from k8s",
  1532  			mockFn:          mockAPIResourcesFn,
  1533  		},
  1534  		{
  1535  			name: "resource doesn't exist in the target cluster",
  1536  			res: common.ResourceSyncResult{
  1537  				ResourceKey: kube.ResourceKey{
  1538  					Name:      "persistent-volume-resource",
  1539  					Namespace: "test-namespace",
  1540  					Kind:      "PersistentVolume",
  1541  					Group:     "",
  1542  				},
  1543  				Version: "v1",
  1544  			},
  1545  			msg:             "the server could not find the requested resource",
  1546  			expectedMessage: "The Kubernetes API could not find /PersistentVolume for requested resource test-namespace/persistent-volume-resource. Make sure the \"PersistentVolume\" CRD is installed on the destination cluster.",
  1547  			mockFn:          mockAPIResourcesFn,
  1548  		},
  1549  		{
  1550  			name: "API Resource returns error",
  1551  			res: common.ResourceSyncResult{
  1552  				ResourceKey: kube.ResourceKey{
  1553  					Name:      "persistent-volume-resource",
  1554  					Namespace: "test-namespace",
  1555  					Kind:      "PersistentVolume",
  1556  					Group:     "",
  1557  				},
  1558  				Version: "v1",
  1559  			},
  1560  			msg:             "the server could not find the requested resource",
  1561  			expectedMessage: "the server could not find the requested resource",
  1562  			mockFn: func() ([]kube.APIResourceInfo, error) {
  1563  				return nil, errors.New("failed to fetch resource of given kind %s from the target cluster")
  1564  			},
  1565  			errMsg: "failed to get API resource info for group \"\" and kind \"PersistentVolume\": failed to get API resource info: failed to fetch resource of given kind %s from the target cluster",
  1566  		},
  1567  		{
  1568  			name: "old Ingress type returns error suggesting new Ingress type",
  1569  			res: common.ResourceSyncResult{
  1570  				ResourceKey: kube.ResourceKey{
  1571  					Name:      "ingress-resource",
  1572  					Namespace: "test-namespace",
  1573  					Kind:      "Ingress",
  1574  					Group:     "extensions",
  1575  				},
  1576  				Version: "v1beta1",
  1577  			},
  1578  			msg:             "the server could not find the requested resource",
  1579  			expectedMessage: "The Kubernetes API could not find version \"v1beta1\" of extensions/Ingress for requested resource test-namespace/ingress-resource. Version \"v1\" of networking.k8s.io/Ingress is installed on the destination cluster.",
  1580  			mockFn:          mockAPIResourcesFn,
  1581  		},
  1582  	}
  1583  
  1584  	for _, tt := range testcases {
  1585  		t.Run(tt.name, func(t *testing.T) {
  1586  			tt.res.Message = tt.msg
  1587  			msg, err := AugmentSyncMsg(tt.res, tt.mockFn)
  1588  			if tt.errMsg != "" {
  1589  				assert.EqualError(t, err, tt.errMsg)
  1590  			} else {
  1591  				require.NoError(t, err)
  1592  				assert.Equal(t, tt.expectedMessage, msg)
  1593  			}
  1594  		})
  1595  	}
  1596  }
  1597  
  1598  func TestGetAppEventLabels(t *testing.T) {
  1599  	tests := []struct {
  1600  		name                string
  1601  		cmInEventLabelKeys  string
  1602  		cmExEventLabelKeys  string
  1603  		appLabels           map[string]string
  1604  		projLabels          map[string]string
  1605  		expectedEventLabels map[string]string
  1606  	}{
  1607  		{
  1608  			name:                "no label keys in cm - no event labels",
  1609  			cmInEventLabelKeys:  "",
  1610  			appLabels:           map[string]string{"team": "A", "tier": "frontend"},
  1611  			projLabels:          map[string]string{"environment": "dev"},
  1612  			expectedEventLabels: nil,
  1613  		},
  1614  		{
  1615  			name:                "label keys in cm, no labels on app & proj - no event labels",
  1616  			cmInEventLabelKeys:  "team, environment",
  1617  			appLabels:           nil,
  1618  			projLabels:          nil,
  1619  			expectedEventLabels: nil,
  1620  		},
  1621  		{
  1622  			name:                "labels on app, no labels on proj - event labels matched on app only",
  1623  			cmInEventLabelKeys:  "team, environment",
  1624  			appLabels:           map[string]string{"team": "A", "tier": "frontend"},
  1625  			projLabels:          nil,
  1626  			expectedEventLabels: map[string]string{"team": "A"},
  1627  		},
  1628  		{
  1629  			name:                "no labels on app, labels on proj - event labels matched on proj only",
  1630  			cmInEventLabelKeys:  "team, environment",
  1631  			appLabels:           nil,
  1632  			projLabels:          map[string]string{"environment": "dev"},
  1633  			expectedEventLabels: map[string]string{"environment": "dev"},
  1634  		},
  1635  		{
  1636  			name:                "labels on app & proj with conflicts - event labels matched on both app & proj and app labels prioritized on conflict",
  1637  			cmInEventLabelKeys:  "team, environment",
  1638  			appLabels:           map[string]string{"team": "A", "environment": "stage", "tier": "frontend"},
  1639  			projLabels:          map[string]string{"environment": "dev"},
  1640  			expectedEventLabels: map[string]string{"team": "A", "environment": "stage"},
  1641  		},
  1642  		{
  1643  			name:                "wildcard support - matched all labels",
  1644  			cmInEventLabelKeys:  "*",
  1645  			appLabels:           map[string]string{"team": "A", "tier": "frontend"},
  1646  			projLabels:          map[string]string{"environment": "dev"},
  1647  			expectedEventLabels: map[string]string{"team": "A", "tier": "frontend", "environment": "dev"},
  1648  		},
  1649  		{
  1650  			name:                "exclude event labels",
  1651  			cmInEventLabelKeys:  "example.com/team,tier,env*",
  1652  			cmExEventLabelKeys:  "tie*",
  1653  			appLabels:           map[string]string{"example.com/team": "A", "tier": "frontend"},
  1654  			projLabels:          map[string]string{"environment": "dev"},
  1655  			expectedEventLabels: map[string]string{"example.com/team": "A", "environment": "dev"},
  1656  		},
  1657  	}
  1658  	for _, tt := range tests {
  1659  		t.Run(tt.name, func(t *testing.T) {
  1660  			cm := corev1.ConfigMap{
  1661  				ObjectMeta: metav1.ObjectMeta{
  1662  					Name:      "argocd-cm",
  1663  					Namespace: test.FakeArgoCDNamespace,
  1664  					Labels: map[string]string{
  1665  						"app.kubernetes.io/part-of": "argocd",
  1666  					},
  1667  				},
  1668  				Data: map[string]string{
  1669  					"resource.includeEventLabelKeys": tt.cmInEventLabelKeys,
  1670  					"resource.excludeEventLabelKeys": tt.cmExEventLabelKeys,
  1671  				},
  1672  			}
  1673  
  1674  			proj := &argoappv1.AppProject{
  1675  				ObjectMeta: metav1.ObjectMeta{
  1676  					Name:      "default",
  1677  					Namespace: test.FakeArgoCDNamespace,
  1678  					Labels:    tt.projLabels,
  1679  				},
  1680  			}
  1681  
  1682  			var app argoappv1.Application
  1683  			app.Name = "test-app"
  1684  			app.Namespace = test.FakeArgoCDNamespace
  1685  			app.Labels = tt.appLabels
  1686  			appClientset := appclientset.NewSimpleClientset(proj)
  1687  			ctx, cancel := context.WithCancel(t.Context())
  1688  			defer cancel()
  1689  			indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
  1690  			informer := v1alpha1.NewAppProjectInformer(appClientset, test.FakeArgoCDNamespace, 0, indexers)
  1691  			go informer.Run(ctx.Done())
  1692  			cache.WaitForCacheSync(ctx.Done(), informer.HasSynced)
  1693  
  1694  			kubeClient := fake.NewSimpleClientset(&cm)
  1695  			settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
  1696  			argoDB := db.NewDB("default", settingsMgr, kubeClient)
  1697  
  1698  			eventLabels := GetAppEventLabels(ctx, &app, applisters.NewAppProjectLister(informer.GetIndexer()), test.FakeArgoCDNamespace, settingsMgr, argoDB)
  1699  			assert.Len(t, eventLabels, len(tt.expectedEventLabels))
  1700  			for ek, ev := range tt.expectedEventLabels {
  1701  				v, found := eventLabels[ek]
  1702  				assert.True(t, found)
  1703  				assert.Equal(t, ev, v)
  1704  			}
  1705  		})
  1706  	}
  1707  }