github.com/verrazzano/verrazzano@v1.7.0/application-operator/mcagent/mcagent_project_test.go (about)

     1  // Copyright (c) 2021, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package mcagent
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	"github.com/golang/mock/gomock"
    13  	asserts "github.com/stretchr/testify/assert"
    14  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    15  	"github.com/verrazzano/verrazzano/application-operator/constants"
    16  	clusterstest "github.com/verrazzano/verrazzano/application-operator/controllers/clusters/test"
    17  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    18  	vzstring "github.com/verrazzano/verrazzano/pkg/string"
    19  	"go.uber.org/zap"
    20  	corev1 "k8s.io/api/core/v1"
    21  	"k8s.io/apimachinery/pkg/api/errors"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/runtime/schema"
    24  	"k8s.io/apimachinery/pkg/types"
    25  	"sigs.k8s.io/controller-runtime/pkg/client"
    26  )
    27  
    28  var testLabels = map[string]string{"label1": "test1", "label2": "test2"}
    29  var testAnnotations = map[string]string{"annot1": "test1", "annot2": "test2"}
    30  
    31  var testNamespace1 = clustersv1alpha1.NamespaceTemplate{
    32  	Metadata: metav1.ObjectMeta{
    33  		Name:        "newNS1",
    34  		Labels:      testLabels,
    35  		Annotations: testAnnotations,
    36  	},
    37  }
    38  
    39  var testNamespace2 = clustersv1alpha1.NamespaceTemplate{
    40  	Metadata: metav1.ObjectMeta{
    41  		Name:        "newNS2",
    42  		Labels:      testLabels,
    43  		Annotations: testAnnotations,
    44  	},
    45  }
    46  
    47  var testNamespace3 = clustersv1alpha1.NamespaceTemplate{
    48  	Metadata: metav1.ObjectMeta{
    49  		Name:        "newNS3",
    50  		Labels:      testLabels,
    51  		Annotations: testAnnotations,
    52  	},
    53  }
    54  
    55  var testNamespace4 = clustersv1alpha1.NamespaceTemplate{
    56  	Metadata: metav1.ObjectMeta{
    57  		Name:   "newNS4",
    58  		Labels: testLabels,
    59  	},
    60  }
    61  
    62  // TestSyncer_syncVerrazzanoProjects tests the synchronization method for the following use case.
    63  // GIVEN a request to sync VerrazzanoProject objects
    64  // WHEN the a new object exists
    65  // THEN ensure that the VerrazzanoProject is created.
    66  func TestSyncer_syncVerrazzanoProjects(t *testing.T) {
    67  	const existingVP = "existingVP"
    68  	newNamespaces := []clustersv1alpha1.NamespaceTemplate{testNamespace1, testNamespace2}
    69  
    70  	type fields struct {
    71  		vpNamespace string
    72  		vpName      string
    73  		nsList      []clustersv1alpha1.NamespaceTemplate
    74  		clusters    []clustersv1alpha1.Cluster
    75  	}
    76  	tests := []struct {
    77  		name    string
    78  		fields  fields
    79  		wantErr bool
    80  	}{
    81  		{
    82  			"Update VP",
    83  			fields{
    84  				constants.VerrazzanoMultiClusterNamespace,
    85  				existingVP,
    86  				newNamespaces,
    87  				[]clustersv1alpha1.Cluster{{Name: testClusterName}},
    88  			},
    89  			false,
    90  		},
    91  		{
    92  			"Create VP",
    93  			fields{
    94  				constants.VerrazzanoMultiClusterNamespace,
    95  				"newVP",
    96  				newNamespaces,
    97  				[]clustersv1alpha1.Cluster{{Name: testClusterName}},
    98  			},
    99  			false,
   100  		},
   101  	}
   102  	for _, tt := range tests {
   103  		t.Run(tt.name, func(t *testing.T) {
   104  			assert := asserts.New(t)
   105  			log := zap.S().With("test")
   106  
   107  			// Managed cluster mocks
   108  			localMocker := gomock.NewController(t)
   109  			localMock := mocks.NewMockClient(localMocker)
   110  
   111  			// Admin cluster mocks
   112  			adminMocker := gomock.NewController(t)
   113  			adminMock := mocks.NewMockClient(adminMocker)
   114  
   115  			// Test data
   116  			testProj, err := getTestVerrazzanoProject(tt.fields.vpNamespace, tt.fields.vpName, tt.fields.nsList, tt.fields.clusters)
   117  			assert.NoError(err, "failed to get sample project")
   118  
   119  			// Admin Cluster - expect call to list VerrazzanoProject objects - return list with one object
   120  			adminMock.EXPECT().
   121  				List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   122  				DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, listOptions *client.ListOptions) error {
   123  					assert.Equal(constants.VerrazzanoMultiClusterNamespace, listOptions.Namespace)
   124  					list.Items = append(list.Items, testProj)
   125  					return nil
   126  				})
   127  
   128  			if tt.fields.vpName == existingVP {
   129  				// Managed Cluster - expect call to get VerrazzanoProject
   130  				localMock.EXPECT().
   131  					Get(gomock.Any(), types.NamespacedName{Namespace: tt.fields.vpNamespace, Name: tt.fields.vpName}, gomock.Not(gomock.Nil()), gomock.Any()).
   132  					DoAndReturn(func(ctx context.Context, name types.NamespacedName, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.GetOption) error {
   133  						vp.Namespace = tt.fields.vpNamespace
   134  						vp.Name = tt.fields.vpName
   135  						vp.Spec.Template.Namespaces = []clustersv1alpha1.NamespaceTemplate{testNamespace1, testNamespace2, testNamespace3}
   136  						vp.Spec.Placement.Clusters = []clustersv1alpha1.Cluster{{Name: testClusterName}}
   137  						return nil
   138  					})
   139  
   140  				// Managed Cluster - expect call to update a VerrazzanoProject
   141  				localMock.EXPECT().
   142  					Update(gomock.Any(), gomock.Any()).
   143  					DoAndReturn(func(ctx context.Context, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.UpdateOption) error {
   144  						assert.Equal(tt.fields.vpNamespace, vp.Namespace, "VerrazzanoProject namespace did not match")
   145  						assert.Equal(tt.fields.vpName, vp.Name, "VerrazzanoProject name did not match")
   146  						assert.ElementsMatch(tt.fields.nsList, vp.Spec.Template.Namespaces)
   147  						return nil
   148  					})
   149  			} else {
   150  				// Managed Cluster - expect call to get VerrazzanoProject
   151  				localMock.EXPECT().
   152  					Get(gomock.Any(), types.NamespacedName{Namespace: tt.fields.vpNamespace, Name: tt.fields.vpName}, gomock.Not(gomock.Nil()), gomock.Any()).
   153  					Return(errors.NewNotFound(schema.GroupResource{Group: "clusters.verrazzano.io", Resource: "VerrazzanoProject"}, tt.fields.vpName))
   154  
   155  				// Managed Cluster - expect call to create a VerrazzanoProject
   156  				localMock.EXPECT().
   157  					Create(gomock.Any(), gomock.Any()).
   158  					DoAndReturn(func(ctx context.Context, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.CreateOption) error {
   159  						assert.Equal(tt.fields.vpNamespace, vp.Namespace, "VerrazzanoProject namespace did not match")
   160  						assert.Equal(tt.fields.vpName, vp.Name, "VerrazzanoProject name did not match")
   161  						assert.ElementsMatch(tt.fields.nsList, vp.Spec.Template.Namespaces)
   162  						return nil
   163  					})
   164  			}
   165  
   166  			// Managed Cluster - expect call to list VerrazzanoProject objects on the local cluster
   167  			localMock.EXPECT().
   168  				List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   169  				DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, listOptions *client.ListOptions) error {
   170  					assert.Equal(constants.VerrazzanoMultiClusterNamespace, listOptions.Namespace)
   171  					list.Items = append(list.Items, testProj)
   172  					return nil
   173  				})
   174  
   175  			// Managed cluster - expect call to list Namespace objects - return list defined for this test run
   176  			localMock.EXPECT().
   177  				List(gomock.Any(), &corev1.NamespaceList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   178  				DoAndReturn(func(ctx context.Context, list *corev1.NamespaceList, listOptions *client.ListOptions) error {
   179  					for _, namespace := range tt.fields.nsList {
   180  						list.Items = append(list.Items, corev1.Namespace{
   181  							ObjectMeta: namespace.Metadata,
   182  							Spec:       namespace.Spec,
   183  						})
   184  					}
   185  					return nil
   186  				})
   187  
   188  			// Make the request
   189  			s := &Syncer{
   190  				AdminClient:        adminMock,
   191  				LocalClient:        localMock,
   192  				Log:                log,
   193  				ManagedClusterName: testClusterName,
   194  				Context:            context.TODO(),
   195  			}
   196  			err = s.syncVerrazzanoProjects()
   197  
   198  			// Validate the results
   199  			adminMocker.Finish()
   200  			localMocker.Finish()
   201  
   202  			if (err != nil) != tt.wantErr {
   203  				t.Errorf("syncVerrazzanoProjects() error = %v, wantErr %v", err, tt.wantErr)
   204  			}
   205  
   206  			// Validate the namespace list that resulted from processing the VerrazzanoProject objects
   207  			assert.Equal(len(tt.fields.nsList), len(s.ProjectNamespaces), "number of expected namespaces did not match")
   208  			for _, namespace := range tt.fields.nsList {
   209  				assert.True(vzstring.SliceContainsString(s.ProjectNamespaces, namespace.Metadata.Name), "expected namespace not being watched")
   210  			}
   211  		})
   212  	}
   213  }
   214  
   215  // TestDeleteVerrazzanoProject tests the synchronization method for the following use case.
   216  // GIVEN a request to sync VerrazzanoProject objects
   217  // WHEN the object exists on the local cluster but not on the admin cluster
   218  // THEN ensure that the VerrazzanoProject is deleted.
   219  func TestDeleteVerrazzanoProject(t *testing.T) {
   220  	type fields struct {
   221  		vpNamespace string
   222  		vpName      string
   223  		nsList      []clustersv1alpha1.NamespaceTemplate
   224  		clusters    []clustersv1alpha1.Cluster
   225  	}
   226  	tests := []struct {
   227  		name    string
   228  		fields  fields
   229  		wantErr bool
   230  	}{
   231  		{
   232  			"Orphaned VP",
   233  			fields{
   234  				constants.VerrazzanoMultiClusterNamespace,
   235  				"TestVP",
   236  				[]clustersv1alpha1.NamespaceTemplate{testNamespace1},
   237  				[]clustersv1alpha1.Cluster{{Name: testClusterName}},
   238  			},
   239  			false,
   240  		},
   241  	}
   242  	for _, tt := range tests {
   243  		t.Run(tt.name, func(t *testing.T) {
   244  			assert := asserts.New(t)
   245  			log := zap.S().With("test")
   246  
   247  			// Managed cluster mocks
   248  			localMocker := gomock.NewController(t)
   249  			localMock := mocks.NewMockClient(localMocker)
   250  
   251  			// Admin cluster mocks
   252  			adminMocker := gomock.NewController(t)
   253  			adminMock := mocks.NewMockClient(adminMocker)
   254  
   255  			// Test data
   256  			testProj, err := getTestVerrazzanoProject(tt.fields.vpNamespace, tt.fields.vpName, tt.fields.nsList, tt.fields.clusters)
   257  			assert.NoError(err, "failed to get sample project")
   258  			testProjOrphan, err := getTestVerrazzanoProject(tt.fields.vpNamespace, tt.fields.vpName+"-orphan", tt.fields.nsList, tt.fields.clusters)
   259  			assert.NoError(err, "failed to get sample project")
   260  
   261  			// Admin Cluster - expect call to list VerrazzanoProject objects - return list with one object
   262  			adminMock.EXPECT().
   263  				List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   264  				DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, listOptions *client.ListOptions) error {
   265  					assert.Equal(constants.VerrazzanoMultiClusterNamespace, listOptions.Namespace)
   266  					list.Items = append(list.Items, testProj)
   267  					return nil
   268  				})
   269  
   270  			// Managed Cluster - expect call to get VerrazzanoProject
   271  			localMock.EXPECT().
   272  				Get(gomock.Any(), types.NamespacedName{Namespace: tt.fields.vpNamespace, Name: tt.fields.vpName}, gomock.Not(gomock.Nil()), gomock.Any()).
   273  				DoAndReturn(func(ctx context.Context, name types.NamespacedName, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.GetOption) error {
   274  					vp.Namespace = tt.fields.vpNamespace
   275  					vp.Name = tt.fields.vpName
   276  					vp.Spec.Template.Namespaces = tt.fields.nsList
   277  					vp.Spec.Placement.Clusters = tt.fields.clusters
   278  					vp.Labels = testProj.Labels
   279  					vp.Annotations = testProj.Annotations
   280  					return nil
   281  				})
   282  
   283  			// Managed Cluster - expect call to list VerrazzanoProject objects on the local cluster, return an object that
   284  			// does not exist on the admin cluster
   285  			localMock.EXPECT().
   286  				List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   287  				DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, opts ...*client.ListOptions) error {
   288  					list.Items = append(list.Items, testProj)
   289  					list.Items = append(list.Items, testProjOrphan)
   290  					return nil
   291  				})
   292  
   293  			// Managed cluster - expect call to list Namespace objects - return list defined for this test run
   294  			localMock.EXPECT().
   295  				List(gomock.Any(), &corev1.NamespaceList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   296  				DoAndReturn(func(ctx context.Context, list *corev1.NamespaceList, listOptions *client.ListOptions) error {
   297  					for _, namespace := range tt.fields.nsList {
   298  						list.Items = append(list.Items, corev1.Namespace{
   299  							ObjectMeta: namespace.Metadata,
   300  							Spec:       namespace.Spec,
   301  						})
   302  					}
   303  					return nil
   304  				})
   305  
   306  			// Managed Cluster - expect a call to delete a VerrazzanoProject object
   307  			localMock.EXPECT().
   308  				Delete(gomock.Any(), gomock.Eq(&testProjOrphan), gomock.Any()).
   309  				Return(nil)
   310  
   311  			// Make the request
   312  			s := &Syncer{
   313  				AdminClient:        adminMock,
   314  				LocalClient:        localMock,
   315  				Log:                log,
   316  				ManagedClusterName: testClusterName,
   317  				Context:            context.TODO(),
   318  			}
   319  			err = s.syncVerrazzanoProjects()
   320  
   321  			// Validate the results
   322  			adminMocker.Finish()
   323  			localMocker.Finish()
   324  
   325  			if (err != nil) != tt.wantErr {
   326  				t.Errorf("syncVerrazzanoProjects() error = %v, wantErr %v", err, tt.wantErr)
   327  			}
   328  
   329  			// Validate the namespace list that resulted from processing the VerrazzanoProject objects
   330  			assert.Equal(len(tt.fields.nsList), len(s.ProjectNamespaces), "number of expected namespaces did not match")
   331  			for _, namespace := range tt.fields.nsList {
   332  				assert.True(vzstring.SliceContainsString(s.ProjectNamespaces, namespace.Metadata.Name), "expected namespace not being watched")
   333  			}
   334  		})
   335  	}
   336  }
   337  
   338  // TestVerrazzanoProjectMulti tests the synchronization method for the following use case.
   339  // GIVEN a request to sync multiple VerrazzanoProject objects
   340  // WHEN the a new object exists
   341  // THEN ensure that the list of namespaces to watch is correct
   342  func TestVerrazzanoProjectMulti(t *testing.T) {
   343  	type fields struct {
   344  		vpNamespace string
   345  		vpName      string
   346  		nsList      []clustersv1alpha1.NamespaceTemplate
   347  		clusters    []clustersv1alpha1.Cluster
   348  	}
   349  	tests := []struct {
   350  		name               string
   351  		vp1Fields          fields
   352  		vp2Fields          fields
   353  		expectedNamespaces int
   354  		wantErr            bool
   355  	}{
   356  		{
   357  			"TwoVP",
   358  			fields{
   359  				constants.VerrazzanoMultiClusterNamespace,
   360  				"newVP",
   361  				[]clustersv1alpha1.NamespaceTemplate{testNamespace1, testNamespace2},
   362  				[]clustersv1alpha1.Cluster{{Name: testClusterName}},
   363  			},
   364  			fields{
   365  				constants.VerrazzanoMultiClusterNamespace,
   366  				"newVP",
   367  				[]clustersv1alpha1.NamespaceTemplate{testNamespace3, testNamespace4},
   368  				[]clustersv1alpha1.Cluster{{Name: testClusterName}},
   369  			},
   370  			4,
   371  			false,
   372  		},
   373  	}
   374  	for _, tt := range tests {
   375  		t.Run(tt.name, func(t *testing.T) {
   376  			assert := asserts.New(t)
   377  			log := zap.S().With("test")
   378  
   379  			// Managed cluster mocks
   380  			localMocker := gomock.NewController(t)
   381  			localMock := mocks.NewMockClient(localMocker)
   382  
   383  			// Admin cluster mocks
   384  			adminMocker := gomock.NewController(t)
   385  			adminMock := mocks.NewMockClient(adminMocker)
   386  
   387  			// Test data
   388  			testProj1, err := getTestVerrazzanoProject(tt.vp1Fields.vpNamespace, tt.vp1Fields.vpName, tt.vp1Fields.nsList, tt.vp1Fields.clusters)
   389  			assert.NoError(err, "failed to get sample project")
   390  			testProj2, err := getTestVerrazzanoProject(tt.vp2Fields.vpNamespace, tt.vp2Fields.vpName, tt.vp2Fields.nsList, tt.vp2Fields.clusters)
   391  			assert.NoError(err, "failed to get sample project")
   392  
   393  			// Admin Cluster - expect call to list VerrazzanoProject objects - return list with two objects
   394  			adminMock.EXPECT().
   395  				List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   396  				DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, opts ...*client.ListOptions) error {
   397  					list.Items = append(list.Items, testProj1)
   398  					list.Items = append(list.Items, testProj2)
   399  					return nil
   400  				})
   401  
   402  			if tt.vp1Fields.vpNamespace == constants.VerrazzanoMultiClusterNamespace {
   403  				// Managed Cluster - expect call to get VerrazzanoProject
   404  				localMock.EXPECT().
   405  					Get(gomock.Any(), types.NamespacedName{Namespace: tt.vp1Fields.vpNamespace, Name: tt.vp1Fields.vpName}, gomock.Not(gomock.Nil()), gomock.Any()).
   406  					Return(errors.NewNotFound(schema.GroupResource{Group: "clusters.verrazzano.io", Resource: "VerrazzanoProject"}, tt.vp1Fields.vpName))
   407  
   408  				// Managed Cluster - expect call to create a VerrazzanoProject
   409  				localMock.EXPECT().
   410  					Create(gomock.Any(), gomock.Any()).
   411  					DoAndReturn(func(ctx context.Context, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.CreateOption) error {
   412  						assert.Equal(tt.vp1Fields.vpNamespace, vp.Namespace, "VerrazzanoProject namespace did not match")
   413  						assert.Equal(tt.vp1Fields.vpName, vp.Name, "VerrazzanoProject name did not match")
   414  						assert.ElementsMatch(tt.vp1Fields.nsList, vp.Spec.Template.Namespaces)
   415  						return nil
   416  					})
   417  
   418  				// Managed Cluster - expect call to get VerrazzanoProject
   419  				localMock.EXPECT().
   420  					Get(gomock.Any(), types.NamespacedName{Namespace: tt.vp2Fields.vpNamespace, Name: tt.vp2Fields.vpName}, gomock.Not(gomock.Nil()), gomock.Any()).
   421  					Return(errors.NewNotFound(schema.GroupResource{Group: "clusters.verrazzano.io", Resource: "VerrazzanoProject"}, tt.vp2Fields.vpName))
   422  
   423  				// Managed Cluster - expect call to create a VerrazzanoProject
   424  				localMock.EXPECT().
   425  					Create(gomock.Any(), gomock.Any()).
   426  					DoAndReturn(func(ctx context.Context, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.CreateOption) error {
   427  						assert.Equal(tt.vp2Fields.vpNamespace, vp.Namespace, "VerrazzanoProject namespace did not match")
   428  						assert.Equal(tt.vp2Fields.vpName, vp.Name, "VerrazzanoProject name did not match")
   429  						assert.ElementsMatch(tt.vp2Fields.nsList, vp.Spec.Template.Namespaces)
   430  						return nil
   431  					})
   432  
   433  				// Managed Cluster - expect call to list VerrazzanoProject objects on the local cluster
   434  				localMock.EXPECT().
   435  					List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   436  					DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, opts ...*client.ListOptions) error {
   437  						list.Items = append(list.Items, testProj1)
   438  						list.Items = append(list.Items, testProj2)
   439  						return nil
   440  					})
   441  
   442  				// Managed cluster - expect call to list Namespace objects - return list defined for this test run
   443  				localMock.EXPECT().
   444  					List(gomock.Any(), &corev1.NamespaceList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   445  					DoAndReturn(func(ctx context.Context, list *corev1.NamespaceList, listOptions *client.ListOptions) error {
   446  						for _, namespace := range tt.vp1Fields.nsList {
   447  							list.Items = append(list.Items, corev1.Namespace{
   448  								ObjectMeta: namespace.Metadata,
   449  								Spec:       namespace.Spec,
   450  							})
   451  						}
   452  						for _, namespace := range tt.vp2Fields.nsList {
   453  							list.Items = append(list.Items, corev1.Namespace{
   454  								ObjectMeta: namespace.Metadata,
   455  								Spec:       namespace.Spec,
   456  							})
   457  						}
   458  						return nil
   459  					})
   460  
   461  			}
   462  
   463  			// Make the request
   464  			s := &Syncer{
   465  				AdminClient:        adminMock,
   466  				LocalClient:        localMock,
   467  				Log:                log,
   468  				ManagedClusterName: testClusterName,
   469  				Context:            context.TODO(),
   470  			}
   471  			err = s.syncVerrazzanoProjects()
   472  
   473  			// Validate the results
   474  			adminMocker.Finish()
   475  			localMocker.Finish()
   476  
   477  			if (err != nil) != tt.wantErr {
   478  				t.Errorf("syncVerrazzanoProjects() error = %v, wantErr %v", err, tt.wantErr)
   479  			}
   480  
   481  			// Validate the namespace list that resulted from processing the VerrazzanoProject objects
   482  			assert.Equal(tt.expectedNamespaces, len(s.ProjectNamespaces), "number of expected namespaces did not match")
   483  			for _, namespace := range tt.vp1Fields.nsList {
   484  				assert.True(vzstring.SliceContainsString(s.ProjectNamespaces, namespace.Metadata.Name), "expected namespace not being watched")
   485  			}
   486  			for _, namespace := range tt.vp2Fields.nsList {
   487  				assert.True(vzstring.SliceContainsString(s.ProjectNamespaces, namespace.Metadata.Name), "expected namespace not being watched")
   488  			}
   489  		})
   490  	}
   491  }
   492  
   493  // TestRemovePlacementVerrazzanoProject tests the synchronization method for the following use case.
   494  // GIVEN a request to sync VerrazzanoProject objects
   495  // WHEN the object exists on the local cluster but is no longer placed on the local cluster
   496  // THEN ensure that the VerrazzanoProject is deleted.
   497  func TestRemovePlacementVerrazzanoProject(t *testing.T) {
   498  	assert := asserts.New(t)
   499  	log := zap.S().With("test")
   500  	vpName := "test"
   501  	vpNamespace := constants.VerrazzanoMultiClusterNamespace
   502  	nsList := []clustersv1alpha1.NamespaceTemplate{testNamespace1, testNamespace2}
   503  	clusters := []clustersv1alpha1.Cluster{{Name: testClusterName}}
   504  	clustersUpdated := []clustersv1alpha1.Cluster{{Name: constants.DefaultClusterName}}
   505  
   506  	// Managed cluster mocks
   507  	localMocker := gomock.NewController(t)
   508  	localMock := mocks.NewMockClient(localMocker)
   509  
   510  	// Admin cluster mocks
   511  	adminMocker := gomock.NewController(t)
   512  	adminMock := mocks.NewMockClient(adminMocker)
   513  
   514  	// Test data
   515  	testProj, err := getTestVerrazzanoProject(vpNamespace, vpName, nsList, clusters)
   516  	assert.NoError(err, "failed to get sample project")
   517  	testProjUpdated, err := getTestVerrazzanoProject(vpNamespace, vpName, nsList, clustersUpdated)
   518  	assert.NoError(err, "failed to get sample project")
   519  
   520  	// Admin Cluster - expect call to list VerrazzanoProject objects - return list with one object
   521  	adminMock.EXPECT().
   522  		List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   523  		DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, listOptions *client.ListOptions) error {
   524  			assert.Equal(constants.VerrazzanoMultiClusterNamespace, listOptions.Namespace)
   525  			list.Items = append(list.Items, testProjUpdated)
   526  			return nil
   527  		})
   528  
   529  	// Managed Cluster - expect call to get VerrazzanoProject
   530  	localMock.EXPECT().
   531  		Get(gomock.Any(), types.NamespacedName{Namespace: vpNamespace, Name: vpName}, gomock.Not(gomock.Nil()), gomock.Any()).
   532  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, vp *clustersv1alpha1.VerrazzanoProject, opts ...client.GetOption) error {
   533  			testProj.DeepCopyInto(vp)
   534  			return nil
   535  		})
   536  
   537  	// Managed cluster - expect call to list Namespace objects - return list defined for this test run
   538  	localMock.EXPECT().
   539  		List(gomock.Any(), &corev1.NamespaceList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   540  		DoAndReturn(func(ctx context.Context, list *corev1.NamespaceList, listOptions *client.ListOptions) error {
   541  			for _, namespace := range nsList {
   542  				list.Items = append(list.Items, corev1.Namespace{
   543  					ObjectMeta: namespace.Metadata,
   544  					Spec:       namespace.Spec,
   545  				})
   546  			}
   547  			return nil
   548  		})
   549  
   550  	// Managed Cluster - expect a call to delete a VerrazzanoProject object
   551  	localMock.EXPECT().
   552  		Delete(gomock.Any(), gomock.Eq(&testProj), gomock.Any()).
   553  		Return(nil)
   554  
   555  	// Managed Cluster - expect call to list VerrazzanoProject objects on the local cluster, return an empty list
   556  	localMock.EXPECT().
   557  		List(gomock.Any(), &clustersv1alpha1.VerrazzanoProjectList{}, gomock.AssignableToTypeOf(&client.ListOptions{})).
   558  		DoAndReturn(func(ctx context.Context, list *clustersv1alpha1.VerrazzanoProjectList, opts ...*client.ListOptions) error {
   559  			return nil
   560  		})
   561  
   562  	// Make the request
   563  	s := &Syncer{
   564  		AdminClient:        adminMock,
   565  		LocalClient:        localMock,
   566  		Log:                log,
   567  		ManagedClusterName: testClusterName,
   568  		Context:            context.TODO(),
   569  	}
   570  	err = s.syncVerrazzanoProjects()
   571  
   572  	// Validate the results
   573  	adminMocker.Finish()
   574  	localMocker.Finish()
   575  	assert.NoError(err)
   576  }
   577  
   578  // getTestVerrazzanoProject creates and returns VerrazzanoProject used in tests
   579  func getTestVerrazzanoProject(vpNamespace string, vpName string, nsNames []clustersv1alpha1.NamespaceTemplate, clusters []clustersv1alpha1.Cluster) (clustersv1alpha1.VerrazzanoProject, error) {
   580  	proj := clustersv1alpha1.VerrazzanoProject{}
   581  	templateFile, err := filepath.Abs("testdata/verrazzanoproject.yaml")
   582  	if err != nil {
   583  		return proj, err
   584  	}
   585  
   586  	// Convert template file to VerrazzanoProject struct
   587  	rawMcComp, err := clusterstest.ReadYaml2Json(templateFile)
   588  	if err != nil {
   589  		return proj, err
   590  	}
   591  	err = json.Unmarshal(rawMcComp, &proj)
   592  
   593  	// Populate the content
   594  	proj.Namespace = vpNamespace
   595  	proj.Name = vpName
   596  	proj.Spec.Template.Namespaces = nsNames
   597  	proj.Spec.Placement.Clusters = clusters
   598  	return proj, err
   599  }