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

     1  package application
     2  
     3  import (
     4  	"context"
     5  	stderrors "errors"
     6  	"fmt"
     7  	"io"
     8  	"slices"
     9  	"strconv"
    10  	"strings"
    11  	"sync/atomic"
    12  	"testing"
    13  	"time"
    14  
    15  	"k8s.io/apimachinery/pkg/labels"
    16  
    17  	"github.com/argoproj/gitops-engine/pkg/health"
    18  	synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
    19  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    20  	"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
    21  	"github.com/argoproj/pkg/v2/sync"
    22  	"github.com/golang-jwt/jwt/v5"
    23  	"github.com/stretchr/testify/assert"
    24  	"github.com/stretchr/testify/mock"
    25  	"github.com/stretchr/testify/require"
    26  	"google.golang.org/grpc/codes"
    27  	"google.golang.org/grpc/metadata"
    28  	"google.golang.org/grpc/status"
    29  	appsv1 "k8s.io/api/apps/v1"
    30  	k8sbatchv1 "k8s.io/api/batch/v1"
    31  	corev1 "k8s.io/api/core/v1"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"k8s.io/apimachinery/pkg/watch"
    37  	"k8s.io/client-go/kubernetes/fake"
    38  	"k8s.io/client-go/rest"
    39  	kubetesting "k8s.io/client-go/testing"
    40  	k8scache "k8s.io/client-go/tools/cache"
    41  	"k8s.io/utils/ptr"
    42  	"sigs.k8s.io/yaml"
    43  
    44  	"github.com/argoproj/argo-cd/v3/common"
    45  	"github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
    46  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    47  	apps "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake"
    48  	appinformer "github.com/argoproj/argo-cd/v3/pkg/client/informers/externalversions"
    49  	"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
    50  	"github.com/argoproj/argo-cd/v3/reposerver/apiclient/mocks"
    51  	servercache "github.com/argoproj/argo-cd/v3/server/cache"
    52  	"github.com/argoproj/argo-cd/v3/server/rbacpolicy"
    53  	"github.com/argoproj/argo-cd/v3/test"
    54  	"github.com/argoproj/argo-cd/v3/util/argo"
    55  	"github.com/argoproj/argo-cd/v3/util/assets"
    56  	"github.com/argoproj/argo-cd/v3/util/cache"
    57  	"github.com/argoproj/argo-cd/v3/util/cache/appstate"
    58  	"github.com/argoproj/argo-cd/v3/util/db"
    59  	"github.com/argoproj/argo-cd/v3/util/grpc"
    60  	"github.com/argoproj/argo-cd/v3/util/rbac"
    61  	"github.com/argoproj/argo-cd/v3/util/settings"
    62  )
    63  
    64  const (
    65  	testNamespace = "default"
    66  	fakeRepoURL   = "https://git.com/repo.git"
    67  )
    68  
    69  var testEnableEventList []string = argo.DefaultEnableEventList()
    70  
    71  type broadcasterMock struct {
    72  	objects []runtime.Object
    73  }
    74  
    75  func (b broadcasterMock) Subscribe(ch chan *v1alpha1.ApplicationWatchEvent, _ ...func(event *v1alpha1.ApplicationWatchEvent) bool) func() {
    76  	// Simulate the broadcaster notifying the subscriber of an application update.
    77  	// The second parameter to Subscribe is filters. For the purposes of tests, we ignore the filters. Future tests
    78  	// might require implementing those.
    79  	go func() {
    80  		for _, obj := range b.objects {
    81  			app, ok := obj.(*v1alpha1.Application)
    82  			if ok {
    83  				oldVersion, err := strconv.Atoi(app.ResourceVersion)
    84  				if err != nil {
    85  					oldVersion = 0
    86  				}
    87  				clonedApp := app.DeepCopy()
    88  				clonedApp.ResourceVersion = strconv.Itoa(oldVersion + 1)
    89  				ch <- &v1alpha1.ApplicationWatchEvent{Type: watch.Added, Application: *clonedApp}
    90  			}
    91  		}
    92  	}()
    93  	return func() {}
    94  }
    95  
    96  func (broadcasterMock) OnAdd(any, bool)   {}
    97  func (broadcasterMock) OnUpdate(any, any) {}
    98  func (broadcasterMock) OnDelete(any)      {}
    99  
   100  func fakeRepo() *v1alpha1.Repository {
   101  	return &v1alpha1.Repository{
   102  		Repo: fakeRepoURL,
   103  	}
   104  }
   105  
   106  func fakeCluster() *v1alpha1.Cluster {
   107  	return &v1alpha1.Cluster{
   108  		Server: "https://cluster-api.example.com",
   109  		Name:   "fake-cluster",
   110  		Config: v1alpha1.ClusterConfig{},
   111  	}
   112  }
   113  
   114  func fakeAppList() *apiclient.AppList {
   115  	return &apiclient.AppList{
   116  		Apps: map[string]string{
   117  			"some/path": "Ksonnet",
   118  		},
   119  	}
   120  }
   121  
   122  func fakeResolveRevisionResponse() *apiclient.ResolveRevisionResponse {
   123  	return &apiclient.ResolveRevisionResponse{
   124  		Revision:          "f9ba9e98119bf8c1176fbd65dbae26a71d044add",
   125  		AmbiguousRevision: "HEAD (f9ba9e98119bf8c1176fbd65dbae26a71d044add)",
   126  	}
   127  }
   128  
   129  func fakeResolveRevisionResponseHelm() *apiclient.ResolveRevisionResponse {
   130  	return &apiclient.ResolveRevisionResponse{
   131  		Revision:          "0.7.*",
   132  		AmbiguousRevision: "0.7.* (0.7.2)",
   133  	}
   134  }
   135  
   136  func fakeRepoServerClient(isHelm bool) *mocks.RepoServerServiceClient {
   137  	mockRepoServiceClient := mocks.RepoServerServiceClient{}
   138  	mockRepoServiceClient.On("GetProcessableApps", mock.Anything, mock.Anything).Return(fakeAppList(), nil)
   139  	mockRepoServiceClient.On("GenerateManifest", mock.Anything, mock.Anything).Return(&apiclient.ManifestResponse{}, nil)
   140  	mockRepoServiceClient.On("GetAppDetails", mock.Anything, mock.Anything).Return(&apiclient.RepoAppDetailsResponse{}, nil)
   141  	mockRepoServiceClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
   142  	mockRepoServiceClient.On("GetRevisionMetadata", mock.Anything, mock.Anything).Return(&v1alpha1.RevisionMetadata{}, nil)
   143  	mockWithFilesClient := &mocks.RepoServerService_GenerateManifestWithFilesClient{}
   144  	mockWithFilesClient.On("Send", mock.Anything).Return(nil)
   145  	mockWithFilesClient.On("CloseAndRecv").Return(&apiclient.ManifestResponse{}, nil)
   146  	mockRepoServiceClient.On("GenerateManifestWithFiles", mock.Anything, mock.Anything).Return(mockWithFilesClient, nil)
   147  	mockRepoServiceClient.On("GetRevisionChartDetails", mock.Anything, mock.Anything).Return(&v1alpha1.ChartDetails{}, nil)
   148  
   149  	if isHelm {
   150  		mockRepoServiceClient.On("ResolveRevision", mock.Anything, mock.Anything).Return(fakeResolveRevisionResponseHelm(), nil)
   151  	} else {
   152  		mockRepoServiceClient.On("ResolveRevision", mock.Anything, mock.Anything).Return(fakeResolveRevisionResponse(), nil)
   153  	}
   154  
   155  	return &mockRepoServiceClient
   156  }
   157  
   158  // return an ApplicationServiceServer which returns fake data
   159  func newTestAppServer(t *testing.T, objects ...runtime.Object) *Server {
   160  	t.Helper()
   161  	f := func(enf *rbac.Enforcer) {
   162  		_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
   163  		enf.SetDefaultRole("role:admin")
   164  	}
   165  	return newTestAppServerWithEnforcerConfigure(t, f, map[string]string{}, objects...)
   166  }
   167  
   168  func newTestAppServerWithEnforcerConfigure(t *testing.T, f func(*rbac.Enforcer), additionalConfig map[string]string, objects ...runtime.Object) *Server {
   169  	t.Helper()
   170  	kubeclientset := fake.NewClientset(&corev1.ConfigMap{
   171  		ObjectMeta: metav1.ObjectMeta{
   172  			Namespace: testNamespace,
   173  			Name:      "argocd-cm",
   174  			Labels: map[string]string{
   175  				"app.kubernetes.io/part-of": "argocd",
   176  			},
   177  		},
   178  		Data: additionalConfig,
   179  	}, &corev1.Secret{
   180  		ObjectMeta: metav1.ObjectMeta{
   181  			Name:      "argocd-secret",
   182  			Namespace: testNamespace,
   183  		},
   184  		Data: map[string][]byte{
   185  			"admin.password":   []byte("test"),
   186  			"server.secretkey": []byte("test"),
   187  		},
   188  	})
   189  	ctx := t.Context()
   190  	db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset)
   191  	_, err := db.CreateRepository(ctx, fakeRepo())
   192  	require.NoError(t, err)
   193  	_, err = db.CreateCluster(ctx, fakeCluster())
   194  	require.NoError(t, err)
   195  
   196  	mockRepoClient := &mocks.Clientset{RepoServerServiceClient: fakeRepoServerClient(false)}
   197  
   198  	defaultProj := &v1alpha1.AppProject{
   199  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"},
   200  		Spec: v1alpha1.AppProjectSpec{
   201  			SourceRepos:  []string{"*"},
   202  			Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   203  		},
   204  	}
   205  
   206  	myProj := &v1alpha1.AppProject{
   207  		ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"},
   208  		Spec: v1alpha1.AppProjectSpec{
   209  			SourceRepos:  []string{"*"},
   210  			Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   211  		},
   212  	}
   213  	projWithSyncWindows := &v1alpha1.AppProject{
   214  		ObjectMeta: metav1.ObjectMeta{Name: "proj-maint", Namespace: "default"},
   215  		Spec: v1alpha1.AppProjectSpec{
   216  			SourceRepos:  []string{"*"},
   217  			Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   218  			SyncWindows:  v1alpha1.SyncWindows{},
   219  		},
   220  	}
   221  	matchingWindow := &v1alpha1.SyncWindow{
   222  		Kind:         "allow",
   223  		Schedule:     "* * * * *",
   224  		Duration:     "1h",
   225  		Applications: []string{"test-app"},
   226  	}
   227  	projWithSyncWindows.Spec.SyncWindows = append(projWithSyncWindows.Spec.SyncWindows, matchingWindow)
   228  
   229  	objects = append(objects, defaultProj, myProj, projWithSyncWindows)
   230  
   231  	fakeAppsClientset := apps.NewSimpleClientset(objects...)
   232  	factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(_ *metav1.ListOptions) {}))
   233  	fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace)
   234  
   235  	enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
   236  	f(enforcer)
   237  	enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims)
   238  
   239  	settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace)
   240  
   241  	// populate the app informer with the fake objects
   242  	appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
   243  	// TODO(jessesuen): probably should return cancel function so tests can stop background informer
   244  	// ctx, cancel := context.WithCancel(t.Context())
   245  	go appInformer.Run(ctx.Done())
   246  	if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) {
   247  		panic("Timed out waiting for caches to sync")
   248  	}
   249  
   250  	projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
   251  	go projInformer.Run(ctx.Done())
   252  	if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) {
   253  		panic("Timed out waiting for caches to sync")
   254  	}
   255  
   256  	broadcaster := broadcasterMock{
   257  		objects: objects,
   258  	}
   259  
   260  	appStateCache := appstate.NewCache(cache.NewCache(cache.NewInMemoryCache(time.Hour)), time.Hour)
   261  	// pre-populate the app cache
   262  	for _, obj := range objects {
   263  		app, ok := obj.(*v1alpha1.Application)
   264  		if ok {
   265  			err := appStateCache.SetAppManagedResources(app.Name, []*v1alpha1.ResourceDiff{})
   266  			require.NoError(t, err)
   267  
   268  			// Pre-populate the resource tree based on the app's resources.
   269  			nodes := make([]v1alpha1.ResourceNode, len(app.Status.Resources))
   270  			for i, res := range app.Status.Resources {
   271  				nodes[i] = v1alpha1.ResourceNode{
   272  					ResourceRef: v1alpha1.ResourceRef{
   273  						Group:     res.Group,
   274  						Kind:      res.Kind,
   275  						Version:   res.Version,
   276  						Name:      res.Name,
   277  						Namespace: res.Namespace,
   278  						UID:       "fake",
   279  					},
   280  				}
   281  			}
   282  			err = appStateCache.SetAppResourcesTree(app.Name, &v1alpha1.ApplicationTree{
   283  				Nodes: nodes,
   284  			})
   285  			require.NoError(t, err)
   286  		}
   287  	}
   288  	appCache := servercache.NewCache(appStateCache, time.Hour, time.Hour)
   289  
   290  	kubectl := &kubetest.MockKubectlCmd{}
   291  	kubectl = kubectl.WithGetResourceFunc(func(_ context.Context, _ *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error) {
   292  		for _, obj := range objects {
   293  			if obj.GetObjectKind().GroupVersionKind().GroupKind() == gvk.GroupKind() {
   294  				if obj, ok := obj.(*unstructured.Unstructured); ok && obj.GetName() == name && obj.GetNamespace() == namespace {
   295  					return obj, nil
   296  				}
   297  			}
   298  		}
   299  		return nil, nil
   300  	})
   301  
   302  	server, _ := NewServer(
   303  		testNamespace,
   304  		kubeclientset,
   305  		fakeAppsClientset,
   306  		factory.Argoproj().V1alpha1().Applications().Lister(),
   307  		appInformer,
   308  		broadcaster,
   309  		mockRepoClient,
   310  		appCache,
   311  		kubectl,
   312  		db,
   313  		enforcer,
   314  		sync.NewKeyLock(),
   315  		settingsMgr,
   316  		projInformer,
   317  		[]string{},
   318  		testEnableEventList,
   319  		true,
   320  	)
   321  	return server.(*Server)
   322  }
   323  
   324  // return an ApplicationServiceServer which returns fake data
   325  func newTestAppServerWithBenchmark(b *testing.B, objects ...runtime.Object) *Server {
   326  	b.Helper()
   327  	f := func(enf *rbac.Enforcer) {
   328  		_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
   329  		enf.SetDefaultRole("role:admin")
   330  	}
   331  	return newTestAppServerWithEnforcerConfigureWithBenchmark(b, f, objects...)
   332  }
   333  
   334  func newTestAppServerWithEnforcerConfigureWithBenchmark(b *testing.B, f func(*rbac.Enforcer), objects ...runtime.Object) *Server {
   335  	b.Helper()
   336  	kubeclientset := fake.NewClientset(&corev1.ConfigMap{
   337  		ObjectMeta: metav1.ObjectMeta{
   338  			Namespace: testNamespace,
   339  			Name:      "argocd-cm",
   340  			Labels: map[string]string{
   341  				"app.kubernetes.io/part-of": "argocd",
   342  			},
   343  		},
   344  	}, &corev1.Secret{
   345  		ObjectMeta: metav1.ObjectMeta{
   346  			Name:      "argocd-secret",
   347  			Namespace: testNamespace,
   348  		},
   349  		Data: map[string][]byte{
   350  			"admin.password":   []byte("test"),
   351  			"server.secretkey": []byte("test"),
   352  		},
   353  	})
   354  	ctx := b.Context()
   355  	db := db.NewDB(testNamespace, settings.NewSettingsManager(ctx, kubeclientset, testNamespace), kubeclientset)
   356  	_, err := db.CreateRepository(ctx, fakeRepo())
   357  	require.NoError(b, err)
   358  	_, err = db.CreateCluster(ctx, fakeCluster())
   359  	require.NoError(b, err)
   360  
   361  	mockRepoClient := &mocks.Clientset{RepoServerServiceClient: fakeRepoServerClient(false)}
   362  
   363  	defaultProj := &v1alpha1.AppProject{
   364  		ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"},
   365  		Spec: v1alpha1.AppProjectSpec{
   366  			SourceRepos:  []string{"*"},
   367  			Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   368  		},
   369  	}
   370  	myProj := &v1alpha1.AppProject{
   371  		ObjectMeta: metav1.ObjectMeta{Name: "my-proj", Namespace: "default"},
   372  		Spec: v1alpha1.AppProjectSpec{
   373  			SourceRepos:  []string{"*"},
   374  			Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   375  		},
   376  	}
   377  	projWithSyncWindows := &v1alpha1.AppProject{
   378  		ObjectMeta: metav1.ObjectMeta{Name: "proj-maint", Namespace: "default"},
   379  		Spec: v1alpha1.AppProjectSpec{
   380  			SourceRepos:  []string{"*"},
   381  			Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
   382  			SyncWindows:  v1alpha1.SyncWindows{},
   383  		},
   384  	}
   385  	matchingWindow := &v1alpha1.SyncWindow{
   386  		Kind:         "allow",
   387  		Schedule:     "* * * * *",
   388  		Duration:     "1h",
   389  		Applications: []string{"test-app"},
   390  	}
   391  	projWithSyncWindows.Spec.SyncWindows = append(projWithSyncWindows.Spec.SyncWindows, matchingWindow)
   392  
   393  	objects = append(objects, defaultProj, myProj, projWithSyncWindows)
   394  
   395  	fakeAppsClientset := apps.NewSimpleClientset(objects...)
   396  	factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(_ *metav1.ListOptions) {}))
   397  	fakeProjLister := factory.Argoproj().V1alpha1().AppProjects().Lister().AppProjects(testNamespace)
   398  
   399  	enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
   400  	f(enforcer)
   401  	enforcer.SetClaimsEnforcerFunc(rbacpolicy.NewRBACPolicyEnforcer(enforcer, fakeProjLister).EnforceClaims)
   402  
   403  	settingsMgr := settings.NewSettingsManager(ctx, kubeclientset, testNamespace)
   404  
   405  	// populate the app informer with the fake objects
   406  	appInformer := factory.Argoproj().V1alpha1().Applications().Informer()
   407  
   408  	go appInformer.Run(ctx.Done())
   409  	if !k8scache.WaitForCacheSync(ctx.Done(), appInformer.HasSynced) {
   410  		panic("Timed out waiting for caches to sync")
   411  	}
   412  
   413  	projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
   414  	go projInformer.Run(ctx.Done())
   415  	if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) {
   416  		panic("Timed out waiting for caches to sync")
   417  	}
   418  
   419  	broadcaster := broadcasterMock{
   420  		objects: objects,
   421  	}
   422  
   423  	appStateCache := appstate.NewCache(cache.NewCache(cache.NewInMemoryCache(time.Hour)), time.Hour)
   424  	// pre-populate the app cache
   425  	for _, obj := range objects {
   426  		app, ok := obj.(*v1alpha1.Application)
   427  		if ok {
   428  			err := appStateCache.SetAppManagedResources(app.Name, []*v1alpha1.ResourceDiff{})
   429  			require.NoError(b, err)
   430  
   431  			// Pre-populate the resource tree based on the app's resources.
   432  			nodes := make([]v1alpha1.ResourceNode, len(app.Status.Resources))
   433  			for i, res := range app.Status.Resources {
   434  				nodes[i] = v1alpha1.ResourceNode{
   435  					ResourceRef: v1alpha1.ResourceRef{
   436  						Group:     res.Group,
   437  						Kind:      res.Kind,
   438  						Version:   res.Version,
   439  						Name:      res.Name,
   440  						Namespace: res.Namespace,
   441  						UID:       "fake",
   442  					},
   443  				}
   444  			}
   445  			err = appStateCache.SetAppResourcesTree(app.Name, &v1alpha1.ApplicationTree{
   446  				Nodes: nodes,
   447  			})
   448  			require.NoError(b, err)
   449  		}
   450  	}
   451  	appCache := servercache.NewCache(appStateCache, time.Hour, time.Hour)
   452  
   453  	kubectl := &kubetest.MockKubectlCmd{}
   454  	kubectl = kubectl.WithGetResourceFunc(func(_ context.Context, _ *rest.Config, gvk schema.GroupVersionKind, name string, namespace string) (*unstructured.Unstructured, error) {
   455  		for _, obj := range objects {
   456  			if obj.GetObjectKind().GroupVersionKind().GroupKind() == gvk.GroupKind() {
   457  				if obj, ok := obj.(*unstructured.Unstructured); ok && obj.GetName() == name && obj.GetNamespace() == namespace {
   458  					return obj, nil
   459  				}
   460  			}
   461  		}
   462  		return nil, nil
   463  	})
   464  
   465  	server, _ := NewServer(
   466  		testNamespace,
   467  		kubeclientset,
   468  		fakeAppsClientset,
   469  		factory.Argoproj().V1alpha1().Applications().Lister(),
   470  		appInformer,
   471  		broadcaster,
   472  		mockRepoClient,
   473  		appCache,
   474  		kubectl,
   475  		db,
   476  		enforcer,
   477  		sync.NewKeyLock(),
   478  		settingsMgr,
   479  		projInformer,
   480  		[]string{},
   481  		testEnableEventList,
   482  		true,
   483  	)
   484  	return server.(*Server)
   485  }
   486  
   487  const fakeApp = `
   488  apiVersion: argoproj.io/v1alpha1
   489  kind: Application
   490  metadata:
   491    name: test-app
   492    namespace: default
   493  spec:
   494    source:
   495      path: some/path
   496      repoURL: https://github.com/argoproj/argocd-example-apps.git
   497      targetRevision: HEAD
   498      ksonnet:
   499        environment: default
   500    destination:
   501      namespace: ` + test.FakeDestNamespace + `
   502      server: https://cluster-api.example.com
   503  `
   504  
   505  const fakeAppWithDestName = `
   506  apiVersion: argoproj.io/v1alpha1
   507  kind: Application
   508  metadata:
   509    name: test-app
   510    namespace: default
   511  spec:
   512    source:
   513      path: some/path
   514      repoURL: https://github.com/argoproj/argocd-example-apps.git
   515      targetRevision: HEAD
   516      ksonnet:
   517        environment: default
   518    destination:
   519      namespace: ` + test.FakeDestNamespace + `
   520      name: fake-cluster
   521  `
   522  
   523  const fakeAppWithAnnotations = `
   524  apiVersion: argoproj.io/v1alpha1
   525  kind: Application
   526  metadata:
   527    name: test-app
   528    namespace: default
   529    annotations:
   530      test.annotation: test
   531  spec:
   532    source:
   533      path: some/path
   534      repoURL: https://github.com/argoproj/argocd-example-apps.git
   535      targetRevision: HEAD
   536      ksonnet:
   537        environment: default
   538    destination:
   539      namespace: ` + test.FakeDestNamespace + `
   540      server: https://cluster-api.example.com
   541  `
   542  
   543  func newTestAppWithDestName(opts ...func(app *v1alpha1.Application)) *v1alpha1.Application {
   544  	return createTestApp(fakeAppWithDestName, opts...)
   545  }
   546  
   547  func newTestApp(opts ...func(app *v1alpha1.Application)) *v1alpha1.Application {
   548  	return createTestApp(fakeApp, opts...)
   549  }
   550  
   551  func newTestAppWithAnnotations(opts ...func(app *v1alpha1.Application)) *v1alpha1.Application {
   552  	return createTestApp(fakeAppWithAnnotations, opts...)
   553  }
   554  
   555  func createTestApp(testApp string, opts ...func(app *v1alpha1.Application)) *v1alpha1.Application {
   556  	var app v1alpha1.Application
   557  	err := yaml.Unmarshal([]byte(testApp), &app)
   558  	if err != nil {
   559  		panic(err)
   560  	}
   561  	for i := range opts {
   562  		opts[i](&app)
   563  	}
   564  	return &app
   565  }
   566  
   567  type TestServerStream struct {
   568  	ctx        context.Context
   569  	appName    string
   570  	headerSent bool
   571  	project    string
   572  }
   573  
   574  func (t *TestServerStream) SetHeader(metadata.MD) error {
   575  	return nil
   576  }
   577  
   578  func (t *TestServerStream) SendHeader(metadata.MD) error {
   579  	return nil
   580  }
   581  
   582  func (t *TestServerStream) SetTrailer(metadata.MD) {}
   583  
   584  func (t *TestServerStream) Context() context.Context {
   585  	return t.ctx
   586  }
   587  
   588  func (t *TestServerStream) SendMsg(_ any) error {
   589  	return nil
   590  }
   591  
   592  func (t *TestServerStream) RecvMsg(_ any) error {
   593  	return nil
   594  }
   595  
   596  func (t *TestServerStream) SendAndClose(_ *apiclient.ManifestResponse) error {
   597  	return nil
   598  }
   599  
   600  func (t *TestServerStream) Recv() (*application.ApplicationManifestQueryWithFilesWrapper, error) {
   601  	if !t.headerSent {
   602  		t.headerSent = true
   603  		return &application.ApplicationManifestQueryWithFilesWrapper{Part: &application.ApplicationManifestQueryWithFilesWrapper_Query{
   604  			Query: &application.ApplicationManifestQueryWithFiles{
   605  				Name:     ptr.To(t.appName),
   606  				Project:  ptr.To(t.project),
   607  				Checksum: ptr.To(""),
   608  			},
   609  		}}, nil
   610  	}
   611  	return nil, io.EOF
   612  }
   613  
   614  func (t *TestServerStream) ServerStream() TestServerStream {
   615  	return TestServerStream{}
   616  }
   617  
   618  type TestResourceTreeServer struct {
   619  	ctx context.Context
   620  }
   621  
   622  func (t *TestResourceTreeServer) Send(_ *v1alpha1.ApplicationTree) error {
   623  	return nil
   624  }
   625  
   626  func (t *TestResourceTreeServer) SetHeader(metadata.MD) error {
   627  	return nil
   628  }
   629  
   630  func (t *TestResourceTreeServer) SendHeader(metadata.MD) error {
   631  	return nil
   632  }
   633  
   634  func (t *TestResourceTreeServer) SetTrailer(metadata.MD) {}
   635  
   636  func (t *TestResourceTreeServer) Context() context.Context {
   637  	return t.ctx
   638  }
   639  
   640  func (t *TestResourceTreeServer) SendMsg(_ any) error {
   641  	return nil
   642  }
   643  
   644  func (t *TestResourceTreeServer) RecvMsg(_ any) error {
   645  	return nil
   646  }
   647  
   648  type TestPodLogsServer struct {
   649  	ctx context.Context
   650  }
   651  
   652  func (t *TestPodLogsServer) Send(_ *application.LogEntry) error {
   653  	return nil
   654  }
   655  
   656  func (t *TestPodLogsServer) SetHeader(metadata.MD) error {
   657  	return nil
   658  }
   659  
   660  func (t *TestPodLogsServer) SendHeader(metadata.MD) error {
   661  	return nil
   662  }
   663  
   664  func (t *TestPodLogsServer) SetTrailer(metadata.MD) {}
   665  
   666  func (t *TestPodLogsServer) Context() context.Context {
   667  	return t.ctx
   668  }
   669  
   670  func (t *TestPodLogsServer) SendMsg(_ any) error {
   671  	return nil
   672  }
   673  
   674  func (t *TestPodLogsServer) RecvMsg(_ any) error {
   675  	return nil
   676  }
   677  
   678  func TestNoAppEnumeration(t *testing.T) {
   679  	// This test ensures that malicious users can't infer the existence or non-existence of Applications by inspecting
   680  	// error messages. The errors for "app does not exist" must be the same as errors for "you aren't allowed to
   681  	// interact with this app."
   682  
   683  	// These tests are only important on API calls where the full app RBAC name (project, namespace, and name) is _not_
   684  	// known based on the query parameters. For example, the Create call cannot leak existence of Applications, because
   685  	// the Application's project, namespace, and name are all specified in the API call. The call can be rejected
   686  	// immediately if the user does not have access. But the Delete endpoint may be called with just the Application
   687  	// name. So we cannot return a different error message for "does not exist" and "you don't have delete permissions,"
   688  	// because the user could infer that the Application exists if they do not get the "does not exist" message. For
   689  	// endpoints that do not require the full RBAC name, we must return a generic "permission denied" for both "does not
   690  	// exist" and "no access."
   691  
   692  	f := func(enf *rbac.Enforcer) {
   693  		_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
   694  		enf.SetDefaultRole("role:none")
   695  	}
   696  	deployment := appsv1.Deployment{
   697  		TypeMeta: metav1.TypeMeta{
   698  			APIVersion: "apps/v1",
   699  			Kind:       "Deployment",
   700  		},
   701  		ObjectMeta: metav1.ObjectMeta{
   702  			Name:      "test",
   703  			Namespace: "test",
   704  		},
   705  	}
   706  	testApp := newTestApp(func(app *v1alpha1.Application) {
   707  		app.Name = "test"
   708  		app.Status.Resources = []v1alpha1.ResourceStatus{
   709  			{
   710  				Group:     deployment.GroupVersionKind().Group,
   711  				Kind:      deployment.GroupVersionKind().Kind,
   712  				Version:   deployment.GroupVersionKind().Version,
   713  				Name:      deployment.Name,
   714  				Namespace: deployment.Namespace,
   715  				Status:    "Synced",
   716  			},
   717  		}
   718  		app.Status.History = []v1alpha1.RevisionHistory{
   719  			{
   720  				ID: 0,
   721  				Source: v1alpha1.ApplicationSource{
   722  					TargetRevision: "something-old",
   723  				},
   724  			},
   725  		}
   726  	})
   727  	testHelmApp := newTestApp(func(app *v1alpha1.Application) {
   728  		app.Name = "test-helm"
   729  		app.Spec.Source.Path = ""
   730  		app.Spec.Source.Chart = "test"
   731  		app.Status.Resources = []v1alpha1.ResourceStatus{
   732  			{
   733  				Group:     deployment.GroupVersionKind().Group,
   734  				Kind:      deployment.GroupVersionKind().Kind,
   735  				Version:   deployment.GroupVersionKind().Version,
   736  				Name:      deployment.Name,
   737  				Namespace: deployment.Namespace,
   738  				Status:    "Synced",
   739  			},
   740  		}
   741  		app.Status.History = []v1alpha1.RevisionHistory{
   742  			{
   743  				ID: 0,
   744  				Source: v1alpha1.ApplicationSource{
   745  					TargetRevision: "something-old",
   746  				},
   747  			},
   748  		}
   749  	})
   750  	testAppMulti := newTestApp(func(app *v1alpha1.Application) {
   751  		app.Name = "test-multi"
   752  		app.Spec.Sources = v1alpha1.ApplicationSources{
   753  			v1alpha1.ApplicationSource{
   754  				TargetRevision: "something-old",
   755  			},
   756  			v1alpha1.ApplicationSource{
   757  				TargetRevision: "something-old",
   758  			},
   759  		}
   760  		app.Status.Resources = []v1alpha1.ResourceStatus{
   761  			{
   762  				Group:     deployment.GroupVersionKind().Group,
   763  				Kind:      deployment.GroupVersionKind().Kind,
   764  				Version:   deployment.GroupVersionKind().Version,
   765  				Name:      deployment.Name,
   766  				Namespace: deployment.Namespace,
   767  				Status:    "Synced",
   768  			},
   769  		}
   770  		app.Status.History = []v1alpha1.RevisionHistory{
   771  			{
   772  				ID: 1,
   773  				Sources: v1alpha1.ApplicationSources{
   774  					v1alpha1.ApplicationSource{
   775  						TargetRevision: "something-old",
   776  					},
   777  					v1alpha1.ApplicationSource{
   778  						TargetRevision: "something-old",
   779  					},
   780  				},
   781  			},
   782  		}
   783  	})
   784  	testDeployment := kube.MustToUnstructured(&deployment)
   785  	appServer := newTestAppServerWithEnforcerConfigure(t, f, map[string]string{}, testApp, testHelmApp, testAppMulti, testDeployment)
   786  
   787  	noRoleCtx := t.Context()
   788  	//nolint:staticcheck
   789  	adminCtx := context.WithValue(noRoleCtx, "claims", &jwt.MapClaims{"groups": []string{"admin"}})
   790  
   791  	t.Run("Get", func(t *testing.T) {
   792  		_, err := appServer.Get(adminCtx, &application.ApplicationQuery{Name: ptr.To("test")})
   793  		require.NoError(t, err)
   794  		_, err = appServer.Get(noRoleCtx, &application.ApplicationQuery{Name: ptr.To("test")})
   795  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   796  		_, err = appServer.Get(adminCtx, &application.ApplicationQuery{Name: ptr.To("doest-not-exist")})
   797  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   798  		_, err = appServer.Get(adminCtx, &application.ApplicationQuery{Name: ptr.To("doest-not-exist"), Project: []string{"test"}})
   799  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   800  	})
   801  
   802  	t.Run("GetManifests", func(t *testing.T) {
   803  		_, err := appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: ptr.To("test")})
   804  		require.NoError(t, err)
   805  		_, err = appServer.GetManifests(noRoleCtx, &application.ApplicationManifestQuery{Name: ptr.To("test")})
   806  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   807  		_, err = appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: ptr.To("doest-not-exist")})
   808  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   809  		_, err = appServer.GetManifests(adminCtx, &application.ApplicationManifestQuery{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   810  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   811  	})
   812  
   813  	t.Run("ListResourceEvents", func(t *testing.T) {
   814  		_, err := appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: ptr.To("test")})
   815  		require.NoError(t, err)
   816  		_, err = appServer.ListResourceEvents(noRoleCtx, &application.ApplicationResourceEventsQuery{Name: ptr.To("test")})
   817  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   818  		_, err = appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: ptr.To("doest-not-exist")})
   819  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   820  		_, err = appServer.ListResourceEvents(adminCtx, &application.ApplicationResourceEventsQuery{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   821  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   822  	})
   823  
   824  	t.Run("UpdateSpec", func(t *testing.T) {
   825  		_, err := appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: ptr.To("test"), Spec: &v1alpha1.ApplicationSpec{
   826  			Destination: v1alpha1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.example.com"},
   827  			Source:      &v1alpha1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
   828  		}})
   829  		require.NoError(t, err)
   830  		_, err = appServer.UpdateSpec(noRoleCtx, &application.ApplicationUpdateSpecRequest{Name: ptr.To("test"), Spec: &v1alpha1.ApplicationSpec{
   831  			Destination: v1alpha1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.example.com"},
   832  			Source:      &v1alpha1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
   833  		}})
   834  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   835  		_, err = appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: ptr.To("doest-not-exist"), Spec: &v1alpha1.ApplicationSpec{
   836  			Destination: v1alpha1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.example.com"},
   837  			Source:      &v1alpha1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
   838  		}})
   839  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   840  		_, err = appServer.UpdateSpec(adminCtx, &application.ApplicationUpdateSpecRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test"), Spec: &v1alpha1.ApplicationSpec{
   841  			Destination: v1alpha1.ApplicationDestination{Namespace: "default", Server: "https://cluster-api.example.com"},
   842  			Source:      &v1alpha1.ApplicationSource{RepoURL: "https://some-fake-source", Path: "."},
   843  		}})
   844  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   845  	})
   846  
   847  	t.Run("Patch", func(t *testing.T) {
   848  		_, err := appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: ptr.To("test"), Patch: ptr.To(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
   849  		require.NoError(t, err)
   850  		_, err = appServer.Patch(noRoleCtx, &application.ApplicationPatchRequest{Name: ptr.To("test"), Patch: ptr.To(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
   851  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   852  		_, err = appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: ptr.To("doest-not-exist")})
   853  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   854  		_, err = appServer.Patch(adminCtx, &application.ApplicationPatchRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   855  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   856  	})
   857  
   858  	t.Run("GetResource", func(t *testing.T) {
   859  		_, err := appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   860  		require.NoError(t, err)
   861  		_, err = appServer.GetResource(noRoleCtx, &application.ApplicationResourceRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   862  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   863  		_, err = appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("doest-not-exist"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   864  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   865  		_, err = appServer.GetResource(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   866  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   867  	})
   868  
   869  	t.Run("PatchResource", func(t *testing.T) {
   870  		_, err := appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test"), Patch: ptr.To(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
   871  		// This will always throw an error, because the kubectl mock for PatchResource is hard-coded to return nil.
   872  		// The best we can do is to confirm we get past the permission check.
   873  		assert.NotEqual(t, common.PermissionDeniedAPIError.Error(), err.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   874  		_, err = appServer.PatchResource(noRoleCtx, &application.ApplicationResourcePatchRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test"), Patch: ptr.To(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
   875  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   876  		_, err = appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: ptr.To("doest-not-exist"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test"), Patch: ptr.To(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
   877  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   878  		_, err = appServer.PatchResource(adminCtx, &application.ApplicationResourcePatchRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test"), Patch: ptr.To(`[{"op": "replace", "path": "/spec/replicas", "value": 3}]`)})
   879  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   880  	})
   881  
   882  	t.Run("DeleteResource", func(t *testing.T) {
   883  		_, err := appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   884  		require.NoError(t, err)
   885  		_, err = appServer.DeleteResource(noRoleCtx, &application.ApplicationResourceDeleteRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   886  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   887  		_, err = appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: ptr.To("doest-not-exist"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   888  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   889  		_, err = appServer.DeleteResource(adminCtx, &application.ApplicationResourceDeleteRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   890  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   891  	})
   892  
   893  	t.Run("ResourceTree", func(t *testing.T) {
   894  		_, err := appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: ptr.To("test")})
   895  		require.NoError(t, err)
   896  		_, err = appServer.ResourceTree(noRoleCtx, &application.ResourcesQuery{ApplicationName: ptr.To("test")})
   897  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   898  		_, err = appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: ptr.To("doest-not-exist")})
   899  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   900  		_, err = appServer.ResourceTree(adminCtx, &application.ResourcesQuery{ApplicationName: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   901  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   902  	})
   903  
   904  	t.Run("RevisionMetadata", func(t *testing.T) {
   905  		_, err := appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test")})
   906  		require.NoError(t, err)
   907  		_, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test-multi"), SourceIndex: ptr.To(int32(0)), VersionId: ptr.To(int32(1))})
   908  		require.NoError(t, err)
   909  		_, err = appServer.RevisionMetadata(noRoleCtx, &application.RevisionMetadataQuery{Name: ptr.To("test")})
   910  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   911  		_, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("doest-not-exist")})
   912  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   913  		_, err = appServer.RevisionMetadata(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   914  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   915  	})
   916  
   917  	t.Run("RevisionChartDetails", func(t *testing.T) {
   918  		_, err := appServer.RevisionChartDetails(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("test-helm")})
   919  		require.NoError(t, err)
   920  		_, err = appServer.RevisionChartDetails(noRoleCtx, &application.RevisionMetadataQuery{Name: ptr.To("test-helm")})
   921  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   922  		_, err = appServer.RevisionChartDetails(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("doest-not-exist")})
   923  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   924  		_, err = appServer.RevisionChartDetails(adminCtx, &application.RevisionMetadataQuery{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   925  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   926  	})
   927  
   928  	t.Run("ManagedResources", func(t *testing.T) {
   929  		_, err := appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: ptr.To("test")})
   930  		require.NoError(t, err)
   931  		_, err = appServer.ManagedResources(noRoleCtx, &application.ResourcesQuery{ApplicationName: ptr.To("test")})
   932  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   933  		_, err = appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: ptr.To("doest-not-exist")})
   934  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   935  		_, err = appServer.ManagedResources(adminCtx, &application.ResourcesQuery{ApplicationName: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   936  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   937  	})
   938  
   939  	t.Run("Sync", func(t *testing.T) {
   940  		_, err := appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: ptr.To("test")})
   941  		require.NoError(t, err)
   942  		_, err = appServer.Sync(noRoleCtx, &application.ApplicationSyncRequest{Name: ptr.To("test")})
   943  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   944  		_, err = appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: ptr.To("doest-not-exist")})
   945  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   946  		_, err = appServer.Sync(adminCtx, &application.ApplicationSyncRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   947  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   948  	})
   949  
   950  	t.Run("TerminateOperation", func(t *testing.T) {
   951  		// The sync operation is already started from the previous test. We just need to set the field that the
   952  		// controller would set if this were an actual Argo CD environment.
   953  		setSyncRunningOperationState(t, appServer)
   954  		_, err := appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: ptr.To("test")})
   955  		require.NoError(t, err)
   956  		_, err = appServer.TerminateOperation(noRoleCtx, &application.OperationTerminateRequest{Name: ptr.To("test")})
   957  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   958  		_, err = appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: ptr.To("doest-not-exist")})
   959  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   960  		_, err = appServer.TerminateOperation(adminCtx, &application.OperationTerminateRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   961  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   962  	})
   963  
   964  	t.Run("Rollback", func(t *testing.T) {
   965  		unsetSyncRunningOperationState(t, appServer)
   966  		_, err := appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("test")})
   967  		require.NoError(t, err)
   968  		_, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("test-multi"), Id: ptr.To(int64(1))})
   969  		require.NoError(t, err)
   970  		_, err = appServer.Rollback(noRoleCtx, &application.ApplicationRollbackRequest{Name: ptr.To("test")})
   971  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   972  		_, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("doest-not-exist")})
   973  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   974  		_, err = appServer.Rollback(adminCtx, &application.ApplicationRollbackRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   975  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   976  	})
   977  
   978  	t.Run("ListResourceActions", func(t *testing.T) {
   979  		_, err := appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
   980  		require.NoError(t, err)
   981  		_, err = appServer.ListResourceActions(noRoleCtx, &application.ApplicationResourceRequest{Name: ptr.To("test")})
   982  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   983  		_, err = appServer.ListResourceActions(noRoleCtx, &application.ApplicationResourceRequest{Group: ptr.To("argoproj.io"), Kind: ptr.To("Application"), Name: ptr.To("test")})
   984  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   985  		_, err = appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("doest-not-exist")})
   986  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   987  		_, err = appServer.ListResourceActions(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
   988  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
   989  	})
   990  
   991  	//nolint:staticcheck,SA1019 // RunResourceAction is deprecated, but we still need to support it for backward compatibility.
   992  	t.Run("RunResourceAction", func(t *testing.T) {
   993  		_, err := appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test"), Action: ptr.To("restart")})
   994  		require.NoError(t, err)
   995  		_, err = appServer.RunResourceAction(noRoleCtx, &application.ResourceActionRunRequest{Name: ptr.To("test")})
   996  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   997  		_, err = appServer.RunResourceAction(noRoleCtx, &application.ResourceActionRunRequest{Group: ptr.To("argoproj.io"), Kind: ptr.To("Application"), Name: ptr.To("test")})
   998  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
   999  		_, err = appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: ptr.To("doest-not-exist")})
  1000  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1001  		_, err = appServer.RunResourceAction(adminCtx, &application.ResourceActionRunRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
  1002  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1003  	})
  1004  
  1005  	t.Run("RunResourceActionV2", func(t *testing.T) {
  1006  		_, err := appServer.RunResourceActionV2(adminCtx, &application.ResourceActionRunRequestV2{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test"), Action: ptr.To("restart")})
  1007  		require.NoError(t, err)
  1008  		_, err = appServer.RunResourceActionV2(noRoleCtx, &application.ResourceActionRunRequestV2{Name: ptr.To("test")})
  1009  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1010  		_, err = appServer.RunResourceActionV2(noRoleCtx, &application.ResourceActionRunRequestV2{Group: ptr.To("argoproj.io"), Kind: ptr.To("Application"), Name: ptr.To("test")})
  1011  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1012  		_, err = appServer.RunResourceActionV2(adminCtx, &application.ResourceActionRunRequestV2{Name: ptr.To("doest-not-exist")})
  1013  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1014  		_, err = appServer.RunResourceActionV2(adminCtx, &application.ResourceActionRunRequestV2{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
  1015  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1016  	})
  1017  
  1018  	t.Run("GetApplicationSyncWindows", func(t *testing.T) {
  1019  		_, err := appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: ptr.To("test")})
  1020  		require.NoError(t, err)
  1021  		_, err = appServer.GetApplicationSyncWindows(noRoleCtx, &application.ApplicationSyncWindowsQuery{Name: ptr.To("test")})
  1022  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1023  		_, err = appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: ptr.To("doest-not-exist")})
  1024  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1025  		_, err = appServer.GetApplicationSyncWindows(adminCtx, &application.ApplicationSyncWindowsQuery{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
  1026  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1027  	})
  1028  
  1029  	t.Run("GetManifestsWithFiles", func(t *testing.T) {
  1030  		err := appServer.GetManifestsWithFiles(&TestServerStream{ctx: adminCtx, appName: "test"})
  1031  		require.NoError(t, err)
  1032  		err = appServer.GetManifestsWithFiles(&TestServerStream{ctx: noRoleCtx, appName: "test"})
  1033  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1034  		err = appServer.GetManifestsWithFiles(&TestServerStream{ctx: adminCtx, appName: "does-not-exist"})
  1035  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1036  		err = appServer.GetManifestsWithFiles(&TestServerStream{ctx: adminCtx, appName: "does-not-exist", project: "test"})
  1037  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1038  	})
  1039  
  1040  	t.Run("WatchResourceTree", func(t *testing.T) {
  1041  		err := appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: ptr.To("test")}, &TestResourceTreeServer{ctx: adminCtx})
  1042  		require.NoError(t, err)
  1043  		err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: ptr.To("test")}, &TestResourceTreeServer{ctx: noRoleCtx})
  1044  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1045  		err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: ptr.To("does-not-exist")}, &TestResourceTreeServer{ctx: adminCtx})
  1046  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1047  		err = appServer.WatchResourceTree(&application.ResourcesQuery{ApplicationName: ptr.To("does-not-exist"), Project: ptr.To("test")}, &TestResourceTreeServer{ctx: adminCtx})
  1048  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1049  	})
  1050  
  1051  	t.Run("PodLogs", func(t *testing.T) {
  1052  		err := appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: ptr.To("test")}, &TestPodLogsServer{ctx: adminCtx})
  1053  		require.NoError(t, err)
  1054  		err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: ptr.To("test")}, &TestPodLogsServer{ctx: noRoleCtx})
  1055  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1056  		err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: ptr.To("does-not-exist")}, &TestPodLogsServer{ctx: adminCtx})
  1057  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1058  		err = appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: ptr.To("does-not-exist"), Project: ptr.To("test")}, &TestPodLogsServer{ctx: adminCtx})
  1059  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1060  	})
  1061  
  1062  	t.Run("ListLinks", func(t *testing.T) {
  1063  		_, err := appServer.ListLinks(adminCtx, &application.ListAppLinksRequest{Name: ptr.To("test")})
  1064  		require.NoError(t, err)
  1065  		_, err = appServer.ListLinks(noRoleCtx, &application.ListAppLinksRequest{Name: ptr.To("test")})
  1066  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1067  		_, err = appServer.ListLinks(adminCtx, &application.ListAppLinksRequest{Name: ptr.To("does-not-exist")})
  1068  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1069  		_, err = appServer.ListLinks(adminCtx, &application.ListAppLinksRequest{Name: ptr.To("does-not-exist"), Project: ptr.To("test")})
  1070  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1071  	})
  1072  
  1073  	t.Run("ListResourceLinks", func(t *testing.T) {
  1074  		_, err := appServer.ListResourceLinks(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
  1075  		require.NoError(t, err)
  1076  		_, err = appServer.ListResourceLinks(noRoleCtx, &application.ApplicationResourceRequest{Name: ptr.To("test"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
  1077  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1078  		_, err = appServer.ListResourceLinks(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("does-not-exist"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test")})
  1079  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1080  		_, err = appServer.ListResourceLinks(adminCtx, &application.ApplicationResourceRequest{Name: ptr.To("does-not-exist"), ResourceName: ptr.To("test"), Group: ptr.To("apps"), Kind: ptr.To("Deployment"), Namespace: ptr.To("test"), Project: ptr.To("test")})
  1081  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"does-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1082  	})
  1083  
  1084  	// Do this last so other stuff doesn't fail.
  1085  	t.Run("Delete", func(t *testing.T) {
  1086  		_, err := appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: ptr.To("test")})
  1087  		require.NoError(t, err)
  1088  		_, err = appServer.Delete(noRoleCtx, &application.ApplicationDeleteRequest{Name: ptr.To("test")})
  1089  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1090  		_, err = appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: ptr.To("doest-not-exist")})
  1091  		require.EqualError(t, err, common.PermissionDeniedAPIError.Error(), "error message must be _only_ the permission error, to avoid leaking information about app existence")
  1092  		_, err = appServer.Delete(adminCtx, &application.ApplicationDeleteRequest{Name: ptr.To("doest-not-exist"), Project: ptr.To("test")})
  1093  		assert.EqualError(t, err, "rpc error: code = NotFound desc = applications.argoproj.io \"doest-not-exist\" not found", "when the request specifies a project, we can return the standard k8s error message")
  1094  	})
  1095  }
  1096  
  1097  // setSyncRunningOperationState simulates starting a sync operation on the given app.
  1098  func setSyncRunningOperationState(t *testing.T, appServer *Server) {
  1099  	t.Helper()
  1100  	appIf := appServer.appclientset.ArgoprojV1alpha1().Applications("default")
  1101  	app, err := appIf.Get(t.Context(), "test", metav1.GetOptions{})
  1102  	require.NoError(t, err)
  1103  	// This sets the status that would be set by the controller usually.
  1104  	app.Status.OperationState = &v1alpha1.OperationState{Phase: synccommon.OperationRunning, Operation: v1alpha1.Operation{Sync: &v1alpha1.SyncOperation{}}}
  1105  	_, err = appIf.Update(t.Context(), app, metav1.UpdateOptions{})
  1106  	require.NoError(t, err)
  1107  }
  1108  
  1109  // unsetSyncRunningOperationState simulates finishing a sync operation on the given app.
  1110  func unsetSyncRunningOperationState(t *testing.T, appServer *Server) {
  1111  	t.Helper()
  1112  	appIf := appServer.appclientset.ArgoprojV1alpha1().Applications("default")
  1113  	app, err := appIf.Get(t.Context(), "test", metav1.GetOptions{})
  1114  	require.NoError(t, err)
  1115  	app.Operation = nil
  1116  	app.Status.OperationState = nil
  1117  	_, err = appIf.Update(t.Context(), app, metav1.UpdateOptions{})
  1118  	require.NoError(t, err)
  1119  }
  1120  
  1121  func TestListAppsInNamespaceWithLabels(t *testing.T) {
  1122  	appServer := newTestAppServer(t, newTestApp(func(app *v1alpha1.Application) {
  1123  		app.Name = "App1"
  1124  		app.Namespace = "test-namespace"
  1125  		app.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
  1126  	}), newTestApp(func(app *v1alpha1.Application) {
  1127  		app.Name = "App2"
  1128  		app.Namespace = "test-namespace"
  1129  		app.SetLabels(map[string]string{"key1": "value2"})
  1130  	}), newTestApp(func(app *v1alpha1.Application) {
  1131  		app.Name = "App3"
  1132  		app.Namespace = "test-namespace"
  1133  		app.SetLabels(map[string]string{"key1": "value3"})
  1134  	}))
  1135  	appServer.ns = "test-namespace"
  1136  	appQuery := application.ApplicationQuery{}
  1137  	namespace := "test-namespace"
  1138  	appQuery.AppNamespace = &namespace
  1139  	testListAppsWithLabels(t, appQuery, appServer)
  1140  }
  1141  
  1142  func TestListAppsInDefaultNSWithLabels(t *testing.T) {
  1143  	appServer := newTestAppServer(t, newTestApp(func(app *v1alpha1.Application) {
  1144  		app.Name = "App1"
  1145  		app.SetLabels(map[string]string{"key1": "value1", "key2": "value1"})
  1146  	}), newTestApp(func(app *v1alpha1.Application) {
  1147  		app.Name = "App2"
  1148  		app.SetLabels(map[string]string{"key1": "value2"})
  1149  	}), newTestApp(func(app *v1alpha1.Application) {
  1150  		app.Name = "App3"
  1151  		app.SetLabels(map[string]string{"key1": "value3"})
  1152  	}))
  1153  	appQuery := application.ApplicationQuery{}
  1154  	testListAppsWithLabels(t, appQuery, appServer)
  1155  }
  1156  
  1157  func testListAppsWithLabels(t *testing.T, appQuery application.ApplicationQuery, appServer *Server) {
  1158  	t.Helper()
  1159  	validTests := []struct {
  1160  		testName       string
  1161  		label          string
  1162  		expectedResult []string
  1163  	}{
  1164  		{
  1165  			testName:       "Equality based filtering using '=' operator",
  1166  			label:          "key1=value1",
  1167  			expectedResult: []string{"App1"},
  1168  		},
  1169  		{
  1170  			testName:       "Equality based filtering using '==' operator",
  1171  			label:          "key1==value1",
  1172  			expectedResult: []string{"App1"},
  1173  		},
  1174  		{
  1175  			testName:       "Equality based filtering using '!=' operator",
  1176  			label:          "key1!=value1",
  1177  			expectedResult: []string{"App2", "App3"},
  1178  		},
  1179  		{
  1180  			testName:       "Set based filtering using 'in' operator",
  1181  			label:          "key1 in (value1, value3)",
  1182  			expectedResult: []string{"App1", "App3"},
  1183  		},
  1184  		{
  1185  			testName:       "Set based filtering using 'notin' operator",
  1186  			label:          "key1 notin (value1, value3)",
  1187  			expectedResult: []string{"App2"},
  1188  		},
  1189  		{
  1190  			testName:       "Set based filtering using 'exists' operator",
  1191  			label:          "key1",
  1192  			expectedResult: []string{"App1", "App2", "App3"},
  1193  		},
  1194  		{
  1195  			testName:       "Set based filtering using 'not exists' operator",
  1196  			label:          "!key2",
  1197  			expectedResult: []string{"App2", "App3"},
  1198  		},
  1199  	}
  1200  	// test valid scenarios
  1201  	for _, validTest := range validTests {
  1202  		t.Run(validTest.testName, func(t *testing.T) {
  1203  			appQuery.Selector = &validTest.label
  1204  			res, err := appServer.List(t.Context(), &appQuery)
  1205  			require.NoError(t, err)
  1206  			apps := []string{}
  1207  			for i := range res.Items {
  1208  				apps = append(apps, res.Items[i].Name)
  1209  			}
  1210  			assert.Equal(t, validTest.expectedResult, apps)
  1211  		})
  1212  	}
  1213  
  1214  	invalidTests := []struct {
  1215  		testName    string
  1216  		label       string
  1217  		errorMesage string
  1218  	}{
  1219  		{
  1220  			testName:    "Set based filtering using '>' operator",
  1221  			label:       "key1>value1",
  1222  			errorMesage: "error parsing the selector",
  1223  		},
  1224  		{
  1225  			testName:    "Set based filtering using '<' operator",
  1226  			label:       "key1<value1",
  1227  			errorMesage: "error parsing the selector",
  1228  		},
  1229  	}
  1230  	// test invalid scenarios
  1231  	for _, invalidTest := range invalidTests {
  1232  		t.Run(invalidTest.testName, func(t *testing.T) {
  1233  			appQuery.Selector = &invalidTest.label
  1234  			_, err := appServer.List(t.Context(), &appQuery)
  1235  			assert.ErrorContains(t, err, invalidTest.errorMesage)
  1236  		})
  1237  	}
  1238  }
  1239  
  1240  func TestListAppWithProjects(t *testing.T) {
  1241  	appServer := newTestAppServer(t, newTestApp(func(app *v1alpha1.Application) {
  1242  		app.Name = "App1"
  1243  		app.Spec.Project = "test-project1"
  1244  	}), newTestApp(func(app *v1alpha1.Application) {
  1245  		app.Name = "App2"
  1246  		app.Spec.Project = "test-project2"
  1247  	}), newTestApp(func(app *v1alpha1.Application) {
  1248  		app.Name = "App3"
  1249  		app.Spec.Project = "test-project3"
  1250  	}))
  1251  
  1252  	t.Run("List all apps", func(t *testing.T) {
  1253  		appQuery := application.ApplicationQuery{}
  1254  		appList, err := appServer.List(t.Context(), &appQuery)
  1255  		require.NoError(t, err)
  1256  		assert.Len(t, appList.Items, 3)
  1257  	})
  1258  
  1259  	t.Run("List apps with projects filter set", func(t *testing.T) {
  1260  		appQuery := application.ApplicationQuery{Projects: []string{"test-project1"}}
  1261  		appList, err := appServer.List(t.Context(), &appQuery)
  1262  		require.NoError(t, err)
  1263  		assert.Len(t, appList.Items, 1)
  1264  		for _, app := range appList.Items {
  1265  			assert.Equal(t, "test-project1", app.Spec.Project)
  1266  		}
  1267  	})
  1268  
  1269  	t.Run("List apps with project filter set (legacy field)", func(t *testing.T) {
  1270  		appQuery := application.ApplicationQuery{Project: []string{"test-project1"}}
  1271  		appList, err := appServer.List(t.Context(), &appQuery)
  1272  		require.NoError(t, err)
  1273  		assert.Len(t, appList.Items, 1)
  1274  		for _, app := range appList.Items {
  1275  			assert.Equal(t, "test-project1", app.Spec.Project)
  1276  		}
  1277  	})
  1278  
  1279  	t.Run("List apps with both projects and project filter set", func(t *testing.T) {
  1280  		// If the older field is present, we should use it instead of the newer field.
  1281  		appQuery := application.ApplicationQuery{Project: []string{"test-project1"}, Projects: []string{"test-project2"}}
  1282  		appList, err := appServer.List(t.Context(), &appQuery)
  1283  		require.NoError(t, err)
  1284  		assert.Len(t, appList.Items, 1)
  1285  		for _, app := range appList.Items {
  1286  			assert.Equal(t, "test-project1", app.Spec.Project)
  1287  		}
  1288  	})
  1289  }
  1290  
  1291  func TestListApps(t *testing.T) {
  1292  	appServer := newTestAppServer(t, newTestApp(func(app *v1alpha1.Application) {
  1293  		app.Name = "bcd"
  1294  	}), newTestApp(func(app *v1alpha1.Application) {
  1295  		app.Name = "abc"
  1296  	}), newTestApp(func(app *v1alpha1.Application) {
  1297  		app.Name = "def"
  1298  	}))
  1299  
  1300  	res, err := appServer.List(t.Context(), &application.ApplicationQuery{})
  1301  	require.NoError(t, err)
  1302  	var names []string
  1303  	for i := range res.Items {
  1304  		names = append(names, res.Items[i].Name)
  1305  	}
  1306  	assert.Equal(t, []string{"abc", "bcd", "def"}, names)
  1307  }
  1308  
  1309  func TestCoupleAppsListApps(t *testing.T) {
  1310  	var objects []runtime.Object
  1311  	ctx := t.Context()
  1312  
  1313  	var groups []string
  1314  	for i := 0; i < 50; i++ {
  1315  		groups = append(groups, fmt.Sprintf("group-%d", i))
  1316  	}
  1317  	//nolint:staticcheck
  1318  	ctx = context.WithValue(ctx, "claims", &jwt.MapClaims{"groups": groups})
  1319  	for projectId := 0; projectId < 100; projectId++ {
  1320  		projectName := fmt.Sprintf("proj-%d", projectId)
  1321  		for appId := 0; appId < 100; appId++ {
  1322  			objects = append(objects, newTestApp(func(app *v1alpha1.Application) {
  1323  				app.Name = fmt.Sprintf("app-%d-%d", projectId, appId)
  1324  				app.Spec.Project = projectName
  1325  			}))
  1326  		}
  1327  	}
  1328  
  1329  	f := func(enf *rbac.Enforcer) {
  1330  		policy := `
  1331  p, role:test, applications, *, proj-10/*, allow
  1332  g, group-45, role:test
  1333  p, role:test2, applications, *, proj-15/*, allow
  1334  g, group-47, role:test2
  1335  p, role:test3, applications, *, proj-20/*, allow
  1336  g, group-49, role:test3
  1337  `
  1338  		_ = enf.SetUserPolicy(policy)
  1339  	}
  1340  	appServer := newTestAppServerWithEnforcerConfigure(t, f, map[string]string{}, objects...)
  1341  
  1342  	res, err := appServer.List(ctx, &application.ApplicationQuery{})
  1343  
  1344  	require.NoError(t, err)
  1345  	var names []string
  1346  	for i := range res.Items {
  1347  		names = append(names, res.Items[i].Name)
  1348  	}
  1349  	assert.Len(t, names, 300)
  1350  }
  1351  
  1352  func generateTestApp(num int) []*v1alpha1.Application {
  1353  	apps := []*v1alpha1.Application{}
  1354  	for i := 0; i < num; i++ {
  1355  		apps = append(apps, newTestApp(func(app *v1alpha1.Application) {
  1356  			app.Name = fmt.Sprintf("test-app%.6d", i)
  1357  		}))
  1358  	}
  1359  
  1360  	return apps
  1361  }
  1362  
  1363  func BenchmarkListMuchApps(b *testing.B) {
  1364  	// 10000 apps
  1365  	apps := generateTestApp(10000)
  1366  	obj := make([]runtime.Object, len(apps))
  1367  	for i, v := range apps {
  1368  		obj[i] = v
  1369  	}
  1370  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1371  
  1372  	b.ResetTimer()
  1373  	for n := 0; n < b.N; n++ {
  1374  		_, err := appServer.List(b.Context(), &application.ApplicationQuery{})
  1375  		if err != nil {
  1376  			break
  1377  		}
  1378  	}
  1379  }
  1380  
  1381  func BenchmarkListSomeApps(b *testing.B) {
  1382  	// 500 apps
  1383  	apps := generateTestApp(500)
  1384  	obj := make([]runtime.Object, len(apps))
  1385  	for i, v := range apps {
  1386  		obj[i] = v
  1387  	}
  1388  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1389  
  1390  	b.ResetTimer()
  1391  	for n := 0; n < b.N; n++ {
  1392  		_, err := appServer.List(b.Context(), &application.ApplicationQuery{})
  1393  		if err != nil {
  1394  			break
  1395  		}
  1396  	}
  1397  }
  1398  
  1399  func BenchmarkListFewApps(b *testing.B) {
  1400  	// 10 apps
  1401  	apps := generateTestApp(10)
  1402  	obj := make([]runtime.Object, len(apps))
  1403  	for i, v := range apps {
  1404  		obj[i] = v
  1405  	}
  1406  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1407  
  1408  	b.ResetTimer()
  1409  	for n := 0; n < b.N; n++ {
  1410  		_, err := appServer.List(b.Context(), &application.ApplicationQuery{})
  1411  		if err != nil {
  1412  			break
  1413  		}
  1414  	}
  1415  }
  1416  
  1417  func strToPtr(v string) *string {
  1418  	return &v
  1419  }
  1420  
  1421  func BenchmarkListMuchAppsWithName(b *testing.B) {
  1422  	// 10000 apps
  1423  	appsMuch := generateTestApp(10000)
  1424  	obj := make([]runtime.Object, len(appsMuch))
  1425  	for i, v := range appsMuch {
  1426  		obj[i] = v
  1427  	}
  1428  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1429  
  1430  	b.ResetTimer()
  1431  	for n := 0; n < b.N; n++ {
  1432  		app := &application.ApplicationQuery{Name: strToPtr("test-app000099")}
  1433  		_, err := appServer.List(b.Context(), app)
  1434  		if err != nil {
  1435  			break
  1436  		}
  1437  	}
  1438  }
  1439  
  1440  func BenchmarkListMuchAppsWithProjects(b *testing.B) {
  1441  	// 10000 apps
  1442  	appsMuch := generateTestApp(10000)
  1443  	appsMuch[999].Spec.Project = "test-project1"
  1444  	appsMuch[1999].Spec.Project = "test-project2"
  1445  	obj := make([]runtime.Object, len(appsMuch))
  1446  	for i, v := range appsMuch {
  1447  		obj[i] = v
  1448  	}
  1449  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1450  
  1451  	b.ResetTimer()
  1452  	for n := 0; n < b.N; n++ {
  1453  		app := &application.ApplicationQuery{Project: []string{"test-project1", "test-project2"}}
  1454  		_, err := appServer.List(b.Context(), app)
  1455  		if err != nil {
  1456  			break
  1457  		}
  1458  	}
  1459  }
  1460  
  1461  func BenchmarkListMuchAppsWithRepo(b *testing.B) {
  1462  	// 10000 apps
  1463  	appsMuch := generateTestApp(10000)
  1464  	appsMuch[999].Spec.Source.RepoURL = "https://some-fake-source"
  1465  	obj := make([]runtime.Object, len(appsMuch))
  1466  	for i, v := range appsMuch {
  1467  		obj[i] = v
  1468  	}
  1469  	appServer := newTestAppServerWithBenchmark(b, obj...)
  1470  
  1471  	b.ResetTimer()
  1472  	for n := 0; n < b.N; n++ {
  1473  		app := &application.ApplicationQuery{Repo: strToPtr("https://some-fake-source")}
  1474  		_, err := appServer.List(b.Context(), app)
  1475  		if err != nil {
  1476  			break
  1477  		}
  1478  	}
  1479  }
  1480  
  1481  func TestCreateApp(t *testing.T) {
  1482  	testApp := newTestApp()
  1483  	appServer := newTestAppServer(t)
  1484  	testApp.Spec.Project = ""
  1485  	createReq := application.ApplicationCreateRequest{
  1486  		Application: testApp,
  1487  	}
  1488  	app, err := appServer.Create(t.Context(), &createReq)
  1489  	require.NoError(t, err)
  1490  	assert.NotNil(t, app)
  1491  	assert.NotNil(t, app.Spec)
  1492  	assert.Equal(t, "default", app.Spec.Project)
  1493  }
  1494  
  1495  func TestCreateAppWithDestName(t *testing.T) {
  1496  	appServer := newTestAppServer(t)
  1497  	testApp := newTestAppWithDestName()
  1498  	createReq := application.ApplicationCreateRequest{
  1499  		Application: testApp,
  1500  	}
  1501  	app, err := appServer.Create(t.Context(), &createReq)
  1502  	require.NoError(t, err)
  1503  	assert.NotNil(t, app)
  1504  }
  1505  
  1506  // TestCreateAppWithOperation tests that an application created with an operation is created with the operation removed.
  1507  // Avoids regressions of https://github.com/argoproj/argo-cd/security/advisories/GHSA-g623-jcgg-mhmm
  1508  func TestCreateAppWithOperation(t *testing.T) {
  1509  	appServer := newTestAppServer(t)
  1510  	testApp := newTestAppWithDestName()
  1511  	testApp.Operation = &v1alpha1.Operation{
  1512  		Sync: &v1alpha1.SyncOperation{
  1513  			Manifests: []string{
  1514  				"test",
  1515  			},
  1516  		},
  1517  	}
  1518  	createReq := application.ApplicationCreateRequest{
  1519  		Application: testApp,
  1520  	}
  1521  	app, err := appServer.Create(t.Context(), &createReq)
  1522  	require.NoError(t, err)
  1523  	require.NotNil(t, app)
  1524  	assert.Nil(t, app.Operation)
  1525  }
  1526  
  1527  func TestCreateAppUpsert(t *testing.T) {
  1528  	t.Parallel()
  1529  	t.Run("No error when spec equals", func(t *testing.T) {
  1530  		t.Parallel()
  1531  		appServer := newTestAppServer(t)
  1532  		testApp := newTestApp()
  1533  
  1534  		createReq := application.ApplicationCreateRequest{
  1535  			Application: testApp,
  1536  		}
  1537  		// Call Create() instead of adding the object to the tesst server to make sure the app is correctly normalized.
  1538  		_, err := appServer.Create(t.Context(), &createReq)
  1539  		require.NoError(t, err)
  1540  
  1541  		app, err := appServer.Create(t.Context(), &createReq)
  1542  		require.NoError(t, err)
  1543  		require.NotNil(t, app)
  1544  	})
  1545  	t.Run("Error on update without upsert", func(t *testing.T) {
  1546  		t.Parallel()
  1547  		appServer := newTestAppServer(t)
  1548  		testApp := newTestApp()
  1549  
  1550  		// Call Create() instead of adding the object to the tesst server to make sure the app is correctly normalized.
  1551  		_, err := appServer.Create(t.Context(), &application.ApplicationCreateRequest{
  1552  			Application: testApp,
  1553  		})
  1554  		require.NoError(t, err)
  1555  
  1556  		newApp := newTestApp()
  1557  		newApp.Spec.Source.Name = "updated"
  1558  		createReq := application.ApplicationCreateRequest{
  1559  			Application: newApp,
  1560  		}
  1561  		_, err = appServer.Create(t.Context(), &createReq)
  1562  		require.EqualError(t, err, "rpc error: code = InvalidArgument desc = existing application spec is different, use upsert flag to force update")
  1563  	})
  1564  	t.Run("Invalid existing app can be updated", func(t *testing.T) {
  1565  		t.Parallel()
  1566  		testApp := newTestApp()
  1567  		testApp.Spec.Destination.Server = "https://invalid-cluster"
  1568  		appServer := newTestAppServer(t, testApp)
  1569  
  1570  		newApp := newTestAppWithDestName()
  1571  		newApp.TypeMeta = testApp.TypeMeta
  1572  		newApp.Spec.Source.Name = "updated"
  1573  		createReq := application.ApplicationCreateRequest{
  1574  			Application: newApp,
  1575  			Upsert:      ptr.To(true),
  1576  		}
  1577  		app, err := appServer.Create(t.Context(), &createReq)
  1578  		require.NoError(t, err)
  1579  		require.NotNil(t, app)
  1580  		assert.Equal(t, "updated", app.Spec.Source.Name)
  1581  	})
  1582  	t.Run("Can update application project", func(t *testing.T) {
  1583  		t.Parallel()
  1584  		testApp := newTestApp()
  1585  		appServer := newTestAppServer(t, testApp)
  1586  
  1587  		newApp := newTestAppWithDestName()
  1588  		newApp.TypeMeta = testApp.TypeMeta
  1589  		newApp.Spec.Project = "my-proj"
  1590  		createReq := application.ApplicationCreateRequest{
  1591  			Application: newApp,
  1592  			Upsert:      ptr.To(true),
  1593  		}
  1594  		app, err := appServer.Create(t.Context(), &createReq)
  1595  		require.NoError(t, err)
  1596  		require.NotNil(t, app)
  1597  		assert.Equal(t, "my-proj", app.Spec.Project)
  1598  	})
  1599  	t.Run("Existing label and annotations are preserved", func(t *testing.T) {
  1600  		t.Parallel()
  1601  		testApp := newTestApp()
  1602  		testApp.Annotations = map[string]string{"test": "test-value", "update": "old"}
  1603  		testApp.Labels = map[string]string{"test": "test-value", "update": "old"}
  1604  		appServer := newTestAppServer(t, testApp)
  1605  
  1606  		newApp := newTestAppWithDestName()
  1607  		newApp.TypeMeta = testApp.TypeMeta
  1608  		newApp.Annotations = map[string]string{"update": "new"}
  1609  		newApp.Labels = map[string]string{"update": "new"}
  1610  		createReq := application.ApplicationCreateRequest{
  1611  			Application: newApp,
  1612  			Upsert:      ptr.To(true),
  1613  		}
  1614  		app, err := appServer.Create(t.Context(), &createReq)
  1615  		require.NoError(t, err)
  1616  		require.NotNil(t, app)
  1617  		assert.Len(t, app.Annotations, 2)
  1618  		assert.Equal(t, "new", app.GetAnnotations()["update"])
  1619  		assert.Len(t, app.Labels, 2)
  1620  		assert.Equal(t, "new", app.GetLabels()["update"])
  1621  	})
  1622  }
  1623  
  1624  func TestUpdateApp(t *testing.T) {
  1625  	t.Parallel()
  1626  	t.Run("Same spec", func(t *testing.T) {
  1627  		t.Parallel()
  1628  		testApp := newTestApp()
  1629  		appServer := newTestAppServer(t, testApp)
  1630  		testApp.Spec.Project = ""
  1631  		app, err := appServer.Update(t.Context(), &application.ApplicationUpdateRequest{
  1632  			Application: testApp,
  1633  		})
  1634  		require.NoError(t, err)
  1635  		assert.Equal(t, "default", app.Spec.Project)
  1636  	})
  1637  	t.Run("Invalid existing app can be updated", func(t *testing.T) {
  1638  		t.Parallel()
  1639  		testApp := newTestApp()
  1640  		testApp.Spec.Destination.Server = "https://invalid-cluster"
  1641  		appServer := newTestAppServer(t, testApp)
  1642  
  1643  		updateApp := newTestAppWithDestName()
  1644  		updateApp.TypeMeta = testApp.TypeMeta
  1645  		updateApp.Spec.Source.Name = "updated"
  1646  		app, err := appServer.Update(t.Context(), &application.ApplicationUpdateRequest{
  1647  			Application: updateApp,
  1648  		})
  1649  		require.NoError(t, err)
  1650  		require.NotNil(t, app)
  1651  		assert.Equal(t, "updated", app.Spec.Source.Name)
  1652  	})
  1653  	t.Run("Can update application project from invalid", func(t *testing.T) {
  1654  		t.Parallel()
  1655  		testApp := newTestApp()
  1656  		restrictedProj := &v1alpha1.AppProject{
  1657  			ObjectMeta: metav1.ObjectMeta{Name: "restricted-proj", Namespace: "default"},
  1658  			Spec: v1alpha1.AppProjectSpec{
  1659  				SourceRepos:  []string{"not-your-repo"},
  1660  				Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "not-your-namespace"}},
  1661  			},
  1662  		}
  1663  		testApp.Spec.Project = restrictedProj.Name
  1664  		appServer := newTestAppServer(t, testApp, restrictedProj)
  1665  
  1666  		updateApp := newTestAppWithDestName()
  1667  		updateApp.TypeMeta = testApp.TypeMeta
  1668  		updateApp.Spec.Project = "my-proj"
  1669  		app, err := appServer.Update(t.Context(), &application.ApplicationUpdateRequest{
  1670  			Application: updateApp,
  1671  		})
  1672  		require.NoError(t, err)
  1673  		require.NotNil(t, app)
  1674  		assert.Equal(t, "my-proj", app.Spec.Project)
  1675  	})
  1676  	t.Run("Cannot update application project to invalid", func(t *testing.T) {
  1677  		t.Parallel()
  1678  		testApp := newTestApp()
  1679  		restrictedProj := &v1alpha1.AppProject{
  1680  			ObjectMeta: metav1.ObjectMeta{Name: "restricted-proj", Namespace: "default"},
  1681  			Spec: v1alpha1.AppProjectSpec{
  1682  				SourceRepos:  []string{"not-your-repo"},
  1683  				Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "not-your-namespace"}},
  1684  			},
  1685  		}
  1686  		appServer := newTestAppServer(t, testApp, restrictedProj)
  1687  
  1688  		updateApp := newTestAppWithDestName()
  1689  		updateApp.TypeMeta = testApp.TypeMeta
  1690  		updateApp.Spec.Project = restrictedProj.Name
  1691  		_, err := appServer.Update(t.Context(), &application.ApplicationUpdateRequest{
  1692  			Application: updateApp,
  1693  		})
  1694  		require.Error(t, err)
  1695  		require.ErrorContains(t, err, "application repo https://github.com/argoproj/argocd-example-apps.git is not permitted in project 'restricted-proj'")
  1696  		require.ErrorContains(t, err, "application destination server 'fake-cluster' and namespace 'fake-dest-ns' do not match any of the allowed destinations in project 'restricted-proj'")
  1697  	})
  1698  	t.Run("Cannot update application project to inexisting", func(t *testing.T) {
  1699  		t.Parallel()
  1700  		testApp := newTestApp()
  1701  		appServer := newTestAppServer(t, testApp)
  1702  
  1703  		updateApp := newTestAppWithDestName()
  1704  		updateApp.TypeMeta = testApp.TypeMeta
  1705  		updateApp.Spec.Project = "i-do-not-exist"
  1706  		_, err := appServer.Update(t.Context(), &application.ApplicationUpdateRequest{
  1707  			Application: updateApp,
  1708  		})
  1709  		require.Error(t, err)
  1710  		require.ErrorContains(t, err, "app is not allowed in project \"i-do-not-exist\", or the project does not exist")
  1711  	})
  1712  	t.Run("Can update application project with project argument", func(t *testing.T) {
  1713  		t.Parallel()
  1714  		testApp := newTestApp()
  1715  		appServer := newTestAppServer(t, testApp)
  1716  
  1717  		updateApp := newTestAppWithDestName()
  1718  		updateApp.TypeMeta = testApp.TypeMeta
  1719  		updateApp.Spec.Project = "my-proj"
  1720  		app, err := appServer.Update(t.Context(), &application.ApplicationUpdateRequest{
  1721  			Application: updateApp,
  1722  			Project:     ptr.To("default"),
  1723  		})
  1724  		require.NoError(t, err)
  1725  		require.NotNil(t, app)
  1726  		assert.Equal(t, "my-proj", app.Spec.Project)
  1727  	})
  1728  	t.Run("Existing label and annotations are replaced", func(t *testing.T) {
  1729  		t.Parallel()
  1730  		testApp := newTestApp()
  1731  		testApp.Annotations = map[string]string{"test": "test-value", "update": "old"}
  1732  		testApp.Labels = map[string]string{"test": "test-value", "update": "old"}
  1733  		appServer := newTestAppServer(t, testApp)
  1734  
  1735  		updateApp := newTestAppWithDestName()
  1736  		updateApp.TypeMeta = testApp.TypeMeta
  1737  		updateApp.Annotations = map[string]string{"update": "new"}
  1738  		updateApp.Labels = map[string]string{"update": "new"}
  1739  		app, err := appServer.Update(t.Context(), &application.ApplicationUpdateRequest{
  1740  			Application: updateApp,
  1741  		})
  1742  		require.NoError(t, err)
  1743  		require.NotNil(t, app)
  1744  		assert.Len(t, app.Annotations, 1)
  1745  		assert.Equal(t, "new", app.GetAnnotations()["update"])
  1746  		assert.Len(t, app.Labels, 1)
  1747  		assert.Equal(t, "new", app.GetLabels()["update"])
  1748  	})
  1749  }
  1750  
  1751  func TestUpdateAppSpec(t *testing.T) {
  1752  	testApp := newTestApp()
  1753  	appServer := newTestAppServer(t, testApp)
  1754  	testApp.Spec.Project = ""
  1755  	spec, err := appServer.UpdateSpec(t.Context(), &application.ApplicationUpdateSpecRequest{
  1756  		Name: &testApp.Name,
  1757  		Spec: &testApp.Spec,
  1758  	})
  1759  	require.NoError(t, err)
  1760  	assert.Equal(t, "default", spec.Project)
  1761  	app, err := appServer.Get(t.Context(), &application.ApplicationQuery{Name: &testApp.Name})
  1762  	require.NoError(t, err)
  1763  	assert.Equal(t, "default", app.Spec.Project)
  1764  }
  1765  
  1766  func TestDeleteApp(t *testing.T) {
  1767  	ctx := t.Context()
  1768  	appServer := newTestAppServer(t)
  1769  	createReq := application.ApplicationCreateRequest{
  1770  		Application: newTestApp(),
  1771  	}
  1772  	app, err := appServer.Create(ctx, &createReq)
  1773  	require.NoError(t, err)
  1774  
  1775  	app, err = appServer.Get(ctx, &application.ApplicationQuery{Name: &app.Name})
  1776  	require.NoError(t, err)
  1777  	assert.NotNil(t, app)
  1778  
  1779  	fakeAppCs := appServer.appclientset.(*deepCopyAppClientset).GetUnderlyingClientSet().(*apps.Clientset)
  1780  	// this removes the default */* reactor so we can set our own patch/delete reactor
  1781  	fakeAppCs.ReactionChain = nil
  1782  	patched := false
  1783  	deleted := false
  1784  	fakeAppCs.AddReactor("patch", "applications", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1785  		patched = true
  1786  		return true, nil, nil
  1787  	})
  1788  	fakeAppCs.AddReactor("delete", "applications", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1789  		deleted = true
  1790  		return true, nil, nil
  1791  	})
  1792  	fakeAppCs.AddReactor("get", "applications", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  1793  		return true, &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Source: &v1alpha1.ApplicationSource{}}}, nil
  1794  	})
  1795  	appServer.appclientset = fakeAppCs
  1796  
  1797  	trueVar := true
  1798  	_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &trueVar})
  1799  	require.NoError(t, err)
  1800  	assert.True(t, patched)
  1801  	assert.True(t, deleted)
  1802  
  1803  	// now call delete with cascade=false. patch should not be called
  1804  	falseVar := false
  1805  	patched = false
  1806  	deleted = false
  1807  	_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &falseVar})
  1808  	require.NoError(t, err)
  1809  	assert.False(t, patched)
  1810  	assert.True(t, deleted)
  1811  
  1812  	patched = false
  1813  	deleted = false
  1814  	revertValues := func() {
  1815  		patched = false
  1816  		deleted = false
  1817  	}
  1818  
  1819  	t.Run("Delete with background propagation policy", func(t *testing.T) {
  1820  		policy := backgroundPropagationPolicy
  1821  		_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, PropagationPolicy: &policy})
  1822  		require.NoError(t, err)
  1823  		assert.True(t, patched)
  1824  		assert.True(t, deleted)
  1825  		t.Cleanup(revertValues)
  1826  	})
  1827  
  1828  	t.Run("Delete with cascade disabled and background propagation policy", func(t *testing.T) {
  1829  		policy := backgroundPropagationPolicy
  1830  		_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &falseVar, PropagationPolicy: &policy})
  1831  		require.EqualError(t, err, "rpc error: code = InvalidArgument desc = cannot set propagation policy when cascading is disabled")
  1832  		assert.False(t, patched)
  1833  		assert.False(t, deleted)
  1834  		t.Cleanup(revertValues)
  1835  	})
  1836  
  1837  	t.Run("Delete with invalid propagation policy", func(t *testing.T) {
  1838  		invalidPolicy := "invalid"
  1839  		_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &trueVar, PropagationPolicy: &invalidPolicy})
  1840  		require.EqualError(t, err, "rpc error: code = InvalidArgument desc = invalid propagation policy: invalid")
  1841  		assert.False(t, patched)
  1842  		assert.False(t, deleted)
  1843  		t.Cleanup(revertValues)
  1844  	})
  1845  
  1846  	t.Run("Delete with foreground propagation policy", func(t *testing.T) {
  1847  		policy := foregroundPropagationPolicy
  1848  		_, err = appServer.Delete(ctx, &application.ApplicationDeleteRequest{Name: &app.Name, Cascade: &trueVar, PropagationPolicy: &policy})
  1849  		require.NoError(t, err)
  1850  		assert.True(t, patched)
  1851  		assert.True(t, deleted)
  1852  		t.Cleanup(revertValues)
  1853  	})
  1854  }
  1855  
  1856  func TestDeleteResourcesRBAC(t *testing.T) {
  1857  	ctx := t.Context()
  1858  	//nolint:staticcheck
  1859  	ctx = context.WithValue(ctx, "claims", &jwt.RegisteredClaims{Subject: "test-user"})
  1860  	testApp := newTestApp()
  1861  	appServer := newTestAppServer(t, testApp)
  1862  	appServer.enf.SetDefaultRole("")
  1863  
  1864  	argoCM := map[string]string{"server.rbac.disableApplicationFineGrainedRBACInheritance": "false"}
  1865  	appServerWithRBACInheritance := newTestAppServerWithEnforcerConfigure(t, func(_ *rbac.Enforcer) {}, argoCM, testApp)
  1866  	appServerWithRBACInheritance.enf.SetDefaultRole("")
  1867  
  1868  	req := application.ApplicationResourceDeleteRequest{
  1869  		Name:         &testApp.Name,
  1870  		AppNamespace: &testApp.Namespace,
  1871  		Group:        strToPtr("fake.io"),
  1872  		Kind:         strToPtr("PodTest"),
  1873  		Namespace:    strToPtr("fake-ns"),
  1874  		ResourceName: strToPtr("my-pod-test"),
  1875  	}
  1876  
  1877  	expectedErrorWhenDeleteAllowed := "rpc error: code = InvalidArgument desc = PodTest fake.io my-pod-test not found as part of application test-app"
  1878  
  1879  	t.Run("delete with application permission", func(t *testing.T) {
  1880  		_ = appServer.enf.SetBuiltinPolicy(`
  1881  p, test-user, applications, delete, default/test-app, allow
  1882  `)
  1883  		_, err := appServer.DeleteResource(ctx, &req)
  1884  		assert.Equal(t, codes.PermissionDenied.String(), status.Code(err).String())
  1885  	})
  1886  
  1887  	t.Run("delete with application permission with inheritance", func(t *testing.T) {
  1888  		_ = appServerWithRBACInheritance.enf.SetBuiltinPolicy(`
  1889  p, test-user, applications, delete, default/test-app, allow
  1890  `)
  1891  		_, err := appServerWithRBACInheritance.DeleteResource(ctx, &req)
  1892  		assert.EqualError(t, err, expectedErrorWhenDeleteAllowed)
  1893  	})
  1894  
  1895  	t.Run("delete with application permission but deny subresource", func(t *testing.T) {
  1896  		_ = appServer.enf.SetBuiltinPolicy(`
  1897  p, test-user, applications, delete, default/test-app, allow
  1898  p, test-user, applications, delete/*, default/test-app, deny
  1899  `)
  1900  		_, err := appServer.DeleteResource(ctx, &req)
  1901  		assert.Equal(t, codes.PermissionDenied.String(), status.Code(err).String())
  1902  	})
  1903  
  1904  	t.Run("delete with application permission but deny subresource with inheritance", func(t *testing.T) {
  1905  		_ = appServerWithRBACInheritance.enf.SetBuiltinPolicy(`
  1906  p, test-user, applications, delete, default/test-app, allow
  1907  p, test-user, applications, delete/*, default/test-app, deny
  1908  `)
  1909  		_, err := appServerWithRBACInheritance.DeleteResource(ctx, &req)
  1910  		assert.EqualError(t, err, expectedErrorWhenDeleteAllowed)
  1911  	})
  1912  
  1913  	t.Run("delete with subresource", func(t *testing.T) {
  1914  		_ = appServer.enf.SetBuiltinPolicy(`
  1915  p, test-user, applications, delete/*, default/test-app, allow
  1916  `)
  1917  		_, err := appServer.DeleteResource(ctx, &req)
  1918  		assert.EqualError(t, err, expectedErrorWhenDeleteAllowed)
  1919  	})
  1920  
  1921  	t.Run("delete with subresource but deny applications", func(t *testing.T) {
  1922  		_ = appServer.enf.SetBuiltinPolicy(`
  1923  p, test-user, applications, delete, default/test-app, deny
  1924  p, test-user, applications, delete/*, default/test-app, allow
  1925  `)
  1926  		_, err := appServer.DeleteResource(ctx, &req)
  1927  		assert.EqualError(t, err, expectedErrorWhenDeleteAllowed)
  1928  	})
  1929  
  1930  	t.Run("delete with subresource but deny applications with inheritance", func(t *testing.T) {
  1931  		_ = appServerWithRBACInheritance.enf.SetBuiltinPolicy(`
  1932  p, test-user, applications, delete, default/test-app, deny
  1933  p, test-user, applications, delete/*, default/test-app, allow
  1934  `)
  1935  		_, err := appServerWithRBACInheritance.DeleteResource(ctx, &req)
  1936  		assert.EqualError(t, err, expectedErrorWhenDeleteAllowed)
  1937  	})
  1938  
  1939  	t.Run("delete with specific subresource denied", func(t *testing.T) {
  1940  		_ = appServer.enf.SetBuiltinPolicy(`
  1941  p, test-user, applications, delete/*, default/test-app, allow
  1942  p, test-user, applications, delete/fake.io/PodTest/*, default/test-app, deny
  1943  `)
  1944  		_, err := appServer.DeleteResource(ctx, &req)
  1945  		assert.Equal(t, codes.PermissionDenied.String(), status.Code(err).String())
  1946  	})
  1947  }
  1948  
  1949  func TestPatchResourcesRBAC(t *testing.T) {
  1950  	ctx := t.Context()
  1951  	//nolint:staticcheck
  1952  	ctx = context.WithValue(ctx, "claims", &jwt.RegisteredClaims{Subject: "test-user"})
  1953  	testApp := newTestApp()
  1954  	appServer := newTestAppServer(t, testApp)
  1955  	appServer.enf.SetDefaultRole("")
  1956  
  1957  	argoCM := map[string]string{"server.rbac.disableApplicationFineGrainedRBACInheritance": "false"}
  1958  	appServerWithRBACInheritance := newTestAppServerWithEnforcerConfigure(t, func(_ *rbac.Enforcer) {}, argoCM, testApp)
  1959  	appServerWithRBACInheritance.enf.SetDefaultRole("")
  1960  
  1961  	req := application.ApplicationResourcePatchRequest{
  1962  		Name:         &testApp.Name,
  1963  		AppNamespace: &testApp.Namespace,
  1964  		Group:        strToPtr("fake.io"),
  1965  		Kind:         strToPtr("PodTest"),
  1966  		Namespace:    strToPtr("fake-ns"),
  1967  		ResourceName: strToPtr("my-pod-test"),
  1968  	}
  1969  
  1970  	expectedErrorWhenUpdateAllowed := "rpc error: code = InvalidArgument desc = PodTest fake.io my-pod-test not found as part of application test-app"
  1971  
  1972  	t.Run("patch with application permission", func(t *testing.T) {
  1973  		_ = appServer.enf.SetBuiltinPolicy(`
  1974  p, test-user, applications, update, default/test-app, allow
  1975  `)
  1976  		_, err := appServer.PatchResource(ctx, &req)
  1977  		assert.Equal(t, codes.PermissionDenied.String(), status.Code(err).String())
  1978  	})
  1979  
  1980  	t.Run("patch with application permission with inheritance", func(t *testing.T) {
  1981  		_ = appServerWithRBACInheritance.enf.SetBuiltinPolicy(`
  1982  p, test-user, applications, update, default/test-app, allow
  1983  `)
  1984  		_, err := appServerWithRBACInheritance.PatchResource(ctx, &req)
  1985  		assert.EqualError(t, err, expectedErrorWhenUpdateAllowed)
  1986  	})
  1987  
  1988  	t.Run("patch with application permission but deny subresource", func(t *testing.T) {
  1989  		_ = appServer.enf.SetBuiltinPolicy(`
  1990  p, test-user, applications, update, default/test-app, allow
  1991  p, test-user, applications, update/*, default/test-app, deny
  1992  `)
  1993  		_, err := appServer.PatchResource(ctx, &req)
  1994  		assert.Equal(t, codes.PermissionDenied.String(), status.Code(err).String())
  1995  	})
  1996  
  1997  	t.Run("patch with application permission but deny subresource with inheritance", func(t *testing.T) {
  1998  		_ = appServerWithRBACInheritance.enf.SetBuiltinPolicy(`
  1999  p, test-user, applications, update, default/test-app, allow
  2000  p, test-user, applications, update/*, default/test-app, deny
  2001  `)
  2002  		_, err := appServerWithRBACInheritance.PatchResource(ctx, &req)
  2003  		assert.EqualError(t, err, expectedErrorWhenUpdateAllowed)
  2004  	})
  2005  
  2006  	t.Run("patch with subresource", func(t *testing.T) {
  2007  		_ = appServer.enf.SetBuiltinPolicy(`
  2008  p, test-user, applications, update/*, default/test-app, allow
  2009  `)
  2010  		_, err := appServer.PatchResource(ctx, &req)
  2011  		assert.EqualError(t, err, expectedErrorWhenUpdateAllowed)
  2012  	})
  2013  
  2014  	t.Run("patch with subresource but deny applications", func(t *testing.T) {
  2015  		_ = appServer.enf.SetBuiltinPolicy(`
  2016  p, test-user, applications, update, default/test-app, deny
  2017  p, test-user, applications, update/*, default/test-app, allow
  2018  `)
  2019  		_, err := appServer.PatchResource(ctx, &req)
  2020  		assert.EqualError(t, err, expectedErrorWhenUpdateAllowed)
  2021  	})
  2022  
  2023  	t.Run("patch with subresource but deny applications with inheritance", func(t *testing.T) {
  2024  		_ = appServerWithRBACInheritance.enf.SetBuiltinPolicy(`
  2025  p, test-user, applications, update, default/test-app, deny
  2026  p, test-user, applications, update/*, default/test-app, allow
  2027  `)
  2028  		_, err := appServerWithRBACInheritance.PatchResource(ctx, &req)
  2029  		assert.EqualError(t, err, expectedErrorWhenUpdateAllowed)
  2030  	})
  2031  
  2032  	t.Run("patch with specific subresource denied", func(t *testing.T) {
  2033  		_ = appServer.enf.SetBuiltinPolicy(`
  2034  p, test-user, applications, update/*, default/test-app, allow
  2035  p, test-user, applications, update/fake.io/PodTest/*, default/test-app, deny
  2036  `)
  2037  		_, err := appServer.PatchResource(ctx, &req)
  2038  		assert.Equal(t, codes.PermissionDenied.String(), status.Code(err).String())
  2039  	})
  2040  }
  2041  
  2042  func TestSyncAndTerminate(t *testing.T) {
  2043  	ctx := t.Context()
  2044  	appServer := newTestAppServer(t)
  2045  	testApp := newTestApp()
  2046  	testApp.Spec.Source.RepoURL = "https://github.com/argoproj/argo-cd.git"
  2047  	createReq := application.ApplicationCreateRequest{
  2048  		Application: testApp,
  2049  	}
  2050  	app, err := appServer.Create(ctx, &createReq)
  2051  	require.NoError(t, err)
  2052  	app, err = appServer.Sync(ctx, &application.ApplicationSyncRequest{Name: &app.Name})
  2053  	require.NoError(t, err)
  2054  	assert.NotNil(t, app)
  2055  	assert.NotNil(t, app.Operation)
  2056  	assert.Equal(t, testApp.Spec.GetSource(), *app.Operation.Sync.Source)
  2057  
  2058  	events, err := appServer.kubeclientset.CoreV1().Events(appServer.ns).List(t.Context(), metav1.ListOptions{})
  2059  	require.NoError(t, err)
  2060  	event := events.Items[1]
  2061  
  2062  	assert.Regexp(t, ".*initiated sync to HEAD \\([0-9A-Fa-f]{40}\\).*", event.Message)
  2063  
  2064  	// set status.operationState to pretend that an operation has started by controller
  2065  	app.Status.OperationState = &v1alpha1.OperationState{
  2066  		Operation: *app.Operation,
  2067  		Phase:     synccommon.OperationRunning,
  2068  		StartedAt: metav1.NewTime(time.Now()),
  2069  	}
  2070  	_, err = appServer.appclientset.ArgoprojV1alpha1().Applications(appServer.ns).Update(t.Context(), app, metav1.UpdateOptions{})
  2071  	require.NoError(t, err)
  2072  
  2073  	resp, err := appServer.TerminateOperation(ctx, &application.OperationTerminateRequest{Name: &app.Name})
  2074  	require.NoError(t, err)
  2075  	assert.NotNil(t, resp)
  2076  
  2077  	app, err = appServer.Get(ctx, &application.ApplicationQuery{Name: &app.Name})
  2078  	require.NoError(t, err)
  2079  	assert.NotNil(t, app)
  2080  	assert.Equal(t, synccommon.OperationTerminating, app.Status.OperationState.Phase)
  2081  }
  2082  
  2083  func TestSyncHelm(t *testing.T) {
  2084  	ctx := t.Context()
  2085  	appServer := newTestAppServer(t)
  2086  	testApp := newTestApp()
  2087  	testApp.Spec.Source.RepoURL = "https://argoproj.github.io/argo-helm"
  2088  	testApp.Spec.Source.Path = ""
  2089  	testApp.Spec.Source.Chart = "argo-cd"
  2090  	testApp.Spec.Source.TargetRevision = "0.7.*"
  2091  
  2092  	appServer.repoClientset = &mocks.Clientset{RepoServerServiceClient: fakeRepoServerClient(true)}
  2093  
  2094  	app, err := appServer.Create(ctx, &application.ApplicationCreateRequest{Application: testApp})
  2095  	require.NoError(t, err)
  2096  
  2097  	app, err = appServer.Sync(ctx, &application.ApplicationSyncRequest{Name: &app.Name})
  2098  	require.NoError(t, err)
  2099  	assert.NotNil(t, app)
  2100  	assert.NotNil(t, app.Operation)
  2101  
  2102  	events, err := appServer.kubeclientset.CoreV1().Events(appServer.ns).List(t.Context(), metav1.ListOptions{})
  2103  	require.NoError(t, err)
  2104  	assert.Equal(t, "Unknown user initiated sync to 0.7.* (0.7.2)", events.Items[1].Message)
  2105  }
  2106  
  2107  func TestSyncGit(t *testing.T) {
  2108  	ctx := t.Context()
  2109  	appServer := newTestAppServer(t)
  2110  	testApp := newTestApp()
  2111  	testApp.Spec.Source.RepoURL = "https://github.com/org/test"
  2112  	testApp.Spec.Source.Path = "deploy"
  2113  	testApp.Spec.Source.TargetRevision = "0.7.*"
  2114  	app, err := appServer.Create(ctx, &application.ApplicationCreateRequest{Application: testApp})
  2115  	require.NoError(t, err)
  2116  	syncReq := &application.ApplicationSyncRequest{
  2117  		Name: &app.Name,
  2118  		Manifests: []string{
  2119  			`apiVersion: v1
  2120  			kind: ServiceAccount
  2121  			metadata:
  2122  			  name: test
  2123  			  namespace: test`,
  2124  		},
  2125  	}
  2126  	app, err = appServer.Sync(ctx, syncReq)
  2127  	require.NoError(t, err)
  2128  	assert.NotNil(t, app)
  2129  	assert.NotNil(t, app.Operation)
  2130  	events, err := appServer.kubeclientset.CoreV1().Events(appServer.ns).List(t.Context(), metav1.ListOptions{})
  2131  	require.NoError(t, err)
  2132  	assert.Equal(t, "Unknown user initiated sync locally", events.Items[1].Message)
  2133  }
  2134  
  2135  func TestSync_WithRefresh(t *testing.T) {
  2136  	ctx := t.Context()
  2137  	appServer := newTestAppServer(t)
  2138  	testApp := newTestApp()
  2139  	testApp.Spec.SyncPolicy = &v1alpha1.SyncPolicy{
  2140  		Retry: &v1alpha1.RetryStrategy{
  2141  			Refresh: true,
  2142  		},
  2143  	}
  2144  	testApp.Spec.Source.RepoURL = "https://github.com/argoproj/argo-cd.git"
  2145  	createReq := application.ApplicationCreateRequest{
  2146  		Application: testApp,
  2147  	}
  2148  	app, err := appServer.Create(ctx, &createReq)
  2149  	require.NoError(t, err)
  2150  	app, err = appServer.Sync(ctx, &application.ApplicationSyncRequest{Name: &app.Name})
  2151  	require.NoError(t, err)
  2152  	assert.NotNil(t, app)
  2153  	assert.NotNil(t, app.Operation)
  2154  	assert.True(t, app.Operation.Retry.Refresh)
  2155  }
  2156  
  2157  func TestGetManifests_WithNoCache(t *testing.T) {
  2158  	testApp := newTestApp()
  2159  	appServer := newTestAppServer(t, testApp)
  2160  
  2161  	mockRepoServiceClient := mocks.RepoServerServiceClient{}
  2162  	mockRepoServiceClient.On("GenerateManifest", mock.Anything, mock.MatchedBy(func(mr *apiclient.ManifestRequest) bool {
  2163  		// expected to be true because given NoCache in the ApplicationManifestQuery
  2164  		return mr.NoCache
  2165  	})).Return(&apiclient.ManifestResponse{}, nil)
  2166  
  2167  	appServer.repoClientset = &mocks.Clientset{RepoServerServiceClient: &mockRepoServiceClient}
  2168  
  2169  	_, err := appServer.GetManifests(t.Context(), &application.ApplicationManifestQuery{
  2170  		Name:    &testApp.Name,
  2171  		NoCache: ptr.To(true),
  2172  	})
  2173  	require.NoError(t, err)
  2174  	mockRepoServiceClient.AssertExpectations(t)
  2175  }
  2176  
  2177  func TestRollbackApp(t *testing.T) {
  2178  	testApp := newTestApp()
  2179  	testApp.Status.History = []v1alpha1.RevisionHistory{{
  2180  		ID:        1,
  2181  		Revision:  "abc",
  2182  		Revisions: []string{"abc"},
  2183  		Source:    *testApp.Spec.Source.DeepCopy(),
  2184  		Sources:   []v1alpha1.ApplicationSource{*testApp.Spec.Source.DeepCopy()},
  2185  	}}
  2186  	appServer := newTestAppServer(t, testApp)
  2187  
  2188  	updatedApp, err := appServer.Rollback(t.Context(), &application.ApplicationRollbackRequest{
  2189  		Name: &testApp.Name,
  2190  		Id:   ptr.To(int64(1)),
  2191  	})
  2192  
  2193  	require.NoError(t, err)
  2194  
  2195  	assert.NotNil(t, updatedApp.Operation)
  2196  	assert.NotNil(t, updatedApp.Operation.Sync)
  2197  	assert.NotNil(t, updatedApp.Operation.Sync.Source)
  2198  	assert.Equal(t, testApp.Status.History[0].Source, *updatedApp.Operation.Sync.Source)
  2199  	assert.Equal(t, testApp.Status.History[0].Sources, updatedApp.Operation.Sync.Sources)
  2200  	assert.Equal(t, testApp.Status.History[0].Revision, updatedApp.Operation.Sync.Revision)
  2201  	assert.Equal(t, testApp.Status.History[0].Revisions, updatedApp.Operation.Sync.Revisions)
  2202  }
  2203  
  2204  func TestRollbackApp_WithRefresh(t *testing.T) {
  2205  	testApp := newTestApp()
  2206  	testApp.Spec.SyncPolicy = &v1alpha1.SyncPolicy{
  2207  		Retry: &v1alpha1.RetryStrategy{
  2208  			Refresh: true,
  2209  		},
  2210  	}
  2211  
  2212  	testApp.Status.History = []v1alpha1.RevisionHistory{{
  2213  		ID:       1,
  2214  		Revision: "abc",
  2215  		Source:   *testApp.Spec.Source.DeepCopy(),
  2216  	}}
  2217  	appServer := newTestAppServer(t, testApp)
  2218  
  2219  	updatedApp, err := appServer.Rollback(t.Context(), &application.ApplicationRollbackRequest{
  2220  		Name: &testApp.Name,
  2221  		Id:   ptr.To(int64(1)),
  2222  	})
  2223  
  2224  	require.NoError(t, err)
  2225  
  2226  	assert.NotNil(t, updatedApp.Operation)
  2227  	assert.NotNil(t, updatedApp.Operation.Retry)
  2228  	assert.False(t, updatedApp.Operation.Retry.Refresh, "refresh should never be set on rollback")
  2229  }
  2230  
  2231  func TestUpdateAppProject(t *testing.T) {
  2232  	testApp := newTestApp()
  2233  	ctx := t.Context()
  2234  	//nolint:staticcheck
  2235  	ctx = context.WithValue(ctx, "claims", &jwt.RegisteredClaims{Subject: "admin"})
  2236  	appServer := newTestAppServer(t, testApp)
  2237  	appServer.enf.SetDefaultRole("")
  2238  
  2239  	t.Run("update without changing project", func(t *testing.T) {
  2240  		_ = appServer.enf.SetBuiltinPolicy(`p, admin, applications, update, default/test-app, allow`)
  2241  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  2242  		require.NoError(t, err)
  2243  	})
  2244  
  2245  	t.Run("cannot update to another project", func(t *testing.T) {
  2246  		testApp.Spec.Project = "my-proj"
  2247  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  2248  		assert.Equal(t, codes.PermissionDenied, status.Code(err))
  2249  	})
  2250  
  2251  	t.Run("cannot change projects without create privileges", func(t *testing.T) {
  2252  		_ = appServer.enf.SetBuiltinPolicy(`
  2253  p, admin, applications, update, default/test-app, allow
  2254  p, admin, applications, update, my-proj/test-app, allow
  2255  `)
  2256  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  2257  		statusErr := grpc.UnwrapGRPCStatus(err)
  2258  		assert.NotNil(t, statusErr)
  2259  		assert.Equal(t, codes.PermissionDenied, statusErr.Code())
  2260  	})
  2261  
  2262  	t.Run("cannot change projects without update privileges in new project", func(t *testing.T) {
  2263  		_ = appServer.enf.SetBuiltinPolicy(`
  2264  p, admin, applications, update, default/test-app, allow
  2265  p, admin, applications, create, my-proj/test-app, allow
  2266  `)
  2267  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  2268  		assert.Equal(t, codes.PermissionDenied, status.Code(err))
  2269  	})
  2270  
  2271  	t.Run("cannot change projects without update privileges in old project", func(t *testing.T) {
  2272  		_ = appServer.enf.SetBuiltinPolicy(`
  2273  p, admin, applications, create, my-proj/test-app, allow
  2274  p, admin, applications, update, my-proj/test-app, allow
  2275  `)
  2276  		_, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  2277  		statusErr := grpc.UnwrapGRPCStatus(err)
  2278  		assert.NotNil(t, statusErr)
  2279  		assert.Equal(t, codes.PermissionDenied, statusErr.Code())
  2280  	})
  2281  
  2282  	t.Run("can update project with proper permissions", func(t *testing.T) {
  2283  		// Verify can update project with proper permissions
  2284  		_ = appServer.enf.SetBuiltinPolicy(`
  2285  p, admin, applications, update, default/test-app, allow
  2286  p, admin, applications, create, my-proj/test-app, allow
  2287  p, admin, applications, update, my-proj/test-app, allow
  2288  `)
  2289  		updatedApp, err := appServer.Update(ctx, &application.ApplicationUpdateRequest{Application: testApp})
  2290  		require.NoError(t, err)
  2291  		assert.Equal(t, "my-proj", updatedApp.Spec.Project)
  2292  	})
  2293  }
  2294  
  2295  func TestAppJsonPatch(t *testing.T) {
  2296  	testApp := newTestAppWithAnnotations()
  2297  	ctx := t.Context()
  2298  	//nolint:staticcheck
  2299  	ctx = context.WithValue(ctx, "claims", &jwt.RegisteredClaims{Subject: "admin"})
  2300  	appServer := newTestAppServer(t, testApp)
  2301  	appServer.enf.SetDefaultRole("")
  2302  
  2303  	app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: ptr.To("garbage")})
  2304  	require.Error(t, err)
  2305  	assert.Nil(t, app)
  2306  
  2307  	app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: ptr.To("[]")})
  2308  	require.NoError(t, err)
  2309  	assert.NotNil(t, app)
  2310  
  2311  	app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: ptr.To(`[{"op": "replace", "path": "/spec/source/path", "value": "foo"}]`)})
  2312  	require.NoError(t, err)
  2313  	assert.Equal(t, "foo", app.Spec.Source.Path)
  2314  
  2315  	app, err = appServer.Patch(ctx, &application.ApplicationPatchRequest{Name: &testApp.Name, Patch: ptr.To(`[{"op": "remove", "path": "/metadata/annotations/test.annotation"}]`)})
  2316  	require.NoError(t, err)
  2317  	assert.NotContains(t, app.Annotations, "test.annotation")
  2318  }
  2319  
  2320  func TestAppMergePatch(t *testing.T) {
  2321  	testApp := newTestApp()
  2322  	ctx := t.Context()
  2323  	//nolint:staticcheck
  2324  	ctx = context.WithValue(ctx, "claims", &jwt.RegisteredClaims{Subject: "admin"})
  2325  	appServer := newTestAppServer(t, testApp)
  2326  	appServer.enf.SetDefaultRole("")
  2327  
  2328  	app, err := appServer.Patch(ctx, &application.ApplicationPatchRequest{
  2329  		Name: &testApp.Name, Patch: ptr.To(`{"spec": { "source": { "path": "foo" } }}`), PatchType: ptr.To("merge"),
  2330  	})
  2331  	require.NoError(t, err)
  2332  	assert.Equal(t, "foo", app.Spec.Source.Path)
  2333  }
  2334  
  2335  func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
  2336  	t.Run("Active", func(t *testing.T) {
  2337  		testApp := newTestApp()
  2338  		testApp.Spec.Project = "proj-maint"
  2339  		appServer := newTestAppServer(t, testApp)
  2340  
  2341  		active, err := appServer.GetApplicationSyncWindows(t.Context(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
  2342  		require.NoError(t, err)
  2343  		assert.Len(t, active.ActiveWindows, 1)
  2344  	})
  2345  	t.Run("Inactive", func(t *testing.T) {
  2346  		testApp := newTestApp()
  2347  		testApp.Spec.Project = "default"
  2348  		appServer := newTestAppServer(t, testApp)
  2349  
  2350  		active, err := appServer.GetApplicationSyncWindows(t.Context(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
  2351  		require.NoError(t, err)
  2352  		assert.Empty(t, active.ActiveWindows)
  2353  	})
  2354  	t.Run("ProjectDoesNotExist", func(t *testing.T) {
  2355  		testApp := newTestApp()
  2356  		testApp.Spec.Project = "none"
  2357  		appServer := newTestAppServer(t, testApp)
  2358  
  2359  		active, err := appServer.GetApplicationSyncWindows(t.Context(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
  2360  		require.ErrorContains(t, err, "not exist")
  2361  		assert.Nil(t, active)
  2362  	})
  2363  }
  2364  
  2365  func TestGetCachedAppState(t *testing.T) {
  2366  	testApp := newTestApp()
  2367  	testApp.ResourceVersion = "1"
  2368  	testApp.Spec.Project = "test-proj"
  2369  	testProj := &v1alpha1.AppProject{
  2370  		ObjectMeta: metav1.ObjectMeta{
  2371  			Name:      "test-proj",
  2372  			Namespace: testNamespace,
  2373  		},
  2374  	}
  2375  	appServer := newTestAppServer(t, testApp, testProj)
  2376  	fakeClientSet := appServer.appclientset.(*deepCopyAppClientset).GetUnderlyingClientSet().(*apps.Clientset)
  2377  	fakeClientSet.AddReactor("get", "applications", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  2378  		return true, &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Source: &v1alpha1.ApplicationSource{}}}, nil
  2379  	})
  2380  	t.Run("NoError", func(t *testing.T) {
  2381  		err := appServer.getCachedAppState(t.Context(), testApp, func() error {
  2382  			return nil
  2383  		})
  2384  		require.NoError(t, err)
  2385  	})
  2386  	t.Run("CacheMissErrorTriggersRefresh", func(t *testing.T) {
  2387  		retryCount := 0
  2388  		patched := false
  2389  		watcher := watch.NewFakeWithChanSize(1, true)
  2390  
  2391  		// Configure fakeClientSet within lock, before requesting cached app state, to avoid data race
  2392  		fakeClientSet.Lock()
  2393  		fakeClientSet.ReactionChain = nil
  2394  		fakeClientSet.WatchReactionChain = nil
  2395  		fakeClientSet.AddReactor("patch", "applications", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  2396  			patched = true
  2397  			updated := testApp.DeepCopy()
  2398  			updated.ResourceVersion = "2"
  2399  			appServer.appBroadcaster.OnUpdate(testApp, updated)
  2400  			return true, testApp, nil
  2401  		})
  2402  		fakeClientSet.AddReactor("get", "applications", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) {
  2403  			return true, &v1alpha1.Application{Spec: v1alpha1.ApplicationSpec{Source: &v1alpha1.ApplicationSource{}}}, nil
  2404  		})
  2405  		fakeClientSet.Unlock()
  2406  		fakeClientSet.AddWatchReactor("applications", func(_ kubetesting.Action) (handled bool, ret watch.Interface, err error) {
  2407  			return true, watcher, nil
  2408  		})
  2409  
  2410  		err := appServer.getCachedAppState(t.Context(), testApp, func() error {
  2411  			res := cache.ErrCacheMiss
  2412  			if retryCount == 1 {
  2413  				res = nil
  2414  			}
  2415  			retryCount++
  2416  			return res
  2417  		})
  2418  		require.NoError(t, err)
  2419  		assert.Equal(t, 2, retryCount)
  2420  		assert.True(t, patched)
  2421  	})
  2422  
  2423  	t.Run("NonCacheErrorDoesNotTriggerRefresh", func(t *testing.T) {
  2424  		randomError := stderrors.New("random error")
  2425  		err := appServer.getCachedAppState(t.Context(), testApp, func() error {
  2426  			return randomError
  2427  		})
  2428  		assert.Equal(t, randomError, err)
  2429  	})
  2430  }
  2431  
  2432  func TestSplitStatusPatch(t *testing.T) {
  2433  	specPatch := `{"spec":{"aaa":"bbb"}}`
  2434  	statusPatch := `{"status":{"ccc":"ddd"}}`
  2435  	{
  2436  		nonStatus, status, err := splitStatusPatch([]byte(specPatch))
  2437  		require.NoError(t, err)
  2438  		assert.Equal(t, specPatch, string(nonStatus))
  2439  		assert.Nil(t, status)
  2440  	}
  2441  	{
  2442  		nonStatus, status, err := splitStatusPatch([]byte(statusPatch))
  2443  		require.NoError(t, err)
  2444  		assert.Nil(t, nonStatus)
  2445  		assert.Equal(t, statusPatch, string(status))
  2446  	}
  2447  	{
  2448  		bothPatch := `{"spec":{"aaa":"bbb"},"status":{"ccc":"ddd"}}`
  2449  		nonStatus, status, err := splitStatusPatch([]byte(bothPatch))
  2450  		require.NoError(t, err)
  2451  		assert.Equal(t, specPatch, string(nonStatus))
  2452  		assert.Equal(t, statusPatch, string(status))
  2453  	}
  2454  	{
  2455  		otherFields := `{"operation":{"eee":"fff"},"spec":{"aaa":"bbb"},"status":{"ccc":"ddd"}}`
  2456  		nonStatus, status, err := splitStatusPatch([]byte(otherFields))
  2457  		require.NoError(t, err)
  2458  		assert.JSONEq(t, `{"operation":{"eee":"fff"},"spec":{"aaa":"bbb"}}`, string(nonStatus))
  2459  		assert.Equal(t, statusPatch, string(status))
  2460  	}
  2461  }
  2462  
  2463  func TestLogsGetSelectedPod(t *testing.T) {
  2464  	deployment := v1alpha1.ResourceRef{Group: "", Version: "v1", Kind: "Deployment", Name: "deployment", UID: "1"}
  2465  	rs := v1alpha1.ResourceRef{Group: "", Version: "v1", Kind: "ReplicaSet", Name: "rs", UID: "2"}
  2466  	podRS := v1alpha1.ResourceRef{Group: "", Version: "v1", Kind: "Pod", Name: "podrs", UID: "3"}
  2467  	pod := v1alpha1.ResourceRef{Group: "", Version: "v1", Kind: "Pod", Name: "pod", UID: "4"}
  2468  	treeNodes := []v1alpha1.ResourceNode{
  2469  		{ResourceRef: deployment, ParentRefs: nil},
  2470  		{ResourceRef: rs, ParentRefs: []v1alpha1.ResourceRef{deployment}},
  2471  		{ResourceRef: podRS, ParentRefs: []v1alpha1.ResourceRef{rs}},
  2472  		{ResourceRef: pod, ParentRefs: nil},
  2473  	}
  2474  	appName := "appName"
  2475  
  2476  	t.Run("GetAllPods", func(t *testing.T) {
  2477  		podQuery := application.ApplicationPodLogsQuery{
  2478  			Name: &appName,
  2479  		}
  2480  		pods := getSelectedPods(treeNodes, &podQuery)
  2481  		assert.Len(t, pods, 2)
  2482  	})
  2483  
  2484  	t.Run("GetRSPods", func(t *testing.T) {
  2485  		group := ""
  2486  		kind := "ReplicaSet"
  2487  		name := "rs"
  2488  		podQuery := application.ApplicationPodLogsQuery{
  2489  			Name:         &appName,
  2490  			Group:        &group,
  2491  			Kind:         &kind,
  2492  			ResourceName: &name,
  2493  		}
  2494  		pods := getSelectedPods(treeNodes, &podQuery)
  2495  		assert.Len(t, pods, 1)
  2496  	})
  2497  
  2498  	t.Run("GetDeploymentPods", func(t *testing.T) {
  2499  		group := ""
  2500  		kind := "Deployment"
  2501  		name := "deployment"
  2502  		podQuery := application.ApplicationPodLogsQuery{
  2503  			Name:         &appName,
  2504  			Group:        &group,
  2505  			Kind:         &kind,
  2506  			ResourceName: &name,
  2507  		}
  2508  		pods := getSelectedPods(treeNodes, &podQuery)
  2509  		assert.Len(t, pods, 1)
  2510  	})
  2511  
  2512  	t.Run("NoMatchingPods", func(t *testing.T) {
  2513  		group := ""
  2514  		kind := "Service"
  2515  		name := "service"
  2516  		podQuery := application.ApplicationPodLogsQuery{
  2517  			Name:         &appName,
  2518  			Group:        &group,
  2519  			Kind:         &kind,
  2520  			ResourceName: &name,
  2521  		}
  2522  		pods := getSelectedPods(treeNodes, &podQuery)
  2523  		assert.Empty(t, pods)
  2524  	})
  2525  }
  2526  
  2527  func TestMaxPodLogsRender(t *testing.T) {
  2528  	defaultMaxPodLogsToRender, _ := newTestAppServer(t).settingsMgr.GetMaxPodLogsToRender()
  2529  
  2530  	// Case: number of pods to view logs is less than defaultMaxPodLogsToRender
  2531  	podNumber := int(defaultMaxPodLogsToRender - 1)
  2532  	appServer, adminCtx := createAppServerWithMaxLodLogs(t, podNumber)
  2533  
  2534  	t.Run("PodLogs", func(t *testing.T) {
  2535  		err := appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: ptr.To("test")}, &TestPodLogsServer{ctx: adminCtx})
  2536  		statusCode, _ := status.FromError(err)
  2537  		assert.Equal(t, codes.OK, statusCode.Code())
  2538  	})
  2539  
  2540  	// Case: number of pods higher than defaultMaxPodLogsToRender
  2541  	podNumber = int(defaultMaxPodLogsToRender + 1)
  2542  	appServer, adminCtx = createAppServerWithMaxLodLogs(t, podNumber)
  2543  
  2544  	t.Run("PodLogs", func(t *testing.T) {
  2545  		err := appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: ptr.To("test")}, &TestPodLogsServer{ctx: adminCtx})
  2546  		require.Error(t, err)
  2547  		statusCode, _ := status.FromError(err)
  2548  		assert.Equal(t, codes.InvalidArgument, statusCode.Code())
  2549  		assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = max pods to view logs are reached. Please provide more granular query")
  2550  	})
  2551  
  2552  	// Case: number of pods to view logs is less than customMaxPodLogsToRender
  2553  	customMaxPodLogsToRender := int64(15)
  2554  	podNumber = int(customMaxPodLogsToRender - 1)
  2555  	appServer, adminCtx = createAppServerWithMaxLodLogs(t, podNumber, customMaxPodLogsToRender)
  2556  
  2557  	t.Run("PodLogs", func(t *testing.T) {
  2558  		err := appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: ptr.To("test")}, &TestPodLogsServer{ctx: adminCtx})
  2559  		statusCode, _ := status.FromError(err)
  2560  		assert.Equal(t, codes.OK, statusCode.Code())
  2561  	})
  2562  
  2563  	// Case: number of pods higher than customMaxPodLogsToRender
  2564  	customMaxPodLogsToRender = int64(15)
  2565  	podNumber = int(customMaxPodLogsToRender + 1)
  2566  	appServer, adminCtx = createAppServerWithMaxLodLogs(t, podNumber, customMaxPodLogsToRender)
  2567  
  2568  	t.Run("PodLogs", func(t *testing.T) {
  2569  		err := appServer.PodLogs(&application.ApplicationPodLogsQuery{Name: ptr.To("test")}, &TestPodLogsServer{ctx: adminCtx})
  2570  		require.Error(t, err)
  2571  		statusCode, _ := status.FromError(err)
  2572  		assert.Equal(t, codes.InvalidArgument, statusCode.Code())
  2573  		assert.EqualError(t, err, "rpc error: code = InvalidArgument desc = max pods to view logs are reached. Please provide more granular query")
  2574  	})
  2575  }
  2576  
  2577  // createAppServerWithMaxLodLogs creates a new app server with given number of pods and resources
  2578  func createAppServerWithMaxLodLogs(t *testing.T, podNumber int, maxPodLogsToRender ...int64) (*Server, context.Context) {
  2579  	t.Helper()
  2580  	runtimeObjects := make([]runtime.Object, podNumber+1)
  2581  	resources := make([]v1alpha1.ResourceStatus, podNumber)
  2582  
  2583  	for i := 0; i < podNumber; i++ {
  2584  		pod := corev1.Pod{
  2585  			TypeMeta: metav1.TypeMeta{
  2586  				APIVersion: "v1",
  2587  				Kind:       "Pod",
  2588  			},
  2589  			ObjectMeta: metav1.ObjectMeta{
  2590  				Name:      fmt.Sprintf("pod-%d", i),
  2591  				Namespace: "test",
  2592  			},
  2593  		}
  2594  		resources[i] = v1alpha1.ResourceStatus{
  2595  			Group:     pod.GroupVersionKind().Group,
  2596  			Kind:      pod.GroupVersionKind().Kind,
  2597  			Version:   pod.GroupVersionKind().Version,
  2598  			Name:      pod.Name,
  2599  			Namespace: pod.Namespace,
  2600  			Status:    "Synced",
  2601  		}
  2602  		runtimeObjects[i] = kube.MustToUnstructured(&pod)
  2603  	}
  2604  
  2605  	testApp := newTestApp(func(app *v1alpha1.Application) {
  2606  		app.Name = "test"
  2607  		app.Status.Resources = resources
  2608  	})
  2609  	runtimeObjects[podNumber] = testApp
  2610  
  2611  	noRoleCtx := t.Context()
  2612  	//nolint:staticcheck
  2613  	adminCtx := context.WithValue(noRoleCtx, "claims", &jwt.MapClaims{"groups": []string{"admin"}})
  2614  
  2615  	if len(maxPodLogsToRender) > 0 {
  2616  		f := func(enf *rbac.Enforcer) {
  2617  			_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
  2618  			enf.SetDefaultRole("role:admin")
  2619  		}
  2620  		formatInt := strconv.FormatInt(maxPodLogsToRender[0], 10)
  2621  		appServer := newTestAppServerWithEnforcerConfigure(t, f, map[string]string{"server.maxPodLogsToRender": formatInt}, runtimeObjects...)
  2622  		return appServer, adminCtx
  2623  	}
  2624  	appServer := newTestAppServer(t, runtimeObjects...)
  2625  	return appServer, adminCtx
  2626  }
  2627  
  2628  // refreshAnnotationRemover runs an infinite loop until it detects and removes refresh annotation or given context is done
  2629  func refreshAnnotationRemover(t *testing.T, ctx context.Context, patched *int32, appServer *Server, appName string, ch chan string) {
  2630  	t.Helper()
  2631  	for ctx.Err() == nil {
  2632  		aName, appNs := argo.ParseFromQualifiedName(appName, appServer.ns)
  2633  		a, err := appServer.appLister.Applications(appNs).Get(aName)
  2634  		require.NoError(t, err)
  2635  		if a.GetAnnotations() != nil && a.GetAnnotations()[v1alpha1.AnnotationKeyRefresh] != "" {
  2636  			a.SetAnnotations(map[string]string{})
  2637  			a.SetResourceVersion("999")
  2638  			_, err = appServer.appclientset.ArgoprojV1alpha1().Applications(a.Namespace).Update(
  2639  				t.Context(), a, metav1.UpdateOptions{})
  2640  			require.NoError(t, err)
  2641  			atomic.AddInt32(patched, 1)
  2642  			ch <- ""
  2643  		}
  2644  		time.Sleep(100 * time.Millisecond)
  2645  	}
  2646  }
  2647  
  2648  func TestGetAppRefresh_NormalRefresh(t *testing.T) {
  2649  	ctx, cancel := context.WithCancel(t.Context())
  2650  	defer cancel()
  2651  	testApp := newTestApp()
  2652  	testApp.ResourceVersion = "1"
  2653  	appServer := newTestAppServer(t, testApp)
  2654  
  2655  	var patched int32
  2656  
  2657  	ch := make(chan string, 1)
  2658  
  2659  	go refreshAnnotationRemover(t, ctx, &patched, appServer, testApp.Name, ch)
  2660  
  2661  	_, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  2662  		Name:    &testApp.Name,
  2663  		Refresh: ptr.To(string(v1alpha1.RefreshTypeNormal)),
  2664  	})
  2665  	require.NoError(t, err)
  2666  
  2667  	select {
  2668  	case <-ch:
  2669  		assert.Equal(t, int32(1), atomic.LoadInt32(&patched))
  2670  	case <-time.After(10 * time.Second):
  2671  		assert.Fail(t, "Out of time ( 10 seconds )")
  2672  	}
  2673  }
  2674  
  2675  func TestGetAppRefresh_HardRefresh(t *testing.T) {
  2676  	ctx, cancel := context.WithCancel(t.Context())
  2677  	defer cancel()
  2678  	testApp := newTestApp()
  2679  	testApp.ResourceVersion = "1"
  2680  	appServer := newTestAppServer(t, testApp)
  2681  
  2682  	var getAppDetailsQuery *apiclient.RepoServerAppDetailsQuery
  2683  	mockRepoServiceClient := mocks.RepoServerServiceClient{}
  2684  	mockRepoServiceClient.On("GetAppDetails", mock.Anything, mock.MatchedBy(func(q *apiclient.RepoServerAppDetailsQuery) bool {
  2685  		getAppDetailsQuery = q
  2686  		return true
  2687  	})).Return(&apiclient.RepoAppDetailsResponse{}, nil)
  2688  	appServer.repoClientset = &mocks.Clientset{RepoServerServiceClient: &mockRepoServiceClient}
  2689  
  2690  	var patched int32
  2691  
  2692  	ch := make(chan string, 1)
  2693  
  2694  	go refreshAnnotationRemover(t, ctx, &patched, appServer, testApp.Name, ch)
  2695  
  2696  	_, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  2697  		Name:    &testApp.Name,
  2698  		Refresh: ptr.To(string(v1alpha1.RefreshTypeHard)),
  2699  	})
  2700  	require.NoError(t, err)
  2701  	require.NotNil(t, getAppDetailsQuery)
  2702  	assert.True(t, getAppDetailsQuery.NoCache)
  2703  	assert.Equal(t, testApp.Spec.Source, getAppDetailsQuery.Source)
  2704  
  2705  	require.NoError(t, err)
  2706  	select {
  2707  	case <-ch:
  2708  		assert.Equal(t, int32(1), atomic.LoadInt32(&patched))
  2709  	case <-time.After(10 * time.Second):
  2710  		assert.Fail(t, "Out of time ( 10 seconds )")
  2711  	}
  2712  }
  2713  
  2714  func TestGetApp_HealthStatusPropagation(t *testing.T) {
  2715  	newServerWithTree := func(t *testing.T) (*Server, *v1alpha1.Application) {
  2716  		t.Helper()
  2717  		cacheClient := cache.NewCache(cache.NewInMemoryCache(1 * time.Hour))
  2718  
  2719  		testApp := newTestApp()
  2720  		testApp.Status.ResourceHealthSource = v1alpha1.ResourceHealthLocationAppTree
  2721  		testApp.Status.Resources = []v1alpha1.ResourceStatus{
  2722  			{
  2723  				Group:     "apps",
  2724  				Kind:      "Deployment",
  2725  				Name:      "guestbook",
  2726  				Namespace: "default",
  2727  			},
  2728  		}
  2729  
  2730  		appServer := newTestAppServer(t, testApp)
  2731  
  2732  		appStateCache := appstate.NewCache(cacheClient, time.Minute)
  2733  		appInstanceName := testApp.InstanceName(appServer.appNamespaceOrDefault(testApp.Namespace))
  2734  		err := appStateCache.SetAppResourcesTree(appInstanceName, &v1alpha1.ApplicationTree{
  2735  			Nodes: []v1alpha1.ResourceNode{{
  2736  				ResourceRef: v1alpha1.ResourceRef{
  2737  					Group:     "apps",
  2738  					Kind:      "Deployment",
  2739  					Name:      "guestbook",
  2740  					Namespace: "default",
  2741  				},
  2742  				Health: &v1alpha1.HealthStatus{Status: health.HealthStatusDegraded},
  2743  			}},
  2744  		})
  2745  		require.NoError(t, err)
  2746  		appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute)
  2747  
  2748  		return appServer, testApp
  2749  	}
  2750  
  2751  	t.Run("propagated health status on get with no refresh", func(t *testing.T) {
  2752  		appServer, testApp := newServerWithTree(t)
  2753  		fetchedApp, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  2754  			Name: &testApp.Name,
  2755  		})
  2756  		require.NoError(t, err)
  2757  		assert.Equal(t, health.HealthStatusDegraded, fetchedApp.Status.Resources[0].Health.Status)
  2758  	})
  2759  
  2760  	t.Run("propagated health status on normal refresh", func(t *testing.T) {
  2761  		appServer, testApp := newServerWithTree(t)
  2762  		var patched int32
  2763  		ch := make(chan string, 1)
  2764  		ctx, cancel := context.WithCancel(t.Context())
  2765  		defer cancel()
  2766  		go refreshAnnotationRemover(t, ctx, &patched, appServer, testApp.Name, ch)
  2767  
  2768  		fetchedApp, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  2769  			Name:    &testApp.Name,
  2770  			Refresh: ptr.To(string(v1alpha1.RefreshTypeNormal)),
  2771  		})
  2772  		require.NoError(t, err)
  2773  
  2774  		select {
  2775  		case <-ch:
  2776  			assert.Equal(t, int32(1), atomic.LoadInt32(&patched))
  2777  		case <-time.After(10 * time.Second):
  2778  			assert.Fail(t, "Out of time ( 10 seconds )")
  2779  		}
  2780  		assert.Equal(t, health.HealthStatusDegraded, fetchedApp.Status.Resources[0].Health.Status)
  2781  	})
  2782  
  2783  	t.Run("propagated health status on hard refresh", func(t *testing.T) {
  2784  		appServer, testApp := newServerWithTree(t)
  2785  		var patched int32
  2786  		ch := make(chan string, 1)
  2787  		ctx, cancel := context.WithCancel(t.Context())
  2788  		defer cancel()
  2789  		go refreshAnnotationRemover(t, ctx, &patched, appServer, testApp.Name, ch)
  2790  
  2791  		fetchedApp, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  2792  			Name:    &testApp.Name,
  2793  			Refresh: ptr.To(string(v1alpha1.RefreshTypeHard)),
  2794  		})
  2795  		require.NoError(t, err)
  2796  
  2797  		select {
  2798  		case <-ch:
  2799  			assert.Equal(t, int32(1), atomic.LoadInt32(&patched))
  2800  		case <-time.After(10 * time.Second):
  2801  			assert.Fail(t, "Out of time ( 10 seconds )")
  2802  		}
  2803  		assert.Equal(t, health.HealthStatusDegraded, fetchedApp.Status.Resources[0].Health.Status)
  2804  	})
  2805  }
  2806  
  2807  func TestInferResourcesStatusHealth(t *testing.T) {
  2808  	cacheClient := cache.NewCache(cache.NewInMemoryCache(1 * time.Hour))
  2809  
  2810  	testApp := newTestApp()
  2811  	testApp.Status.ResourceHealthSource = v1alpha1.ResourceHealthLocationAppTree
  2812  	testApp.Status.Resources = []v1alpha1.ResourceStatus{{
  2813  		Group:     "apps",
  2814  		Kind:      "Deployment",
  2815  		Name:      "guestbook",
  2816  		Namespace: "default",
  2817  	}, {
  2818  		Group:     "apps",
  2819  		Kind:      "StatefulSet",
  2820  		Name:      "guestbook-stateful",
  2821  		Namespace: "default",
  2822  	}}
  2823  	appServer := newTestAppServer(t, testApp)
  2824  	appStateCache := appstate.NewCache(cacheClient, time.Minute)
  2825  	err := appStateCache.SetAppResourcesTree(testApp.Name, &v1alpha1.ApplicationTree{Nodes: []v1alpha1.ResourceNode{{
  2826  		ResourceRef: v1alpha1.ResourceRef{
  2827  			Group:     "apps",
  2828  			Kind:      "Deployment",
  2829  			Name:      "guestbook",
  2830  			Namespace: "default",
  2831  		},
  2832  		Health: &v1alpha1.HealthStatus{
  2833  			Status: health.HealthStatusDegraded,
  2834  		},
  2835  	}}})
  2836  
  2837  	require.NoError(t, err)
  2838  
  2839  	appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute)
  2840  
  2841  	appServer.inferResourcesStatusHealth(testApp)
  2842  
  2843  	assert.Equal(t, health.HealthStatusDegraded, testApp.Status.Resources[0].Health.Status)
  2844  	assert.Nil(t, testApp.Status.Resources[1].Health)
  2845  }
  2846  
  2847  func TestInferResourcesStatusHealthWithAppInAnyNamespace(t *testing.T) {
  2848  	cacheClient := cache.NewCache(cache.NewInMemoryCache(1 * time.Hour))
  2849  
  2850  	testApp := newTestApp()
  2851  	testApp.Namespace = "otherNamespace"
  2852  	testApp.Status.ResourceHealthSource = v1alpha1.ResourceHealthLocationAppTree
  2853  	testApp.Status.Resources = []v1alpha1.ResourceStatus{{
  2854  		Group:     "apps",
  2855  		Kind:      "Deployment",
  2856  		Name:      "guestbook",
  2857  		Namespace: "otherNamespace",
  2858  	}, {
  2859  		Group:     "apps",
  2860  		Kind:      "StatefulSet",
  2861  		Name:      "guestbook-stateful",
  2862  		Namespace: "otherNamespace",
  2863  	}}
  2864  	appServer := newTestAppServer(t, testApp)
  2865  	appStateCache := appstate.NewCache(cacheClient, time.Minute)
  2866  	err := appStateCache.SetAppResourcesTree("otherNamespace"+"_"+testApp.Name, &v1alpha1.ApplicationTree{Nodes: []v1alpha1.ResourceNode{{
  2867  		ResourceRef: v1alpha1.ResourceRef{
  2868  			Group:     "apps",
  2869  			Kind:      "Deployment",
  2870  			Name:      "guestbook",
  2871  			Namespace: "otherNamespace",
  2872  		},
  2873  		Health: &v1alpha1.HealthStatus{
  2874  			Status: health.HealthStatusDegraded,
  2875  		},
  2876  	}}})
  2877  
  2878  	require.NoError(t, err)
  2879  
  2880  	appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute)
  2881  
  2882  	appServer.inferResourcesStatusHealth(testApp)
  2883  
  2884  	assert.Equal(t, health.HealthStatusDegraded, testApp.Status.Resources[0].Health.Status)
  2885  	assert.Nil(t, testApp.Status.Resources[1].Health)
  2886  }
  2887  
  2888  func TestRunNewStyleResourceAction(t *testing.T) {
  2889  	cacheClient := cache.NewCache(cache.NewInMemoryCache(1 * time.Hour))
  2890  
  2891  	group := "batch"
  2892  	kind := "CronJob"
  2893  	version := "v1"
  2894  	resourceName := "my-cron-job"
  2895  	namespace := testNamespace
  2896  	action := "create-job"
  2897  	uid := "1"
  2898  
  2899  	resources := []v1alpha1.ResourceStatus{{
  2900  		Group:     group,
  2901  		Kind:      kind,
  2902  		Name:      resourceName,
  2903  		Namespace: testNamespace,
  2904  		Version:   version,
  2905  	}}
  2906  
  2907  	appStateCache := appstate.NewCache(cacheClient, time.Minute)
  2908  
  2909  	nodes := []v1alpha1.ResourceNode{{
  2910  		ResourceRef: v1alpha1.ResourceRef{
  2911  			Group:     group,
  2912  			Kind:      kind,
  2913  			Version:   version,
  2914  			Name:      resourceName,
  2915  			Namespace: testNamespace,
  2916  			UID:       uid,
  2917  		},
  2918  	}}
  2919  
  2920  	createJobDenyingProj := &v1alpha1.AppProject{
  2921  		ObjectMeta: metav1.ObjectMeta{Name: "createJobDenyingProj", Namespace: "default"},
  2922  		Spec: v1alpha1.AppProjectSpec{
  2923  			SourceRepos:                []string{"*"},
  2924  			Destinations:               []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  2925  			NamespaceResourceWhitelist: []metav1.GroupKind{{Group: "never", Kind: "mind"}},
  2926  		},
  2927  	}
  2928  
  2929  	cronJob := k8sbatchv1.CronJob{
  2930  		TypeMeta: metav1.TypeMeta{
  2931  			APIVersion: "batch/v1",
  2932  			Kind:       "CronJob",
  2933  		},
  2934  		ObjectMeta: metav1.ObjectMeta{
  2935  			Name:      "my-cron-job",
  2936  			Namespace: testNamespace,
  2937  			Labels: map[string]string{
  2938  				"some": "label",
  2939  			},
  2940  		},
  2941  		Spec: k8sbatchv1.CronJobSpec{
  2942  			Schedule: "* * * * *",
  2943  			JobTemplate: k8sbatchv1.JobTemplateSpec{
  2944  				Spec: k8sbatchv1.JobSpec{
  2945  					Template: corev1.PodTemplateSpec{
  2946  						Spec: corev1.PodSpec{
  2947  							Containers: []corev1.Container{
  2948  								{
  2949  									Name:            "hello",
  2950  									Image:           "busybox:1.28",
  2951  									ImagePullPolicy: "IfNotPresent",
  2952  									Command:         []string{"/bin/sh", "-c", "date; echo Hello from the Kubernetes cluster"},
  2953  								},
  2954  							},
  2955  							RestartPolicy: corev1.RestartPolicyOnFailure,
  2956  						},
  2957  					},
  2958  				},
  2959  			},
  2960  		},
  2961  	}
  2962  
  2963  	t.Run("CreateOperationNotPermitted", func(t *testing.T) {
  2964  		testApp := newTestApp()
  2965  		testApp.Spec.Project = "createJobDenyingProj"
  2966  		testApp.Status.ResourceHealthSource = v1alpha1.ResourceHealthLocationAppTree
  2967  		testApp.Status.Resources = resources
  2968  
  2969  		appServer := newTestAppServer(t, testApp, createJobDenyingProj, kube.MustToUnstructured(&cronJob))
  2970  		appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute)
  2971  
  2972  		err := appStateCache.SetAppResourcesTree(testApp.Name, &v1alpha1.ApplicationTree{Nodes: nodes})
  2973  		require.NoError(t, err)
  2974  
  2975  		appResponse, runErr := appServer.RunResourceActionV2(t.Context(), &application.ResourceActionRunRequestV2{
  2976  			Name:         &testApp.Name,
  2977  			Namespace:    &namespace,
  2978  			Action:       &action,
  2979  			AppNamespace: &testApp.Namespace,
  2980  			ResourceName: &resourceName,
  2981  			Version:      &version,
  2982  			Group:        &group,
  2983  			Kind:         &kind,
  2984  		})
  2985  
  2986  		require.ErrorContains(t, runErr, "is not permitted to manage")
  2987  		assert.Nil(t, appResponse)
  2988  	})
  2989  
  2990  	t.Run("CreateOperationPermitted", func(t *testing.T) {
  2991  		testApp := newTestApp()
  2992  		testApp.Status.ResourceHealthSource = v1alpha1.ResourceHealthLocationAppTree
  2993  		testApp.Status.Resources = resources
  2994  
  2995  		appServer := newTestAppServer(t, testApp, kube.MustToUnstructured(&cronJob))
  2996  		appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute)
  2997  
  2998  		err := appStateCache.SetAppResourcesTree(testApp.Name, &v1alpha1.ApplicationTree{Nodes: nodes})
  2999  		require.NoError(t, err)
  3000  
  3001  		appResponse, runErr := appServer.RunResourceActionV2(t.Context(), &application.ResourceActionRunRequestV2{
  3002  			Name:         &testApp.Name,
  3003  			Namespace:    &namespace,
  3004  			Action:       &action,
  3005  			AppNamespace: &testApp.Namespace,
  3006  			ResourceName: &resourceName,
  3007  			Version:      &version,
  3008  			Group:        &group,
  3009  			Kind:         &kind,
  3010  		})
  3011  
  3012  		require.NoError(t, runErr)
  3013  		assert.NotNil(t, appResponse)
  3014  	})
  3015  }
  3016  
  3017  func TestRunOldStyleResourceAction(t *testing.T) {
  3018  	cacheClient := cache.NewCache(cache.NewInMemoryCache(1 * time.Hour))
  3019  
  3020  	group := "apps"
  3021  	kind := "Deployment"
  3022  	version := "v1"
  3023  	resourceName := "nginx-deploy"
  3024  	namespace := testNamespace
  3025  	action := "pause"
  3026  	uid := "2"
  3027  
  3028  	resources := []v1alpha1.ResourceStatus{{
  3029  		Group:     group,
  3030  		Kind:      kind,
  3031  		Name:      resourceName,
  3032  		Namespace: testNamespace,
  3033  		Version:   version,
  3034  	}}
  3035  
  3036  	appStateCache := appstate.NewCache(cacheClient, time.Minute)
  3037  
  3038  	nodes := []v1alpha1.ResourceNode{{
  3039  		ResourceRef: v1alpha1.ResourceRef{
  3040  			Group:     group,
  3041  			Kind:      kind,
  3042  			Version:   version,
  3043  			Name:      resourceName,
  3044  			Namespace: testNamespace,
  3045  			UID:       uid,
  3046  		},
  3047  	}}
  3048  
  3049  	deployment := appsv1.Deployment{
  3050  		TypeMeta: metav1.TypeMeta{
  3051  			APIVersion: "apps/v1",
  3052  			Kind:       "Deployment",
  3053  		},
  3054  		ObjectMeta: metav1.ObjectMeta{
  3055  			Name:      "nginx-deploy",
  3056  			Namespace: testNamespace,
  3057  		},
  3058  	}
  3059  
  3060  	t.Run("DefaultPatchOperation", func(t *testing.T) {
  3061  		testApp := newTestApp()
  3062  		testApp.Status.ResourceHealthSource = v1alpha1.ResourceHealthLocationAppTree
  3063  		testApp.Status.Resources = resources
  3064  
  3065  		// appServer := newTestAppServer(t, testApp, returnDeployment())
  3066  		appServer := newTestAppServer(t, testApp, kube.MustToUnstructured(&deployment))
  3067  		appServer.cache = servercache.NewCache(appStateCache, time.Minute, time.Minute)
  3068  
  3069  		err := appStateCache.SetAppResourcesTree(testApp.Name, &v1alpha1.ApplicationTree{Nodes: nodes})
  3070  		require.NoError(t, err)
  3071  
  3072  		appResponse, runErr := appServer.RunResourceActionV2(t.Context(), &application.ResourceActionRunRequestV2{
  3073  			Name:         &testApp.Name,
  3074  			Namespace:    &namespace,
  3075  			Action:       &action,
  3076  			AppNamespace: &testApp.Namespace,
  3077  			ResourceName: &resourceName,
  3078  			Version:      &version,
  3079  			Group:        &group,
  3080  			Kind:         &kind,
  3081  		})
  3082  
  3083  		require.NoError(t, runErr)
  3084  		assert.NotNil(t, appResponse)
  3085  	})
  3086  }
  3087  
  3088  func TestIsApplicationPermitted(t *testing.T) {
  3089  	t.Run("Incorrect project", func(t *testing.T) {
  3090  		testApp := newTestApp()
  3091  		appServer := newTestAppServer(t, testApp)
  3092  		projects := map[string]bool{"test-app": false}
  3093  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, "test", "default", projects, *testApp)
  3094  		assert.False(t, permitted)
  3095  	})
  3096  
  3097  	t.Run("Version is incorrect", func(t *testing.T) {
  3098  		testApp := newTestApp()
  3099  		appServer := newTestAppServer(t, testApp)
  3100  		minVersion := 100000
  3101  		testApp.ResourceVersion = strconv.Itoa(minVersion - 1)
  3102  		permitted := appServer.isApplicationPermitted(labels.Everything(), minVersion, nil, "test", "default", nil, *testApp)
  3103  		assert.False(t, permitted)
  3104  	})
  3105  
  3106  	t.Run("Application name is incorrect", func(t *testing.T) {
  3107  		testApp := newTestApp()
  3108  		appServer := newTestAppServer(t, testApp)
  3109  		appName := "test"
  3110  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, appName, "default", nil, *testApp)
  3111  		assert.False(t, permitted)
  3112  	})
  3113  
  3114  	t.Run("Application namespace is incorrect", func(t *testing.T) {
  3115  		testApp := newTestApp()
  3116  		appServer := newTestAppServer(t, testApp)
  3117  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, testApp.Name, "demo", nil, *testApp)
  3118  		assert.False(t, permitted)
  3119  	})
  3120  
  3121  	t.Run("Application is not part of enabled namespace", func(t *testing.T) {
  3122  		testApp := newTestApp()
  3123  		appServer := newTestAppServer(t, testApp)
  3124  		appServer.ns = "server-ns"
  3125  		appServer.enabledNamespaces = []string{"demo"}
  3126  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, testApp.Name, testApp.Namespace, nil, *testApp)
  3127  		assert.False(t, permitted)
  3128  	})
  3129  
  3130  	t.Run("Application is part of enabled namespace", func(t *testing.T) {
  3131  		testApp := newTestApp()
  3132  		appServer := newTestAppServer(t, testApp)
  3133  		appServer.ns = "server-ns"
  3134  		appServer.enabledNamespaces = []string{testApp.Namespace}
  3135  		permitted := appServer.isApplicationPermitted(labels.Everything(), 0, nil, testApp.Name, testApp.Namespace, nil, *testApp)
  3136  		assert.True(t, permitted)
  3137  	})
  3138  }
  3139  
  3140  func TestAppNamespaceRestrictions(t *testing.T) {
  3141  	t.Parallel()
  3142  
  3143  	t.Run("List applications in controller namespace", func(t *testing.T) {
  3144  		t.Parallel()
  3145  		testApp := newTestApp()
  3146  		appServer := newTestAppServer(t, testApp)
  3147  		apps, err := appServer.List(t.Context(), &application.ApplicationQuery{})
  3148  		require.NoError(t, err)
  3149  		require.Len(t, apps.Items, 1)
  3150  	})
  3151  
  3152  	t.Run("List applications with non-allowed apps existing", func(t *testing.T) {
  3153  		t.Parallel()
  3154  		testApp1 := newTestApp()
  3155  		testApp1.Namespace = "argocd-1"
  3156  		appServer := newTestAppServer(t, testApp1)
  3157  		apps, err := appServer.List(t.Context(), &application.ApplicationQuery{})
  3158  		require.NoError(t, err)
  3159  		require.Empty(t, apps.Items)
  3160  	})
  3161  
  3162  	t.Run("List applications with non-allowed apps existing and explicit ns request", func(t *testing.T) {
  3163  		t.Parallel()
  3164  		testApp1 := newTestApp()
  3165  		testApp2 := newTestApp()
  3166  		testApp2.Namespace = "argocd-1"
  3167  		appServer := newTestAppServer(t, testApp1, testApp2)
  3168  		apps, err := appServer.List(t.Context(), &application.ApplicationQuery{AppNamespace: ptr.To("argocd-1")})
  3169  		require.NoError(t, err)
  3170  		require.Empty(t, apps.Items)
  3171  	})
  3172  
  3173  	t.Run("List applications with allowed apps in other namespaces", func(t *testing.T) {
  3174  		t.Parallel()
  3175  		testApp1 := newTestApp()
  3176  		testApp1.Namespace = "argocd-1"
  3177  		appServer := newTestAppServer(t, testApp1)
  3178  		appServer.enabledNamespaces = []string{"argocd-1"}
  3179  		apps, err := appServer.List(t.Context(), &application.ApplicationQuery{})
  3180  		require.NoError(t, err)
  3181  		require.Len(t, apps.Items, 1)
  3182  	})
  3183  
  3184  	t.Run("Get application in control plane namespace", func(t *testing.T) {
  3185  		t.Parallel()
  3186  		testApp := newTestApp()
  3187  		appServer := newTestAppServer(t, testApp)
  3188  		app, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  3189  			Name: ptr.To("test-app"),
  3190  		})
  3191  		require.NoError(t, err)
  3192  		assert.Equal(t, "test-app", app.GetName())
  3193  	})
  3194  	t.Run("Get application in other namespace when forbidden", func(t *testing.T) {
  3195  		t.Parallel()
  3196  		testApp := newTestApp()
  3197  		testApp.Namespace = "argocd-1"
  3198  		appServer := newTestAppServer(t, testApp)
  3199  		app, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  3200  			Name:         ptr.To("test-app"),
  3201  			AppNamespace: ptr.To("argocd-1"),
  3202  		})
  3203  		require.ErrorContains(t, err, "permission denied")
  3204  		require.Nil(t, app)
  3205  	})
  3206  	t.Run("Get application in other namespace when allowed", func(t *testing.T) {
  3207  		t.Parallel()
  3208  		testApp := newTestApp()
  3209  		testApp.Namespace = "argocd-1"
  3210  		testApp.Spec.Project = "other-ns"
  3211  		otherNsProj := &v1alpha1.AppProject{
  3212  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3213  			Spec: v1alpha1.AppProjectSpec{
  3214  				SourceRepos:      []string{"*"},
  3215  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3216  				SourceNamespaces: []string{"argocd-1"},
  3217  			},
  3218  		}
  3219  		appServer := newTestAppServer(t, testApp, otherNsProj)
  3220  		appServer.enabledNamespaces = []string{"argocd-1"}
  3221  		app, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  3222  			Name:         ptr.To("test-app"),
  3223  			AppNamespace: ptr.To("argocd-1"),
  3224  		})
  3225  		require.NoError(t, err)
  3226  		require.NotNil(t, app)
  3227  		require.Equal(t, "argocd-1", app.Namespace)
  3228  		require.Equal(t, "test-app", app.Name)
  3229  	})
  3230  	t.Run("Get application in other namespace when project is not allowed", func(t *testing.T) {
  3231  		t.Parallel()
  3232  		testApp := newTestApp()
  3233  		testApp.Namespace = "argocd-1"
  3234  		testApp.Spec.Project = "other-ns"
  3235  		otherNsProj := &v1alpha1.AppProject{
  3236  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3237  			Spec: v1alpha1.AppProjectSpec{
  3238  				SourceRepos:      []string{"*"},
  3239  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3240  				SourceNamespaces: []string{"argocd-2"},
  3241  			},
  3242  		}
  3243  		appServer := newTestAppServer(t, testApp, otherNsProj)
  3244  		appServer.enabledNamespaces = []string{"argocd-1"}
  3245  		app, err := appServer.Get(t.Context(), &application.ApplicationQuery{
  3246  			Name:         ptr.To("test-app"),
  3247  			AppNamespace: ptr.To("argocd-1"),
  3248  		})
  3249  		require.Error(t, err)
  3250  		require.Nil(t, app)
  3251  		require.ErrorContains(t, err, "app is not allowed in project")
  3252  	})
  3253  	t.Run("Create application in other namespace when allowed", func(t *testing.T) {
  3254  		t.Parallel()
  3255  		testApp := newTestApp()
  3256  		testApp.Namespace = "argocd-1"
  3257  		testApp.Spec.Project = "other-ns"
  3258  		otherNsProj := &v1alpha1.AppProject{
  3259  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3260  			Spec: v1alpha1.AppProjectSpec{
  3261  				SourceRepos:      []string{"*"},
  3262  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3263  				SourceNamespaces: []string{"argocd-1"},
  3264  			},
  3265  		}
  3266  		appServer := newTestAppServer(t, otherNsProj)
  3267  		appServer.enabledNamespaces = []string{"argocd-1"}
  3268  		app, err := appServer.Create(t.Context(), &application.ApplicationCreateRequest{
  3269  			Application: testApp,
  3270  		})
  3271  		require.NoError(t, err)
  3272  		require.NotNil(t, app)
  3273  		assert.Equal(t, "test-app", app.Name)
  3274  		assert.Equal(t, "argocd-1", app.Namespace)
  3275  	})
  3276  
  3277  	t.Run("Create application in other namespace when not allowed by project", func(t *testing.T) {
  3278  		t.Parallel()
  3279  		testApp := newTestApp()
  3280  		testApp.Namespace = "argocd-1"
  3281  		testApp.Spec.Project = "other-ns"
  3282  		otherNsProj := &v1alpha1.AppProject{
  3283  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3284  			Spec: v1alpha1.AppProjectSpec{
  3285  				SourceRepos:      []string{"*"},
  3286  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3287  				SourceNamespaces: []string{},
  3288  			},
  3289  		}
  3290  		appServer := newTestAppServer(t, otherNsProj)
  3291  		appServer.enabledNamespaces = []string{"argocd-1"}
  3292  		app, err := appServer.Create(t.Context(), &application.ApplicationCreateRequest{
  3293  			Application: testApp,
  3294  		})
  3295  		require.Error(t, err)
  3296  		require.Nil(t, app)
  3297  		require.ErrorContains(t, err, "app is not allowed in project")
  3298  	})
  3299  
  3300  	t.Run("Create application in other namespace when not allowed by configuration", func(t *testing.T) {
  3301  		t.Parallel()
  3302  		testApp := newTestApp()
  3303  		testApp.Namespace = "argocd-1"
  3304  		testApp.Spec.Project = "other-ns"
  3305  		otherNsProj := &v1alpha1.AppProject{
  3306  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3307  			Spec: v1alpha1.AppProjectSpec{
  3308  				SourceRepos:      []string{"*"},
  3309  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3310  				SourceNamespaces: []string{"argocd-1"},
  3311  			},
  3312  		}
  3313  		appServer := newTestAppServer(t, otherNsProj)
  3314  		appServer.enabledNamespaces = []string{"argocd-2"}
  3315  		app, err := appServer.Create(t.Context(), &application.ApplicationCreateRequest{
  3316  			Application: testApp,
  3317  		})
  3318  		require.Error(t, err)
  3319  		require.Nil(t, app)
  3320  		require.ErrorContains(t, err, "namespace 'argocd-1' is not permitted")
  3321  	})
  3322  	t.Run("Get application sync window in other namespace when project is allowed", func(t *testing.T) {
  3323  		t.Parallel()
  3324  		testApp := newTestApp()
  3325  		testApp.Namespace = "argocd-1"
  3326  		testApp.Spec.Project = "other-ns"
  3327  		otherNsProj := &v1alpha1.AppProject{
  3328  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3329  			Spec: v1alpha1.AppProjectSpec{
  3330  				SourceRepos:      []string{"*"},
  3331  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3332  				SourceNamespaces: []string{"argocd-1"},
  3333  			},
  3334  		}
  3335  		appServer := newTestAppServer(t, testApp, otherNsProj)
  3336  		appServer.enabledNamespaces = []string{"argocd-1"}
  3337  		active, err := appServer.GetApplicationSyncWindows(t.Context(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
  3338  		require.NoError(t, err)
  3339  		assert.Empty(t, active.ActiveWindows)
  3340  	})
  3341  	t.Run("Get application sync window in other namespace when project is not allowed", func(t *testing.T) {
  3342  		t.Parallel()
  3343  		testApp := newTestApp()
  3344  		testApp.Namespace = "argocd-1"
  3345  		testApp.Spec.Project = "other-ns"
  3346  		otherNsProj := &v1alpha1.AppProject{
  3347  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3348  			Spec: v1alpha1.AppProjectSpec{
  3349  				SourceRepos:      []string{"*"},
  3350  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3351  				SourceNamespaces: []string{"argocd-2"},
  3352  			},
  3353  		}
  3354  		appServer := newTestAppServer(t, testApp, otherNsProj)
  3355  		appServer.enabledNamespaces = []string{"argocd-1"}
  3356  		active, err := appServer.GetApplicationSyncWindows(t.Context(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
  3357  		require.Error(t, err)
  3358  		require.Nil(t, active)
  3359  		require.ErrorContains(t, err, "app is not allowed in project")
  3360  	})
  3361  	t.Run("Get list of links in other namespace when project is not allowed", func(t *testing.T) {
  3362  		t.Parallel()
  3363  		testApp := newTestApp()
  3364  		testApp.Namespace = "argocd-1"
  3365  		testApp.Spec.Project = "other-ns"
  3366  		otherNsProj := &v1alpha1.AppProject{
  3367  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3368  			Spec: v1alpha1.AppProjectSpec{
  3369  				SourceRepos:      []string{"*"},
  3370  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3371  				SourceNamespaces: []string{"argocd-2"},
  3372  			},
  3373  		}
  3374  		appServer := newTestAppServer(t, testApp, otherNsProj)
  3375  		appServer.enabledNamespaces = []string{"argocd-1"}
  3376  		links, err := appServer.ListLinks(t.Context(), &application.ListAppLinksRequest{
  3377  			Name:      ptr.To("test-app"),
  3378  			Namespace: ptr.To("argocd-1"),
  3379  		})
  3380  		require.Error(t, err)
  3381  		require.Nil(t, links)
  3382  		require.ErrorContains(t, err, "app is not allowed in project")
  3383  	})
  3384  	t.Run("Get list of links in other namespace when project is allowed", func(t *testing.T) {
  3385  		t.Parallel()
  3386  		testApp := newTestApp()
  3387  		testApp.Namespace = "argocd-1"
  3388  		testApp.Spec.Project = "other-ns"
  3389  		otherNsProj := &v1alpha1.AppProject{
  3390  			ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
  3391  			Spec: v1alpha1.AppProjectSpec{
  3392  				SourceRepos:      []string{"*"},
  3393  				Destinations:     []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3394  				SourceNamespaces: []string{"argocd-1"},
  3395  			},
  3396  		}
  3397  		appServer := newTestAppServer(t, testApp, otherNsProj)
  3398  		appServer.enabledNamespaces = []string{"argocd-1"}
  3399  		links, err := appServer.ListLinks(t.Context(), &application.ListAppLinksRequest{
  3400  			Name:      ptr.To("test-app"),
  3401  			Namespace: ptr.To("argocd-1"),
  3402  		})
  3403  		require.NoError(t, err)
  3404  		assert.Empty(t, links.Items)
  3405  	})
  3406  }
  3407  
  3408  func TestGetAmbiguousRevision_MultiSource(t *testing.T) {
  3409  	app := &v1alpha1.Application{
  3410  		Spec: v1alpha1.ApplicationSpec{
  3411  			Sources: []v1alpha1.ApplicationSource{
  3412  				{
  3413  					TargetRevision: "revision1",
  3414  				},
  3415  				{
  3416  					TargetRevision: "revision2",
  3417  				},
  3418  			},
  3419  		},
  3420  	}
  3421  	syncReq := &application.ApplicationSyncRequest{
  3422  		SourcePositions: []int64{1, 2},
  3423  		Revisions:       []string{"rev1", "rev2"},
  3424  	}
  3425  
  3426  	sourceIndex := 0
  3427  	expected := "rev1"
  3428  	result := getAmbiguousRevision(app, syncReq, sourceIndex)
  3429  	assert.Equalf(t, expected, result, "Expected ambiguous revision to be %s, but got %s", expected, result)
  3430  
  3431  	sourceIndex = 1
  3432  	expected = "rev2"
  3433  	result = getAmbiguousRevision(app, syncReq, sourceIndex)
  3434  	assert.Equal(t, expected, result, "Expected ambiguous revision to be %s, but got %s", expected, result)
  3435  
  3436  	// Test when app.Spec.HasMultipleSources() is false
  3437  	app.Spec = v1alpha1.ApplicationSpec{
  3438  		Source: &v1alpha1.ApplicationSource{
  3439  			TargetRevision: "revision3",
  3440  		},
  3441  		Sources: nil,
  3442  	}
  3443  	syncReq = &application.ApplicationSyncRequest{
  3444  		Revision: strToPtr("revision3"),
  3445  	}
  3446  	expected = "revision3"
  3447  	result = getAmbiguousRevision(app, syncReq, sourceIndex)
  3448  	assert.Equal(t, expected, result, "Expected ambiguous revision to be %s, but got %s", expected, result)
  3449  }
  3450  
  3451  func TestGetAmbiguousRevision_SingleSource(t *testing.T) {
  3452  	app := &v1alpha1.Application{
  3453  		Spec: v1alpha1.ApplicationSpec{
  3454  			Source: &v1alpha1.ApplicationSource{
  3455  				TargetRevision: "revision1",
  3456  			},
  3457  		},
  3458  	}
  3459  	syncReq := &application.ApplicationSyncRequest{
  3460  		Revision: strToPtr("rev1"),
  3461  	}
  3462  
  3463  	// Test when app.Spec.HasMultipleSources() is true
  3464  	sourceIndex := 1
  3465  	expected := "rev1"
  3466  	result := getAmbiguousRevision(app, syncReq, sourceIndex)
  3467  	assert.Equalf(t, expected, result, "Expected ambiguous revision to be %s, but got %s", expected, result)
  3468  }
  3469  
  3470  func TestServer_ResolveSourceRevisions_MultiSource(t *testing.T) {
  3471  	s := newTestAppServer(t)
  3472  
  3473  	ctx := t.Context()
  3474  	a := &v1alpha1.Application{
  3475  		Spec: v1alpha1.ApplicationSpec{
  3476  			Sources: []v1alpha1.ApplicationSource{
  3477  				{
  3478  					RepoURL: "https://github.com/example/repo.git",
  3479  				},
  3480  			},
  3481  		},
  3482  	}
  3483  
  3484  	syncReq := &application.ApplicationSyncRequest{
  3485  		SourcePositions: []int64{1},
  3486  		Revisions:       []string{"HEAD"},
  3487  	}
  3488  
  3489  	revision, displayRevision, sourceRevisions, displayRevisions, err := s.resolveSourceRevisions(ctx, a, syncReq)
  3490  
  3491  	require.NoError(t, err)
  3492  	assert.Empty(t, revision)
  3493  	assert.Empty(t, displayRevision)
  3494  	assert.Equal(t, []string{fakeResolveRevisionResponse().Revision}, sourceRevisions)
  3495  	assert.Equal(t, []string{fakeResolveRevisionResponse().AmbiguousRevision}, displayRevisions)
  3496  }
  3497  
  3498  func TestServer_ResolveSourceRevisions_SingleSource(t *testing.T) {
  3499  	s := newTestAppServer(t)
  3500  
  3501  	ctx := t.Context()
  3502  	a := &v1alpha1.Application{
  3503  		Spec: v1alpha1.ApplicationSpec{
  3504  			Source: &v1alpha1.ApplicationSource{
  3505  				RepoURL: "https://github.com/example/repo.git",
  3506  			},
  3507  		},
  3508  	}
  3509  
  3510  	syncReq := &application.ApplicationSyncRequest{
  3511  		Revision: strToPtr("HEAD"),
  3512  	}
  3513  
  3514  	revision, displayRevision, sourceRevisions, displayRevisions, err := s.resolveSourceRevisions(ctx, a, syncReq)
  3515  
  3516  	require.NoError(t, err)
  3517  	assert.Equal(t, fakeResolveRevisionResponse().Revision, revision)
  3518  	assert.Equal(t, fakeResolveRevisionResponse().AmbiguousRevision, displayRevision)
  3519  	assert.Equal(t, []string(nil), sourceRevisions)
  3520  	assert.Equal(t, []string(nil), displayRevisions)
  3521  }
  3522  
  3523  func Test_RevisionMetadata(t *testing.T) {
  3524  	t.Parallel()
  3525  
  3526  	singleSourceApp := newTestApp()
  3527  	singleSourceApp.Name = "single-source-app"
  3528  	singleSourceApp.Spec = v1alpha1.ApplicationSpec{
  3529  		Source: &v1alpha1.ApplicationSource{
  3530  			RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
  3531  			Path:           "helm-guestbook",
  3532  			TargetRevision: "HEAD",
  3533  		},
  3534  	}
  3535  
  3536  	multiSourceApp := newTestApp()
  3537  	multiSourceApp.Name = "multi-source-app"
  3538  	multiSourceApp.Spec = v1alpha1.ApplicationSpec{
  3539  		Sources: []v1alpha1.ApplicationSource{
  3540  			{
  3541  				RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
  3542  				Path:           "helm-guestbook",
  3543  				TargetRevision: "HEAD",
  3544  			},
  3545  			{
  3546  				RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
  3547  				Path:           "kustomize-guestbook",
  3548  				TargetRevision: "HEAD",
  3549  			},
  3550  		},
  3551  	}
  3552  
  3553  	singleSourceHistory := []v1alpha1.RevisionHistory{
  3554  		{
  3555  			ID:       1,
  3556  			Source:   singleSourceApp.Spec.GetSource(),
  3557  			Revision: "a",
  3558  		},
  3559  	}
  3560  	multiSourceHistory := []v1alpha1.RevisionHistory{
  3561  		{
  3562  			ID:        1,
  3563  			Sources:   multiSourceApp.Spec.GetSources(),
  3564  			Revisions: []string{"a", "b"},
  3565  		},
  3566  	}
  3567  
  3568  	testCases := []struct {
  3569  		name        string
  3570  		multiSource bool
  3571  		history     *struct {
  3572  			matchesSourceType bool
  3573  		}
  3574  		sourceIndex         *int32
  3575  		versionId           *int32
  3576  		expectErrorContains *string
  3577  	}{
  3578  		{
  3579  			name:        "single-source app without history, no source index, no version ID",
  3580  			multiSource: false,
  3581  		},
  3582  		{
  3583  			name:                "single-source app without history, no source index, missing version ID",
  3584  			multiSource:         false,
  3585  			versionId:           ptr.To(int32(999)),
  3586  			expectErrorContains: ptr.To("the app has no history"),
  3587  		},
  3588  		{
  3589  			name:        "single source app without history, present source index, no version ID",
  3590  			multiSource: false,
  3591  			sourceIndex: ptr.To(int32(0)),
  3592  		},
  3593  		{
  3594  			name:                "single source app without history, invalid source index, no version ID",
  3595  			multiSource:         false,
  3596  			sourceIndex:         ptr.To(int32(999)),
  3597  			expectErrorContains: ptr.To("source index 999 not found"),
  3598  		},
  3599  		{
  3600  			name:        "single source app with matching history, no source index, no version ID",
  3601  			multiSource: false,
  3602  			history:     &struct{ matchesSourceType bool }{true},
  3603  		},
  3604  		{
  3605  			name:                "single source app with matching history, no source index, missing version ID",
  3606  			multiSource:         false,
  3607  			history:             &struct{ matchesSourceType bool }{true},
  3608  			versionId:           ptr.To(int32(999)),
  3609  			expectErrorContains: ptr.To("history not found for version ID 999"),
  3610  		},
  3611  		{
  3612  			name:        "single source app with matching history, no source index, present version ID",
  3613  			multiSource: false,
  3614  			history:     &struct{ matchesSourceType bool }{true},
  3615  			versionId:   ptr.To(int32(1)),
  3616  		},
  3617  		{
  3618  			name:        "single source app with multi-source history, no source index, no version ID",
  3619  			multiSource: false,
  3620  			history:     &struct{ matchesSourceType bool }{false},
  3621  		},
  3622  		{
  3623  			name:                "single source app with multi-source history, no source index, missing version ID",
  3624  			multiSource:         false,
  3625  			history:             &struct{ matchesSourceType bool }{false},
  3626  			versionId:           ptr.To(int32(999)),
  3627  			expectErrorContains: ptr.To("history not found for version ID 999"),
  3628  		},
  3629  		{
  3630  			name:        "single source app with multi-source history, no source index, present version ID",
  3631  			multiSource: false,
  3632  			history:     &struct{ matchesSourceType bool }{false},
  3633  			versionId:   ptr.To(int32(1)),
  3634  		},
  3635  		{
  3636  			name:        "single-source app with multi-source history, source index 1, no version ID",
  3637  			multiSource: false,
  3638  			sourceIndex: ptr.To(int32(1)),
  3639  			history:     &struct{ matchesSourceType bool }{false},
  3640  			// Since the user requested source index 1, but no version ID, we'll get an error when looking at the live
  3641  			// source, because the live source is single-source.
  3642  			expectErrorContains: ptr.To("there is only 1 source"),
  3643  		},
  3644  		{
  3645  			name:                "single-source app with multi-source history, invalid source index, no version ID",
  3646  			multiSource:         false,
  3647  			sourceIndex:         ptr.To(int32(999)),
  3648  			history:             &struct{ matchesSourceType bool }{false},
  3649  			expectErrorContains: ptr.To("source index 999 not found"),
  3650  		},
  3651  		{
  3652  			name:        "single-source app with multi-source history, valid source index, present version ID",
  3653  			multiSource: false,
  3654  			sourceIndex: ptr.To(int32(1)),
  3655  			history:     &struct{ matchesSourceType bool }{false},
  3656  			versionId:   ptr.To(int32(1)),
  3657  		},
  3658  		{
  3659  			name:        "multi-source app without history, no source index, no version ID",
  3660  			multiSource: true,
  3661  		},
  3662  		{
  3663  			name:                "multi-source app without history, no source index, missing version ID",
  3664  			multiSource:         true,
  3665  			versionId:           ptr.To(int32(999)),
  3666  			expectErrorContains: ptr.To("the app has no history"),
  3667  		},
  3668  		{
  3669  			name:        "multi-source app without history, present source index, no version ID",
  3670  			multiSource: true,
  3671  			sourceIndex: ptr.To(int32(1)),
  3672  		},
  3673  		{
  3674  			name:                "multi-source app without history, invalid source index, no version ID",
  3675  			multiSource:         true,
  3676  			sourceIndex:         ptr.To(int32(999)),
  3677  			expectErrorContains: ptr.To("source index 999 not found"),
  3678  		},
  3679  		{
  3680  			name:        "multi-source app with matching history, no source index, no version ID",
  3681  			multiSource: true,
  3682  			history:     &struct{ matchesSourceType bool }{true},
  3683  		},
  3684  		{
  3685  			name:                "multi-source app with matching history, no source index, missing version ID",
  3686  			multiSource:         true,
  3687  			history:             &struct{ matchesSourceType bool }{true},
  3688  			versionId:           ptr.To(int32(999)),
  3689  			expectErrorContains: ptr.To("history not found for version ID 999"),
  3690  		},
  3691  		{
  3692  			name:        "multi-source app with matching history, no source index, present version ID",
  3693  			multiSource: true,
  3694  			history:     &struct{ matchesSourceType bool }{true},
  3695  			versionId:   ptr.To(int32(1)),
  3696  		},
  3697  		{
  3698  			name:        "multi-source app with single-source history, no source index, no version ID",
  3699  			multiSource: true,
  3700  			history:     &struct{ matchesSourceType bool }{false},
  3701  		},
  3702  		{
  3703  			name:                "multi-source app with single-source history, no source index, missing version ID",
  3704  			multiSource:         true,
  3705  			history:             &struct{ matchesSourceType bool }{false},
  3706  			versionId:           ptr.To(int32(999)),
  3707  			expectErrorContains: ptr.To("history not found for version ID 999"),
  3708  		},
  3709  		{
  3710  			name:        "multi-source app with single-source history, no source index, present version ID",
  3711  			multiSource: true,
  3712  			history:     &struct{ matchesSourceType bool }{false},
  3713  			versionId:   ptr.To(int32(1)),
  3714  		},
  3715  		{
  3716  			name:        "multi-source app with single-source history, source index 1, no version ID",
  3717  			multiSource: true,
  3718  			sourceIndex: ptr.To(int32(1)),
  3719  			history:     &struct{ matchesSourceType bool }{false},
  3720  		},
  3721  		{
  3722  			name:                "multi-source app with single-source history, invalid source index, no version ID",
  3723  			multiSource:         true,
  3724  			sourceIndex:         ptr.To(int32(999)),
  3725  			history:             &struct{ matchesSourceType bool }{false},
  3726  			expectErrorContains: ptr.To("source index 999 not found"),
  3727  		},
  3728  		{
  3729  			name:        "multi-source app with single-source history, valid source index, present version ID",
  3730  			multiSource: true,
  3731  			sourceIndex: ptr.To(int32(0)),
  3732  			history:     &struct{ matchesSourceType bool }{false},
  3733  			versionId:   ptr.To(int32(1)),
  3734  		},
  3735  		{
  3736  			name:                "multi-source app with single-source history, source index 1, present version ID",
  3737  			multiSource:         true,
  3738  			sourceIndex:         ptr.To(int32(1)),
  3739  			history:             &struct{ matchesSourceType bool }{false},
  3740  			versionId:           ptr.To(int32(1)),
  3741  			expectErrorContains: ptr.To("source index 1 not found"),
  3742  		},
  3743  	}
  3744  
  3745  	for _, tc := range testCases {
  3746  		tcc := tc
  3747  		t.Run(tcc.name, func(t *testing.T) {
  3748  			t.Parallel()
  3749  
  3750  			app := singleSourceApp.DeepCopy()
  3751  			if tcc.multiSource {
  3752  				app = multiSourceApp.DeepCopy()
  3753  			}
  3754  			if tcc.history != nil {
  3755  				if tcc.history.matchesSourceType {
  3756  					if tcc.multiSource {
  3757  						app.Status.History = multiSourceHistory
  3758  					} else {
  3759  						app.Status.History = singleSourceHistory
  3760  					}
  3761  				} else {
  3762  					if tcc.multiSource {
  3763  						app.Status.History = singleSourceHistory
  3764  					} else {
  3765  						app.Status.History = multiSourceHistory
  3766  					}
  3767  				}
  3768  			}
  3769  
  3770  			s := newTestAppServer(t, app)
  3771  
  3772  			request := &application.RevisionMetadataQuery{
  3773  				Name:        ptr.To(app.Name),
  3774  				Revision:    ptr.To("HEAD"),
  3775  				SourceIndex: tcc.sourceIndex,
  3776  				VersionId:   tcc.versionId,
  3777  			}
  3778  
  3779  			_, err := s.RevisionMetadata(t.Context(), request)
  3780  			if tcc.expectErrorContains != nil {
  3781  				require.ErrorContains(t, err, *tcc.expectErrorContains)
  3782  			} else {
  3783  				require.NoError(t, err)
  3784  			}
  3785  		})
  3786  	}
  3787  }
  3788  
  3789  func Test_DeepCopyInformers(t *testing.T) {
  3790  	t.Parallel()
  3791  
  3792  	namespace := "test-namespace"
  3793  	var ro []runtime.Object
  3794  	appOne := newTestApp(func(app *v1alpha1.Application) {
  3795  		app.Name = "appOne"
  3796  		app.Namespace = namespace
  3797  		app.Spec = v1alpha1.ApplicationSpec{}
  3798  	})
  3799  	appTwo := newTestApp(func(app *v1alpha1.Application) {
  3800  		app.Name = "appTwo"
  3801  		app.Namespace = namespace
  3802  		app.Spec = v1alpha1.ApplicationSpec{}
  3803  	})
  3804  	appThree := newTestApp(func(app *v1alpha1.Application) {
  3805  		app.Name = "appThree"
  3806  		app.Namespace = namespace
  3807  		app.Spec = v1alpha1.ApplicationSpec{}
  3808  	})
  3809  	ro = append(ro, appOne, appTwo, appThree)
  3810  	appls := []v1alpha1.Application{*appOne, *appTwo, *appThree}
  3811  
  3812  	appSetOne := &v1alpha1.ApplicationSet{
  3813  		ObjectMeta: metav1.ObjectMeta{Name: "appSetOne", Namespace: namespace},
  3814  		Spec:       v1alpha1.ApplicationSetSpec{},
  3815  	}
  3816  	appSetTwo := &v1alpha1.ApplicationSet{
  3817  		ObjectMeta: metav1.ObjectMeta{Name: "appSetTwo", Namespace: namespace},
  3818  		Spec:       v1alpha1.ApplicationSetSpec{},
  3819  	}
  3820  	appSetThree := &v1alpha1.ApplicationSet{
  3821  		ObjectMeta: metav1.ObjectMeta{Name: "appSetThree", Namespace: namespace},
  3822  		Spec:       v1alpha1.ApplicationSetSpec{},
  3823  	}
  3824  	ro = append(ro, appSetOne, appSetTwo, appSetThree)
  3825  	appSets := []v1alpha1.ApplicationSet{*appSetOne, *appSetTwo, *appSetThree}
  3826  
  3827  	appProjects := createAppProject("projOne", "projTwo", "projThree")
  3828  	for i := range appProjects {
  3829  		ro = append(ro, &appProjects[i])
  3830  	}
  3831  
  3832  	s := newTestAppServer(t, ro...)
  3833  
  3834  	appList, err := s.appclientset.ArgoprojV1alpha1().Applications(namespace).List(t.Context(), metav1.ListOptions{})
  3835  	require.NoError(t, err)
  3836  	assert.ElementsMatch(t, appls, appList.Items)
  3837  	sAppList := appList.Items
  3838  	slices.SortFunc(sAppList, func(a, b v1alpha1.Application) int {
  3839  		return strings.Compare(a.Name, b.Name)
  3840  	})
  3841  	slices.SortFunc(appls, func(a, b v1alpha1.Application) int {
  3842  		return strings.Compare(a.Name, b.Name)
  3843  	})
  3844  	// ensure there is a deep copy
  3845  	for i := range appls {
  3846  		assert.NotSame(t, &appls[i], &sAppList[i])
  3847  		assert.NotSame(t, &appls[i].Spec, &sAppList[i].Spec)
  3848  		a, err := s.appclientset.ArgoprojV1alpha1().Applications(namespace).Get(t.Context(), sAppList[i].Name, metav1.GetOptions{})
  3849  		require.NoError(t, err)
  3850  		assert.NotSame(t, a, &sAppList[i])
  3851  	}
  3852  
  3853  	appSetList, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).List(t.Context(), metav1.ListOptions{})
  3854  	require.NoError(t, err)
  3855  	assert.ElementsMatch(t, appSets, appSetList.Items)
  3856  	sAppSetList := appSetList.Items
  3857  	slices.SortFunc(sAppSetList, func(a, b v1alpha1.ApplicationSet) int {
  3858  		return strings.Compare(a.Name, b.Name)
  3859  	})
  3860  	slices.SortFunc(appSets, func(a, b v1alpha1.ApplicationSet) int {
  3861  		return strings.Compare(a.Name, b.Name)
  3862  	})
  3863  	for i := range appSets {
  3864  		assert.NotSame(t, &appSets[i], &sAppSetList[i])
  3865  		assert.NotSame(t, &appSets[i].Spec, &sAppSetList[i].Spec)
  3866  		a, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(namespace).Get(t.Context(),
  3867  			sAppSetList[i].Name, metav1.GetOptions{})
  3868  		require.NoError(t, err)
  3869  		assert.NotSame(t, a, &sAppSetList[i])
  3870  	}
  3871  
  3872  	projList, err := s.appclientset.ArgoprojV1alpha1().AppProjects("deep-copy-ns").List(t.Context(), metav1.ListOptions{})
  3873  	require.NoError(t, err)
  3874  	assert.ElementsMatch(t, appProjects, projList.Items)
  3875  	spList := projList.Items
  3876  	slices.SortFunc(spList, func(a, b v1alpha1.AppProject) int {
  3877  		return strings.Compare(a.Name, b.Name)
  3878  	})
  3879  	slices.SortFunc(appProjects, func(a, b v1alpha1.AppProject) int {
  3880  		return strings.Compare(a.Name, b.Name)
  3881  	})
  3882  	for i := range appProjects {
  3883  		assert.NotSame(t, &appProjects[i], &spList[i])
  3884  		assert.NotSame(t, &appProjects[i].Spec, &spList[i].Spec)
  3885  		p, err := s.appclientset.ArgoprojV1alpha1().AppProjects("deep-copy-ns").Get(t.Context(),
  3886  			spList[i].Name, metav1.GetOptions{})
  3887  		require.NoError(t, err)
  3888  		assert.NotSame(t, p, &spList[i])
  3889  	}
  3890  }
  3891  
  3892  func TestServerSideDiff(t *testing.T) {
  3893  	// Create test projects (avoid "default" which is already created by newTestAppServerWithEnforcerConfigure)
  3894  	testProj := &v1alpha1.AppProject{
  3895  		ObjectMeta: metav1.ObjectMeta{Name: "test-project", Namespace: testNamespace},
  3896  		Spec: v1alpha1.AppProjectSpec{
  3897  			SourceRepos:  []string{"*"},
  3898  			Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3899  		},
  3900  	}
  3901  
  3902  	forbiddenProj := &v1alpha1.AppProject{
  3903  		ObjectMeta: metav1.ObjectMeta{Name: "forbidden-project", Namespace: testNamespace},
  3904  		Spec: v1alpha1.AppProjectSpec{
  3905  			SourceRepos:  []string{"*"},
  3906  			Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
  3907  		},
  3908  	}
  3909  
  3910  	// Create test applications that will exist in the server
  3911  	testApp := newTestApp(func(app *v1alpha1.Application) {
  3912  		app.Name = "test-app"
  3913  		app.Namespace = testNamespace
  3914  		app.Spec.Project = "test-project"
  3915  	})
  3916  
  3917  	forbiddenApp := newTestApp(func(app *v1alpha1.Application) {
  3918  		app.Name = "forbidden-app"
  3919  		app.Namespace = testNamespace
  3920  		app.Spec.Project = "forbidden-project"
  3921  	})
  3922  
  3923  	appServer := newTestAppServer(t, testProj, forbiddenProj, testApp, forbiddenApp)
  3924  
  3925  	t.Run("InputValidation", func(t *testing.T) {
  3926  		// Test missing application name
  3927  		query := &application.ApplicationServerSideDiffQuery{
  3928  			AppName:         ptr.To(""), // Empty name instead of nil
  3929  			AppNamespace:    ptr.To(testNamespace),
  3930  			Project:         ptr.To("test-project"),
  3931  			LiveResources:   []*v1alpha1.ResourceDiff{},
  3932  			TargetManifests: []string{},
  3933  		}
  3934  
  3935  		_, err := appServer.ServerSideDiff(t.Context(), query)
  3936  		require.Error(t, err)
  3937  		assert.Contains(t, err.Error(), "not found")
  3938  
  3939  		// Test nil application name
  3940  		queryNil := &application.ApplicationServerSideDiffQuery{
  3941  			AppName:         nil,
  3942  			AppNamespace:    ptr.To(testNamespace),
  3943  			Project:         ptr.To("test-project"),
  3944  			LiveResources:   []*v1alpha1.ResourceDiff{},
  3945  			TargetManifests: []string{},
  3946  		}
  3947  
  3948  		_, err = appServer.ServerSideDiff(t.Context(), queryNil)
  3949  		assert.Error(t, err)
  3950  		// Should get an error when name is nil
  3951  	})
  3952  
  3953  	t.Run("InvalidManifest", func(t *testing.T) {
  3954  		// Test error handling for malformed JSON in target manifests
  3955  		query := &application.ApplicationServerSideDiffQuery{
  3956  			AppName:         ptr.To("test-app"),
  3957  			AppNamespace:    ptr.To(testNamespace),
  3958  			Project:         ptr.To("test-project"),
  3959  			LiveResources:   []*v1alpha1.ResourceDiff{},
  3960  			TargetManifests: []string{`invalid json`},
  3961  		}
  3962  
  3963  		_, err := appServer.ServerSideDiff(t.Context(), query)
  3964  
  3965  		// Should return error for invalid JSON
  3966  		require.Error(t, err)
  3967  		assert.Contains(t, err.Error(), "error unmarshaling target manifest")
  3968  	})
  3969  
  3970  	t.Run("InvalidLiveState", func(t *testing.T) {
  3971  		// Test error handling for malformed JSON in live state
  3972  		liveResource := &v1alpha1.ResourceDiff{
  3973  			Group:       "apps",
  3974  			Kind:        "Deployment",
  3975  			Namespace:   "default",
  3976  			Name:        "test-deployment",
  3977  			LiveState:   `invalid json`,
  3978  			TargetState: "",
  3979  			Modified:    true,
  3980  		}
  3981  
  3982  		query := &application.ApplicationServerSideDiffQuery{
  3983  			AppName:         ptr.To("test-app"),
  3984  			AppNamespace:    ptr.To(testNamespace),
  3985  			Project:         ptr.To("test-project"),
  3986  			LiveResources:   []*v1alpha1.ResourceDiff{liveResource},
  3987  			TargetManifests: []string{`{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"test"}}`},
  3988  		}
  3989  
  3990  		_, err := appServer.ServerSideDiff(t.Context(), query)
  3991  
  3992  		// Should return error for invalid JSON in live state
  3993  		require.Error(t, err)
  3994  		assert.Contains(t, err.Error(), "error unmarshaling live state")
  3995  	})
  3996  
  3997  	t.Run("EmptyRequest", func(t *testing.T) {
  3998  		// Test with empty resources - should succeed without errors but no diffs
  3999  		query := &application.ApplicationServerSideDiffQuery{
  4000  			AppName:         ptr.To("test-app"),
  4001  			AppNamespace:    ptr.To(testNamespace),
  4002  			Project:         ptr.To("test-project"),
  4003  			LiveResources:   []*v1alpha1.ResourceDiff{},
  4004  			TargetManifests: []string{},
  4005  		}
  4006  
  4007  		resp, err := appServer.ServerSideDiff(t.Context(), query)
  4008  
  4009  		// Should succeed with empty response
  4010  		require.NoError(t, err)
  4011  		assert.NotNil(t, resp)
  4012  		assert.False(t, *resp.Modified)
  4013  		assert.Empty(t, resp.Items)
  4014  	})
  4015  
  4016  	t.Run("MissingAppPermission", func(t *testing.T) {
  4017  		// Test RBAC enforcement
  4018  		query := &application.ApplicationServerSideDiffQuery{
  4019  			AppName:         ptr.To("nonexistent-app"),
  4020  			AppNamespace:    ptr.To(testNamespace),
  4021  			Project:         ptr.To("nonexistent-project"),
  4022  			LiveResources:   []*v1alpha1.ResourceDiff{},
  4023  			TargetManifests: []string{},
  4024  		}
  4025  
  4026  		_, err := appServer.ServerSideDiff(t.Context(), query)
  4027  
  4028  		// Should fail with permission error since nonexistent-app doesn't exist
  4029  		require.Error(t, err)
  4030  		assert.Contains(t, err.Error(), "application")
  4031  	})
  4032  }