github.com/argoproj/argo-cd/v2@v2.10.9/server/application/application_test.go (about)

     1  package application
     2  
     3  import (
     4  	"context"
     5  	coreerrors "errors"
     6  	"fmt"
     7  	"io"
     8  	"strconv"
     9  	"sync/atomic"
    10  	"testing"
    11  	"time"
    12  
    13  	"k8s.io/apimachinery/pkg/labels"
    14  
    15  	"github.com/argoproj/gitops-engine/pkg/health"
    16  	synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
    17  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    18  	"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
    19  	"github.com/argoproj/pkg/sync"
    20  	"github.com/golang-jwt/jwt/v4"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/mock"
    23  	"github.com/stretchr/testify/require"
    24  	"google.golang.org/grpc/codes"
    25  	"google.golang.org/grpc/metadata"
    26  	"google.golang.org/grpc/status"
    27  	k8sappsv1 "k8s.io/api/apps/v1"
    28  	k8sbatchv1 "k8s.io/api/batch/v1"
    29  	corev1 "k8s.io/api/core/v1"
    30  	v1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/apimachinery/pkg/watch"
    36  	"k8s.io/client-go/kubernetes/fake"
    37  	"k8s.io/client-go/rest"
    38  	kubetesting "k8s.io/client-go/testing"
    39  	k8scache "k8s.io/client-go/tools/cache"
    40  	"k8s.io/utils/pointer"
    41  	"sigs.k8s.io/yaml"
    42  
    43  	"github.com/argoproj/argo-cd/v2/common"
    44  	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
    45  	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    46  	apps "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
    47  	appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
    48  	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
    49  	"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
    50  	appmocks "github.com/argoproj/argo-cd/v2/server/application/mocks"
    51  	servercache "github.com/argoproj/argo-cd/v2/server/cache"
    52  	"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
    53  	"github.com/argoproj/argo-cd/v2/test"
    54  	"github.com/argoproj/argo-cd/v2/util/argo"
    55  	"github.com/argoproj/argo-cd/v2/util/assets"
    56  	"github.com/argoproj/argo-cd/v2/util/cache"
    57  	cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
    58  	"github.com/argoproj/argo-cd/v2/util/cache/appstate"
    59  	"github.com/argoproj/argo-cd/v2/util/db"
    60  	"github.com/argoproj/argo-cd/v2/util/errors"
    61  	"github.com/argoproj/argo-cd/v2/util/grpc"
    62  	"github.com/argoproj/argo-cd/v2/util/rbac"
    63  	"github.com/argoproj/argo-cd/v2/util/settings"
    64  )
    65  
    66  const (
    67  	testNamespace = "default"
    68  	fakeRepoURL   = "https://git.com/repo.git"
    69  )
    70  
    71  func fakeRepo() *appsv1.Repository {
    72  	return &appsv1.Repository{
    73  		Repo: fakeRepoURL,
    74  	}
    75  }
    76  
    77  func fakeCluster() *appsv1.Cluster {
    78  	return &appsv1.Cluster{
    79  		Server: "https://cluster-api.example.com",
    80  		Name:   "fake-cluster",
    81  		Config: appsv1.ClusterConfig{},
    82  	}
    83  }
    84  
    85  func fakeAppList() *apiclient.AppList {
    86  	return &apiclient.AppList{
    87  		Apps: map[string]string{
    88  			"some/path": "Ksonnet",
    89  		},
    90  	}
    91  }
    92  
    93  func fakeResolveRevisionResponse() *apiclient.ResolveRevisionResponse {
    94  	return &apiclient.ResolveRevisionResponse{
    95  		Revision:          "f9ba9e98119bf8c1176fbd65dbae26a71d044add",
    96  		AmbiguousRevision: "HEAD (f9ba9e98119bf8c1176fbd65dbae26a71d044add)",
    97  	}
    98  }
    99  
   100  func fakeResolveRevisionResponseHelm() *apiclient.ResolveRevisionResponse {
   101  	return &apiclient.ResolveRevisionResponse{
   102  		Revision:          "0.7.*",
   103  		AmbiguousRevision: "0.7.* (0.7.2)",
   104  	}
   105  }
   106  
   107  func fakeRepoServerClient(isHelm bool) *mocks.RepoServerServiceClient {
   108  	mockRepoServiceClient := mocks.RepoServerServiceClient{}
   109  	mockRepoServiceClient.On("ListApps", mock.Anything, mock.Anything).Return(fakeAppList(), nil)
   110  	mockRepoServiceClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(&apiclient.ManifestResponse{}, nil)
   111  	mockRepoServiceClient.On("GetAppDetails", mock.Anything, mock.Anything).Return(&apiclient.RepoAppDetailsResponse{}, nil)
   112  	mockRepoServiceClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
   113  	mockRepoServiceClient.On("GetRevisionMetadata", mock.Anything, mock.Anything).Return(&appsv1.RevisionMetadata{}, nil)
   114  	mockWithFilesClient := &mocks.RepoServerService_GenerateManifestWithFilesClient{}
   115  	mockWithFilesClient.On("Send", mock.Anything).Return(nil)
   116  	mockWithFilesClient.On("CloseAndRecv").Return(&apiclient.ManifestResponse{}, nil)
   117  	mockRepoServiceClient.On("GenerateManifestWithFiles", mock.Anything, mock.Anything).Return(mockWithFilesClient, nil)
   118  	mockRepoServiceClient.On("GetRevisionChartDetails", mock.Anything, mock.Anything).Return(&appsv1.ChartDetails{}, nil)
   119  
   120  	if isHelm {
   121  		mockRepoServiceClient.On("ResolveRevision", mock.Anything, mock.Anything).Return(fakeResolveRevisionResponseHelm(), nil)
   122  	} else {
   123  		mockRepoServiceClient.On("ResolveRevision", mock.Anything, mock.Anything).Return(fakeResolveRevisionResponse(), nil)
   124  	}
   125  
   126  	return &mockRepoServiceClient
   127  }
   128  
   129  // return an ApplicationServiceServer which returns fake data
   130  func newTestAppServer(t *testing.T, objects ...runtime.Object) *Server {
   131  	f := func(enf *rbac.Enforcer) {
   132  		_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
   133  		enf.SetDefaultRole("role:admin")
   134  	}
   135  	return newTestAppServerWithEnforcerConfigure(f, t, objects...)
   136  }
   137  
   138  func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), t *testing.T, objects ...runtime.Object) *Server {
   139  	kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
   140  		ObjectMeta: metav1.ObjectMeta{
   141  			Namespace: testNamespace,
   142  			Name:      "argocd-cm",
   143  			Labels: map[string]string{
   144  				"app.kubernetes.io/part-of": "argocd",
   145  			},
   146  		},
   147  	}, &v1.Secret{
   148  		ObjectMeta: metav1.ObjectMeta{
   149  			Name:      "argocd-secret",
   150  			Namespace: testNamespace,
   151  		},
   152  		Data: map[string][]byte{
   153  			"admin.password":   []byte("test"),
   154  			"server.secretkey": []byte("test"),
   155  		},
   156  	})
   157  	ctx := context.Background()
   158  	db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset)
   159  	_, err := db.CreateRepository(ctx, fakeRepo())
   160  	errors.CheckError(err)
   161  	_, err = db.CreateCluster(ctx, fakeCluster())
   162  	errors.CheckError(err)
   163  
   164  	mockRepoClient := &mocks.Clientset{RepoServerServiceClient: fakeRepoServerClient(false)}
   165  
   166  	defaultProj := &appsv1.AppProject{
   167  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"},
   168  		Spec: appsv1.AppProjectSpec{
   169  			SourceRepos:  []string{"*"},
   170  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   171  		},
   172  	}
   173  
   174  	myProj := &appsv1.AppProject{
   175  		ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"},
   176  		Spec: appsv1.AppProjectSpec{
   177  			SourceRepos:  []string{"*"},
   178  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   179  		},
   180  	}
   181  	projWithSyncWindows := &appsv1.AppProject{
   182  		ObjectMeta: metav1.ObjectMeta{Name: "proj-maint", Namespace: "default"},
   183  		Spec: appsv1.AppProjectSpec{
   184  			SourceRepos:  []string{"*"},
   185  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   186  			SyncWindows:  appsv1.SyncWindows{},
   187  		},
   188  	}
   189  	matchingWindow := &appsv1.SyncWindow{
   190  		Kind:         "allow",
   191  		Schedule:     "* * * * *",
   192  		Duration:     "1h",
   193  		Applications: []string{"test-app"},
   194  	}
   195  	projWithSyncWindows.Spec.SyncWindows = append(projWithSyncWindows.Spec.SyncWindows, matchingWindow)
   196  
   197  	objects = append(objects, defaultProj, myProj, projWithSyncWindows)
   198  
   199  	fakeAppsClientset := apps.NewSimpleClientset(objects...)
   200  	factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
   201  	fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace)
   202  
   203  	enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
   204  	f(enforcer)
   205  	enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims)
   206  
   207  	settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace)
   208  
   209  	// populate the app informer with the fake objects
   210  	appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
   211  	// TODO(jessesuen): probably should return cancel function so tests can stop background informer
   212  	// ctx, cancel := context.WithCancel(context.Background())
   213  	go appInformer.Run(ctx.Done())
   214  	if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) {
   215  		panic("Timed out waiting for caches to sync")
   216  	}
   217  
   218  	projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
   219  	go projInformer.Run(ctx.Done())
   220  	if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) {
   221  		panic("Timed out waiting for caches to sync")
   222  	}
   223  
   224  	broadcaster := new(appmocks.Broadcaster)
   225  	broadcaster.On("Subscribe", mock.Anything, mock.Anything).Return(func() {}).Run(func(args mock.Arguments) {
   226  		// Simulate the broadcaster notifying the subscriber of an application update.
   227  		// The second parameter to Subscribe is filters. For the purposes of tests, we ignore the filters. Future tests
   228  		// might require implementing those.
   229  		go func() {
   230  			events := args.Get(0).(chan *appsv1.ApplicationWatchEvent)
   231  			for _, obj := range objects {
   232  				app, ok := obj.(*appsv1.Application)
   233  				if ok {
   234  					oldVersion, err := strconv.Atoi(app.ResourceVersion)
   235  					if err != nil {
   236  						oldVersion = 0
   237  					}
   238  					clonedApp := app.DeepCopy()
   239  					clonedApp.ResourceVersion = fmt.Sprintf("%d", oldVersion+1)
   240  					events <- &appsv1.ApplicationWatchEvent{Type: watch.Added, Application: *clonedApp}
   241  				}
   242  			}
   243  		}()
   244  	})
   245  	broadcaster.On("OnAdd", mock.Anything).Return()
   246  	broadcaster.On("OnUpdate", mock.Anything, mock.Anything).Return()
   247  	broadcaster.On("OnDelete", mock.Anything).Return()
   248  
   249  	appStateCache := appstate.NewCache(cache.NewCache(cache.NewInMemoryCache(time.Hour)), time.Hour)
   250  	// pre-populate the app cache
   251  	for _, obj := range objects {
   252  		app, ok := obj.(*appsv1.Application)
   253  		if ok {
   254  			err := appStateCache.SetAppManagedResources(app.Name, []*appsv1.ResourceDiff{})
   255  			require.NoError(t, err)
   256  
   257  			// Pre-populate the resource tree based on the app's resources.
   258  			nodes := make([]appsv1.ResourceNode, len(app.Status.Resources))
   259  			for i, res := range app.Status.Resources {
   260  				nodes[i] = appsv1.ResourceNode{
   261  					ResourceRef: appsv1.ResourceRef{
   262  						Group:     res.Group,
   263  						Kind:      res.Kind,
   264  						Version:   res.Version,
   265  						Name:      res.Name,
   266  						Namespace: res.Namespace,
   267  						UID:       "fake",
   268  					},
   269  				}
   270  			}
   271  			err = appStateCache.SetAppResourcesTree(app.Name, &appsv1.ApplicationTree{
   272  				Nodes: nodes,
   273  			})
   274  			require.NoError(t, err)
   275  		}
   276  	}
   277  	appCache := servercache.NewCache(appStateCache, time.Hour, time.Hour, time.Hour)
   278  
   279  	kubectl := &kubetest.MockKubectlCmd{}
   280  	kubectl = kubectl.WithGetResourceFunc(func(_ context.Context, _ *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error) {
   281  		for _, obj := range objects {
   282  			if obj.GetObjectKind().GroupVersionKind().GroupKind() == gvk.GroupKind() {
   283  				if obj, ok := obj.(*unstructured.Unstructured); ok && obj.GetName() == name && obj.GetNamespace() == namespace {
   284  					return obj, nil
   285  				}
   286  			}
   287  		}
   288  		return nil, nil
   289  	})
   290  
   291  	server, _ := NewServer(
   292  		testNamespace,
   293  		kubeclientset,
   294  		fakeAppsClientset,
   295  		factory.Argoproj().V1alpha1().Applications().Lister(),
   296  		appInformer,
   297  		broadcaster,
   298  		mockRepoClient,
   299  		appCache,
   300  		kubectl,
   301  		db,
   302  		enforcer,
   303  		sync.NewKeyLock(),
   304  		settingsMgr,
   305  		projInformer,
   306  		[]string{},
   307  	)
   308  	return server.(*Server)
   309  }
   310  
   311  // return an ApplicationServiceServer which returns fake data
   312  func newTestAppServerWithBenchmark(b *testing.B, objects ...runtime.Object) *Server {
   313  	f := func(enf *rbac.Enforcer) {
   314  		_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
   315  		enf.SetDefaultRole("role:admin")
   316  	}
   317  	return newTestAppServerWithEnforcerConfigureWithBenchmark(f, b, objects...)
   318  }
   319  
   320  func newTestAppServerWithEnforcerConfigureWithBenchmark(f func(*rbac.Enforcer), b *testing.B, objects ...runtime.Object) *Server {
   321  	kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
   322  		ObjectMeta: metav1.ObjectMeta{
   323  			Namespace: testNamespace,
   324  			Name:      "argocd-cm",
   325  			Labels: map[string]string{
   326  				"app.kubernetes.io/part-of": "argocd",
   327  			},
   328  		},
   329  	}, &v1.Secret{
   330  		ObjectMeta: metav1.ObjectMeta{
   331  			Name:      "argocd-secret",
   332  			Namespace: testNamespace,
   333  		},
   334  		Data: map[string][]byte{
   335  			"admin.password":   []byte("test"),
   336  			"server.secretkey": []byte("test"),
   337  		},
   338  	})
   339  	ctx := context.Background()
   340  	db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset)
   341  	_, err := db.CreateRepository(ctx, fakeRepo())
   342  	require.NoError(b, err)
   343  	_, err = db.CreateCluster(ctx, fakeCluster())
   344  	require.NoError(b, err)
   345  
   346  	mockRepoClient := &mocks.Clientset{RepoServerServiceClient: fakeRepoServerClient(false)}
   347  
   348  	defaultProj := &appsv1.AppProject{
   349  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"},
   350  		Spec: appsv1.AppProjectSpec{
   351  			SourceRepos:  []string{"*"},
   352  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   353  		},
   354  	}
   355  	myProj := &appsv1.AppProject{
   356  		ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"},
   357  		Spec: appsv1.AppProjectSpec{
   358  			SourceRepos:  []string{"*"},
   359  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   360  		},
   361  	}
   362  	projWithSyncWindows := &appsv1.AppProject{
   363  		ObjectMeta: metav1.ObjectMeta{Name: "proj-maint", Namespace: "default"},
   364  		Spec: appsv1.AppProjectSpec{
   365  			SourceRepos:  []string{"*"},
   366  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   367  			SyncWindows:  appsv1.SyncWindows{},
   368  		},
   369  	}
   370  	matchingWindow := &appsv1.SyncWindow{
   371  		Kind:         "allow",
   372  		Schedule:     "* * * * *",
   373  		Duration:     "1h",
   374  		Applications: []string{"test-app"},
   375  	}
   376  	projWithSyncWindows.Spec.SyncWindows = append(projWithSyncWindows.Spec.SyncWindows, matchingWindow)
   377  
   378  	objects = append(objects, defaultProj, myProj, projWithSyncWindows)
   379  
   380  	fakeAppsClientset := apps.NewSimpleClientset(objects...)
   381  	factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
   382  	fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace)
   383  
   384  	enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
   385  	f(enforcer)
   386  	enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims)
   387  
   388  	settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace)
   389  
   390  	// populate the app informer with the fake objects
   391  	appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
   392  
   393  	go appInformer.Run(ctx.Done())
   394  	if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) {
   395  		panic("Timed out waiting for caches to sync")
   396  	}
   397  
   398  	projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
   399  	go projInformer.Run(ctx.Done())
   400  	if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) {
   401  		panic("Timed out waiting for caches to sync")
   402  	}
   403  
   404  	broadcaster := new(appmocks.Broadcaster)
   405  	broadcaster.On("Subscribe", mock.Anything, mock.Anything).Return(func() {}).Run(func(args mock.Arguments) {
   406  		// Simulate the broadcaster notifying the subscriber of an application update.
   407  		// The second parameter to Subscribe is filters. For the purposes of tests, we ignore the filters. Future tests
   408  		// might require implementing those.
   409  		go func() {
   410  			events := args.Get(0).(chan *appsv1.ApplicationWatchEvent)
   411  			for _, obj := range objects {
   412  				app, ok := obj.(*appsv1.Application)
   413  				if ok {
   414  					oldVersion, err := strconv.Atoi(app.ResourceVersion)
   415  					if err != nil {
   416  						oldVersion = 0
   417  					}
   418  					clonedApp := app.DeepCopy()
   419  					clonedApp.ResourceVersion = fmt.Sprintf("%d", oldVersion+1)
   420  					events <- &appsv1.ApplicationWatchEvent{Type: watch.Added, Application: *clonedApp}
   421  				}
   422  			}
   423  		}()
   424  	})
   425  	broadcaster.On("OnAdd", mock.Anything).Return()
   426  	broadcaster.On("OnUpdate", mock.Anything, mock.Anything).Return()
   427  	broadcaster.On("OnDelete", mock.Anything).Return()
   428  
   429  	appStateCache := appstate.NewCache(cache.NewCache(cache.NewInMemoryCache(time.Hour)), time.Hour)
   430  	// pre-populate the app cache
   431  	for _, obj := range objects {
   432  		app, ok := obj.(*appsv1.Application)
   433  		if ok {
   434  			err := appStateCache.SetAppManagedResources(app.Name, []*appsv1.ResourceDiff{})
   435  			require.NoError(b, err)
   436  
   437  			// Pre-populate the resource tree based on the app's resources.
   438  			nodes := make([]appsv1.ResourceNode, len(app.Status.Resources))
   439  			for i, res := range app.Status.Resources {
   440  				nodes[i] = appsv1.ResourceNode{
   441  					ResourceRef: appsv1.ResourceRef{
   442  						Group:     res.Group,
   443  						Kind:      res.Kind,
   444  						Version:   res.Version,
   445  						Name:      res.Name,
   446  						Namespace: res.Namespace,
   447  						UID:       "fake",
   448  					},
   449  				}
   450  			}
   451  			err = appStateCache.SetAppResourcesTree(app.Name, &appsv1.ApplicationTree{
   452  				Nodes: nodes,
   453  			})
   454  			require.NoError(b, err)
   455  		}
   456  	}
   457  	appCache := servercache.NewCache(appStateCache, time.Hour, time.Hour, time.Hour)
   458  
   459  	kubectl := &kubetest.MockKubectlCmd{}
   460  	kubectl = kubectl.WithGetResourceFunc(func(_ context.Context, _ *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error) {
   461  		for _, obj := range objects {
   462  			if obj.GetObjectKind().GroupVersionKind().GroupKind() == gvk.GroupKind() {
   463  				if obj, ok := obj.(*unstructured.Unstructured); ok && obj.GetName() == name && obj.GetNamespace() == namespace {
   464  					return obj, nil
   465  				}
   466  			}
   467  		}
   468  		return nil, nil
   469  	})
   470  
   471  	server, _ := NewServer(
   472  		testNamespace,
   473  		kubeclientset,
   474  		fakeAppsClientset,
   475  		factory.Argoproj().V1alpha1().Applications().Lister(),
   476  		appInformer,
   477  		broadcaster,
   478  		mockRepoClient,
   479  		appCache,
   480  		kubectl,
   481  		db,
   482  		enforcer,
   483  		sync.NewKeyLock(),
   484  		settingsMgr,
   485  		projInformer,
   486  		[]string{},
   487  	)
   488  	return server.(*Server)
   489  }
   490  
   491  const fakeApp = `
   492  apiVersion: argoproj.io/v1alpha1
   493  kind: Application
   494  metadata:
   495    name: test-app
   496    namespace: default
   497  spec:
   498    source:
   499      path: some/path
   500      repoURL: https://github.com/argoproj/argocd-example-apps.git
   501      targetRevision: HEAD
   502      ksonnet:
   503        environment: default
   504    destination:
   505      namespace: ` + test.FakeDestNamespace + `
   506      server: https://cluster-api.example.com
   507  `
   508  
   509  const fakeAppWithDestName = `
   510  apiVersion: argoproj.io/v1alpha1
   511  kind: Application
   512  metadata:
   513    name: test-app
   514    namespace: default
   515  spec:
   516    source:
   517      path: some/path
   518      repoURL: https://github.com/argoproj/argocd-example-apps.git
   519      targetRevision: HEAD
   520      ksonnet:
   521        environment: default
   522    destination:
   523      namespace: ` + test.FakeDestNamespace + `
   524      name: fake-cluster
   525  `
   526  
   527  const fakeAppWithAnnotations = `
   528  apiVersion: argoproj.io/v1alpha1
   529  kind: Application
   530  metadata:
   531    name: test-app
   532    namespace: default
   533    annotations:
   534      test.annotation: test
   535  spec:
   536    source:
   537      path: some/path
   538      repoURL: https://github.com/argoproj/argocd-example-apps.git
   539      targetRevision: HEAD
   540      ksonnet:
   541        environment: default
   542    destination:
   543      namespace: ` + test.FakeDestNamespace + `
   544      server: https://cluster-api.example.com
   545  `
   546  
   547  func newTestAppWithDestName(opts ...func(app *appsv1.Application)) *appsv1.Application {
   548  	return createTestApp(fakeAppWithDestName, opts...)
   549  }
   550  
   551  func newTestApp(opts ...func(app *appsv1.Application)) *appsv1.Application {
   552  	return createTestApp(fakeApp, opts...)
   553  }
   554  
   555  func newTestAppWithAnnotations(opts ...func(app *appsv1.Application)) *appsv1.Application {
   556  	return createTestApp(fakeAppWithAnnotations, opts...)
   557  }
   558  
   559  func createTestApp(testApp string, opts ...func(app *appsv1.Application)) *appsv1.Application {
   560  	var app appsv1.Application
   561  	err := yaml.Unmarshal([]byte(testApp), &app)
   562  	if err != nil {
   563  		panic(err)
   564  	}
   565  	for i := range opts {
   566  		opts[i](&app)
   567  	}
   568  	return &app
   569  }
   570  
   571  type TestServerStream struct {
   572  	ctx        context.Context
   573  	appName    string
   574  	headerSent bool
   575  	project    string
   576  }
   577  
   578  func (t *TestServerStream) SetHeader(metadata.MD) error {
   579  	return nil
   580  }
   581  
   582  func (t *TestServerStream) SendHeader(metadata.MD) error {
   583  	return nil
   584  }
   585  
   586  func (t *TestServerStream) SetTrailer(metadata.MD) {}
   587  
   588  func (t *TestServerStream) Context() context.Context {
   589  	return t.ctx
   590  }
   591  
   592  func (t *TestServerStream) SendMsg(m interface{}) error {
   593  	return nil
   594  }
   595  
   596  func (t *TestServerStream) RecvMsg(m interface{}) error {
   597  	return nil
   598  }
   599  
   600  func (t *TestServerStream) SendAndClose(r *apiclient.ManifestResponse) error {
   601  	return nil
   602  }
   603  
   604  func (t *TestServerStream) Recv() (*application.ApplicationManifestQueryWithFilesWrapper, error) {
   605  	if !t.headerSent {
   606  		t.headerSent = true
   607  		return &application.ApplicationManifestQueryWithFilesWrapper{Part: &application.ApplicationManifestQueryWithFilesWrapper_Query{
   608  			Query: &application.ApplicationManifestQueryWithFiles{
   609  				Name:     pointer.String(t.appName),
   610  				Project:  pointer.String(t.project),
   611  				Checksum: pointer.String(""),
   612  			},
   613  		}}, nil
   614  	}
   615  	return nil, io.EOF
   616  }
   617  
   618  func (t *TestServerStream) ServerStream() TestServerStream {
   619  	return TestServerStream{}
   620  }
   621  
   622  type TestResourceTreeServer struct {
   623  	ctx context.Context
   624  }
   625  
   626  func (t *TestResourceTreeServer) Send(tree *appsv1.ApplicationTree) error {
   627  	return nil
   628  }
   629  
   630  func (t *TestResourceTreeServer) SetHeader(metadata.MD) error {
   631  	return nil
   632  }
   633  
   634  func (t *TestResourceTreeServer) SendHeader(metadata.MD) error {
   635  	return nil
   636  }
   637  
   638  func (t *TestResourceTreeServer) SetTrailer(metadata.MD) {}
   639  
   640  func (t *TestResourceTreeServer) Context() context.Context {
   641  	return t.ctx
   642  }
   643  
   644  func (t *TestResourceTreeServer) SendMsg(m interface{}) error {
   645  	return nil
   646  }
   647  
   648  func (t *TestResourceTreeServer) RecvMsg(m interface{}) error {
   649  	return nil
   650  }
   651  
   652  type TestPodLogsServer struct {
   653  	ctx context.Context
   654  }
   655  
   656  func (t *TestPodLogsServer) Send(log *application.LogEntry) error {
   657  	return nil
   658  }
   659  
   660  func (t *TestPodLogsServer) SetHeader(metadata.MD) error {
   661  	return nil
   662  }
   663  
   664  func (t *TestPodLogsServer) SendHeader(metadata.MD) error {
   665  	return nil
   666  }
   667  
   668  func (t *TestPodLogsServer) SetTrailer(metadata.MD) {}
   669  
   670  func (t *TestPodLogsServer) Context() context.Context {
   671  	return t.ctx
   672  }
   673  
   674  func (t *TestPodLogsServer) SendMsg(m interface{}) error {
   675  	return nil
   676  }
   677  
   678  func (t *TestPodLogsServer) RecvMsg(m interface{}) error {
   679  	return nil
   680  }
   681  
   682  func TestNoAppEnumeration(t *testing.T) {
   683  	// This test ensures that malicious users can't infer the existence or non-existence of Applications by inspecting
   684  	// error messages. The errors for "app does not exist" must be the same as errors for "you aren't allowed to
   685  	// interact with this app."
   686  
   687  	// These tests are only important on API calls where the full app RBAC name (project, namespace, and name) is _not_
   688  	// known based on the query parameters. For example, the Create call cannot leak existence of Applications, because
   689  	// the Application's project, namespace, and name are all specified in the API call. The call can be rejected
   690  	// immediately if the user does not have access. But the Delete endpoint may be called with just the Application
   691  	// name. So we cannot return a different error message for "does not exist" and "you don't have delete permissions,"
   692  	// because the user could infer that the Application exists if they do not get the "does not exist" message. For
   693  	// endpoints that do not require the full RBAC name, we must return a generic "permission denied" for both "does not
   694  	// exist" and "no access."
   695  
   696  	f := func(enf *rbac.Enforcer) {
   697  		_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
   698  		enf.SetDefaultRole("role:none")
   699  	}
   700  	deployment := k8sappsv1.Deployment{
   701  		TypeMeta: metav1.TypeMeta{
   702  			APIVersion: "apps/v1",
   703  			Kind:       "Deployment",
   704  		},
   705  		ObjectMeta: metav1.ObjectMeta{
   706  			Name:      "test",
   707  			Namespace: "test",
   708  		},
   709  	}
   710  	testApp := newTestApp(func(app *appsv1.Application) {
   711  		app.Name = "test"
   712  		app.Status.Resources = []appsv1.ResourceStatus{
   713  			{
   714  				Group:     deployment.GroupVersionKind().Group,
   715  				Kind:      deployment.GroupVersionKind().Kind,
   716  				Version:   deployment.GroupVersionKind().Version,
   717  				Name:      deployment.Name,
   718  				Namespace: deployment.Namespace,
   719  				Status:    "Synced",
   720  			},
   721  		}
   722  		app.Status.History = []appsv1.RevisionHistory{
   723  			{
   724  				ID: 0,
   725  				Source: appsv1.ApplicationSource{
   726  					TargetRevision: "something-old",
   727  				},
   728  			},
   729  		}
   730  	})
   731  	testHelmApp := newTestApp(func(app *appsv1.Application) {
   732  		app.Name = "test-helm"
   733  		app.Spec.Source.Path = ""
   734  		app.Spec.Source.Chart = "test"
   735  		app.Status.Resources = []appsv1.ResourceStatus{
   736  			{
   737  				Group:     deployment.GroupVersionKind().Group,
   738  				Kind:      deployment.GroupVersionKind().Kind,
   739  				Version:   deployment.GroupVersionKind().Version,
   740  				Name:      deployment.Name,
   741  				Namespace: deployment.Namespace,
   742  				Status:    "Synced",
   743  			},
   744  		}
   745  		app.Status.History = []appsv1.RevisionHistory{
   746  			{
   747  				ID: 0,
   748  				Source: appsv1.ApplicationSource{
   749  					TargetRevision: "something-old",
   750  				},
   751  			},
   752  		}
   753  	})
   754  	testDeployment := kube.MustToUnstructured(&deployment)
   755  	appServer := newTestAppServerWithEnforcerConfigure(f, t, testApp, testHelmApp, testDeployment)
   756  
   757  	noRoleCtx := context.Background()
   758  	// nolint:staticcheck
   759  	adminCtx := context.WithValue(noRoleCtx, "claims", &jwt.MapClaims{"groups": []string{"admin"}})
   760  
   761  	t.Run("Get", func(t *testing.T) {
   762  		// nolint:staticcheck
   763  		_, err := appServer.Get(adminCtx, &application.ApplicationQuery{Name: pointer.String("test")})
   764  		assert.NoError(t, err)
   765  		// nolint:staticcheck
   766  		_, err = appServer.Get(noRoleCtx, &application.ApplicationQuery{Name: pointer.String("test")})
   767  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   768  		// nolint:staticcheck
   769  		_, err = appServer.Get(adminCtx, &application.ApplicationQuery{Name: pointer.String("doest-not-exist")})
   770  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   771  		// nolint:staticcheck
   772  		_, err = appServer.Get(adminCtx, &application.ApplicationQuery{Name: pointer.String("doest-not-exist"), Project: []string{"test"}})
   773  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   774  	})
   775  
   776  	t.Run("GetManifests", func(t *testing.T) {
   777  		_, err := appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: pointer.String("test")})
   778  		assert.NoError(t, err)
   779  		_, err = appServer.GetManifests(noRoleCtx, &application.ApplicationManifestQuery{Name: pointer.String("test")})
   780  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   781  		_, err = appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: pointer.String("doest-not-exist")})
   782  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   783  		_, err = appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   784  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   785  	})
   786  
   787  	t.Run("ListResourceEvents", func(t *testing.T) {
   788  		_, err := appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("test")})
   789  		assert.NoError(t, err)
   790  		_, err = appServer.ListResourceEvents(noRoleCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("test")})
   791  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   792  		_, err = appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("doest-not-exist")})
   793  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   794  		_, err = appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   795  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   796  	})
   797  
   798  	t.Run("UpdateSpec", func(t *testing.T) {
   799  		_, err := appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("test"), Spec: &appsv1.ApplicationSpec{
   800  			Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.example.com"},
   801  			Source:      &appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
   802  		}})
   803  		assert.NoError(t, err)
   804  		_, err = appServer.UpdateSpec(noRoleCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("test"), Spec: &appsv1.ApplicationSpec{
   805  			Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.example.com"},
   806  			Source:      &appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
   807  		}})
   808  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   809  		_, err = appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("doest-not-exist"), Spec: &appsv1.ApplicationSpec{
   810  			Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.example.com"},
   811  			Source:      &appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
   812  		}})
   813  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   814  		_, err = appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test"), Spec: &appsv1.ApplicationSpec{
   815  			Destination: appsv1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.example.com"},
   816  			Source:      &appsv1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
   817  		}})
   818  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   819  	})
   820  
   821  	t.Run("Patch", func(t *testing.T) {
   822  		_, err := appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
   823  		assert.NoError(t, err)
   824  		_, err = appServer.Patch(noRoleCtx, &application.ApplicationPatchRequest{Name: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
   825  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   826  		_, err = appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: pointer.String("doest-not-exist")})
   827  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   828  		_, err = appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   829  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   830  	})
   831  
   832  	t.Run("GetResource", func(t *testing.T) {
   833  		_, err := appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   834  		assert.NoError(t, err)
   835  		_, err = appServer.GetResource(noRoleCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   836  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   837  		_, err = appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   838  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   839  		_, err = appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   840  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   841  	})
   842  
   843  	t.Run("PatchResource", func(t *testing.T) {
   844  		_, err := appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
   845  		// This will always throw an error, because the kubectl mock for PatchResource is hard-coded to return nil.
   846  		// The best we can do is to confirm we get past the permission check.
   847  		assert.NotEqual(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   848  		_, err = appServer.PatchResource(noRoleCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
   849  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   850  		_, err = appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
   851  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   852  		_, err = appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Patch: pointer.String(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
   853  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   854  	})
   855  
   856  	t.Run("DeleteResource", func(t *testing.T) {
   857  		_, err := appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   858  		assert.NoError(t, err)
   859  		_, err = appServer.DeleteResource(noRoleCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   860  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   861  		_, err = appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("doest-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   862  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   863  		_, err = appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   864  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   865  	})
   866  
   867  	t.Run("ResourceTree", func(t *testing.T) {
   868  		_, err := appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
   869  		assert.NoError(t, err)
   870  		_, err = appServer.ResourceTree(noRoleCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
   871  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   872  		_, err = appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("doest-not-exist")})
   873  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   874  		_, err = appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   875  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   876  	})
   877  
   878  	t.Run("RevisionMetadata", func(t *testing.T) {
   879  		_, err := appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("test")})
   880  		assert.NoError(t, err)
   881  		_, err = appServer.RevisionMetadata(noRoleCtx, &application.RevisionMetadataQuery{Name: pointer.String("test")})
   882  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   883  		_, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("doest-not-exist")})
   884  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   885  		_, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   886  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   887  	})
   888  
   889  	t.Run("RevisionChartDetails", func(t *testing.T) {
   890  		_, err := appServer.RevisionChartDetails(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("test-helm")})
   891  		assert.NoError(t, err)
   892  		_, err = appServer.RevisionChartDetails(noRoleCtx, &application.RevisionMetadataQuery{Name: pointer.String("test-helm")})
   893  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   894  		_, err = appServer.RevisionChartDetails(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("doest-not-exist")})
   895  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   896  		_, err = appServer.RevisionChartDetails(adminCtx, &application.RevisionMetadataQuery{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   897  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   898  	})
   899  
   900  	t.Run("ManagedResources", func(t *testing.T) {
   901  		_, err := appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
   902  		assert.NoError(t, err)
   903  		_, err = appServer.ManagedResources(noRoleCtx, &application.ResourcesQuery{ApplicationName: pointer.String("test")})
   904  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   905  		_, err = appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("doest-not-exist")})
   906  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   907  		_, err = appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   908  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   909  	})
   910  
   911  	t.Run("Sync", func(t *testing.T) {
   912  		_, err := appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: pointer.String("test")})
   913  		assert.NoError(t, err)
   914  		_, err = appServer.Sync(noRoleCtx, &application.ApplicationSyncRequest{Name: pointer.String("test")})
   915  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   916  		_, err = appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: pointer.String("doest-not-exist")})
   917  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   918  		_, err = appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   919  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   920  	})
   921  
   922  	t.Run("TerminateOperation", func(t *testing.T) {
   923  		// The sync operation is already started from the previous test. We just need to set the field that the
   924  		// controller would set if this were an actual Argo CD environment.
   925  		setSyncRunningOperationState(t, appServer)
   926  		_, err := appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: pointer.String("test")})
   927  		assert.NoError(t, err)
   928  		_, err = appServer.TerminateOperation(noRoleCtx, &application.OperationTerminateRequest{Name: pointer.String("test")})
   929  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   930  		_, err = appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: pointer.String("doest-not-exist")})
   931  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   932  		_, err = appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   933  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   934  	})
   935  
   936  	t.Run("Rollback", func(t *testing.T) {
   937  		unsetSyncRunningOperationState(t, appServer)
   938  		_, err := appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.String("test")})
   939  		assert.NoError(t, err)
   940  		_, err = appServer.Rollback(noRoleCtx, &application.ApplicationRollbackRequest{Name: pointer.String("test")})
   941  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   942  		_, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.String("doest-not-exist")})
   943  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   944  		_, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   945  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   946  	})
   947  
   948  	t.Run("ListResourceActions", func(t *testing.T) {
   949  		_, err := appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
   950  		assert.NoError(t, err)
   951  		_, err = appServer.ListResourceActions(noRoleCtx, &application.ApplicationResourceRequest{Name: pointer.String("test")})
   952  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   953  		_, err = appServer.ListResourceActions(noRoleCtx, &application.ApplicationResourceRequest{Group: pointer.String("argoproj.io"), Kind: pointer.String("Application"), Name: pointer.String("test")})
   954  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   955  		_, err = appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("doest-not-exist")})
   956  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   957  		_, err = appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   958  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   959  	})
   960  
   961  	t.Run("RunResourceAction", func(t *testing.T) {
   962  		_, err := appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Action: pointer.String("restart")})
   963  		assert.NoError(t, err)
   964  		_, err = appServer.RunResourceAction(noRoleCtx, &application.ResourceActionRunRequest{Name: pointer.String("test")})
   965  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   966  		_, err = appServer.RunResourceAction(noRoleCtx, &application.ResourceActionRunRequest{Group: pointer.String("argoproj.io"), Kind: pointer.String("Application"), Name: pointer.String("test")})
   967  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   968  		_, err = appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: pointer.String("doest-not-exist")})
   969  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   970  		_, err = appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   971  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   972  	})
   973  
   974  	t.Run("GetApplicationSyncWindows", func(t *testing.T) {
   975  		_, err := appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("test")})
   976  		assert.NoError(t, err)
   977  		_, err = appServer.GetApplicationSyncWindows(noRoleCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("test")})
   978  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   979  		_, err = appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("doest-not-exist")})
   980  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   981  		_, err = appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
   982  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   983  	})
   984  
   985  	t.Run("GetManifestsWithFiles", func(t *testing.T) {
   986  		err := appServer.GetManifestsWithFiles(&TestServerStream{ctx: adminCtx, appName: "test"})
   987  		assert.NoError(t, err)
   988  		err = appServer.GetManifestsWithFiles(&TestServerStream{ctx: noRoleCtx, appName: "test"})
   989  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   990  		err = appServer.GetManifestsWithFiles(&TestServerStream{ctx: adminCtx, appName: "does-not-exist"})
   991  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   992  		err = appServer.GetManifestsWithFiles(&TestServerStream{ctx: adminCtx, appName: "does-not-exist", project: "test"})
   993  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
   994  	})
   995  
   996  	t.Run("WatchResourceTree", func(t *testing.T) {
   997  		err := appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("test")}, &TestResourceTreeServer{ctx: adminCtx})
   998  		assert.NoError(t, err)
   999  		err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("test")}, &TestResourceTreeServer{ctx: noRoleCtx})
  1000  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1001  		err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("does-not-exist")}, &TestResourceTreeServer{ctx: adminCtx})
  1002  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1003  		err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: pointer.String("does-not-exist"), Project: pointer.String("test")}, &TestResourceTreeServer{ctx: adminCtx})
  1004  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
  1005  	})
  1006  
  1007  	t.Run("PodLogs", func(t *testing.T) {
  1008  		err := appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("test")}, &TestPodLogsServer{ctx: adminCtx})
  1009  		assert.NoError(t, err)
  1010  		err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("test")}, &TestPodLogsServer{ctx: noRoleCtx})
  1011  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1012  		err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("does-not-exist")}, &TestPodLogsServer{ctx: adminCtx})
  1013  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1014  		err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: pointer.String("does-not-exist"), Project: pointer.String("test")}, &TestPodLogsServer{ctx: adminCtx})
  1015  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
  1016  	})
  1017  
  1018  	t.Run("ListLinks", func(t *testing.T) {
  1019  		_, err := appServer.ListLinks(adminCtx, &application.ListAppLinksRequest{Name: pointer.String("test")})
  1020  		assert.NoError(t, err)
  1021  		_, err = appServer.ListLinks(noRoleCtx, &application.ListAppLinksRequest{Name: pointer.String("test")})
  1022  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1023  		_, err = appServer.ListLinks(adminCtx, &application.ListAppLinksRequest{Name: pointer.String("does-not-exist")})
  1024  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1025  		_, err = appServer.ListLinks(adminCtx, &application.ListAppLinksRequest{Name: pointer.String("does-not-exist"), Project: pointer.String("test")})
  1026  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
  1027  	})
  1028  
  1029  	t.Run("ListResourceLinks", func(t *testing.T) {
  1030  		_, err := appServer.ListResourceLinks(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
  1031  		assert.NoError(t, err)
  1032  		_, err = appServer.ListResourceLinks(noRoleCtx, &application.ApplicationResourceRequest{Name: pointer.String("test"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
  1033  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1034  		_, err = appServer.ListResourceLinks(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("does-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test")})
  1035  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1036  		_, err = appServer.ListResourceLinks(adminCtx, &application.ApplicationResourceRequest{Name: pointer.String("does-not-exist"), ResourceName: pointer.String("test"), Group: pointer.String("apps"), Kind: pointer.String("Deployment"), Namespace: pointer.String("test"), Project: pointer.String("test")})
  1037  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
  1038  	})
  1039  
  1040  	// Do this last so other stuff doesn't fail.
  1041  	t.Run("Delete", func(t *testing.T) {
  1042  		_, err := appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: pointer.String("test")})
  1043  		assert.NoError(t, err)
  1044  		_, err = appServer.Delete(noRoleCtx, &application.ApplicationDeleteRequest{Name: pointer.String("test")})
  1045  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1046  		_, err = appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: pointer.String("doest-not-exist")})
  1047  		assert.Equal(t, permissionDeniedErr.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1048  		_, err = appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: pointer.String("doest-not-exist"), Project: pointer.String("test")})
  1049  		assert.Equal(t, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", err.Error(), "when the request specifies a project, we can return the standard k8s error message")
  1050  	})
  1051  }
  1052  
  1053  // setSyncRunningOperationState simulates starting a sync operation on the given app.
  1054  func setSyncRunningOperationState(t *testing.T, appServer *Server) {
  1055  	appIf := appServer.appclientset.ArgoprojV1alpha1().Applications("default")
  1056  	app, err := appIf.Get(context.Background(), "test", metav1.GetOptions{})
  1057  	require.NoError(t, err)
  1058  	// This sets the status that would be set by the controller usually.
  1059  	app.Status.OperationState = &appsv1.OperationState{Phase: synccommon.OperationRunning, Operation: appsv1.Operation{Sync: &appsv1.SyncOperation{}}}
  1060  	_, err = appIf.Update(context.Background(), app, metav1.UpdateOptions{})
  1061  	require.NoError(t, err)
  1062  }
  1063  
  1064  // unsetSyncRunningOperationState simulates finishing a sync operation on the given app.
  1065  func unsetSyncRunningOperationState(t *testing.T, appServer *Server) {
  1066  	appIf := appServer.appclientset.ArgoprojV1alpha1().Applications("default")
  1067  	app, err := appIf.Get(context.Background(), "test", metav1.GetOptions{})
  1068  	require.NoError(t, err)
  1069  	app.Operation = nil
  1070  	app.Status.OperationState = nil
  1071  	_, err = appIf.Update(context.Background(), app, metav1.UpdateOptions{})
  1072  	require.NoError(t, err)
  1073  }
  1074  
  1075  func TestListAppsInNamespaceWithLabels(t *testing.T) {
  1076  	appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
  1077  		app.Name = "App1"
  1078  		app.ObjectMeta.Namespace = "test-namespace"
  1079  		app.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
  1080  	}), newTestApp(func(app *appsv1.Application) {
  1081  		app.Name = "App2"
  1082  		app.ObjectMeta.Namespace = "test-namespace"
  1083  		app.SetLabels(map[string]string{"key1": "value2"})
  1084  	}), newTestApp(func(app *appsv1.Application) {
  1085  		app.Name = "App3"
  1086  		app.ObjectMeta.Namespace = "test-namespace"
  1087  		app.SetLabels(map[string]string{"key1": "value3"})
  1088  	}))
  1089  	appServer.ns = "test-namespace"
  1090  	appQuery := application.ApplicationQuery{}
  1091  	namespace := "test-namespace"
  1092  	appQuery.AppNamespace = &namespace
  1093  	testListAppsWithLabels(t, appQuery, appServer)
  1094  }
  1095  
  1096  func TestListAppsInDefaultNSWithLabels(t *testing.T) {
  1097  	appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
  1098  		app.Name = "App1"
  1099  		app.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
  1100  	}), newTestApp(func(app *appsv1.Application) {
  1101  		app.Name = "App2"
  1102  		app.SetLabels(map[string]string{"key1": "value2"})
  1103  	}), newTestApp(func(app *appsv1.Application) {
  1104  		app.Name = "App3"
  1105  		app.SetLabels(map[string]string{"key1": "value3"})
  1106  	}))
  1107  	appQuery := application.ApplicationQuery{}
  1108  	testListAppsWithLabels(t, appQuery, appServer)
  1109  }
  1110  
  1111  func testListAppsWithLabels(t *testing.T, appQuery application.ApplicationQuery, appServer *Server) {
  1112  	validTests := []struct {
  1113  		testName       string
  1114  		label          string
  1115  		expectedResult []string
  1116  	}{
  1117  		{testName: "Equality based filtering using '=' operator",
  1118  			label:          "key1=value1",
  1119  			expectedResult: []string{"App1"}},
  1120  		{testName: "Equality based filtering using '==' operator",
  1121  			label:          "key1==value1",
  1122  			expectedResult: []string{"App1"}},
  1123  		{testName: "Equality based filtering using '!=' operator",
  1124  			label:          "key1!=value1",
  1125  			expectedResult: []string{"App2", "App3"}},
  1126  		{testName: "Set based filtering using 'in' operator",
  1127  			label:          "key1 in (value1, value3)",
  1128  			expectedResult: []string{"App1", "App3"}},
  1129  		{testName: "Set based filtering using 'notin' operator",
  1130  			label:          "key1 notin (value1, value3)",
  1131  			expectedResult: []string{"App2"}},
  1132  		{testName: "Set based filtering using 'exists' operator",
  1133  			label:          "key1",
  1134  			expectedResult: []string{"App1", "App2", "App3"}},
  1135  		{testName: "Set based filtering using 'not exists' operator",
  1136  			label:          "!key2",
  1137  			expectedResult: []string{"App2", "App3"}},
  1138  	}
  1139  	// test valid scenarios
  1140  	for _, validTest := range validTests {
  1141  		t.Run(validTest.testName, func(t *testing.T) {
  1142  			appQuery.Selector = &validTest.label
  1143  			res, err := appServer.List(context.Background(), &appQuery)
  1144  			assert.NoError(t, err)
  1145  			apps := []string{}
  1146  			for i := range res.Items {
  1147  				apps = append(apps, res.Items[i].Name)
  1148  			}
  1149  			assert.Equal(t, validTest.expectedResult, apps)
  1150  		})
  1151  	}
  1152  
  1153  	invalidTests := []struct {
  1154  		testName    string
  1155  		label       string
  1156  		errorMesage string
  1157  	}{
  1158  		{testName: "Set based filtering using '>' operator",
  1159  			label:       "key1>value1",
  1160  			errorMesage: "error parsing the selector"},
  1161  		{testName: "Set based filtering using '<' operator",
  1162  			label:       "key1<value1",
  1163  			errorMesage: "error parsing the selector"},
  1164  	}
  1165  	// test invalid scenarios
  1166  	for _, invalidTest := range invalidTests {
  1167  		t.Run(invalidTest.testName, func(t *testing.T) {
  1168  			appQuery.Selector = &invalidTest.label
  1169  			_, err := appServer.List(context.Background(), &appQuery)
  1170  			assert.ErrorContains(t, err, invalidTest.errorMesage)
  1171  		})
  1172  	}
  1173  }
  1174  
  1175  func TestListAppWithProjects(t *testing.T) {
  1176  	appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
  1177  		app.Name = "App1"
  1178  		app.Spec.Project = "test-project1"
  1179  	}), newTestApp(func(app *appsv1.Application) {
  1180  		app.Name = "App2"
  1181  		app.Spec.Project = "test-project2"
  1182  	}), newTestApp(func(app *appsv1.Application) {
  1183  		app.Name = "App3"
  1184  		app.Spec.Project = "test-project3"
  1185  	}))
  1186  
  1187  	t.Run("List all apps", func(t *testing.T) {
  1188  		appQuery := application.ApplicationQuery{}
  1189  		appList, err := appServer.List(context.Background(), &appQuery)
  1190  		assert.NoError(t, err)
  1191  		assert.Len(t, appList.Items, 3)
  1192  	})
  1193  
  1194  	t.Run("List apps with projects filter set", func(t *testing.T) {
  1195  		appQuery := application.ApplicationQuery{Projects: []string{"test-project1"}}
  1196  		appList, err := appServer.List(context.Background(), &appQuery)
  1197  		assert.NoError(t, err)
  1198  		assert.Len(t, appList.Items, 1)
  1199  		for _, app := range appList.Items {
  1200  			assert.Equal(t, "test-project1", app.Spec.Project)
  1201  		}
  1202  	})
  1203  
  1204  	t.Run("List apps with project filter set (legacy field)", func(t *testing.T) {
  1205  		appQuery := application.ApplicationQuery{Project: []string{"test-project1"}}
  1206  		appList, err := appServer.List(context.Background(), &appQuery)
  1207  		assert.NoError(t, err)
  1208  		assert.Len(t, appList.Items, 1)
  1209  		for _, app := range appList.Items {
  1210  			assert.Equal(t, "test-project1", app.Spec.Project)
  1211  		}
  1212  	})
  1213  
  1214  	t.Run("List apps with both projects and project filter set", func(t *testing.T) {
  1215  		// If the older field is present, we should use it instead of the newer field.
  1216  		appQuery := application.ApplicationQuery{Project: []string{"test-project1"}, Projects: []string{"test-project2"}}
  1217  		appList, err := appServer.List(context.Background(), &appQuery)
  1218  		assert.NoError(t, err)
  1219  		assert.Len(t, appList.Items, 1)
  1220  		for _, app := range appList.Items {
  1221  			assert.Equal(t, "test-project1", app.Spec.Project)
  1222  		}
  1223  	})
  1224  }
  1225  
  1226  func TestListApps(t *testing.T) {
  1227  	appServer := newTestAppServer(t, newTestApp(func(app *appsv1.Application) {
  1228  		app.Name = "bcd"
  1229  	}), newTestApp(func(app *appsv1.Application) {
  1230  		app.Name = "abc"
  1231  	}), newTestApp(func(app *appsv1.Application) {
  1232  		app.Name = "def"
  1233  	}))
  1234  
  1235  	res, err := appServer.List(context.Background(), &application.ApplicationQuery{})
  1236  	assert.NoError(t, err)
  1237  	var names []string
  1238  	for i := range res.Items {
  1239  		names = append(names, res.Items[i].Name)
  1240  	}
  1241  	assert.Equal(t, []string{"abc", "bcd", "def"}, names)
  1242  }
  1243  
  1244  func TestCoupleAppsListApps(t *testing.T) {
  1245  	var objects []runtime.Object
  1246  	ctx := context.Background()
  1247  
  1248  	var groups []string
  1249  	for i := 0; i < 50; i++ {
  1250  		groups = append(groups, fmt.Sprintf("group-%d", i))
  1251  	}
  1252  	// nolint:staticcheck
  1253  	ctx = context.WithValue(ctx, "claims", &jwt.MapClaims{"groups": groups})
  1254  	for projectId := 0; projectId < 100; projectId++ {
  1255  		projectName := fmt.Sprintf("proj-%d", projectId)
  1256  		for appId := 0; appId < 100; appId++ {
  1257  			objects = append(objects, newTestApp(func(app *appsv1.Application) {
  1258  				app.Name = fmt.Sprintf("app-%d-%d", projectId, appId)
  1259  				app.Spec.Project = projectName
  1260  			}))
  1261  		}
  1262  	}
  1263  
  1264  	f := func(enf *rbac.Enforcer) {
  1265  		policy := `
  1266  p, role:test, applications, *, proj-10/*, allow
  1267  g, group-45, role:test
  1268  p, role:test2, applications, *, proj-15/*, allow
  1269  g, group-47, role:test2
  1270  p, role:test3, applications, *, proj-20/*, allow
  1271  g, group-49, role:test3
  1272  `
  1273  		_ = enf.SetUserPolicy(policy)
  1274  	}
  1275  	appServer := newTestAppServerWithEnforcerConfigure(f, t, objects...)
  1276  
  1277  	res, err := appServer.List(ctx, &application.ApplicationQuery{})
  1278  
  1279  	assert.NoError(t, err)
  1280  	var names []string
  1281  	for i := range res.Items {
  1282  		names = append(names, res.Items[i].Name)
  1283  	}
  1284  	assert.Equal(t, 300, len(names))
  1285  }
  1286  
  1287  func generateTestApp(num int) []*appsv1.Application {
  1288  	apps := []*appsv1.Application{}
  1289  	for i := 0; i < num; i++ {
  1290  		apps = append(apps, newTestApp(func(app *appsv1.Application) {
  1291  			app.Name = fmt.Sprintf("test-app%.6d", i)
  1292  		}))
  1293  	}
  1294  
  1295  	return apps
  1296  }
  1297  
  1298  func BenchmarkListMuchApps(b *testing.B) {
  1299  	// 10000 apps
  1300  	apps := generateTestApp(10000)
  1301  	obj := make([]runtime.Object, len(apps))
  1302  	for i, v := range apps {
  1303  		obj[i] = v
  1304  	}
  1305  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1306  
  1307  	b.ResetTimer()
  1308  	for n := 0; n < b.N; n++ {
  1309  		_, err := appServer.List(context.Background(), &application.ApplicationQuery{})
  1310  		if err != nil {
  1311  			break
  1312  		}
  1313  	}
  1314  }
  1315  
  1316  func BenchmarkListSomeApps(b *testing.B) {
  1317  	// 500 apps
  1318  	apps := generateTestApp(500)
  1319  	obj := make([]runtime.Object, len(apps))
  1320  	for i, v := range apps {
  1321  		obj[i] = v
  1322  	}
  1323  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1324  
  1325  	b.ResetTimer()
  1326  	for n := 0; n < b.N; n++ {
  1327  		_, err := appServer.List(context.Background(), &application.ApplicationQuery{})
  1328  		if err != nil {
  1329  			break
  1330  		}
  1331  	}
  1332  }
  1333  
  1334  func BenchmarkListFewApps(b *testing.B) {
  1335  	// 10 apps
  1336  	apps := generateTestApp(10)
  1337  	obj := make([]runtime.Object, len(apps))
  1338  	for i, v := range apps {
  1339  		obj[i] = v
  1340  	}
  1341  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1342  
  1343  	b.ResetTimer()
  1344  	for n := 0; n < b.N; n++ {
  1345  		_, err := appServer.List(context.Background(), &application.ApplicationQuery{})
  1346  		if err != nil {
  1347  			break
  1348  		}
  1349  	}
  1350  }
  1351  
  1352  func strToPtr(v string) *string {
  1353  	return &v
  1354  }
  1355  
  1356  func BenchmarkListMuchAppsWithName(b *testing.B) {
  1357  	// 10000 apps
  1358  	appsMuch := generateTestApp(10000)
  1359  	obj := make([]runtime.Object, len(appsMuch))
  1360  	for i, v := range appsMuch {
  1361  		obj[i] = v
  1362  	}
  1363  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1364  
  1365  	b.ResetTimer()
  1366  	for n := 0; n < b.N; n++ {
  1367  		app := &application.ApplicationQuery{Name: strToPtr("test-app000099")}
  1368  		_, err := appServer.List(context.Background(), app)
  1369  		if err != nil {
  1370  			break
  1371  		}
  1372  	}
  1373  }
  1374  
  1375  func BenchmarkListMuchAppsWithProjects(b *testing.B) {
  1376  	// 10000 apps
  1377  	appsMuch := generateTestApp(10000)
  1378  	appsMuch[999].Spec.Project = "test-project1"
  1379  	appsMuch[1999].Spec.Project = "test-project2"
  1380  	obj := make([]runtime.Object, len(appsMuch))
  1381  	for i, v := range appsMuch {
  1382  		obj[i] = v
  1383  	}
  1384  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1385  
  1386  	b.ResetTimer()
  1387  	for n := 0; n < b.N; n++ {
  1388  		app := &application.ApplicationQuery{Project: []string{"test-project1", "test-project2"}}
  1389  		_, err := appServer.List(context.Background(), app)
  1390  		if err != nil {
  1391  			break
  1392  		}
  1393  	}
  1394  }
  1395  
  1396  func BenchmarkListMuchAppsWithRepo(b *testing.B) {
  1397  	// 10000 apps
  1398  	appsMuch := generateTestApp(10000)
  1399  	appsMuch[999].Spec.Source.RepoURL = "https://some-fake-source"
  1400  	obj := make([]runtime.Object, len(appsMuch))
  1401  	for i, v := range appsMuch {
  1402  		obj[i] = v
  1403  	}
  1404  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1405  
  1406  	b.ResetTimer()
  1407  	for n := 0; n < b.N; n++ {
  1408  		app := &application.ApplicationQuery{Repo: strToPtr("https://some-fake-source")}
  1409  		_, err := appServer.List(context.Background(), app)
  1410  		if err != nil {
  1411  			break
  1412  		}
  1413  	}
  1414  }
  1415  
  1416  func TestCreateApp(t *testing.T) {
  1417  	testApp := newTestApp()
  1418  	appServer := newTestAppServer(t)
  1419  	testApp.Spec.Project = ""
  1420  	createReq := application.ApplicationCreateRequest{
  1421  		Application: testApp,
  1422  	}
  1423  	app, err := appServer.Create(context.Background(), &createReq)
  1424  	assert.NoError(t, err)
  1425  	assert.NotNil(t, app)
  1426  	assert.NotNil(t, app.Spec)
  1427  	assert.Equal(t, app.Spec.Project, "default")
  1428  }
  1429  
  1430  func TestCreateAppWithDestName(t *testing.T) {
  1431  	appServer := newTestAppServer(t)
  1432  	testApp := newTestAppWithDestName()
  1433  	createReq := application.ApplicationCreateRequest{
  1434  		Application: testApp,
  1435  	}
  1436  	app, err := appServer.Create(context.Background(), &createReq)
  1437  	assert.NoError(t, err)
  1438  	assert.NotNil(t, app)
  1439  	assert.Equal(t, app.Spec.Destination.Server, "https://cluster-api.example.com")
  1440  }
  1441  
  1442  // TestCreateAppWithOperation tests that an application created with an operation is created with the operation removed.
  1443  // Avoids regressions of https://github.com/argoproj/argo-cd/security/advisories/GHSA-g623-jcgg-mhmm
  1444  func TestCreateAppWithOperation(t *testing.T) {
  1445  	appServer := newTestAppServer(t)
  1446  	testApp := newTestAppWithDestName()
  1447  	testApp.Operation = &appsv1.Operation{
  1448  		Sync: &appsv1.SyncOperation{
  1449  			Manifests: []string{
  1450  				"test",
  1451  			},
  1452  		},
  1453  	}
  1454  	createReq := application.ApplicationCreateRequest{
  1455  		Application: testApp,
  1456  	}
  1457  	app, err := appServer.Create(context.Background(), &createReq)
  1458  	require.NoError(t, err)
  1459  	require.NotNil(t, app)
  1460  	assert.Nil(t, app.Operation)
  1461  }
  1462  
  1463  func TestUpdateApp(t *testing.T) {
  1464  	testApp := newTestApp()
  1465  	appServer := newTestAppServer(t, testApp)
  1466  	testApp.Spec.Project = ""
  1467  	app, err := appServer.Update(context.Background(), &application.ApplicationUpdateRequest{
  1468  		Application: testApp,
  1469  	})
  1470  	assert.Nil(t, err)
  1471  	assert.Equal(t, app.Spec.Project, "default")
  1472  }
  1473  
  1474  func TestUpdateAppSpec(t *testing.T) {
  1475  	testApp := newTestApp()
  1476  	appServer := newTestAppServer(t, testApp)
  1477  	testApp.Spec.Project = ""
  1478  	spec, err := appServer.UpdateSpec(context.Background(), &application.ApplicationUpdateSpecRequest{
  1479  		Name: &testApp.Name,
  1480  		Spec: &testApp.Spec,
  1481  	})
  1482  	assert.NoError(t, err)
  1483  	assert.Equal(t, "default", spec.Project)
  1484  	app, err := appServer.Get(context.Background(), &application.ApplicationQuery{Name: &testApp.Name})
  1485  	assert.NoError(t, err)
  1486  	assert.Equal(t, "default", app.Spec.Project)
  1487  }
  1488  
  1489  func TestDeleteApp(t *testing.T) {
  1490  	ctx := context.Background()
  1491  	appServer := newTestAppServer(t)
  1492  	createReq := application.ApplicationCreateRequest{
  1493  		Application: newTestApp(),
  1494  	}
  1495  	app, err := appServer.Create(ctx, &createReq)
  1496  	assert.Nil(t, err)
  1497  
  1498  	app, err = appServer.Get(ctx, &application.ApplicationQuery{Name: &app.Name})
  1499  	assert.Nil(t, err)
  1500  	assert.NotNil(t, app)
  1501  
  1502  	fakeAppCs := appServer.appclientset.(*apps.Clientset)
  1503  	// this removes the default */* reactor so we can set our own patch/delete reactor
  1504  	fakeAppCs.ReactionChain = nil
  1505  	patched := false
  1506  	deleted := false
  1507  	fakeAppCs.AddReactor("patch", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1508  		patched = true
  1509  		return true, nil, nil
  1510  	})
  1511  	fakeAppCs.AddReactor("delete", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1512  		deleted = true
  1513  		return true, nil, nil
  1514  	})
  1515  	fakeAppCs.AddReactor("get", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1516  		return true, &appsv1.Application{Spec: appsv1.ApplicationSpec{Source: &appsv1.ApplicationSource{}}}, nil
  1517  	})
  1518  	appServer.appclientset = fakeAppCs
  1519  
  1520  	trueVar := true
  1521  	_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &trueVar})
  1522  	assert.Nil(t, err)
  1523  	assert.True(t, patched)
  1524  	assert.True(t, deleted)
  1525  
  1526  	// now call delete with cascade=false. patch should not be called
  1527  	falseVar := false
  1528  	patched = false
  1529  	deleted = false
  1530  	_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &falseVar})
  1531  	assert.Nil(t, err)
  1532  	assert.False(t, patched)
  1533  	assert.True(t, deleted)
  1534  
  1535  	patched = false
  1536  	deleted = false
  1537  	revertValues := func() {
  1538  		patched = false
  1539  		deleted = false
  1540  	}
  1541  
  1542  	t.Run("Delete with background propagation policy", func(t *testing.T) {
  1543  		policy := backgroundPropagationPolicy
  1544  		_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, PropagationPolicy: &policy})
  1545  		assert.Nil(t, err)
  1546  		assert.True(t, patched)
  1547  		assert.True(t, deleted)
  1548  		t.Cleanup(revertValues)
  1549  	})
  1550  
  1551  	t.Run("Delete with cascade disabled and background propagation policy", func(t *testing.T) {
  1552  		policy := backgroundPropagationPolicy
  1553  		_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &falseVar, PropagationPolicy: &policy})
  1554  		assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = cannot set propagation policy when cascading is disabled")
  1555  		assert.False(t, patched)
  1556  		assert.False(t, deleted)
  1557  		t.Cleanup(revertValues)
  1558  	})
  1559  
  1560  	t.Run("Delete with invalid propagation policy", func(t *testing.T) {
  1561  		invalidPolicy := "invalid"
  1562  		_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &trueVar, PropagationPolicy: &invalidPolicy})
  1563  		assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = invalid propagation policy: invalid")
  1564  		assert.False(t, patched)
  1565  		assert.False(t, deleted)
  1566  		t.Cleanup(revertValues)
  1567  	})
  1568  
  1569  	t.Run("Delete with foreground propagation policy", func(t *testing.T) {
  1570  		policy := foregroundPropagationPolicy
  1571  		_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &trueVar, PropagationPolicy: &policy})
  1572  		assert.Nil(t, err)
  1573  		assert.True(t, patched)
  1574  		assert.True(t, deleted)
  1575  		t.Cleanup(revertValues)
  1576  	})
  1577  }
  1578  
  1579  func TestSyncAndTerminate(t *testing.T) {
  1580  	ctx := context.Background()
  1581  	appServer := newTestAppServer(t)
  1582  	testApp := newTestApp()
  1583  	testApp.Spec.Source.RepoURL = "https://github.com/argoproj/argo-cd.git"
  1584  	createReq := application.ApplicationCreateRequest{
  1585  		Application: testApp,
  1586  	}
  1587  	app, err := appServer.Create(ctx, &createReq)
  1588  	assert.Nil(t, err)
  1589  	app, err = appServer.Sync(ctx, &application.ApplicationSyncRequest{Name: &app.Name})
  1590  	assert.Nil(t, err)
  1591  	assert.NotNil(t, app)
  1592  	assert.NotNil(t, app.Operation)
  1593  
  1594  	events, err := appServer.kubeclientset.CoreV1().Events(appServer.ns).List(context.Background(), metav1.ListOptions{})
  1595  	assert.Nil(t, err)
  1596  	event := events.Items[1]
  1597  
  1598  	assert.Regexp(t, ".*initiated sync to HEAD \\([0-9A-Fa-f]{40}\\).*", event.Message)
  1599  
  1600  	// set status.operationState to pretend that an operation has started by controller
  1601  	app.Status.OperationState = &appsv1.OperationState{
  1602  		Operation: *app.Operation,
  1603  		Phase:     synccommon.OperationRunning,
  1604  		StartedAt: metav1.NewTime(time.Now()),
  1605  	}
  1606  	_, err = appServer.appclientset.ArgoprojV1alpha1().Applications(appServer.ns).Update(context.Background(), app, metav1.UpdateOptions{})
  1607  	assert.Nil(t, err)
  1608  
  1609  	resp, err := appServer.TerminateOperation(ctx, &application.OperationTerminateRequest{Name: &app.Name})
  1610  	assert.Nil(t, err)
  1611  	assert.NotNil(t, resp)
  1612  
  1613  	app, err = appServer.Get(ctx, &application.ApplicationQuery{Name: &app.Name})
  1614  	assert.Nil(t, err)
  1615  	assert.NotNil(t, app)
  1616  	assert.Equal(t, synccommon.OperationTerminating, app.Status.OperationState.Phase)
  1617  }
  1618  
  1619  func TestSyncHelm(t *testing.T) {
  1620  	ctx := context.Background()
  1621  	appServer := newTestAppServer(t)
  1622  	testApp := newTestApp()
  1623  	testApp.Spec.Source.RepoURL = "https://argoproj.github.io/argo-helm"
  1624  	testApp.Spec.Source.Path = ""
  1625  	testApp.Spec.Source.Chart = "argo-cd"
  1626  	testApp.Spec.Source.TargetRevision = "0.7.*"
  1627  
  1628  	appServer.repoClientset = &mocks.Clientset{RepoServerServiceClient: fakeRepoServerClient(true)}
  1629  
  1630  	app, err := appServer.Create(ctx, &application.ApplicationCreateRequest{Application: testApp})
  1631  	assert.NoError(t, err)
  1632  
  1633  	app, err = appServer.Sync(ctx, &application.ApplicationSyncRequest{Name: &app.Name})
  1634  	assert.NoError(t, err)
  1635  	assert.NotNil(t, app)
  1636  	assert.NotNil(t, app.Operation)
  1637  
  1638  	events, err := appServer.kubeclientset.CoreV1().Events(appServer.ns).List(context.Background(), metav1.ListOptions{})
  1639  	assert.NoError(t, err)
  1640  	assert.Equal(t, "Unknown user initiated sync to 0.7.* (0.7.2)", events.Items[1].Message)
  1641  }
  1642  
  1643  func TestSyncGit(t *testing.T) {
  1644  	ctx := context.Background()
  1645  	appServer := newTestAppServer(t)
  1646  	testApp := newTestApp()
  1647  	testApp.Spec.Source.RepoURL = "https://github.com/org/test"
  1648  	testApp.Spec.Source.Path = "deploy"
  1649  	testApp.Spec.Source.TargetRevision = "0.7.*"
  1650  	app, err := appServer.Create(ctx, &application.ApplicationCreateRequest{Application: testApp})
  1651  	assert.NoError(t, err)
  1652  	syncReq := &application.ApplicationSyncRequest{
  1653  		Name: &app.Name,
  1654  		Manifests: []string{
  1655  			`apiVersion: v1
  1656  			kind: ServiceAccount
  1657  			metadata:
  1658  			  name: test
  1659  			  namespace: test`,
  1660  		},
  1661  	}
  1662  	app, err = appServer.Sync(ctx, syncReq)
  1663  	assert.NoError(t, err)
  1664  	assert.NotNil(t, app)
  1665  	assert.NotNil(t, app.Operation)
  1666  	events, err := appServer.kubeclientset.CoreV1().Events(appServer.ns).List(context.Background(), metav1.ListOptions{})
  1667  	assert.NoError(t, err)
  1668  	assert.Equal(t, "Unknown user initiated sync locally", events.Items[1].Message)
  1669  }
  1670  
  1671  func TestRollbackApp(t *testing.T) {
  1672  	testApp := newTestApp()
  1673  	testApp.Status.History = []appsv1.RevisionHistory{{
  1674  		ID:       1,
  1675  		Revision: "abc",
  1676  		Source:   *testApp.Spec.Source.DeepCopy(),
  1677  	}}
  1678  	appServer := newTestAppServer(t, testApp)
  1679  
  1680  	updatedApp, err := appServer.Rollback(context.Background(), &application.ApplicationRollbackRequest{
  1681  		Name: &testApp.Name,
  1682  		Id:   pointer.Int64(1),
  1683  	})
  1684  
  1685  	assert.Nil(t, err)
  1686  
  1687  	assert.NotNil(t, updatedApp.Operation)
  1688  	assert.NotNil(t, updatedApp.Operation.Sync)
  1689  	assert.NotNil(t, updatedApp.Operation.Sync.Source)
  1690  	assert.Equal(t, "abc", updatedApp.Operation.Sync.Revision)
  1691  }
  1692  
  1693  func TestUpdateAppProject(t *testing.T) {
  1694  	testApp := newTestApp()
  1695  	ctx := context.Background()
  1696  	// nolint:staticcheck
  1697  	ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
  1698  	appServer := newTestAppServer(t, testApp)
  1699  	appServer.enf.SetDefaultRole("")
  1700  
  1701  	t.Run("update without changing project", func(t *testing.T) {
  1702  		_ = appServer.enf.SetBuiltinPolicy(`p, admin, applications, update, default/test-app, allow`)
  1703  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  1704  		assert.NoError(t, err)
  1705  	})
  1706  
  1707  	t.Run("cannot update to another project", func(t *testing.T) {
  1708  		testApp.Spec.Project = "my-proj"
  1709  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  1710  		assert.Equal(t, status.Code(err), codes.PermissionDenied)
  1711  	})
  1712  
  1713  	t.Run("cannot change projects without create privileges", func(t *testing.T) {
  1714  		_ = appServer.enf.SetBuiltinPolicy(`
  1715  p, admin, applications, update, default/test-app, allow
  1716  p, admin, applications, update, my-proj/test-app, allow
  1717  `)
  1718  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  1719  		statusErr := grpc.UnwrapGRPCStatus(err)
  1720  		assert.NotNil(t, statusErr)
  1721  		assert.Equal(t, codes.PermissionDenied, statusErr.Code())
  1722  	})
  1723  
  1724  	t.Run("cannot change projects without update privileges in new project", func(t *testing.T) {
  1725  		_ = appServer.enf.SetBuiltinPolicy(`
  1726  p, admin, applications, update, default/test-app, allow
  1727  p, admin, applications, create, my-proj/test-app, allow
  1728  `)
  1729  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  1730  		assert.Equal(t, codes.PermissionDenied, status.Code(err))
  1731  	})
  1732  
  1733  	t.Run("cannot change projects without update privileges in old project", func(t *testing.T) {
  1734  		_ = appServer.enf.SetBuiltinPolicy(`
  1735  p, admin, applications, create, my-proj/test-app, allow
  1736  p, admin, applications, update, my-proj/test-app, allow
  1737  `)
  1738  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  1739  		statusErr := grpc.UnwrapGRPCStatus(err)
  1740  		assert.NotNil(t, statusErr)
  1741  		assert.Equal(t, codes.PermissionDenied, statusErr.Code())
  1742  	})
  1743  
  1744  	t.Run("can update project with proper permissions", func(t *testing.T) {
  1745  		// Verify can update project with proper permissions
  1746  		_ = appServer.enf.SetBuiltinPolicy(`
  1747  p, admin, applications, update, default/test-app, allow
  1748  p, admin, applications, create, my-proj/test-app, allow
  1749  p, admin, applications, update, my-proj/test-app, allow
  1750  `)
  1751  		updatedApp, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  1752  		assert.NoError(t, err)
  1753  		assert.Equal(t, "my-proj", updatedApp.Spec.Project)
  1754  	})
  1755  }
  1756  
  1757  func TestAppJsonPatch(t *testing.T) {
  1758  	testApp := newTestAppWithAnnotations()
  1759  	ctx := context.Background()
  1760  	// nolint:staticcheck
  1761  	ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
  1762  	appServer := newTestAppServer(t, testApp)
  1763  	appServer.enf.SetDefaultRole("")
  1764  
  1765  	app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: pointer.String("garbage")})
  1766  	assert.Error(t, err)
  1767  	assert.Nil(t, app)
  1768  
  1769  	app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: pointer.String("[]")})
  1770  	assert.NoError(t, err)
  1771  	assert.NotNil(t, app)
  1772  
  1773  	app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: pointer.String(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
  1774  	assert.NoError(t, err)
  1775  	assert.Equal(t, "foo", app.Spec.Source.Path)
  1776  
  1777  	app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: pointer.String(`[{"op": "remove", "path": "/metadata/annotations/test.annotation"}]`)})
  1778  	assert.NoError(t, err)
  1779  	assert.NotContains(t, app.Annotations, "test.annotation")
  1780  }
  1781  
  1782  func TestAppMergePatch(t *testing.T) {
  1783  	testApp := newTestApp()
  1784  	ctx := context.Background()
  1785  	// nolint:staticcheck
  1786  	ctx = context.WithValue(ctx, "claims", &jwt.StandardClaims{Subject: "admin"})
  1787  	appServer := newTestAppServer(t, testApp)
  1788  	appServer.enf.SetDefaultRole("")
  1789  
  1790  	app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{
  1791  		Name: &testApp.Name, Patch: pointer.String(`{"spec": { "source": { "path": "foo" } }}`), PatchType: pointer.String("merge")})
  1792  	assert.NoError(t, err)
  1793  	assert.Equal(t, "foo", app.Spec.Source.Path)
  1794  }
  1795  
  1796  func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
  1797  	t.Run("Active", func(t *testing.T) {
  1798  		testApp := newTestApp()
  1799  		testApp.Spec.Project = "proj-maint"
  1800  		appServer := newTestAppServer(t, testApp)
  1801  
  1802  		active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
  1803  		assert.NoError(t, err)
  1804  		assert.Equal(t, 1, len(active.ActiveWindows))
  1805  	})
  1806  	t.Run("Inactive", func(t *testing.T) {
  1807  		testApp := newTestApp()
  1808  		testApp.Spec.Project = "default"
  1809  		appServer := newTestAppServer(t, testApp)
  1810  
  1811  		active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
  1812  		assert.NoError(t, err)
  1813  		assert.Equal(t, 0, len(active.ActiveWindows))
  1814  	})
  1815  	t.Run("ProjectDoesNotExist", func(t *testing.T) {
  1816  		testApp := newTestApp()
  1817  		testApp.Spec.Project = "none"
  1818  		appServer := newTestAppServer(t, testApp)
  1819  
  1820  		active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
  1821  		assert.Contains(t, err.Error(), "not exist")
  1822  		assert.Nil(t, active)
  1823  	})
  1824  }
  1825  
  1826  func TestGetCachedAppState(t *testing.T) {
  1827  	testApp := newTestApp()
  1828  	testApp.ObjectMeta.ResourceVersion = "1"
  1829  	testApp.Spec.Project = "test-proj"
  1830  	testProj := &appsv1.AppProject{
  1831  		ObjectMeta: metav1.ObjectMeta{
  1832  			Name:      "test-proj",
  1833  			Namespace: testNamespace,
  1834  		},
  1835  	}
  1836  	appServer := newTestAppServer(t, testApp, testProj)
  1837  	fakeClientSet := appServer.appclientset.(*apps.Clientset)
  1838  	fakeClientSet.AddReactor("get", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1839  		return true, &appsv1.Application{Spec: appsv1.ApplicationSpec{Source: &appsv1.ApplicationSource{}}}, nil
  1840  	})
  1841  	t.Run("NoError", func(t *testing.T) {
  1842  		err := appServer.getCachedAppState(context.Background(), testApp, func() error {
  1843  			return nil
  1844  		})
  1845  		assert.NoError(t, err)
  1846  	})
  1847  	t.Run("CacheMissErrorTriggersRefresh", func(t *testing.T) {
  1848  		retryCount := 0
  1849  		patched := false
  1850  		watcher := watch.NewFakeWithChanSize(1, true)
  1851  
  1852  		// Configure fakeClientSet within lock, before requesting cached app state, to avoid data race
  1853  		{
  1854  			fakeClientSet.Lock()
  1855  			fakeClientSet.ReactionChain = nil
  1856  			fakeClientSet.WatchReactionChain = nil
  1857  			fakeClientSet.AddReactor("patch", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1858  				patched = true
  1859  				updated := testApp.DeepCopy()
  1860  				updated.ResourceVersion = "2"
  1861  				appServer.appBroadcaster.OnUpdate(testApp, updated)
  1862  				return true, testApp, nil
  1863  			})
  1864  			fakeClientSet.AddReactor("get", "applications", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1865  				return true, &appsv1.Application{Spec: appsv1.ApplicationSpec{Source: &appsv1.ApplicationSource{}}}, nil
  1866  			})
  1867  			fakeClientSet.Unlock()
  1868  			fakeClientSet.AddWatchReactor("applications", func(action kubetesting.Action) (handled bool, ret watch.Interface, err error) {
  1869  				return true, watcher, nil
  1870  			})
  1871  		}
  1872  
  1873  		err := appServer.getCachedAppState(context.Background(), testApp, func() error {
  1874  			res := cache.ErrCacheMiss
  1875  			if retryCount == 1 {
  1876  				res = nil
  1877  			}
  1878  			retryCount++
  1879  			return res
  1880  		})
  1881  		assert.Equal(t, nil, err)
  1882  		assert.Equal(t, 2, retryCount)
  1883  		assert.True(t, patched)
  1884  	})
  1885  
  1886  	t.Run("NonCacheErrorDoesNotTriggerRefresh", func(t *testing.T) {
  1887  		randomError := coreerrors.New("random error")
  1888  		err := appServer.getCachedAppState(context.Background(), testApp, func() error {
  1889  			return randomError
  1890  		})
  1891  		assert.Equal(t, randomError, err)
  1892  	})
  1893  }
  1894  
  1895  func TestSplitStatusPatch(t *testing.T) {
  1896  	specPatch := `{"spec":{"aaa":"bbb"}}`
  1897  	statusPatch := `{"status":{"ccc":"ddd"}}`
  1898  	{
  1899  		nonStatus, status, err := splitStatusPatch([]byte(specPatch))
  1900  		assert.NoError(t, err)
  1901  		assert.Equal(t, specPatch, string(nonStatus))
  1902  		assert.Nil(t, status)
  1903  	}
  1904  	{
  1905  		nonStatus, status, err := splitStatusPatch([]byte(statusPatch))
  1906  		assert.NoError(t, err)
  1907  		assert.Nil(t, nonStatus)
  1908  		assert.Equal(t, statusPatch, string(status))
  1909  	}
  1910  	{
  1911  		bothPatch := `{"spec":{"aaa":"bbb"},"status":{"ccc":"ddd"}}`
  1912  		nonStatus, status, err := splitStatusPatch([]byte(bothPatch))
  1913  		assert.NoError(t, err)
  1914  		assert.Equal(t, specPatch, string(nonStatus))
  1915  		assert.Equal(t, statusPatch, string(status))
  1916  	}
  1917  	{
  1918  		otherFields := `{"operation":{"eee":"fff"},"spec":{"aaa":"bbb"},"status":{"ccc":"ddd"}}`
  1919  		nonStatus, status, err := splitStatusPatch([]byte(otherFields))
  1920  		assert.NoError(t, err)
  1921  		assert.Equal(t, `{"operation":{"eee":"fff"},"spec":{"aaa":"bbb"}}`, string(nonStatus))
  1922  		assert.Equal(t, statusPatch, string(status))
  1923  	}
  1924  }
  1925  
  1926  func TestLogsGetSelectedPod(t *testing.T) {
  1927  	deployment := appsv1.ResourceRef{Group: "", Version: "v1", Kind: "Deployment", Name: "deployment", UID: "1"}
  1928  	rs := appsv1.ResourceRef{Group: "", Version: "v1", Kind: "ReplicaSet", Name: "rs", UID: "2"}
  1929  	podRS := appsv1.ResourceRef{Group: "", Version: "v1", Kind: "Pod", Name: "podrs", UID: "3"}
  1930  	pod := appsv1.ResourceRef{Group: "", Version: "v1", Kind: "Pod", Name: "pod", UID: "4"}
  1931  	treeNodes := []appsv1.ResourceNode{
  1932  		{ResourceRef: deployment, ParentRefs: nil},
  1933  		{ResourceRef: rs, ParentRefs: []appsv1.ResourceRef{deployment}},
  1934  		{ResourceRef: podRS, ParentRefs: []appsv1.ResourceRef{rs}},
  1935  		{ResourceRef: pod, ParentRefs: nil},
  1936  	}
  1937  	appName := "appName"
  1938  
  1939  	t.Run("GetAllPods", func(t *testing.T) {
  1940  		podQuery := application.ApplicationPodLogsQuery{
  1941  			Name: &appName,
  1942  		}
  1943  		pods := getSelectedPods(treeNodes, &podQuery)
  1944  		assert.Equal(t, 2, len(pods))
  1945  	})
  1946  
  1947  	t.Run("GetRSPods", func(t *testing.T) {
  1948  		group := ""
  1949  		kind := "ReplicaSet"
  1950  		name := "rs"
  1951  		podQuery := application.ApplicationPodLogsQuery{
  1952  			Name:         &appName,
  1953  			Group:        &group,
  1954  			Kind:         &kind,
  1955  			ResourceName: &name,
  1956  		}
  1957  		pods := getSelectedPods(treeNodes, &podQuery)
  1958  		assert.Equal(t, 1, len(pods))
  1959  	})
  1960  
  1961  	t.Run("GetDeploymentPods", func(t *testing.T) {
  1962  		group := ""
  1963  		kind := "Deployment"
  1964  		name := "deployment"
  1965  		podQuery := application.ApplicationPodLogsQuery{
  1966  			Name:         &appName,
  1967  			Group:        &group,
  1968  			Kind:         &kind,
  1969  			ResourceName: &name,
  1970  		}
  1971  		pods := getSelectedPods(treeNodes, &podQuery)
  1972  		assert.Equal(t, 1, len(pods))
  1973  	})
  1974  
  1975  	t.Run("NoMatchingPods", func(t *testing.T) {
  1976  		group := ""
  1977  		kind := "Service"
  1978  		name := "service"
  1979  		podQuery := application.ApplicationPodLogsQuery{
  1980  			Name:         &appName,
  1981  			Group:        &group,
  1982  			Kind:         &kind,
  1983  			ResourceName: &name,
  1984  		}
  1985  		pods := getSelectedPods(treeNodes, &podQuery)
  1986  		assert.Equal(t, 0, len(pods))
  1987  	})
  1988  }
  1989  
  1990  // refreshAnnotationRemover runs an infinite loop until it detects and removes refresh annotation or given context is done
  1991  func refreshAnnotationRemover(t *testing.T, ctx context.Context, patched *int32, appServer *Server, appName string, ch chan string) {
  1992  	for ctx.Err() == nil {
  1993  		aName, appNs := argo.ParseFromQualifiedName(appName, appServer.ns)
  1994  		a, err := appServer.appLister.Applications(appNs).Get(aName)
  1995  		require.NoError(t, err)
  1996  		a = a.DeepCopy()
  1997  		if a.GetAnnotations() != nil && a.GetAnnotations()[appsv1.AnnotationKeyRefresh] != "" {
  1998  			a.SetAnnotations(map[string]string{})
  1999  			a.SetResourceVersion("999")
  2000  			_, err = appServer.appclientset.ArgoprojV1alpha1().Applications(a.Namespace).Update(
  2001  				context.Background(), a, metav1.UpdateOptions{})
  2002  			require.NoError(t, err)
  2003  			atomic.AddInt32(patched, 1)
  2004  			ch <- ""
  2005  		}
  2006  		time.Sleep(100 * time.Millisecond)
  2007  	}
  2008  }
  2009  
  2010  func TestGetAppRefresh_NormalRefresh(t *testing.T) {
  2011  	ctx, cancel := context.WithCancel(context.Background())
  2012  	defer cancel()
  2013  	testApp := newTestApp()
  2014  	testApp.ObjectMeta.ResourceVersion = "1"
  2015  	appServer := newTestAppServer(t, testApp)
  2016  
  2017  	var patched int32
  2018  
  2019  	ch := make(chan string, 1)
  2020  
  2021  	go refreshAnnotationRemover(t, ctx, &patched, appServer, testApp.Name, ch)
  2022  
  2023  	_, err := appServer.Get(context.Background(), &application.ApplicationQuery{
  2024  		Name:    &testApp.Name,
  2025  		Refresh: pointer.String(string(appsv1.RefreshTypeNormal)),
  2026  	})
  2027  	assert.NoError(t, err)
  2028  
  2029  	select {
  2030  	case <-ch:
  2031  		assert.Equal(t, atomic.LoadInt32(&patched), int32(1))
  2032  	case <-time.After(10 * time.Second):
  2033  		assert.Fail(t, "Out of time ( 10 seconds )")
  2034  	}
  2035  
  2036  }
  2037  
  2038  func TestGetAppRefresh_HardRefresh(t *testing.T) {
  2039  	ctx, cancel := context.WithCancel(context.Background())
  2040  	defer cancel()
  2041  	testApp := newTestApp()
  2042  	testApp.ObjectMeta.ResourceVersion = "1"
  2043  	appServer := newTestAppServer(t, testApp)
  2044  
  2045  	var getAppDetailsQuery *apiclient.RepoServerAppDetailsQuery
  2046  	mockRepoServiceClient := mocks.RepoServerServiceClient{}
  2047  	mockRepoServiceClient.On("GetAppDetails", mock.Anything, mock.MatchedBy(func(q *apiclient.RepoServerAppDetailsQuery) bool {
  2048  		getAppDetailsQuery = q
  2049  		return true
  2050  	})).Return(&apiclient.RepoAppDetailsResponse{}, nil)
  2051  	appServer.repoClientset = &mocks.Clientset{RepoServerServiceClient: &mockRepoServiceClient}
  2052  
  2053  	var patched int32
  2054  
  2055  	ch := make(chan string, 1)
  2056  
  2057  	go refreshAnnotationRemover(t, ctx, &patched, appServer, testApp.Name, ch)
  2058  
  2059  	_, err := appServer.Get(context.Background(), &application.ApplicationQuery{
  2060  		Name:    &testApp.Name,
  2061  		Refresh: pointer.String(string(appsv1.RefreshTypeHard)),
  2062  	})
  2063  	assert.NoError(t, err)
  2064  	require.NotNil(t, getAppDetailsQuery)
  2065  	assert.True(t, getAppDetailsQuery.NoCache)
  2066  	assert.Equal(t, testApp.Spec.Source, getAppDetailsQuery.Source)
  2067  
  2068  	assert.NoError(t, err)
  2069  	select {
  2070  	case <-ch:
  2071  		assert.Equal(t, atomic.LoadInt32(&patched), int32(1))
  2072  	case <-time.After(10 * time.Second):
  2073  		assert.Fail(t, "Out of time ( 10 seconds )")
  2074  	}
  2075  }
  2076  
  2077  func TestInferResourcesStatusHealth(t *testing.T) {
  2078  	cacheClient := cacheutil.NewCache(cacheutil.NewInMemoryCache(1 * time.Hour))
  2079  
  2080  	testApp := newTestApp()
  2081  	testApp.Status.ResourceHealthSource = appsv1.ResourceHealthLocationAppTree
  2082  	testApp.Status.Resources = []appsv1.ResourceStatus{{
  2083  		Group:     "apps",
  2084  		Kind:      "Deployment",
  2085  		Name:      "guestbook",
  2086  		Namespace: "default",
  2087  	}, {
  2088  		Group:     "apps",
  2089  		Kind:      "StatefulSet",
  2090  		Name:      "guestbook-stateful",
  2091  		Namespace: "default",
  2092  	}}
  2093  	appServer := newTestAppServer(t, testApp)
  2094  	appStateCache := appstate.NewCache(cacheClient, time.Minute)
  2095  	err := appStateCache.SetAppResourcesTree(testApp.Name, &appsv1.ApplicationTree{Nodes: []appsv1.ResourceNode{{
  2096  		ResourceRef: appsv1.ResourceRef{
  2097  			Group:     "apps",
  2098  			Kind:      "Deployment",
  2099  			Name:      "guestbook",
  2100  			Namespace: "default",
  2101  		},
  2102  		Health: &appsv1.HealthStatus{
  2103  			Status: health.HealthStatusDegraded,
  2104  		},
  2105  	}}})
  2106  
  2107  	require.NoError(t, err)
  2108  
  2109  	appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute, time.Minute)
  2110  
  2111  	appServer.inferResourcesStatusHealth(testApp)
  2112  
  2113  	assert.Equal(t, health.HealthStatusDegraded, testApp.Status.Resources[0].Health.Status)
  2114  	assert.Nil(t, testApp.Status.Resources[1].Health)
  2115  }
  2116  
  2117  func TestRunNewStyleResourceAction(t *testing.T) {
  2118  	cacheClient := cacheutil.NewCache(cacheutil.NewInMemoryCache(1 * time.Hour))
  2119  
  2120  	group := "batch"
  2121  	kind := "CronJob"
  2122  	version := "v1"
  2123  	resourceName := "my-cron-job"
  2124  	namespace := testNamespace
  2125  	action := "create-job"
  2126  	uid := "1"
  2127  
  2128  	resources := []appsv1.ResourceStatus{{
  2129  		Group:     group,
  2130  		Kind:      kind,
  2131  		Name:      resourceName,
  2132  		Namespace: testNamespace,
  2133  		Version:   version,
  2134  	}}
  2135  
  2136  	appStateCache := appstate.NewCache(cacheClient, time.Minute)
  2137  
  2138  	nodes := []appsv1.ResourceNode{{
  2139  		ResourceRef: appsv1.ResourceRef{
  2140  			Group:     group,
  2141  			Kind:      kind,
  2142  			Version:   version,
  2143  			Name:      resourceName,
  2144  			Namespace: testNamespace,
  2145  			UID:       uid,
  2146  		},
  2147  	}}
  2148  
  2149  	createJobDenyingProj := &appsv1.AppProject{
  2150  		ObjectMeta: metav1.ObjectMeta{Name: "createJobDenyingProj", Namespace: "default"},
  2151  		Spec: appsv1.AppProjectSpec{
  2152  			SourceRepos:                []string{"*"},
  2153  			Destinations:               []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2154  			NamespaceResourceWhitelist: []metav1.GroupKind{{Group: "never", Kind: "mind"}},
  2155  		},
  2156  	}
  2157  
  2158  	cronJob := k8sbatchv1.CronJob{
  2159  		TypeMeta: metav1.TypeMeta{
  2160  			APIVersion: "batch/v1",
  2161  			Kind:       "CronJob",
  2162  		},
  2163  		ObjectMeta: metav1.ObjectMeta{
  2164  			Name:      "my-cron-job",
  2165  			Namespace: testNamespace,
  2166  			Labels: map[string]string{
  2167  				"some": "label",
  2168  			},
  2169  		},
  2170  		Spec: k8sbatchv1.CronJobSpec{
  2171  			Schedule: "* * * * *",
  2172  			JobTemplate: k8sbatchv1.JobTemplateSpec{
  2173  				Spec: k8sbatchv1.JobSpec{
  2174  					Template: corev1.PodTemplateSpec{
  2175  						Spec: corev1.PodSpec{
  2176  							Containers: []corev1.Container{
  2177  								{
  2178  									Name:            "hello",
  2179  									Image:           "busybox:1.28",
  2180  									ImagePullPolicy: "IfNotPresent",
  2181  									Command:         []string{"/bin/sh", "-c", "date; echo Hello from the Kubernetes cluster"},
  2182  								},
  2183  							},
  2184  							RestartPolicy: corev1.RestartPolicyOnFailure,
  2185  						},
  2186  					},
  2187  				},
  2188  			},
  2189  		},
  2190  	}
  2191  
  2192  	t.Run("CreateOperationNotPermitted", func(t *testing.T) {
  2193  		testApp := newTestApp()
  2194  		testApp.Spec.Project = "createJobDenyingProj"
  2195  		testApp.Status.ResourceHealthSource = appsv1.ResourceHealthLocationAppTree
  2196  		testApp.Status.Resources = resources
  2197  
  2198  		appServer := newTestAppServer(t, testApp, createJobDenyingProj, kube.MustToUnstructured(&cronJob))
  2199  		appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute, time.Minute)
  2200  
  2201  		err := appStateCache.SetAppResourcesTree(testApp.Name, &appsv1.ApplicationTree{Nodes: nodes})
  2202  		require.NoError(t, err)
  2203  
  2204  		appResponse, runErr := appServer.RunResourceAction(context.Background(), &application.ResourceActionRunRequest{
  2205  			Name:         &testApp.Name,
  2206  			Namespace:    &namespace,
  2207  			Action:       &action,
  2208  			AppNamespace: &testApp.Namespace,
  2209  			ResourceName: &resourceName,
  2210  			Version:      &version,
  2211  			Group:        &group,
  2212  			Kind:         &kind,
  2213  		})
  2214  
  2215  		assert.Contains(t, runErr.Error(), "is not permitted to manage")
  2216  		assert.Nil(t, appResponse)
  2217  	})
  2218  
  2219  	t.Run("CreateOperationPermitted", func(t *testing.T) {
  2220  		testApp := newTestApp()
  2221  		testApp.Status.ResourceHealthSource = appsv1.ResourceHealthLocationAppTree
  2222  		testApp.Status.Resources = resources
  2223  
  2224  		appServer := newTestAppServer(t, testApp, kube.MustToUnstructured(&cronJob))
  2225  		appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute, time.Minute)
  2226  
  2227  		err := appStateCache.SetAppResourcesTree(testApp.Name, &appsv1.ApplicationTree{Nodes: nodes})
  2228  		require.NoError(t, err)
  2229  
  2230  		appResponse, runErr := appServer.RunResourceAction(context.Background(), &application.ResourceActionRunRequest{
  2231  			Name:         &testApp.Name,
  2232  			Namespace:    &namespace,
  2233  			Action:       &action,
  2234  			AppNamespace: &testApp.Namespace,
  2235  			ResourceName: &resourceName,
  2236  			Version:      &version,
  2237  			Group:        &group,
  2238  			Kind:         &kind,
  2239  		})
  2240  
  2241  		require.NoError(t, runErr)
  2242  		assert.NotNil(t, appResponse)
  2243  	})
  2244  }
  2245  
  2246  func TestRunOldStyleResourceAction(t *testing.T) {
  2247  	cacheClient := cacheutil.NewCache(cacheutil.NewInMemoryCache(1 * time.Hour))
  2248  
  2249  	group := "apps"
  2250  	kind := "Deployment"
  2251  	version := "v1"
  2252  	resourceName := "nginx-deploy"
  2253  	namespace := testNamespace
  2254  	action := "pause"
  2255  	uid := "2"
  2256  
  2257  	resources := []appsv1.ResourceStatus{{
  2258  		Group:     group,
  2259  		Kind:      kind,
  2260  		Name:      resourceName,
  2261  		Namespace: testNamespace,
  2262  		Version:   version,
  2263  	}}
  2264  
  2265  	appStateCache := appstate.NewCache(cacheClient, time.Minute)
  2266  
  2267  	nodes := []appsv1.ResourceNode{{
  2268  		ResourceRef: appsv1.ResourceRef{
  2269  			Group:     group,
  2270  			Kind:      kind,
  2271  			Version:   version,
  2272  			Name:      resourceName,
  2273  			Namespace: testNamespace,
  2274  			UID:       uid,
  2275  		},
  2276  	}}
  2277  
  2278  	deployment := k8sappsv1.Deployment{
  2279  		TypeMeta: metav1.TypeMeta{
  2280  			APIVersion: "apps/v1",
  2281  			Kind:       "Deployment",
  2282  		},
  2283  		ObjectMeta: metav1.ObjectMeta{
  2284  			Name:      "nginx-deploy",
  2285  			Namespace: testNamespace,
  2286  		},
  2287  	}
  2288  
  2289  	t.Run("DefaultPatchOperation", func(t *testing.T) {
  2290  		testApp := newTestApp()
  2291  		testApp.Status.ResourceHealthSource = appsv1.ResourceHealthLocationAppTree
  2292  		testApp.Status.Resources = resources
  2293  
  2294  		// appServer := newTestAppServer(t, testApp, returnDeployment())
  2295  		appServer := newTestAppServer(t, testApp, kube.MustToUnstructured(&deployment))
  2296  		appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute, time.Minute)
  2297  
  2298  		err := appStateCache.SetAppResourcesTree(testApp.Name, &appsv1.ApplicationTree{Nodes: nodes})
  2299  		require.NoError(t, err)
  2300  
  2301  		appResponse, runErr := appServer.RunResourceAction(context.Background(), &application.ResourceActionRunRequest{
  2302  			Name:         &testApp.Name,
  2303  			Namespace:    &namespace,
  2304  			Action:       &action,
  2305  			AppNamespace: &testApp.Namespace,
  2306  			ResourceName: &resourceName,
  2307  			Version:      &version,
  2308  			Group:        &group,
  2309  			Kind:         &kind,
  2310  		})
  2311  
  2312  		require.NoError(t, runErr)
  2313  		assert.NotNil(t, appResponse)
  2314  	})
  2315  }
  2316  
  2317  func TestIsApplicationPermitted(t *testing.T) {
  2318  	t.Run("Incorrect project", func(t *testing.T) {
  2319  		testApp := newTestApp()
  2320  		appServer := newTestAppServer(t, testApp)
  2321  		projects := map[string]bool{"test-app": false}
  2322  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, "test", "default", projects, *testApp)
  2323  		assert.False(t, permitted)
  2324  	})
  2325  
  2326  	t.Run("Version is incorrect", func(t *testing.T) {
  2327  		testApp := newTestApp()
  2328  		appServer := newTestAppServer(t, testApp)
  2329  		minVersion := 100000
  2330  		testApp.ResourceVersion = strconv.Itoa(minVersion - 1)
  2331  		permitted := appServer.isApplicationPermitted(labels.Everything(), minVersion, nil, "test", "default", nil, *testApp)
  2332  		assert.False(t, permitted)
  2333  	})
  2334  
  2335  	t.Run("Application name is incorrect", func(t *testing.T) {
  2336  		testApp := newTestApp()
  2337  		appServer := newTestAppServer(t, testApp)
  2338  		appName := "test"
  2339  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, appName, "default", nil, *testApp)
  2340  		assert.False(t, permitted)
  2341  	})
  2342  
  2343  	t.Run("Application namespace is incorrect", func(t *testing.T) {
  2344  		testApp := newTestApp()
  2345  		appServer := newTestAppServer(t, testApp)
  2346  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, testApp.Name, "demo", nil, *testApp)
  2347  		assert.False(t, permitted)
  2348  	})
  2349  
  2350  	t.Run("Application is not part of enabled namespace", func(t *testing.T) {
  2351  		testApp := newTestApp()
  2352  		appServer := newTestAppServer(t, testApp)
  2353  		appServer.ns = "server-ns"
  2354  		appServer.enabledNamespaces = []string{"demo"}
  2355  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, testApp.Name, testApp.Namespace, nil, *testApp)
  2356  		assert.False(t, permitted)
  2357  	})
  2358  
  2359  	t.Run("Application is part of enabled namespace", func(t *testing.T) {
  2360  		testApp := newTestApp()
  2361  		appServer := newTestAppServer(t, testApp)
  2362  		appServer.ns = "server-ns"
  2363  		appServer.enabledNamespaces = []string{testApp.Namespace}
  2364  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, testApp.Name, testApp.Namespace, nil, *testApp)
  2365  		assert.True(t, permitted)
  2366  	})
  2367  }
  2368  
  2369  func TestAppNamespaceRestrictions(t *testing.T) {
  2370  	t.Run("List applications in controller namespace", func(t *testing.T) {
  2371  		testApp := newTestApp()
  2372  		appServer := newTestAppServer(t, testApp)
  2373  		apps, err := appServer.List(context.TODO(), &application.ApplicationQuery{})
  2374  		require.NoError(t, err)
  2375  		require.Len(t, apps.Items, 1)
  2376  	})
  2377  
  2378  	t.Run("List applications with non-allowed apps existing", func(t *testing.T) {
  2379  		testApp1 := newTestApp()
  2380  		testApp1.Namespace = "argocd-1"
  2381  		appServer := newTestAppServer(t, testApp1)
  2382  		apps, err := appServer.List(context.TODO(), &application.ApplicationQuery{})
  2383  		require.NoError(t, err)
  2384  		require.Len(t, apps.Items, 0)
  2385  	})
  2386  
  2387  	t.Run("List applications with non-allowed apps existing and explicit ns request", func(t *testing.T) {
  2388  		testApp1 := newTestApp()
  2389  		testApp2 := newTestApp()
  2390  		testApp2.Namespace = "argocd-1"
  2391  		appServer := newTestAppServer(t, testApp1, testApp2)
  2392  		apps, err := appServer.List(context.TODO(), &application.ApplicationQuery{AppNamespace: pointer.String("argocd-1")})
  2393  		require.NoError(t, err)
  2394  		require.Len(t, apps.Items, 0)
  2395  	})
  2396  
  2397  	t.Run("List applications with allowed apps in other namespaces", func(t *testing.T) {
  2398  		testApp1 := newTestApp()
  2399  		testApp1.Namespace = "argocd-1"
  2400  		appServer := newTestAppServer(t, testApp1)
  2401  		appServer.enabledNamespaces = []string{"argocd-1"}
  2402  		apps, err := appServer.List(context.TODO(), &application.ApplicationQuery{})
  2403  		require.NoError(t, err)
  2404  		require.Len(t, apps.Items, 1)
  2405  	})
  2406  
  2407  	t.Run("Get application in control plane namespace", func(t *testing.T) {
  2408  		testApp := newTestApp()
  2409  		appServer := newTestAppServer(t, testApp)
  2410  		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
  2411  			Name: pointer.String("test-app"),
  2412  		})
  2413  		require.NoError(t, err)
  2414  		assert.Equal(t, "test-app", app.GetName())
  2415  	})
  2416  	t.Run("Get application in other namespace when forbidden", func(t *testing.T) {
  2417  		testApp := newTestApp()
  2418  		testApp.Namespace = "argocd-1"
  2419  		appServer := newTestAppServer(t, testApp)
  2420  		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
  2421  			Name:         pointer.String("test-app"),
  2422  			AppNamespace: pointer.String("argocd-1"),
  2423  		})
  2424  		require.Error(t, err)
  2425  		require.ErrorContains(t, err, "permission denied")
  2426  		require.Nil(t, app)
  2427  	})
  2428  	t.Run("Get application in other namespace when allowed", func(t *testing.T) {
  2429  		testApp := newTestApp()
  2430  		testApp.Namespace = "argocd-1"
  2431  		testApp.Spec.Project = "other-ns"
  2432  		otherNsProj := &appsv1.AppProject{
  2433  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2434  			Spec: appsv1.AppProjectSpec{
  2435  				SourceRepos:      []string{"*"},
  2436  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2437  				SourceNamespaces: []string{"argocd-1"},
  2438  			},
  2439  		}
  2440  		appServer := newTestAppServer(t, testApp, otherNsProj)
  2441  		appServer.enabledNamespaces = []string{"argocd-1"}
  2442  		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
  2443  			Name:         pointer.String("test-app"),
  2444  			AppNamespace: pointer.String("argocd-1"),
  2445  		})
  2446  		require.NoError(t, err)
  2447  		require.NotNil(t, app)
  2448  		require.Equal(t, "argocd-1", app.Namespace)
  2449  		require.Equal(t, "test-app", app.Name)
  2450  	})
  2451  	t.Run("Get application in other namespace when project is not allowed", func(t *testing.T) {
  2452  		testApp := newTestApp()
  2453  		testApp.Namespace = "argocd-1"
  2454  		testApp.Spec.Project = "other-ns"
  2455  		otherNsProj := &appsv1.AppProject{
  2456  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2457  			Spec: appsv1.AppProjectSpec{
  2458  				SourceRepos:      []string{"*"},
  2459  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2460  				SourceNamespaces: []string{"argocd-2"},
  2461  			},
  2462  		}
  2463  		appServer := newTestAppServer(t, testApp, otherNsProj)
  2464  		appServer.enabledNamespaces = []string{"argocd-1"}
  2465  		app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
  2466  			Name:         pointer.String("test-app"),
  2467  			AppNamespace: pointer.String("argocd-1"),
  2468  		})
  2469  		require.Error(t, err)
  2470  		require.Nil(t, app)
  2471  		require.ErrorContains(t, err, "app is not allowed in project")
  2472  	})
  2473  	t.Run("Create application in other namespace when allowed", func(t *testing.T) {
  2474  		testApp := newTestApp()
  2475  		testApp.Namespace = "argocd-1"
  2476  		testApp.Spec.Project = "other-ns"
  2477  		otherNsProj := &appsv1.AppProject{
  2478  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2479  			Spec: appsv1.AppProjectSpec{
  2480  				SourceRepos:      []string{"*"},
  2481  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2482  				SourceNamespaces: []string{"argocd-1"},
  2483  			},
  2484  		}
  2485  		appServer := newTestAppServer(t, otherNsProj)
  2486  		appServer.enabledNamespaces = []string{"argocd-1"}
  2487  		app, err := appServer.Create(context.TODO(), &application.ApplicationCreateRequest{
  2488  			Application: testApp,
  2489  		})
  2490  		require.NoError(t, err)
  2491  		require.NotNil(t, app)
  2492  		assert.Equal(t, "test-app", app.Name)
  2493  		assert.Equal(t, "argocd-1", app.Namespace)
  2494  	})
  2495  
  2496  	t.Run("Create application in other namespace when not allowed by project", func(t *testing.T) {
  2497  		testApp := newTestApp()
  2498  		testApp.Namespace = "argocd-1"
  2499  		testApp.Spec.Project = "other-ns"
  2500  		otherNsProj := &appsv1.AppProject{
  2501  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2502  			Spec: appsv1.AppProjectSpec{
  2503  				SourceRepos:      []string{"*"},
  2504  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2505  				SourceNamespaces: []string{},
  2506  			},
  2507  		}
  2508  		appServer := newTestAppServer(t, otherNsProj)
  2509  		appServer.enabledNamespaces = []string{"argocd-1"}
  2510  		app, err := appServer.Create(context.TODO(), &application.ApplicationCreateRequest{
  2511  			Application: testApp,
  2512  		})
  2513  		require.Error(t, err)
  2514  		require.Nil(t, app)
  2515  		require.ErrorContains(t, err, "app is not allowed in project")
  2516  	})
  2517  
  2518  	t.Run("Create application in other namespace when not allowed by configuration", func(t *testing.T) {
  2519  		testApp := newTestApp()
  2520  		testApp.Namespace = "argocd-1"
  2521  		testApp.Spec.Project = "other-ns"
  2522  		otherNsProj := &appsv1.AppProject{
  2523  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2524  			Spec: appsv1.AppProjectSpec{
  2525  				SourceRepos:      []string{"*"},
  2526  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2527  				SourceNamespaces: []string{"argocd-1"},
  2528  			},
  2529  		}
  2530  		appServer := newTestAppServer(t, otherNsProj)
  2531  		appServer.enabledNamespaces = []string{"argocd-2"}
  2532  		app, err := appServer.Create(context.TODO(), &application.ApplicationCreateRequest{
  2533  			Application: testApp,
  2534  		})
  2535  		require.Error(t, err)
  2536  		require.Nil(t, app)
  2537  		require.ErrorContains(t, err, "namespace 'argocd-1' is not permitted")
  2538  	})
  2539  	t.Run("Get application sync window in other namespace when project is allowed", func(t *testing.T) {
  2540  		testApp := newTestApp()
  2541  		testApp.Namespace = "argocd-1"
  2542  		testApp.Spec.Project = "other-ns"
  2543  		otherNsProj := &appsv1.AppProject{
  2544  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2545  			Spec: appsv1.AppProjectSpec{
  2546  				SourceRepos:      []string{"*"},
  2547  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2548  				SourceNamespaces: []string{"argocd-1"},
  2549  			},
  2550  		}
  2551  		appServer := newTestAppServer(t, testApp, otherNsProj)
  2552  		appServer.enabledNamespaces = []string{"argocd-1"}
  2553  		active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
  2554  		assert.NoError(t, err)
  2555  		assert.Equal(t, 0, len(active.ActiveWindows))
  2556  	})
  2557  	t.Run("Get application sync window in other namespace when project is not allowed", func(t *testing.T) {
  2558  		testApp := newTestApp()
  2559  		testApp.Namespace = "argocd-1"
  2560  		testApp.Spec.Project = "other-ns"
  2561  		otherNsProj := &appsv1.AppProject{
  2562  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2563  			Spec: appsv1.AppProjectSpec{
  2564  				SourceRepos:      []string{"*"},
  2565  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2566  				SourceNamespaces: []string{"argocd-2"},
  2567  			},
  2568  		}
  2569  		appServer := newTestAppServer(t, testApp, otherNsProj)
  2570  		appServer.enabledNamespaces = []string{"argocd-1"}
  2571  		active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
  2572  		require.Error(t, err)
  2573  		require.Nil(t, active)
  2574  		require.ErrorContains(t, err, "app is not allowed in project")
  2575  	})
  2576  	t.Run("Get list of links in other namespace when project is not allowed", func(t *testing.T) {
  2577  		testApp := newTestApp()
  2578  		testApp.Namespace = "argocd-1"
  2579  		testApp.Spec.Project = "other-ns"
  2580  		otherNsProj := &appsv1.AppProject{
  2581  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2582  			Spec: appsv1.AppProjectSpec{
  2583  				SourceRepos:      []string{"*"},
  2584  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2585  				SourceNamespaces: []string{"argocd-2"},
  2586  			},
  2587  		}
  2588  		appServer := newTestAppServer(t, testApp, otherNsProj)
  2589  		appServer.enabledNamespaces = []string{"argocd-1"}
  2590  		links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
  2591  			Name:      pointer.String("test-app"),
  2592  			Namespace: pointer.String("argocd-1"),
  2593  		})
  2594  		require.Error(t, err)
  2595  		require.Nil(t, links)
  2596  		require.ErrorContains(t, err, "app is not allowed in project")
  2597  	})
  2598  	t.Run("Get list of links in other namespace when project is allowed", func(t *testing.T) {
  2599  		testApp := newTestApp()
  2600  		testApp.Namespace = "argocd-1"
  2601  		testApp.Spec.Project = "other-ns"
  2602  		otherNsProj := &appsv1.AppProject{
  2603  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  2604  			Spec: appsv1.AppProjectSpec{
  2605  				SourceRepos:      []string{"*"},
  2606  				Destinations:     []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2607  				SourceNamespaces: []string{"argocd-1"},
  2608  			},
  2609  		}
  2610  		appServer := newTestAppServer(t, testApp, otherNsProj)
  2611  		appServer.enabledNamespaces = []string{"argocd-1"}
  2612  		links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
  2613  			Name:      pointer.String("test-app"),
  2614  			Namespace: pointer.String("argocd-1"),
  2615  		})
  2616  		require.NoError(t, err)
  2617  		assert.Equal(t, 0, len(links.Items))
  2618  	})
  2619  }