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

     1  package repository
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/golang-jwt/jwt/v4"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/mock"
    12  	"google.golang.org/grpc/codes"
    13  	"google.golang.org/grpc/status"
    14  	corev1 "k8s.io/api/core/v1"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/client-go/kubernetes/fake"
    19  	k8scache "k8s.io/client-go/tools/cache"
    20  
    21  	"github.com/argoproj/argo-cd/v2/common"
    22  	"github.com/argoproj/argo-cd/v2/pkg/apiclient/repository"
    23  	appsv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    24  	fakeapps "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
    25  	appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
    26  	applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
    27  	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
    28  	"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
    29  	"github.com/argoproj/argo-cd/v2/server/cache"
    30  	"github.com/argoproj/argo-cd/v2/util/assets"
    31  	cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
    32  	appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
    33  	"github.com/argoproj/argo-cd/v2/util/db"
    34  	dbmocks "github.com/argoproj/argo-cd/v2/util/db/mocks"
    35  	"github.com/argoproj/argo-cd/v2/util/rbac"
    36  	"github.com/argoproj/argo-cd/v2/util/settings"
    37  
    38  	"github.com/argoproj/argo-cd/v2/pkg/apis/application"
    39  )
    40  
    41  const testNamespace = "default"
    42  
    43  var (
    44  	argocdCM = corev1.ConfigMap{
    45  		ObjectMeta: v1.ObjectMeta{
    46  			Namespace: testNamespace,
    47  			Name:      "argocd-cm",
    48  			Labels: map[string]string{
    49  				"app.kubernetes.io/part-of": "argocd",
    50  			},
    51  		},
    52  	}
    53  	argocdSecret = corev1.Secret{
    54  		ObjectMeta: v1.ObjectMeta{
    55  			Name:      "argocd-secret",
    56  			Namespace: testNamespace,
    57  		},
    58  		Data: map[string][]byte{
    59  			"admin.password":   []byte("test"),
    60  			"server.secretkey": []byte("test"),
    61  		},
    62  	}
    63  	defaultProj = &appsv1.AppProject{
    64  		TypeMeta: metav1.TypeMeta{
    65  			Kind:       application.AppProjectKind,
    66  			APIVersion: "argoproj.io/v1alpha1",
    67  		},
    68  		ObjectMeta: metav1.ObjectMeta{
    69  			Name:      "default",
    70  			Namespace: testNamespace,
    71  		},
    72  		Spec: appsv1.AppProjectSpec{
    73  			SourceRepos:  []string{"*"},
    74  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    75  		},
    76  	}
    77  
    78  	defaultProjNoSources = &appsv1.AppProject{
    79  		TypeMeta: metav1.TypeMeta{
    80  			Kind:       application.AppProjectKind,
    81  			APIVersion: "argoproj.io/v1alpha1",
    82  		},
    83  		ObjectMeta: metav1.ObjectMeta{
    84  			Name:      "default",
    85  			Namespace: testNamespace,
    86  		},
    87  		Spec: appsv1.AppProjectSpec{
    88  			SourceRepos:  []string{},
    89  			Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
    90  		},
    91  	}
    92  	fakeRepo = appsv1.Repository{
    93  		Repo:           "https://test",
    94  		Type:           "test",
    95  		Name:           "test",
    96  		Username:       "argo",
    97  		Insecure:       false,
    98  		EnableLFS:      false,
    99  		EnableOCI:      false,
   100  		Proxy:          "test",
   101  		Project:        "argocd",
   102  		InheritedCreds: true,
   103  	}
   104  	guestbookApp = &appsv1.Application{
   105  		TypeMeta: metav1.TypeMeta{
   106  			Kind:       application.ApplicationKind,
   107  			APIVersion: "argoproj.io/v1alpha1",
   108  		},
   109  		ObjectMeta: metav1.ObjectMeta{
   110  			Name:      "guestbook",
   111  			Namespace: testNamespace,
   112  		},
   113  		Spec: appsv1.ApplicationSpec{
   114  			Project: "default",
   115  			Source: &appsv1.ApplicationSource{
   116  				RepoURL:        "https://test",
   117  				TargetRevision: "HEAD",
   118  				Helm: &appsv1.ApplicationSourceHelm{
   119  					ValueFiles: []string{"values.yaml"},
   120  				},
   121  			},
   122  		},
   123  		Status: appsv1.ApplicationStatus{
   124  			History: appsv1.RevisionHistories{
   125  				{
   126  					Revision: "abcdef123567",
   127  					Source: appsv1.ApplicationSource{
   128  						RepoURL:        "https://test",
   129  						TargetRevision: "HEAD",
   130  						Helm: &appsv1.ApplicationSourceHelm{
   131  							ValueFiles: []string{"values-old.yaml"},
   132  						},
   133  					},
   134  				},
   135  			},
   136  		},
   137  	}
   138  )
   139  
   140  func newAppAndProjLister(objects ...runtime.Object) (applisters.ApplicationLister, k8scache.SharedIndexInformer) {
   141  	fakeAppsClientset := fakeapps.NewSimpleClientset(objects...)
   142  	factory := appinformer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, appinformer.WithNamespace(""), appinformer.WithTweakListOptions(func(options *metav1.ListOptions) {}))
   143  	projInformer := factory.Argoproj().V1alpha1().AppProjects()
   144  	appsInformer := factory.Argoproj().V1alpha1().Applications()
   145  	for _, obj := range objects {
   146  		switch obj.(type) {
   147  		case *appsv1.AppProject:
   148  			_ = projInformer.Informer().GetStore().Add(obj)
   149  		case *appsv1.Application:
   150  			_ = appsInformer.Informer().GetStore().Add(obj)
   151  		}
   152  	}
   153  	appLister := appsInformer.Lister()
   154  	return appLister, projInformer.Informer()
   155  }
   156  
   157  func Test_createRBACObject(t *testing.T) {
   158  	object := createRBACObject("test-prj", "test-repo")
   159  	assert.Equal(t, "test-prj/test-repo", object)
   160  	objectWithoutPrj := createRBACObject("", "test-repo")
   161  	assert.Equal(t, "test-repo", objectWithoutPrj)
   162  }
   163  
   164  func TestRepositoryServer(t *testing.T) {
   165  	kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
   166  	settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
   167  	enforcer := newEnforcer(kubeclientset)
   168  	appLister, projInformer := newAppAndProjLister(defaultProj)
   169  	argoDB := db.NewDB("default", settingsMgr, kubeclientset)
   170  
   171  	t.Run("Test_getRepo", func(t *testing.T) {
   172  		repoServerClient := mocks.RepoServerServiceClient{}
   173  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   174  
   175  		s := NewServer(&repoServerClientset, argoDB, enforcer, nil, appLister, projInformer, testNamespace, settingsMgr)
   176  		url := "https://test"
   177  		repo, _ := s.getRepo(context.TODO(), url)
   178  		assert.Equal(t, repo.Repo, url)
   179  	})
   180  
   181  	t.Run("Test_validateAccess", func(t *testing.T) {
   182  		repoServerClient := mocks.RepoServerServiceClient{}
   183  		repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
   184  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   185  
   186  		s := NewServer(&repoServerClientset, argoDB, enforcer, nil, appLister, projInformer, testNamespace, settingsMgr)
   187  		url := "https://test"
   188  		_, err := s.ValidateAccess(context.TODO(), &repository.RepoAccessQuery{
   189  			Repo: url,
   190  		})
   191  		assert.Nil(t, err)
   192  	})
   193  
   194  	t.Run("Test_Get", func(t *testing.T) {
   195  		repoServerClient := mocks.RepoServerServiceClient{}
   196  		repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
   197  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   198  
   199  		url := "https://test"
   200  		db := &dbmocks.ArgoDB{}
   201  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   202  		db.On("RepositoryExists", context.TODO(), url).Return(true, nil)
   203  
   204  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr)
   205  		repo, err := s.Get(context.TODO(), &repository.RepoQuery{
   206  			Repo: url,
   207  		})
   208  		assert.Nil(t, err)
   209  		assert.Equal(t, repo.Repo, url)
   210  	})
   211  
   212  	t.Run("Test_GetInherited", func(t *testing.T) {
   213  		repoServerClient := mocks.RepoServerServiceClient{}
   214  		repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
   215  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   216  
   217  		url := "https://test"
   218  		db := &dbmocks.ArgoDB{}
   219  		testRepo := &appsv1.Repository{
   220  			Repo:           url,
   221  			Type:           "git",
   222  			Username:       "foo",
   223  			InheritedCreds: true,
   224  		}
   225  		db.On("GetRepository", context.TODO(), url).Return(testRepo, nil)
   226  		db.On("RepositoryExists", context.TODO(), url).Return(true, nil)
   227  
   228  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr)
   229  		repo, err := s.Get(context.TODO(), &repository.RepoQuery{
   230  			Repo: url,
   231  		})
   232  		assert.Nil(t, err)
   233  
   234  		testRepo.ConnectionState = repo.ConnectionState // overwrite connection state on our test object to simplify comparison below
   235  
   236  		assert.Equal(t, testRepo, repo)
   237  	})
   238  
   239  	t.Run("Test_GetWithErrorShouldReturn403", func(t *testing.T) {
   240  		repoServerClient := mocks.RepoServerServiceClient{}
   241  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   242  
   243  		url := "https://test"
   244  		db := &dbmocks.ArgoDB{}
   245  		db.On("GetRepository", context.TODO(), url).Return(nil, errors.New("some error"))
   246  		db.On("RepositoryExists", context.TODO(), url).Return(true, nil)
   247  
   248  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr)
   249  		repo, err := s.Get(context.TODO(), &repository.RepoQuery{
   250  			Repo: url,
   251  		})
   252  		assert.Nil(t, repo)
   253  		assert.Equal(t, err, errPermissionDenied)
   254  	})
   255  
   256  	t.Run("Test_GetWithNotExistRepoShouldReturn404", func(t *testing.T) {
   257  		repoServerClient := mocks.RepoServerServiceClient{}
   258  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   259  
   260  		url := "https://test"
   261  		db := &dbmocks.ArgoDB{}
   262  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   263  		db.On("RepositoryExists", context.TODO(), url).Return(false, nil)
   264  
   265  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr)
   266  		repo, err := s.Get(context.TODO(), &repository.RepoQuery{
   267  			Repo: url,
   268  		})
   269  		assert.Nil(t, repo)
   270  		assert.Equal(t, "rpc error: code = NotFound desc = repo 'https://test' not found", err.Error())
   271  	})
   272  
   273  	t.Run("Test_CreateRepositoryWithoutUpsert", func(t *testing.T) {
   274  		repoServerClient := mocks.RepoServerServiceClient{}
   275  		repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
   276  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   277  
   278  		db := &dbmocks.ArgoDB{}
   279  		db.On("GetRepository", context.TODO(), "test").Return(nil, errors.New("not found"))
   280  		db.On("CreateRepository", context.TODO(), mock.Anything).Return(&apiclient.TestRepositoryResponse{}).Return(&appsv1.Repository{
   281  			Repo:    "repo",
   282  			Project: "proj",
   283  		}, nil)
   284  
   285  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr)
   286  		repo, err := s.CreateRepository(context.TODO(), &repository.RepoCreateRequest{
   287  			Repo: &appsv1.Repository{
   288  				Repo:     "test",
   289  				Username: "test",
   290  			},
   291  		})
   292  		assert.Nil(t, err)
   293  		assert.Equal(t, repo.Repo, "repo")
   294  	})
   295  
   296  	t.Run("Test_CreateRepositoryWithUpsert", func(t *testing.T) {
   297  		repoServerClient := mocks.RepoServerServiceClient{}
   298  		repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
   299  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   300  
   301  		db := &dbmocks.ArgoDB{}
   302  		db.On("GetRepository", context.TODO(), "test").Return(&appsv1.Repository{
   303  			Repo:     "test",
   304  			Username: "test",
   305  		}, nil)
   306  		db.On("CreateRepository", context.TODO(), mock.Anything).Return(nil, status.Errorf(codes.AlreadyExists, "repository already exists"))
   307  		db.On("UpdateRepository", context.TODO(), mock.Anything).Return(nil, nil)
   308  
   309  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr)
   310  		repo, err := s.CreateRepository(context.TODO(), &repository.RepoCreateRequest{
   311  			Repo: &appsv1.Repository{
   312  				Repo:     "test",
   313  				Username: "test",
   314  			},
   315  			Upsert: true,
   316  		})
   317  
   318  		assert.Nil(t, err)
   319  		assert.Equal(t, repo.Repo, "test")
   320  	})
   321  
   322  	t.Run("Test_ListRepositories", func(t *testing.T) {
   323  		repoServerClient := mocks.RepoServerServiceClient{}
   324  		repoServerClient.On("TestRepository", mock.Anything, mock.Anything).Return(&apiclient.TestRepositoryResponse{}, nil)
   325  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   326  		enforcer := newEnforcer(kubeclientset)
   327  
   328  		url := "https://test"
   329  		db := &dbmocks.ArgoDB{}
   330  		db.On("GetRepository", context.TODO(), url).Return(nil, nil)
   331  		db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
   332  		db.On("ListRepositories", context.TODO()).Return([]*appsv1.Repository{&fakeRepo, &fakeRepo}, nil)
   333  
   334  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projInformer, testNamespace, settingsMgr)
   335  		resp, err := s.ListRepositories(context.TODO(), &repository.RepoQuery{})
   336  		assert.NoError(t, err)
   337  		assert.Equal(t, 2, len(resp.Items))
   338  	})
   339  }
   340  
   341  func TestRepositoryServerListApps(t *testing.T) {
   342  	kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
   343  	settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
   344  
   345  	t.Run("Test_WithoutAppCreateUpdatePrivileges", func(t *testing.T) {
   346  		repoServerClient := mocks.RepoServerServiceClient{}
   347  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   348  		enforcer := newEnforcer(kubeclientset)
   349  		enforcer.SetDefaultRole("role:readonly")
   350  
   351  		url := "https://test"
   352  		db := &dbmocks.ArgoDB{}
   353  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   354  		appLister, projLister := newAppAndProjLister(defaultProj)
   355  
   356  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   357  		resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
   358  			Repo:       "https://test",
   359  			Revision:   "HEAD",
   360  			AppName:    "foo",
   361  			AppProject: "default",
   362  		})
   363  		assert.Nil(t, resp)
   364  		assert.Equal(t, err, errPermissionDenied)
   365  	})
   366  
   367  	t.Run("Test_WithAppCreateUpdatePrivileges", func(t *testing.T) {
   368  		repoServerClient := mocks.RepoServerServiceClient{}
   369  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   370  		enforcer := newEnforcer(kubeclientset)
   371  		enforcer.SetDefaultRole("role:admin")
   372  		appLister, projLister := newAppAndProjLister(defaultProj)
   373  
   374  		url := "https://test"
   375  		db := &dbmocks.ArgoDB{}
   376  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   377  		db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil)
   378  		db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil)
   379  		repoServerClient.On("ListApps", context.TODO(), mock.Anything).Return(&apiclient.AppList{
   380  			Apps: map[string]string{
   381  				"path/to/dir": "Kustomize",
   382  			},
   383  		}, nil)
   384  
   385  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   386  		resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
   387  			Repo:       "https://test",
   388  			Revision:   "HEAD",
   389  			AppName:    "foo",
   390  			AppProject: "default",
   391  		})
   392  		assert.NoError(t, err)
   393  		assert.Len(t, resp.Items, 1)
   394  		assert.Equal(t, "path/to/dir", resp.Items[0].Path)
   395  		assert.Equal(t, "Kustomize", resp.Items[0].Type)
   396  	})
   397  
   398  	t.Run("Test_WithAppCreateUpdatePrivilegesRepoNotAllowed", func(t *testing.T) {
   399  		repoServerClient := mocks.RepoServerServiceClient{}
   400  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   401  		enforcer := newEnforcer(kubeclientset)
   402  		enforcer.SetDefaultRole("role:admin")
   403  		appLister, projLister := newAppAndProjLister(defaultProjNoSources)
   404  
   405  		url := "https://test"
   406  		db := &dbmocks.ArgoDB{}
   407  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   408  		db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil)
   409  		db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil)
   410  		repoServerClient.On("ListApps", context.TODO(), mock.Anything).Return(&apiclient.AppList{
   411  			Apps: map[string]string{
   412  				"path/to/dir": "Kustomize",
   413  			},
   414  		}, nil)
   415  
   416  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   417  		resp, err := s.ListApps(context.TODO(), &repository.RepoAppsQuery{
   418  			Repo:       "https://test",
   419  			Revision:   "HEAD",
   420  			AppName:    "foo",
   421  			AppProject: "default",
   422  		})
   423  		assert.Nil(t, resp)
   424  		assert.Error(t, err, "repository 'https://test' not permitted in project 'default'")
   425  	})
   426  }
   427  
   428  func TestRepositoryServerGetAppDetails(t *testing.T) {
   429  	kubeclientset := fake.NewSimpleClientset(&argocdCM, &argocdSecret)
   430  	settingsMgr := settings.NewSettingsManager(context.Background(), kubeclientset, testNamespace)
   431  
   432  	t.Run("Test_WithoutRepoReadPrivileges", func(t *testing.T) {
   433  		repoServerClient := mocks.RepoServerServiceClient{}
   434  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   435  		enforcer := newEnforcer(kubeclientset)
   436  		enforcer.SetDefaultRole("")
   437  
   438  		url := "https://test"
   439  		db := &dbmocks.ArgoDB{}
   440  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   441  		appLister, projLister := newAppAndProjLister(defaultProj)
   442  
   443  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   444  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   445  			Source: &appsv1.ApplicationSource{
   446  				RepoURL: url,
   447  			},
   448  			AppName:    "newapp",
   449  			AppProject: "default",
   450  		})
   451  		assert.Nil(t, resp)
   452  		assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: repositories, get, https://test")
   453  	})
   454  	t.Run("Test_WithoutAppReadPrivileges", func(t *testing.T) {
   455  		repoServerClient := mocks.RepoServerServiceClient{}
   456  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   457  		enforcer := newEnforcer(kubeclientset)
   458  		_ = enforcer.SetUserPolicy("p, role:readrepos, repositories, get, *, allow")
   459  		enforcer.SetDefaultRole("role:readrepos")
   460  
   461  		url := "https://test"
   462  		db := &dbmocks.ArgoDB{}
   463  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   464  		appLister, projLister := newAppAndProjLister(defaultProj)
   465  
   466  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   467  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   468  			Source: &appsv1.ApplicationSource{
   469  				RepoURL: url,
   470  			},
   471  			AppName:    "newapp",
   472  			AppProject: "default",
   473  		})
   474  		assert.Nil(t, resp)
   475  		assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: applications, get, default/newapp")
   476  	})
   477  	t.Run("Test_WithoutCreatePrivileges", func(t *testing.T) {
   478  		repoServerClient := mocks.RepoServerServiceClient{}
   479  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   480  		enforcer := newEnforcer(kubeclientset)
   481  		enforcer.SetDefaultRole("role:readonly")
   482  
   483  		url := "https://test"
   484  		db := &dbmocks.ArgoDB{}
   485  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   486  		appLister, projLister := newAppAndProjLister(defaultProj)
   487  
   488  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   489  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   490  			Source: &appsv1.ApplicationSource{
   491  				RepoURL: url,
   492  			},
   493  			AppName:    "newapp",
   494  			AppProject: "default",
   495  		})
   496  		assert.Nil(t, resp)
   497  		assert.Error(t, err, "rpc error: code = PermissionDenied desc = permission denied: applications, create, default/newapp")
   498  	})
   499  	t.Run("Test_WithCreatePrivileges", func(t *testing.T) {
   500  		repoServerClient := mocks.RepoServerServiceClient{}
   501  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   502  		enforcer := newEnforcer(kubeclientset)
   503  
   504  		url := "https://test"
   505  		db := &dbmocks.ArgoDB{}
   506  		db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
   507  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   508  		db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil)
   509  		db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil)
   510  		expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
   511  		repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
   512  		appLister, projLister := newAppAndProjLister(defaultProj)
   513  
   514  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   515  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   516  			Source: &appsv1.ApplicationSource{
   517  				RepoURL: url,
   518  			},
   519  			AppName:    "newapp",
   520  			AppProject: "default",
   521  		})
   522  		assert.NoError(t, err)
   523  		assert.Equal(t, expectedResp, *resp)
   524  	})
   525  	t.Run("Test_RepoNotPermitted", func(t *testing.T) {
   526  		repoServerClient := mocks.RepoServerServiceClient{}
   527  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   528  		enforcer := newEnforcer(kubeclientset)
   529  
   530  		url := "https://test"
   531  		db := &dbmocks.ArgoDB{}
   532  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   533  		db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil)
   534  		db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil)
   535  		expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
   536  		repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
   537  		appLister, projLister := newAppAndProjLister(defaultProjNoSources)
   538  
   539  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   540  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   541  			Source: &appsv1.ApplicationSource{
   542  				RepoURL: url,
   543  			},
   544  			AppName:    "newapp",
   545  			AppProject: "default",
   546  		})
   547  		assert.Error(t, err, "repository 'https://test' not permitted in project 'default'")
   548  		assert.Nil(t, resp)
   549  	})
   550  	t.Run("Test_ExistingApp", func(t *testing.T) {
   551  		repoServerClient := mocks.RepoServerServiceClient{}
   552  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   553  		enforcer := newEnforcer(kubeclientset)
   554  
   555  		url := "https://test"
   556  		db := &dbmocks.ArgoDB{}
   557  		db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
   558  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   559  		db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil)
   560  		db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil)
   561  		expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
   562  		repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
   563  		appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
   564  
   565  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   566  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   567  			Source:     guestbookApp.Spec.GetSourcePtr(),
   568  			AppName:    "guestbook",
   569  			AppProject: "default",
   570  		})
   571  		assert.NoError(t, err)
   572  		assert.Equal(t, expectedResp, *resp)
   573  	})
   574  	t.Run("Test_ExistingAppMismatchedProjectName", func(t *testing.T) {
   575  		repoServerClient := mocks.RepoServerServiceClient{}
   576  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   577  		enforcer := newEnforcer(kubeclientset)
   578  
   579  		url := "https://test"
   580  		db := &dbmocks.ArgoDB{}
   581  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   582  		appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
   583  
   584  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   585  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   586  			Source:     guestbookApp.Spec.GetSourcePtr(),
   587  			AppName:    "guestbook",
   588  			AppProject: "mismatch",
   589  		})
   590  		assert.Equal(t, errPermissionDenied, err)
   591  		assert.Nil(t, resp)
   592  	})
   593  	t.Run("Test_ExistingAppSourceNotInHistory", func(t *testing.T) {
   594  		repoServerClient := mocks.RepoServerServiceClient{}
   595  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   596  		enforcer := newEnforcer(kubeclientset)
   597  
   598  		url := "https://test"
   599  		db := &dbmocks.ArgoDB{}
   600  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   601  		appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
   602  		differentSource := guestbookApp.Spec.Source.DeepCopy()
   603  		differentSource.Helm.ValueFiles = []string{"/etc/passwd"}
   604  
   605  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   606  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   607  			Source:     differentSource,
   608  			AppName:    "guestbook",
   609  			AppProject: "default",
   610  		})
   611  		assert.Equal(t, errPermissionDenied, err)
   612  		assert.Nil(t, resp)
   613  	})
   614  	t.Run("Test_ExistingAppSourceInHistory", func(t *testing.T) {
   615  		repoServerClient := mocks.RepoServerServiceClient{}
   616  		repoServerClientset := mocks.Clientset{RepoServerServiceClient: &repoServerClient}
   617  		enforcer := newEnforcer(kubeclientset)
   618  
   619  		url := "https://test"
   620  		db := &dbmocks.ArgoDB{}
   621  		db.On("GetRepository", context.TODO(), url).Return(&appsv1.Repository{Repo: url}, nil)
   622  		db.On("ListHelmRepositories", context.TODO(), mock.Anything).Return(nil, nil)
   623  		db.On("GetProjectRepositories", context.TODO(), "default").Return(nil, nil)
   624  		db.On("GetProjectClusters", context.TODO(), "default").Return(nil, nil)
   625  		expectedResp := apiclient.RepoAppDetailsResponse{Type: "Directory"}
   626  		repoServerClient.On("GetAppDetails", context.TODO(), mock.Anything).Return(&expectedResp, nil)
   627  		appLister, projLister := newAppAndProjLister(defaultProj, guestbookApp)
   628  		previousSource := guestbookApp.Status.History[0].Source.DeepCopy()
   629  		previousSource.TargetRevision = guestbookApp.Status.History[0].Revision
   630  
   631  		s := NewServer(&repoServerClientset, db, enforcer, newFixtures().Cache, appLister, projLister, testNamespace, settingsMgr)
   632  		resp, err := s.GetAppDetails(context.TODO(), &repository.RepoAppDetailsQuery{
   633  			Source:     previousSource,
   634  			AppName:    "guestbook",
   635  			AppProject: "default",
   636  		})
   637  		assert.NoError(t, err)
   638  		assert.Equal(t, expectedResp, *resp)
   639  	})
   640  }
   641  
   642  type fixtures struct {
   643  	*cache.Cache
   644  }
   645  
   646  func newFixtures() *fixtures {
   647  	return &fixtures{cache.NewCache(
   648  		appstatecache.NewCache(
   649  			cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Hour)),
   650  			1*time.Minute,
   651  		),
   652  		1*time.Minute,
   653  		1*time.Minute,
   654  		1*time.Minute,
   655  	)}
   656  }
   657  
   658  func newEnforcer(kubeclientset *fake.Clientset) *rbac.Enforcer {
   659  	enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
   660  	_ = enforcer.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
   661  	enforcer.SetDefaultRole("role:admin")
   662  	enforcer.SetClaimsEnforcerFunc(func(claims jwt.Claims, rvals ...interface{}) bool {
   663  		return true
   664  	})
   665  	return enforcer
   666  }