github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/clusters/cluster_utils_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 clusters
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    14  	"github.com/golang/mock/gomock"
    15  	asserts "github.com/stretchr/testify/assert"
    16  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    17  	"github.com/verrazzano/verrazzano/application-operator/constants"
    18  	"github.com/verrazzano/verrazzano/application-operator/mocks"
    19  	"go.uber.org/zap"
    20  	v1 "k8s.io/api/core/v1"
    21  	kerr "k8s.io/apimachinery/pkg/api/errors"
    22  	"k8s.io/apimachinery/pkg/runtime/schema"
    23  	"k8s.io/apimachinery/pkg/types"
    24  	controllerruntime "sigs.k8s.io/controller-runtime"
    25  	"sigs.k8s.io/controller-runtime/pkg/client"
    26  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    27  )
    28  
    29  // TestGetClusterName tests fetching the cluster name from the managed cluster registration secret
    30  // GIVEN The managed cluster registration secret exists
    31  // WHEN GetClusterName function is called
    32  // THEN expect the managed-cluster-name from the secret to be returned
    33  // GIVEN the managed cluster secret does not exist but the local resgistration secret does
    34  // WHEN GetClusterName function is called
    35  // THEN expect that managed-cluster-name from the local secret to be returned
    36  // GIVEN the managed cluster secret and the local do not exist
    37  // WHEN GetClusterName function is called
    38  // THEN expect that empty string is returned
    39  func TestGetClusterName(t *testing.T) {
    40  	mocker := gomock.NewController(t)
    41  	cli := mocks.NewMockClient(mocker)
    42  
    43  	expectMCRegistrationSecret(cli, "mycluster1", MCRegistrationSecretFullName, 1)
    44  
    45  	name := GetClusterName(context.TODO(), cli)
    46  	asserts.Equal(t, "mycluster1", name)
    47  
    48  	// Repeat test for registration secret not found, then local registration secret should be used
    49  	cli.EXPECT().
    50  		Get(gomock.Any(), MCRegistrationSecretFullName, gomock.Not(gomock.Nil()), gomock.Any()).
    51  		Return(kerr.NewNotFound(schema.ParseGroupResource("Secret"), MCRegistrationSecretFullName.Name))
    52  
    53  	expectMCRegistrationSecret(cli, "mycluster1", MCLocalRegistrationSecretFullName, 1)
    54  
    55  	name = GetClusterName(context.TODO(), cli)
    56  	asserts.Equal(t, "mycluster1", name)
    57  
    58  	// Repeat test for registration secret and local secret not found
    59  	cli.EXPECT().
    60  		Get(gomock.Any(), MCRegistrationSecretFullName, gomock.Not(gomock.Nil()), gomock.Any()).
    61  		Return(kerr.NewNotFound(schema.ParseGroupResource("Secret"), MCRegistrationSecretFullName.Name))
    62  
    63  	cli.EXPECT().
    64  		Get(gomock.Any(), MCLocalRegistrationSecretFullName, gomock.Not(gomock.Nil()), gomock.Any()).
    65  		Return(kerr.NewNotFound(schema.ParseGroupResource("Secret"), MCLocalRegistrationSecretFullName.Name))
    66  
    67  	name = GetClusterName(context.TODO(), cli)
    68  	asserts.Equal(t, "", name)
    69  
    70  	mocker.Finish()
    71  }
    72  
    73  // TestGetConditionFromResult tests the GetConditionFromResult function
    74  // GIVEN a nil or non-nil err
    75  // WHEN GetConditionFromResult is called
    76  // The returned condition and state show success or failure depending on err == nil or not,
    77  // and the message is correctly populated
    78  func TestGetConditionFromResult(t *testing.T) {
    79  	// GIVEN err == nil
    80  	condition := GetConditionFromResult(nil, controllerutil.OperationResultCreated, "myresource type")
    81  	// TODO Mar 3 asserts.Equal(t, clustersv1alpha1.Succeeded, stateType)
    82  	asserts.Equal(t, clustersv1alpha1.DeployComplete, condition.Type)
    83  	asserts.Equal(t, v1.ConditionTrue, condition.Status)
    84  	asserts.Contains(t, condition.Message, "myresource type")
    85  	asserts.Contains(t, condition.Message, controllerutil.OperationResultCreated)
    86  
    87  	// GIVEN err != nil
    88  	someerr := errors.New("some error msg")
    89  	condition = GetConditionFromResult(someerr, controllerutil.OperationResultCreated, "myresource type")
    90  	// TODO Mar 3 asserts.Equal(t, clustersv1alpha1.Failed, stateType)
    91  	asserts.Equal(t, clustersv1alpha1.DeployFailed, condition.Type)
    92  	asserts.Equal(t, v1.ConditionTrue, condition.Status)
    93  	asserts.Contains(t, condition.Message, someerr.Error())
    94  }
    95  
    96  // TestIgnoreNotFoundWithLog tests the IgnoreNotFoundWithLog function
    97  // GIVEN a K8S NotFound error
    98  // WHEN IgnoreNotFoundWithLog is called
    99  // THEN a nil error is returned
   100  // GIVEN any other type of error
   101  // WHEN IgnoreNotFoundWithLog is called
   102  // THEN the error is returned
   103  func TestIgnoreNotFoundWithLog(t *testing.T) {
   104  	log := zap.S().With("somelogger")
   105  	result, err := IgnoreNotFoundWithLog(kerr.NewNotFound(controllerruntime.GroupResource{}, ""), log)
   106  	asserts.Nil(t, err)
   107  	asserts.False(t, result.Requeue)
   108  
   109  	otherErr := kerr.NewBadRequest("some other error")
   110  	result, err = IgnoreNotFoundWithLog(otherErr, log)
   111  	asserts.Nil(t, err)
   112  	asserts.True(t, result.Requeue)
   113  }
   114  
   115  // TestIsPlacedInThisCluster tests the IsPlacedInThisCluster function
   116  // GIVEN a placement object
   117  // WHEN IsPlacedInThisCluster is called
   118  // THEN it returns true if the placement includes this cluster's name, false otherwise
   119  func TestIsPlacedInThisCluster(t *testing.T) {
   120  	mocker := gomock.NewController(t)
   121  	cli := mocks.NewMockClient(mocker)
   122  	placementOnlyMyCluster := clustersv1alpha1.Placement{Clusters: []clustersv1alpha1.Cluster{{Name: "mycluster"}}}
   123  	placementWithMyCluster := clustersv1alpha1.Placement{Clusters: []clustersv1alpha1.Cluster{{Name: "othercluster"}, {Name: "mycluster"}}}
   124  	placementNotMyCluster := clustersv1alpha1.Placement{Clusters: []clustersv1alpha1.Cluster{{Name: "othercluster"}, {Name: "NOTmycluster"}}}
   125  
   126  	expectMCRegistrationSecret(cli, "mycluster", MCRegistrationSecretFullName, 3)
   127  
   128  	asserts.True(t, IsPlacedInThisCluster(context.TODO(), cli, placementOnlyMyCluster))
   129  	asserts.True(t, IsPlacedInThisCluster(context.TODO(), cli, placementWithMyCluster))
   130  	asserts.False(t, IsPlacedInThisCluster(context.TODO(), cli, placementNotMyCluster))
   131  
   132  	mocker.Finish()
   133  }
   134  
   135  // TestStatusNeedsUpdate tests various combinations of input to the StatusNeedsUpdate function
   136  // GIVEN a current status present on the resource
   137  // WHEN StatusNeedsUpdate is called with a new condition, state and cluster level status
   138  // THEN it returns false if the new condition and cluster level status are already present
   139  // in the status, AND the new state matches the current state, true otherwise
   140  func TestStatusNeedsUpdate(t *testing.T) {
   141  	conditionTimestamp := time.Now()
   142  	formattedConditionTimestamp := conditionTimestamp.Format(time.RFC3339)
   143  	curConditions := []clustersv1alpha1.Condition{
   144  		{Type: clustersv1alpha1.DeployComplete, Status: v1.ConditionTrue, LastTransitionTime: formattedConditionTimestamp},
   145  	}
   146  	curState := clustersv1alpha1.Failed
   147  	curCluster1Status := clustersv1alpha1.ClusterLevelStatus{Name: "cluster1", State: clustersv1alpha1.Succeeded, Message: "success msg", LastUpdateTime: formattedConditionTimestamp}
   148  	curCluster2Status := clustersv1alpha1.ClusterLevelStatus{Name: "cluster2", State: clustersv1alpha1.Failed, Message: "failure msg", LastUpdateTime: formattedConditionTimestamp}
   149  
   150  	curStatus := clustersv1alpha1.MultiClusterResourceStatus{
   151  		Conditions: curConditions,
   152  		State:      curState,
   153  		Clusters:   []clustersv1alpha1.ClusterLevelStatus{curCluster1Status, curCluster2Status},
   154  	}
   155  
   156  	otherTimestamp := conditionTimestamp.AddDate(0, 0, 1).Format(time.RFC3339)
   157  	newCond := clustersv1alpha1.Condition{Type: clustersv1alpha1.DeployFailed, Status: v1.ConditionTrue}
   158  	existingCond := curConditions[0]
   159  	newCluster1Status := clustersv1alpha1.ClusterLevelStatus{
   160  		Name:           curCluster1Status.Name,
   161  		Message:        "cluster failed",
   162  		State:          clustersv1alpha1.Failed,
   163  		LastUpdateTime: formattedConditionTimestamp}
   164  	newCluster2Status := clustersv1alpha1.ClusterLevelStatus{
   165  		Name:           curCluster2Status.Name,
   166  		Message:        "cluster succeeded",
   167  		State:          clustersv1alpha1.Succeeded,
   168  		LastUpdateTime: formattedConditionTimestamp}
   169  
   170  	existingCondDiffTimestampCluster1 := clustersv1alpha1.Condition{
   171  		Type: curConditions[0].Type, Status: curConditions[0].Status,
   172  		Message: curConditions[0].Message, LastTransitionTime: otherTimestamp}
   173  
   174  	existingCondDiffMessageCluster1 := clustersv1alpha1.Condition{
   175  		Type: curConditions[0].Type, Status: curConditions[0].Status,
   176  		Message: "Some other different message", LastTransitionTime: curConditions[0].LastTransitionTime}
   177  
   178  	cluster1StatusDiffTimestamp := clustersv1alpha1.ClusterLevelStatus{
   179  		Name:           curCluster1Status.Name,
   180  		Message:        curCluster1Status.Message,
   181  		State:          curCluster1Status.State,
   182  		LastUpdateTime: otherTimestamp}
   183  
   184  	newClusterStatus := clustersv1alpha1.ClusterLevelStatus{
   185  		Name:           "newCluster",
   186  		Message:        "cluster succeeded",
   187  		State:          clustersv1alpha1.Succeeded,
   188  		LastUpdateTime: otherTimestamp}
   189  
   190  	// Asserts new condition, same cluster status for each cluster- needs update
   191  	asserts.True(t, StatusNeedsUpdate(curStatus, newCond, curCluster1Status))
   192  	asserts.True(t, StatusNeedsUpdate(curStatus, newCond, curCluster2Status))
   193  
   194  	// new condition, same cluster status for each cluster - needs update
   195  	asserts.True(t, StatusNeedsUpdate(curStatus, newCond, curCluster1Status))
   196  	asserts.True(t, StatusNeedsUpdate(curStatus, newCond, curCluster2Status))
   197  
   198  	// same condition, same cluster status for each clusters - does not need update
   199  	asserts.False(t, StatusNeedsUpdate(curStatus, existingCond, curCluster1Status))
   200  	asserts.False(t, StatusNeedsUpdate(curStatus, existingCond, curCluster2Status))
   201  
   202  	// same condition, different cluster status for each cluster - needs update
   203  	asserts.True(t, StatusNeedsUpdate(curStatus, existingCond, newCluster1Status))
   204  	asserts.True(t, StatusNeedsUpdate(curStatus, existingCond, newCluster2Status))
   205  
   206  	// same condition, differing in condition timestamp - does not need update
   207  	asserts.False(t, StatusNeedsUpdate(curStatus, existingCondDiffTimestampCluster1, curCluster1Status))
   208  
   209  	// same condition, differing in cluster status timestamp - does not need update
   210  	asserts.False(t, StatusNeedsUpdate(curStatus, existingCondDiffTimestampCluster1, cluster1StatusDiffTimestamp))
   211  
   212  	// same condition, differing in condition message - needs update
   213  	asserts.True(t, StatusNeedsUpdate(curStatus, existingCondDiffMessageCluster1, curCluster1Status))
   214  
   215  	// same condition, differing in condition message - needs update
   216  	asserts.True(t, StatusNeedsUpdate(curStatus, existingCondDiffMessageCluster1, cluster1StatusDiffTimestamp))
   217  
   218  	// same condition, new cluster not present in conditions - needs update
   219  	asserts.True(t, StatusNeedsUpdate(curStatus, existingCond, newClusterStatus))
   220  }
   221  
   222  // TestCreateClusterLevelStatus tests the CreateClusterLevelStatus function
   223  // GIVEN a condition and state
   224  // WHEN CreateClusterLevelStatus is called
   225  // THEN it returns a cluster state correctly populated
   226  func TestCreateClusterLevelStatus(t *testing.T) {
   227  	formattedConditionTimestamp := time.Now().Format(time.RFC3339)
   228  	condition1 := clustersv1alpha1.Condition{
   229  		Type: clustersv1alpha1.DeployComplete, Status: v1.ConditionTrue, Message: "cond1 msg", LastTransitionTime: formattedConditionTimestamp,
   230  	}
   231  	condition2 := clustersv1alpha1.Condition{
   232  		Type: clustersv1alpha1.DeployFailed, Status: v1.ConditionTrue, Message: "cond2 msg", LastTransitionTime: formattedConditionTimestamp,
   233  	}
   234  	clusterState1 := CreateClusterLevelStatus(condition1, "cluster1")
   235  	asserts.Equal(t, "cluster1", clusterState1.Name)
   236  	asserts.Equal(t, clustersv1alpha1.Succeeded, clusterState1.State)
   237  	asserts.Equal(t, formattedConditionTimestamp, clusterState1.LastUpdateTime)
   238  	asserts.Equal(t, condition1.Message, clusterState1.Message)
   239  
   240  	clusterState2 := CreateClusterLevelStatus(condition2, "somecluster")
   241  	asserts.Equal(t, "somecluster", clusterState2.Name)
   242  	asserts.Equal(t, clustersv1alpha1.Failed, clusterState2.State)
   243  	asserts.Equal(t, formattedConditionTimestamp, clusterState2.LastUpdateTime)
   244  	asserts.Equal(t, condition2.Message, clusterState2.Message)
   245  }
   246  
   247  // TestComputeEffectiveState tests the ComputeEffectiveState function
   248  // GIVEN a multi cluster resource status and its placements
   249  // WHEN ComputeEffectiveState is called
   250  // THEN it returns a cluster state that rolls up the states of individual cluster placements
   251  func TestComputeEffectiveState(t *testing.T) {
   252  	placement := clustersv1alpha1.Placement{
   253  		Clusters: []clustersv1alpha1.Cluster{
   254  			{Name: "cluster1"},
   255  			{Name: "cluster2"},
   256  		},
   257  	}
   258  	allSucceededStatus := clustersv1alpha1.MultiClusterResourceStatus{
   259  		Clusters: []clustersv1alpha1.ClusterLevelStatus{
   260  			{Name: "cluster1", State: clustersv1alpha1.Succeeded},
   261  			{Name: "cluster2", State: clustersv1alpha1.Succeeded},
   262  		},
   263  	}
   264  	somePendingStatus := clustersv1alpha1.MultiClusterResourceStatus{
   265  		Clusters: []clustersv1alpha1.ClusterLevelStatus{
   266  			{Name: "cluster1", State: clustersv1alpha1.Succeeded},
   267  			{Name: "cluster2", State: clustersv1alpha1.Pending},
   268  		},
   269  	}
   270  	someFailedSomePendingStatus := clustersv1alpha1.MultiClusterResourceStatus{
   271  		Clusters: []clustersv1alpha1.ClusterLevelStatus{
   272  			{Name: "cluster1", State: clustersv1alpha1.Failed},
   273  			{Name: "cluster2", State: clustersv1alpha1.Pending},
   274  		},
   275  	}
   276  	someFailedSomeSucceededStatus := clustersv1alpha1.MultiClusterResourceStatus{
   277  		Clusters: []clustersv1alpha1.ClusterLevelStatus{
   278  			{Name: "cluster1", State: clustersv1alpha1.Succeeded},
   279  			{Name: "cluster2", State: clustersv1alpha1.Failed},
   280  		},
   281  	}
   282  	failedWithUnknownClusterSucceededStatus := clustersv1alpha1.MultiClusterResourceStatus{
   283  		Clusters: []clustersv1alpha1.ClusterLevelStatus{
   284  			{Name: "cluster3", State: clustersv1alpha1.Succeeded},
   285  			{Name: "cluster2", State: clustersv1alpha1.Failed},
   286  		},
   287  	}
   288  	pendingWithUnknownClusterSucceededStatus := clustersv1alpha1.MultiClusterResourceStatus{
   289  		Clusters: []clustersv1alpha1.ClusterLevelStatus{
   290  			{Name: "cluster3", State: clustersv1alpha1.Succeeded},
   291  			{Name: "cluster2", State: clustersv1alpha1.Pending},
   292  		},
   293  	}
   294  	asserts.Equal(t, clustersv1alpha1.Succeeded, ComputeEffectiveState(allSucceededStatus, placement))
   295  	asserts.Equal(t, clustersv1alpha1.Pending, ComputeEffectiveState(somePendingStatus, placement))
   296  	asserts.Equal(t, clustersv1alpha1.Failed, ComputeEffectiveState(someFailedSomePendingStatus, placement))
   297  	asserts.Equal(t, clustersv1alpha1.Failed, ComputeEffectiveState(someFailedSomeSucceededStatus, placement))
   298  	asserts.Equal(t, clustersv1alpha1.Failed, ComputeEffectiveState(failedWithUnknownClusterSucceededStatus, placement))
   299  	asserts.Equal(t, clustersv1alpha1.Pending, ComputeEffectiveState(pendingWithUnknownClusterSucceededStatus, placement))
   300  }
   301  
   302  func TestUpdateClusterLevelStatus(t *testing.T) {
   303  	resourceStatus := clustersv1alpha1.MultiClusterResourceStatus{
   304  		Clusters: []clustersv1alpha1.ClusterLevelStatus{
   305  			{Name: "cluster1", State: clustersv1alpha1.Failed},
   306  			{Name: "cluster2", State: clustersv1alpha1.Pending},
   307  		},
   308  	}
   309  
   310  	cluster1Succeeded := clustersv1alpha1.ClusterLevelStatus{
   311  		Name: "cluster1", State: clustersv1alpha1.Succeeded,
   312  	}
   313  
   314  	cluster2Failed := clustersv1alpha1.ClusterLevelStatus{
   315  		Name: "cluster2", State: clustersv1alpha1.Failed,
   316  	}
   317  
   318  	newClusterPending := clustersv1alpha1.ClusterLevelStatus{
   319  		Name: "newCluster", State: clustersv1alpha1.Pending,
   320  	}
   321  
   322  	// existing cluster cluster1 should be updated
   323  	SetClusterLevelStatus(&resourceStatus, cluster1Succeeded)
   324  	asserts.Equal(t, 2, len(resourceStatus.Clusters))
   325  	asserts.Equal(t, clustersv1alpha1.Succeeded, resourceStatus.Clusters[0].State)
   326  
   327  	// existing cluster cluster2 should be updated
   328  	SetClusterLevelStatus(&resourceStatus, cluster2Failed)
   329  	asserts.Equal(t, 2, len(resourceStatus.Clusters))
   330  	asserts.Equal(t, clustersv1alpha1.Failed, resourceStatus.Clusters[1].State)
   331  
   332  	// hitherto unseen cluster should be added to the cluster level statuses list
   333  	SetClusterLevelStatus(&resourceStatus, newClusterPending)
   334  	asserts.Equal(t, 3, len(resourceStatus.Clusters))
   335  	asserts.Equal(t, clustersv1alpha1.Pending, resourceStatus.Clusters[2].State)
   336  
   337  }
   338  
   339  func expectMCRegistrationSecret(cli *mocks.MockClient, clusterName string, secreteNameFullName types.NamespacedName, times int) {
   340  	regSecretData := map[string][]byte{constants.ClusterNameData: []byte(clusterName)}
   341  	cli.EXPECT().
   342  		Get(gomock.Any(), secreteNameFullName, gomock.Not(gomock.Nil()), gomock.Any()).
   343  		Times(times).
   344  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, secret *v1.Secret, opts ...client.GetOption) error {
   345  			secret.Name = secreteNameFullName.Name
   346  			secret.Namespace = secreteNameFullName.Namespace
   347  			secret.Data = regSecretData
   348  			return nil
   349  		})
   350  }
   351  
   352  // TestSetEffectiveStateIfChanged tests that if the effective state of a resource has changed, it's
   353  // state is changed
   354  // GIVEN a MultiCluster resource whose effective state is unchanged
   355  // WHEN SetEffectiveStateIfChanged is called
   356  // THEN the state should not be updated
   357  // GIVEN a MultiCluster resource whose effective state has changed
   358  // WHEN SetEffectiveStateIfChanged is called
   359  // THEN the state should be updated
   360  func TestSetEffectiveStateIfChanged(t *testing.T) {
   361  	placement := clustersv1alpha1.Placement{
   362  		Clusters: []clustersv1alpha1.Cluster{
   363  			{Name: "cluster1"},
   364  			{Name: "cluster2"},
   365  		},
   366  	}
   367  	secret := clustersv1alpha1.MultiClusterSecret{
   368  		Spec: clustersv1alpha1.MultiClusterSecretSpec{
   369  			Placement: placement,
   370  		},
   371  		Status: clustersv1alpha1.MultiClusterResourceStatus{State: clustersv1alpha1.Pending},
   372  	}
   373  	secret.Name = "mysecret"
   374  	secret.Namespace = "myns"
   375  
   376  	// Make a call with the effective state of the resource unchanged, and no change should occur
   377  	SetEffectiveStateIfChanged(placement, &secret.Status)
   378  	asserts.Equal(t, clustersv1alpha1.Pending, secret.Status.State)
   379  
   380  	// add cluster level status info to the secret's status, and make a call again - this time
   381  	// it should update the status of the resource since the effective state changes
   382  	secret.Status.Clusters = []clustersv1alpha1.ClusterLevelStatus{
   383  		{Name: "cluster1", State: clustersv1alpha1.Failed},
   384  	}
   385  
   386  	SetEffectiveStateIfChanged(placement, &secret.Status)
   387  	asserts.Equal(t, clustersv1alpha1.Failed, secret.Status.State)
   388  }
   389  
   390  // TestDeleteAssociatedResource tests that if DeleteAssociatedResource is called
   391  // the given resourceToDelete is deleted and the finalizer on the mcResource is removed
   392  // GIVEN a MultiCluster resource and a resourceToDelete,
   393  // WHEN TestDeleteAssociatedResource is called and the resourceToDelete is successfully deleted
   394  // THEN the finalizer should be removed
   395  // GIVEN a MultiCluster resource and a resourceToDelete,
   396  // WHEN TestDeleteAssociatedResource is called and it fails to delete the resourceToDelete
   397  // THEN the finalizer should NOT be removed
   398  func TestDeleteAssociatedResource(t *testing.T) {
   399  	mocker := gomock.NewController(t)
   400  	cli := mocks.NewMockClient(mocker)
   401  
   402  	mcResource := clustersv1alpha1.MultiClusterApplicationConfiguration{
   403  		Spec: clustersv1alpha1.MultiClusterApplicationConfigurationSpec{
   404  			Placement: clustersv1alpha1.Placement{
   405  				Clusters: []clustersv1alpha1.Cluster{{Name: "mycluster"}},
   406  			},
   407  		},
   408  	}
   409  	mcResource.Name = "mymcappconfig"
   410  	mcResource.Namespace = "myns"
   411  
   412  	resourceToDeleteName := types.NamespacedName{Name: "myappconfig", Namespace: "myns"}
   413  	finalizerToDelete := "thisfinalizergoes"
   414  	finalizerNotDelete := "thisfinalizerstays"
   415  
   416  	mcResource.SetFinalizers([]string{finalizerNotDelete, finalizerToDelete})
   417  
   418  	// GIVEN that the deletion succeeds
   419  	// THEN the finalizer should be removed
   420  
   421  	// expect get and delete the app config with name resourceToDeleteName, mocking successful deletion
   422  	expectGetAndDeleteAppConfig(t, cli, resourceToDeleteName, nil)
   423  
   424  	// The finalizer should be removed
   425  	cli.EXPECT().
   426  		Update(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()).
   427  		DoAndReturn(func(ctx context.Context, mcApp *clustersv1alpha1.MultiClusterApplicationConfiguration, opts ...client.UpdateOption) error {
   428  			asserts.NotContains(t, mcApp.GetFinalizers(), finalizerToDelete)
   429  			asserts.Contains(t, mcApp.GetFinalizers(), finalizerNotDelete)
   430  			return nil
   431  		})
   432  
   433  	err := DeleteAssociatedResource(context.TODO(), cli, &mcResource, finalizerToDelete, &v1alpha2.ApplicationConfiguration{}, resourceToDeleteName)
   434  	asserts.Nil(t, err)
   435  
   436  	// GIVEN that the deletion fails
   437  	// THEN the finalizer should NOT be removed
   438  
   439  	// expect get and delete the app config with name resourceToDeleteName, mocking FAILED deletion
   440  	expectGetAndDeleteAppConfig(t, cli, resourceToDeleteName, errors.New("I will not delete you, resource"))
   441  
   442  	// There should be no more interactions i.e. the finalizer should not be removed
   443  
   444  	err = DeleteAssociatedResource(context.TODO(), cli, &mcResource, finalizerToDelete, &v1alpha2.ApplicationConfiguration{}, resourceToDeleteName)
   445  	asserts.NotNil(t, err)
   446  
   447  	mocker.Finish()
   448  }
   449  
   450  // TestRequeueWithDelay tests that when a result is requested it has requeue set to true and a requeue after greater
   451  // than 2 seconds
   452  // GIVEN a need for a requeue result
   453  // WHEN NewRequeueWithDelay is called
   454  // THEN the returned result indicates a requeue with a requeueAfter time greaater than or equal to 2 seconds
   455  func TestRequeueWithDelay(t *testing.T) {
   456  	result := NewRequeueWithDelay()
   457  	asserts.True(t, result.Requeue)
   458  	asserts.GreaterOrEqual(t, result.RequeueAfter.Seconds(), time.Duration(2).Seconds())
   459  }
   460  
   461  // GIVEN a need for a testing a result for requeueing
   462  // WHEN ShouldRequeue is called
   463  // THEN a value of true is returned if Requeue is set to true or the RequeueAfter time is greater than 0, false otherwise
   464  func TestShouldRequeue(t *testing.T) {
   465  	val := ShouldRequeue(reconcile.Result{Requeue: true})
   466  	asserts.True(t, val)
   467  	val = ShouldRequeue(reconcile.Result{Requeue: false})
   468  	asserts.False(t, val)
   469  	val = ShouldRequeue(reconcile.Result{RequeueAfter: time.Duration(2)})
   470  	asserts.True(t, val)
   471  	val = ShouldRequeue(reconcile.Result{RequeueAfter: time.Duration(0)})
   472  	asserts.False(t, val)
   473  }
   474  
   475  func expectGetAndDeleteAppConfig(t *testing.T, cli *mocks.MockClient, name types.NamespacedName, deleteErr error) {
   476  	cli.EXPECT().
   477  		Get(gomock.Any(), name, gomock.Not(gomock.Nil()), gomock.Any()).
   478  		DoAndReturn(func(ctx context.Context, name types.NamespacedName, appConfig *v1alpha2.ApplicationConfiguration, opts ...client.GetOption) error {
   479  			appConfig.Name = name.Name
   480  			appConfig.Namespace = name.Namespace
   481  			return nil
   482  		})
   483  
   484  	cli.EXPECT().
   485  		Delete(gomock.Any(), gomock.Not(gomock.Nil()), gomock.Any()).
   486  		DoAndReturn(func(ctx context.Context, appConfig *v1alpha2.ApplicationConfiguration, opt ...client.DeleteOption) error {
   487  			asserts.Equal(t, name.Name, appConfig.Name)
   488  			asserts.Equal(t, name.Namespace, appConfig.Namespace)
   489  			return deleteErr
   490  		})
   491  }