From 109a421380f45f8bab33e685b9509b9197f90c1d Mon Sep 17 00:00:00 2001 From: DQ Date: Fri, 11 Apr 2025 14:39:55 +1000 Subject: [PATCH 1/3] Add AppDeployment Adapter and Unit Tests - Introduced AppDeploymentAdapter to manage application deployment lifecycle, including validation, finalizer management, dependency readiness, and job handling. - Implemented unit tests for the AppDeploymentAdapter to ensure functionality across various scenarios, including application validation, finalizer operations, and job completion checks. - Generated mock interfaces for the AppDeploymentAdapter to facilitate testing and improve code maintainability. --- internal/controller/appdeployment_adapter.go | 228 +++++++ .../controller/appdeployment_adapter_test.go | 599 ++++++++++++++++++ .../mocks/mock_appdeployment_adapter.go | 132 ++++ internal/mocks/generate.go | 12 + internal/mocks/mock_cr_client.go | 264 ++++++++ internal/mocks/mock_cr_recorder.go | 87 +++ internal/mocks/mock_cr_status_writer.go | 99 +++ 7 files changed, 1421 insertions(+) create mode 100644 internal/controller/appdeployment_adapter.go create mode 100644 internal/controller/appdeployment_adapter_test.go create mode 100644 internal/controller/mocks/mock_appdeployment_adapter.go create mode 100644 internal/mocks/generate.go create mode 100644 internal/mocks/mock_cr_client.go create mode 100644 internal/mocks/mock_cr_recorder.go create mode 100644 internal/mocks/mock_cr_status_writer.go diff --git a/internal/controller/appdeployment_adapter.go b/internal/controller/appdeployment_adapter.go new file mode 100644 index 0000000..74a615b --- /dev/null +++ b/internal/controller/appdeployment_adapter.go @@ -0,0 +1,228 @@ +package controller + +import ( + "context" + "fmt" + + "github.com/go-logr/logr" + batchv1 "k8s.io/api/batch/v1" + apierror "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + appsv1 "github.com/Azure/operation-cache-controller/api/v1" + apdutil "github.com/Azure/operation-cache-controller/internal/utils/controller/appdeployment" + "github.com/Azure/operation-cache-controller/internal/utils/reconciler" +) + +type appdeploymentAdapterContextKey struct{} + +//go:generate mockgen -destination=./mocks/mock_appdeployment_adapter.go -package=mocks github.com/Azure/operation-cache-controller/internal/controller AppDeploymentAdapterInterface +type AppDeploymentAdapterInterface interface { + EnsureApplicationValid(ctx context.Context) (reconciler.OperationResult, error) + EnsureFinalizer(ctx context.Context) (reconciler.OperationResult, error) + EnsureFinalizerDeleted(ctx context.Context) (reconciler.OperationResult, error) + EnsureDependenciesReady(ctx context.Context) (reconciler.OperationResult, error) + EnsureDeployingFinished(ctx context.Context) (reconciler.OperationResult, error) + EnsureTeardownFinished(ctx context.Context) (reconciler.OperationResult, error) +} + +type AppDeploymentAdapter struct { + appDeployment *appsv1.AppDeployment + logger logr.Logger + client client.Client + recorder record.EventRecorder +} + +func NewAppDeploymentAdapter(ctx context.Context, appDeployment *appsv1.AppDeployment, logger logr.Logger, client client.Client, recorder record.EventRecorder) AppDeploymentAdapterInterface { + if appdeploymentAdapter, ok := ctx.Value(appdeploymentAdapterContextKey{}).(AppDeploymentAdapterInterface); ok { + return appdeploymentAdapter + } + return &AppDeploymentAdapter{ + appDeployment: appDeployment, + logger: logger, + recorder: recorder, + client: client, + } +} + +func (a *AppDeploymentAdapter) phaseIs(phase ...string) bool { + for _, p := range phase { + if a.appDeployment.Status.Phase == p { + return true + } + } + return false +} + +func (a *AppDeploymentAdapter) EnsureApplicationValid(ctx context.Context) (reconciler.OperationResult, error) { + a.logger.V(1).Info("Operation EnsureApplicationValid") + if err := apdutil.Validate(a.appDeployment); err != nil { + a.recorder.Event(a.appDeployment, "Error", "InvalidApplication", err.Error()) + return reconciler.RequeueWithError(err) + } + // initialize the appdeployment status + if a.phaseIs(apdutil.PhaseEmpty) { + a.logger.V(1).Info("Initializing appdeployment status") + a.appDeployment.Status.Phase = apdutil.PhasePending + apdutil.ClearConditions(ctx, a.appDeployment) + return reconciler.RequeueOnErrorOrContinue(a.client.Status().Update(ctx, a.appDeployment)) + } + + return reconciler.ContinueProcessing() +} + +func (a *AppDeploymentAdapter) EnsureFinalizer(ctx context.Context) (reconciler.OperationResult, error) { + a.logger.V(1).Info("Operation EnsureFinalizer") + if a.appDeployment.ObjectMeta.DeletionTimestamp.IsZero() && !controllerutil.ContainsFinalizer(a.appDeployment, apdutil.FinalizerName) { + controllerutil.AddFinalizer(a.appDeployment, apdutil.FinalizerName) + } + return reconciler.RequeueOnErrorOrContinue(a.client.Update(ctx, a.appDeployment)) +} + +func (a *AppDeploymentAdapter) EnsureFinalizerDeleted(ctx context.Context) (reconciler.OperationResult, error) { + a.logger.V(1).Info("Operation EnsureFinalizerDeleted") + if !a.appDeployment.ObjectMeta.DeletionTimestamp.IsZero() && controllerutil.ContainsFinalizer(a.appDeployment, apdutil.FinalizerName) { + if a.phaseIs(apdutil.PhaseDeleted) { + a.logger.V(1).Info("All app deleted removing finalizer") + controllerutil.RemoveFinalizer(a.appDeployment, apdutil.FinalizerName) + return reconciler.RequeueOnErrorOrContinue(a.client.Update(ctx, a.appDeployment)) + } + if !a.phaseIs(apdutil.PhaseDeleting) { + a.logger.V(1).Info("App is not deleted yet, setting phase to deleting") + a.appDeployment.Status.Phase = apdutil.PhaseDeleting + return reconciler.RequeueOnErrorOrContinue(a.client.Status().Update(ctx, a.appDeployment)) + } + } + return reconciler.ContinueProcessing() +} + +func (a *AppDeploymentAdapter) EnsureDependenciesReady(ctx context.Context) (reconciler.OperationResult, error) { + if !a.phaseIs(apdutil.PhasePending) { + return reconciler.ContinueProcessing() + } + a.logger.V(1).Info("Operation EnsureDependenciesReady") + // list all dependencies and check if they are ready + for _, dep := range a.appDeployment.Spec.Dependencies { + // check if dependency is ready + appdeployment := &appsv1.AppDeployment{} + realAppName := apdutil.OperationScopedAppDeployment(dep, a.appDeployment.Spec.OpId) + if err := a.client.Get(ctx, client.ObjectKey{Namespace: a.appDeployment.Namespace, Name: realAppName}, appdeployment); err != nil { + a.logger.V(1).Error(err, "dependency not found", "dependency", realAppName) + return reconciler.RequeueWithError(fmt.Errorf("dependency not found: %s ", realAppName)) + } + if appdeployment.Status.Phase != apdutil.PhaseReady { + return reconciler.RequeueWithError(fmt.Errorf("dependency is not ready: %s", realAppName)) + } + } + // all dependencies are ready + a.appDeployment.Status.Phase = apdutil.PhaseDeploying + return reconciler.RequeueOnErrorOrContinue(a.client.Status().Update(ctx, a.appDeployment)) +} + +var ( + errJobNotCompleted = fmt.Errorf("job not completed") +) + +func (a *AppDeploymentAdapter) createJob(ctx context.Context, jobTemplate *batchv1.Job) error { + if err := ctrl.SetControllerReference(a.appDeployment, jobTemplate, a.client.Scheme()); err != nil { + return fmt.Errorf("failed to set controller reference for job %s: %w", jobTemplate.Name, err) + } + if err := a.client.Create(ctx, jobTemplate); err != nil { + return fmt.Errorf("failed to create job %s: %w", jobTemplate.Name, err) + } + return nil +} + +func (a *AppDeploymentAdapter) initializeJobAndAwaitCompletion(ctx context.Context, jobTemplate *batchv1.Job) error { + job := &batchv1.Job{} + // check if the job exists + if err := a.client.Get(ctx, client.ObjectKey{Namespace: a.appDeployment.Namespace, Name: jobTemplate.Name}, job); err != nil { + if !apierror.IsNotFound(err) { + return fmt.Errorf("failed to get job %s: %w", jobTemplate.Name, err) + } + // create a new job + if err := a.createJob(ctx, jobTemplate); err != nil { + a.recorder.Event(a.appDeployment, "Error", "FailedCreateJob", err.Error()) + return fmt.Errorf("failed to create job %s: %w", jobTemplate.Name, err) + } + return errJobNotCompleted // requeue + } + + // check if the job is running + switch apdutil.CheckJobStatus(ctx, job) { + // if job is failed then delete the job and create a new one + case apdutil.JobStatusFailed: + // delete the failed job + if err := a.client.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil { + a.recorder.Event(a.appDeployment, "Error", "FailedDeleteJob", err.Error()) + return fmt.Errorf("failed to delete job %s: %w", job.Name, err) + } + // create a new job + if err := ctrl.SetControllerReference(a.appDeployment, jobTemplate, a.client.Scheme()); err != nil { + return fmt.Errorf("failed to set controller reference for job %s: %w", job.Name, err) + } + if err := a.client.Create(ctx, jobTemplate); err != nil { + return fmt.Errorf("failed to create job %s: %w", jobTemplate.Name, err) + } + + // if job is succeeded then delete the job + case apdutil.JobStatusSucceeded: + // delete the succeeded job + if err := a.client.Delete(ctx, job, client.PropagationPolicy(metav1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil { + return fmt.Errorf("failed to delete succeeded job %s: %w", job.Name, err) + } + return nil + } + return errJobNotCompleted +} + +// EnsureDeployingFinished checks if the provision job exists +// if not exist then create a new provision job +// if job is exist && running then requeue and waiting for the job complete +// if job is exist && failed then delete the job and create a new one +// if job is exist && succeeded then update the appdeployment status to ready +func (a *AppDeploymentAdapter) EnsureDeployingFinished(ctx context.Context) (reconciler.OperationResult, error) { + a.logger.V(1).Info("Operation EnsureDeployingFinished") + if !a.phaseIs(apdutil.PhaseDeploying) { + return reconciler.ContinueProcessing() + } + provisionJob := apdutil.ProvisionJobFromAppDeploymentSpec(a.appDeployment) + err := a.initializeJobAndAwaitCompletion(ctx, provisionJob) + switch err { + case nil: + // provision job is succeeded move the appdeployment to ready phase + a.appDeployment.Status.Phase = apdutil.PhaseReady + return reconciler.RequeueOnErrorOrContinue(a.client.Status().Update(ctx, a.appDeployment)) + case errJobNotCompleted: + a.logger.V(1).WithValues(apdutil.LogKeyJobName, provisionJob.Name).Info("provision job is not completed yet") + return reconciler.Requeue() + default: + a.logger.Error(err, "provision job failed %s", provisionJob.Name) + return reconciler.RequeueWithError(err) + } +} + +func (a *AppDeploymentAdapter) EnsureTeardownFinished(ctx context.Context) (reconciler.OperationResult, error) { + a.logger.V(1).Info("Operation EnsureTeardownFinished") + if !a.phaseIs(apdutil.PhaseDeleting) { + return reconciler.ContinueProcessing() + } + teardownJob := apdutil.TeardownJobFromAppDeploymentSpec(a.appDeployment) + err := a.initializeJobAndAwaitCompletion(ctx, teardownJob) + switch err { + case nil: + // teardown job is succeeded move the appdeployment to deleted phase + a.appDeployment.Status.Phase = apdutil.PhaseDeleted + return reconciler.RequeueOnErrorOrContinue(a.client.Status().Update(ctx, a.appDeployment)) + case errJobNotCompleted: + a.logger.V(1).WithValues(apdutil.LogKeyJobName, teardownJob.Name).Info("teardown job is not completed yet") + return reconciler.Requeue() + default: + a.logger.WithValues(apdutil.LogKeyJobName, teardownJob.Name).Error(err, "teardown job failed %s") + return reconciler.RequeueWithError(err) + } +} diff --git a/internal/controller/appdeployment_adapter_test.go b/internal/controller/appdeployment_adapter_test.go new file mode 100644 index 0000000..4f4a96a --- /dev/null +++ b/internal/controller/appdeployment_adapter_test.go @@ -0,0 +1,599 @@ +package controller + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + batchv1 "k8s.io/api/batch/v1" + k8serr "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + appsv1 "github.com/Azure/operation-cache-controller/api/v1" + mockpkg "github.com/Azure/operation-cache-controller/internal/mocks" + apdutil "github.com/Azure/operation-cache-controller/internal/utils/controller/appdeployment" + "github.com/Azure/operation-cache-controller/internal/utils/reconciler" +) + +var validAppDeployment = &appsv1.AppDeployment{ + Spec: appsv1.AppDeploymentSpec{ + Provision: newTestJobSpec(), + Teardown: newTestJobSpec(), + OpId: "test-op-id", + }, +} + +func TestNewAppDeploymentAdapter(t *testing.T) { + ctx := context.Background() + appDeployment := validAppDeployment.DeepCopy() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorder := mockpkg.NewMockEventRecorder(mockCtrl) + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) +} + +func TestAppDeploymentAdapter_EnsureApplicationValid(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockClient := mockpkg.NewMockClient(mockCtrl) + + mockRecorderCtrl := gomock.NewController(t) + defer mockRecorderCtrl.Finish() + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + + mockStatusCtrl := gomock.NewController(t) + defer mockStatusCtrl.Finish() + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusCtrl) + + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + mockRecorder.EXPECT().Event(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + + t.Run("Happy path: application valid", func(t *testing.T) { + appDeployment := validAppDeployment.DeepCopy() + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + res, err := adapter.EnsureApplicationValid(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + }) + + t.Run("Happy path: application invalid and not in empty phase", func(t *testing.T) { + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhaseDeploying + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + res, err := adapter.EnsureApplicationValid(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("Sad path: application return error", func(t *testing.T) { + appDeployment := &appsv1.AppDeployment{} + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + res, err := adapter.EnsureApplicationValid(ctx) + assert.Error(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) +} + +func TestAppDeploymentAdapter_EnsureFinalizer(t *testing.T) { + ctx := context.Background() + appDeployment := validAppDeployment.DeepCopy() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorder := mockpkg.NewMockEventRecorder(mockCtrl) + // mockStatusWriter := mockpkg.NewMockStatusWriter(mockCtrl) + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + + t.Run("Happy path: finalizer not present", func(t *testing.T) { + mockClient.EXPECT().Update(ctx, gomock.Any()).Return(nil) + res, err := adapter.EnsureFinalizer(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{ + RequeueDelay: reconciler.DefaultRequeueDelay, + }, res) + }) + + t.Run("Sad path: update fails", func(t *testing.T) { + testErr := errors.New("update error") + mockClient.EXPECT().Update(ctx, gomock.Any()).Return(testErr) + res, err := adapter.EnsureFinalizer(ctx) + assert.ErrorIs(t, err, testErr) + assert.Equal(t, reconciler.OperationResult{ + RequeueDelay: reconciler.DefaultRequeueDelay, + RequeueRequest: false, + CancelRequest: false, + }, res) + }) +} + +func TestAppDeploymentAdapter_EnsureFinalizerDeleted(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + var ( + mockClient *mockpkg.MockClient + mockRecorder *mockpkg.MockEventRecorder + ) + + t.Run("Happy path: not triggered", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockClient = mockpkg.NewMockClient(mockCtrl) + + mockRecorder = mockpkg.NewMockEventRecorder(mockCtrl) + + appDeployment := validAppDeployment.DeepCopy() + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + + res, err := adapter.EnsureFinalizerDeleted(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("Happy path: finalizer deleted", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockClient = mockpkg.NewMockClient(mockCtrl) + + mockClient = mockpkg.NewMockClient(mockCtrl) + mockRecorder = mockpkg.NewMockEventRecorder(mockCtrl) + + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Finalizers = []string{apdutil.FinalizerName} + appDeployment.DeletionTimestamp = &metav1.Time{Time: time.Now()} + appDeployment.Status.Phase = apdutil.PhaseDeleted + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + mockClient.EXPECT().Update(ctx, gomock.Any()).Return(nil) + res, err := adapter.EnsureFinalizerDeleted(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{ + RequeueDelay: reconciler.DefaultRequeueDelay, + }, res) + }) + + t.Run("Sad path: update fails", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockClient = mockpkg.NewMockClient(mockCtrl) + + mockClient = mockpkg.NewMockClient(mockCtrl) + mockRecorder = mockpkg.NewMockEventRecorder(mockCtrl) + + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Finalizers = []string{apdutil.FinalizerName} + appDeployment.DeletionTimestamp = &metav1.Time{Time: time.Now()} + appDeployment.Status.Phase = apdutil.PhaseDeleted + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + mockClient.EXPECT().Update(ctx, gomock.Any()).Return(assert.AnError) + + res, err := adapter.EnsureFinalizerDeleted(ctx) + assert.ErrorIs(t, err, assert.AnError) + assert.Equal(t, reconciler.OperationResult{ + RequeueDelay: reconciler.DefaultRequeueDelay, + RequeueRequest: false, + CancelRequest: false, + }, res) + }) + + t.Run("Happy path: finalizer started but phase not in deleting", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + mockClient = mockpkg.NewMockClient(mockCtrl) + mockEventCtrl := gomock.NewController(t) + mockRecorder = mockpkg.NewMockEventRecorder(mockEventCtrl) + mockStatusCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Finalizers = []string{apdutil.FinalizerName} + appDeployment.DeletionTimestamp = &metav1.Time{Time: time.Now()} + appDeployment.Status.Phase = apdutil.PhasePending + + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + res, err := adapter.EnsureFinalizerDeleted(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{ + RequeueDelay: reconciler.DefaultRequeueDelay, + }, res) + }) +} + +func TestAppDeploymentAdapter_EnsureDependenciesReady(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorder := mockpkg.NewMockEventRecorder(mockCtrl) + + t.Run("Happy path: skip dependencies check", func(t *testing.T) { + appDeployment := validAppDeployment.DeepCopy() + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + res, err := adapter.EnsureDependenciesReady(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("Happy path: no dependencies ready", func(t *testing.T) { + mockStatusWriter := mockpkg.NewMockStatusWriter(mockCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhasePending + appDeployment.Spec.OpId = "test-op-id" + appDeployment.Spec.Dependencies = []string{ + "test-app-1", + } + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + + dependendApp := &appsv1.AppDeployment{ + Status: appsv1.AppDeploymentStatus{ + Phase: apdutil.PhaseReady, + }, + } + + mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&appsv1.AppDeployment{}), gomock.Any()).DoAndReturn( + func(ctx context.Context, key client.ObjectKey, obj runtime.Object, opts ...client.GetOption) error { + *obj.(*appsv1.AppDeployment) = *dependendApp + assert.Equal(t, "test-op-id-test-app-1", key.Name) + return nil + }).Times(1) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + + res, err := adapter.EnsureDependenciesReady(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: false}, res) + }) + + t.Run("Sad path: dependency not found", func(t *testing.T) { + mockStatusWriter := mockpkg.NewMockStatusWriter(mockCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhasePending + appDeployment.Spec.OpId = "test-op-id" + appDeployment.Spec.Dependencies = []string{ + "test-app-1", + } + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + + mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&appsv1.AppDeployment{}), gomock.Any()).Return(assert.AnError).Times(1) + + res, err := adapter.EnsureDependenciesReady(ctx) + assert.ErrorContains(t, err, "dependency not found: test-op-id-test-app-1") + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) + + t.Run("Sad path: dependency not ready", func(t *testing.T) { + mockStatusWriter := mockpkg.NewMockStatusWriter(mockCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhasePending + appDeployment.Spec.OpId = "test-op-id" + appDeployment.Spec.Dependencies = []string{ + "test-app-1", + } + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + + dependendApp := &appsv1.AppDeployment{ + Status: appsv1.AppDeploymentStatus{ + Phase: apdutil.PhasePending, + }, + } + + mockClient.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.AssignableToTypeOf(&appsv1.AppDeployment{}), gomock.Any()).DoAndReturn( + func(ctx context.Context, key client.ObjectKey, obj runtime.Object, opts ...client.GetOption) error { + *obj.(*appsv1.AppDeployment) = *dependendApp + assert.Equal(t, "test-op-id-test-app-1", key.Name) + return nil + }).Times(1) + + res, err := adapter.EnsureDependenciesReady(ctx) + assert.ErrorContains(t, err, "dependency is not ready: test-op-id-test-app-1") + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) +} + +func TestAppDeploymentAdapter_EnsureDeployingFinished(t *testing.T) { + ctx := context.Background() + logger := log.FromContext(ctx) + + t.Run("Happy path: skip when not in deploying phase", func(t *testing.T) { + appDeployment := validAppDeployment.DeepCopy() + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + + res, err := adapter.EnsureDeployingFinished(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("Happy path: deploying finished", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhaseDeploying + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&batchv1.Job{})). + DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj runtime.Object, opts ...client.GetOption) error { + *obj.(*batchv1.Job) = batchv1.Job{ + Status: batchv1.JobStatus{ + Conditions: []batchv1.JobCondition{ + { + Type: batchv1.JobComplete, + Status: "True", + }, + }, + Succeeded: 1, + }, + + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + } + return nil + }) + mockClient.EXPECT().Delete(ctx, gomock.Any(), gomock.Any()).Return(nil) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + res, err := adapter.EnsureDeployingFinished(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + }) + + t.Run("Happy path: deploying create new job", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + mockClient.EXPECT().Scheme().Return(scheme).AnyTimes() + + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhaseDeploying + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&batchv1.Job{})). + Return(k8serr.NewNotFound(batchv1.Resource("job"), "test-job")) + + mockClient.EXPECT().Create(ctx, gomock.Any()).Return(nil) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + res, err := adapter.EnsureDeployingFinished(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) + + t.Run("Happy path: deploying job failed, create new job", func(t *testing.T) { + failedJob := batchv1.Job{ + Status: batchv1.JobStatus{ + Conditions: []batchv1.JobCondition{ + { + Type: batchv1.JobFailed, + Status: "True", + }, + }, + Failed: 1, + }, + + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + } + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + mockClient.EXPECT().Scheme().Return(scheme).AnyTimes() + + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhaseDeploying + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&batchv1.Job{})).DoAndReturn( + func(ctx context.Context, key client.ObjectKey, obj runtime.Object, opts ...client.GetOption) error { + *obj.(*batchv1.Job) = failedJob + return nil + }) + mockClient.EXPECT().Delete(ctx, gomock.Any(), gomock.Any()).Return(nil) + mockClient.EXPECT().Create(ctx, gomock.Any()).Return(nil) + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + res, err := adapter.EnsureDeployingFinished(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) +} + +func TestAppDeploymentAdapter_EnsureTeardownFinished(t *testing.T) { + ctx := context.Background() + + succeededJob := batchv1.Job{ + Status: batchv1.JobStatus{ + Conditions: []batchv1.JobCondition{ + { + Type: batchv1.JobComplete, + Status: "True", + }, + }, + Succeeded: 1, + }, + + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + } + + failedJob := batchv1.Job{ + Status: batchv1.JobStatus{ + Conditions: []batchv1.JobCondition{ + { + Type: batchv1.JobFailed, + Status: "True", + }, + }, + Failed: 1, + }, + + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + } + + t.Run("Happy path: skip when not in deploying phase", func(t *testing.T) { + appDeployment := validAppDeployment.DeepCopy() + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorder := mockpkg.NewMockEventRecorder(mockCtrl) + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + res, err := adapter.EnsureTeardownFinished(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{}, res) + }) + + t.Run("Happy path: teardown finished", func(t *testing.T) { + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhaseDeleting + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&batchv1.Job{})). + DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj runtime.Object, opts ...client.GetOption) error { + *obj.(*batchv1.Job) = succeededJob + return nil + }) + mockClient.EXPECT().Delete(ctx, gomock.Any(), gomock.Any()).Return(nil) + mockRecorder.EXPECT().Event(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + res, err := adapter.EnsureTeardownFinished(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay}, res) + }) + t.Run("Happy path: teardown create new job", func(t *testing.T) { + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhaseDeleting + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + mockClient.EXPECT().Scheme().Return(scheme).AnyTimes() + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&batchv1.Job{})). + Return(k8serr.NewNotFound(batchv1.Resource("job"), "test-job")) + + mockClient.EXPECT().Create(ctx, gomock.Any()).Return(nil) + mockRecorder.EXPECT().Event(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + res, err := adapter.EnsureTeardownFinished(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) + t.Run("Happy path: teardown job failed, create new job", func(t *testing.T) { + appDeployment := validAppDeployment.DeepCopy() + appDeployment.Status.Phase = apdutil.PhaseDeleting + logger := log.FromContext(ctx) + + mockCtrl := gomock.NewController(t) + mockClient := mockpkg.NewMockClient(mockCtrl) + mockRecorderCtrl := gomock.NewController(t) + mockRecorder := mockpkg.NewMockEventRecorder(mockRecorderCtrl) + mockStatusCtrl := gomock.NewController(t) + mockStatusWriter := mockpkg.NewMockStatusWriter(mockStatusCtrl) + mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() + scheme := runtime.NewScheme() + _ = appsv1.AddToScheme(scheme) + mockClient.EXPECT().Scheme().Return(scheme).AnyTimes() + + adapter := NewAppDeploymentAdapter(ctx, appDeployment, logger, mockClient, mockRecorder) + assert.NotNil(t, adapter) + + mockClient.EXPECT().Get(ctx, gomock.Any(), gomock.AssignableToTypeOf(&batchv1.Job{})). + DoAndReturn(func(ctx context.Context, key client.ObjectKey, obj runtime.Object, opts ...client.GetOption) error { + *obj.(*batchv1.Job) = failedJob + return nil + }) + mockClient.EXPECT().Delete(ctx, gomock.Any(), gomock.Any()).Return(nil) + mockClient.EXPECT().Create(ctx, gomock.Any()).Return(nil) + mockRecorder.EXPECT().Event(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() + mockStatusWriter.EXPECT().Update(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() + res, err := adapter.EnsureTeardownFinished(ctx) + assert.NoError(t, err) + assert.Equal(t, reconciler.OperationResult{RequeueDelay: reconciler.DefaultRequeueDelay, RequeueRequest: true}, res) + }) +} diff --git a/internal/controller/mocks/mock_appdeployment_adapter.go b/internal/controller/mocks/mock_appdeployment_adapter.go new file mode 100644 index 0000000..0aae904 --- /dev/null +++ b/internal/controller/mocks/mock_appdeployment_adapter.go @@ -0,0 +1,132 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/Azure/operation-cache-controller/internal/controller (interfaces: AppDeploymentAdapterInterface) +// +// Generated by this command: +// +// mockgen -destination=./mocks/mock_appdeployment_adapter.go -package=mocks github.com/Azure/operation-cache-controller/internal/controller AppDeploymentAdapterInterface +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + reconciler "github.com/Azure/operation-cache-controller/internal/utils/reconciler" + gomock "go.uber.org/mock/gomock" +) + +// MockAppDeploymentAdapterInterface is a mock of AppDeploymentAdapterInterface interface. +type MockAppDeploymentAdapterInterface struct { + ctrl *gomock.Controller + recorder *MockAppDeploymentAdapterInterfaceMockRecorder + isgomock struct{} +} + +// MockAppDeploymentAdapterInterfaceMockRecorder is the mock recorder for MockAppDeploymentAdapterInterface. +type MockAppDeploymentAdapterInterfaceMockRecorder struct { + mock *MockAppDeploymentAdapterInterface +} + +// NewMockAppDeploymentAdapterInterface creates a new mock instance. +func NewMockAppDeploymentAdapterInterface(ctrl *gomock.Controller) *MockAppDeploymentAdapterInterface { + mock := &MockAppDeploymentAdapterInterface{ctrl: ctrl} + mock.recorder = &MockAppDeploymentAdapterInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAppDeploymentAdapterInterface) EXPECT() *MockAppDeploymentAdapterInterfaceMockRecorder { + return m.recorder +} + +// EnsureApplicationValid mocks base method. +func (m *MockAppDeploymentAdapterInterface) EnsureApplicationValid(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureApplicationValid", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureApplicationValid indicates an expected call of EnsureApplicationValid. +func (mr *MockAppDeploymentAdapterInterfaceMockRecorder) EnsureApplicationValid(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureApplicationValid", reflect.TypeOf((*MockAppDeploymentAdapterInterface)(nil).EnsureApplicationValid), ctx) +} + +// EnsureDependenciesReady mocks base method. +func (m *MockAppDeploymentAdapterInterface) EnsureDependenciesReady(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureDependenciesReady", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureDependenciesReady indicates an expected call of EnsureDependenciesReady. +func (mr *MockAppDeploymentAdapterInterfaceMockRecorder) EnsureDependenciesReady(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureDependenciesReady", reflect.TypeOf((*MockAppDeploymentAdapterInterface)(nil).EnsureDependenciesReady), ctx) +} + +// EnsureDeployingFinished mocks base method. +func (m *MockAppDeploymentAdapterInterface) EnsureDeployingFinished(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureDeployingFinished", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureDeployingFinished indicates an expected call of EnsureDeployingFinished. +func (mr *MockAppDeploymentAdapterInterfaceMockRecorder) EnsureDeployingFinished(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureDeployingFinished", reflect.TypeOf((*MockAppDeploymentAdapterInterface)(nil).EnsureDeployingFinished), ctx) +} + +// EnsureFinalizer mocks base method. +func (m *MockAppDeploymentAdapterInterface) EnsureFinalizer(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureFinalizer", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureFinalizer indicates an expected call of EnsureFinalizer. +func (mr *MockAppDeploymentAdapterInterfaceMockRecorder) EnsureFinalizer(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureFinalizer", reflect.TypeOf((*MockAppDeploymentAdapterInterface)(nil).EnsureFinalizer), ctx) +} + +// EnsureFinalizerDeleted mocks base method. +func (m *MockAppDeploymentAdapterInterface) EnsureFinalizerDeleted(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureFinalizerDeleted", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureFinalizerDeleted indicates an expected call of EnsureFinalizerDeleted. +func (mr *MockAppDeploymentAdapterInterfaceMockRecorder) EnsureFinalizerDeleted(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureFinalizerDeleted", reflect.TypeOf((*MockAppDeploymentAdapterInterface)(nil).EnsureFinalizerDeleted), ctx) +} + +// EnsureTeardownFinished mocks base method. +func (m *MockAppDeploymentAdapterInterface) EnsureTeardownFinished(ctx context.Context) (reconciler.OperationResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureTeardownFinished", ctx) + ret0, _ := ret[0].(reconciler.OperationResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EnsureTeardownFinished indicates an expected call of EnsureTeardownFinished. +func (mr *MockAppDeploymentAdapterInterfaceMockRecorder) EnsureTeardownFinished(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureTeardownFinished", reflect.TypeOf((*MockAppDeploymentAdapterInterface)(nil).EnsureTeardownFinished), ctx) +} diff --git a/internal/mocks/generate.go b/internal/mocks/generate.go new file mode 100644 index 0000000..8e24d36 --- /dev/null +++ b/internal/mocks/generate.go @@ -0,0 +1,12 @@ +package mocks + +// This is a dummy source file whose job is to contain the directives to (re)produce the generated +// mock fixtures in this package that come from source files outside of this project (otherwise the +// directives should go in the source files themselves). +// Run `make generate` from the project root. +// Dependency: mockgen, qua: +// GO111MODULE=on go get go.uber.org/mock/mockgen@latest + +//go:generate mockgen -destination ./mock_cr_client.go -package mocks sigs.k8s.io/controller-runtime/pkg/client Client +//go:generate mockgen -destination ./mock_cr_status_writer.go -package mocks sigs.k8s.io/controller-runtime/pkg/client StatusWriter +//go:generate mockgen -destination ./mock_cr_recorder.go -package mocks k8s.io/client-go/tools/record EventRecorder diff --git a/internal/mocks/mock_cr_client.go b/internal/mocks/mock_cr_client.go new file mode 100644 index 0000000..c380399 --- /dev/null +++ b/internal/mocks/mock_cr_client.go @@ -0,0 +1,264 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: sigs.k8s.io/controller-runtime/pkg/client (interfaces: Client) +// +// Generated by this command: +// +// mockgen -destination ./mock_cr_client.go -package mocks sigs.k8s.io/controller-runtime/pkg/client Client +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + meta "k8s.io/apimachinery/pkg/api/meta" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockClient is a mock of Client interface. +type MockClient struct { + ctrl *gomock.Controller + recorder *MockClientMockRecorder + isgomock struct{} +} + +// MockClientMockRecorder is the mock recorder for MockClient. +type MockClientMockRecorder struct { + mock *MockClient +} + +// NewMockClient creates a new mock instance. +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &MockClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClient) EXPECT() *MockClientMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockClientMockRecorder) Create(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockClient)(nil).Create), varargs...) +} + +// Delete mocks base method. +func (m *MockClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockClientMockRecorder) Delete(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockClient)(nil).Delete), varargs...) +} + +// DeleteAllOf mocks base method. +func (m *MockClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteAllOf", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAllOf indicates an expected call of DeleteAllOf. +func (mr *MockClientMockRecorder) DeleteAllOf(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAllOf", reflect.TypeOf((*MockClient)(nil).DeleteAllOf), varargs...) +} + +// Get mocks base method. +func (m *MockClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, key, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Get", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Get indicates an expected call of Get. +func (mr *MockClientMockRecorder) Get(ctx, key, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, key, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockClient)(nil).Get), varargs...) +} + +// GroupVersionKindFor mocks base method. +func (m *MockClient) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GroupVersionKindFor", obj) + ret0, _ := ret[0].(schema.GroupVersionKind) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GroupVersionKindFor indicates an expected call of GroupVersionKindFor. +func (mr *MockClientMockRecorder) GroupVersionKindFor(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupVersionKindFor", reflect.TypeOf((*MockClient)(nil).GroupVersionKindFor), obj) +} + +// IsObjectNamespaced mocks base method. +func (m *MockClient) IsObjectNamespaced(obj runtime.Object) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsObjectNamespaced", obj) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// IsObjectNamespaced indicates an expected call of IsObjectNamespaced. +func (mr *MockClientMockRecorder) IsObjectNamespaced(obj any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsObjectNamespaced", reflect.TypeOf((*MockClient)(nil).IsObjectNamespaced), obj) +} + +// List mocks base method. +func (m *MockClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, list} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// List indicates an expected call of List. +func (mr *MockClientMockRecorder) List(ctx, list any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, list}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockClient)(nil).List), varargs...) +} + +// Patch mocks base method. +func (m *MockClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, patch} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockClientMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, patch}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockClient)(nil).Patch), varargs...) +} + +// RESTMapper mocks base method. +func (m *MockClient) RESTMapper() meta.RESTMapper { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RESTMapper") + ret0, _ := ret[0].(meta.RESTMapper) + return ret0 +} + +// RESTMapper indicates an expected call of RESTMapper. +func (mr *MockClientMockRecorder) RESTMapper() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RESTMapper", reflect.TypeOf((*MockClient)(nil).RESTMapper)) +} + +// Scheme mocks base method. +func (m *MockClient) Scheme() *runtime.Scheme { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Scheme") + ret0, _ := ret[0].(*runtime.Scheme) + return ret0 +} + +// Scheme indicates an expected call of Scheme. +func (mr *MockClientMockRecorder) Scheme() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Scheme", reflect.TypeOf((*MockClient)(nil).Scheme)) +} + +// Status mocks base method. +func (m *MockClient) Status() client.StatusWriter { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Status") + ret0, _ := ret[0].(client.StatusWriter) + return ret0 +} + +// Status indicates an expected call of Status. +func (mr *MockClientMockRecorder) Status() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Status", reflect.TypeOf((*MockClient)(nil).Status)) +} + +// SubResource mocks base method. +func (m *MockClient) SubResource(subResource string) client.SubResourceClient { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SubResource", subResource) + ret0, _ := ret[0].(client.SubResourceClient) + return ret0 +} + +// SubResource indicates an expected call of SubResource. +func (mr *MockClientMockRecorder) SubResource(subResource any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubResource", reflect.TypeOf((*MockClient)(nil).SubResource), subResource) +} + +// Update mocks base method. +func (m *MockClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockClientMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockClient)(nil).Update), varargs...) +} diff --git a/internal/mocks/mock_cr_recorder.go b/internal/mocks/mock_cr_recorder.go new file mode 100644 index 0000000..6ebcea3 --- /dev/null +++ b/internal/mocks/mock_cr_recorder.go @@ -0,0 +1,87 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: k8s.io/client-go/tools/record (interfaces: EventRecorder) +// +// Generated by this command: +// +// mockgen -destination ./mock_cr_recorder.go -package mocks k8s.io/client-go/tools/record EventRecorder +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// MockEventRecorder is a mock of EventRecorder interface. +type MockEventRecorder struct { + ctrl *gomock.Controller + recorder *MockEventRecorderMockRecorder + isgomock struct{} +} + +// MockEventRecorderMockRecorder is the mock recorder for MockEventRecorder. +type MockEventRecorderMockRecorder struct { + mock *MockEventRecorder +} + +// NewMockEventRecorder creates a new mock instance. +func NewMockEventRecorder(ctrl *gomock.Controller) *MockEventRecorder { + mock := &MockEventRecorder{ctrl: ctrl} + mock.recorder = &MockEventRecorderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEventRecorder) EXPECT() *MockEventRecorderMockRecorder { + return m.recorder +} + +// AnnotatedEventf mocks base method. +func (m *MockEventRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{object, annotations, eventtype, reason, messageFmt} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "AnnotatedEventf", varargs...) +} + +// AnnotatedEventf indicates an expected call of AnnotatedEventf. +func (mr *MockEventRecorderMockRecorder) AnnotatedEventf(object, annotations, eventtype, reason, messageFmt any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{object, annotations, eventtype, reason, messageFmt}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnotatedEventf", reflect.TypeOf((*MockEventRecorder)(nil).AnnotatedEventf), varargs...) +} + +// Event mocks base method. +func (m *MockEventRecorder) Event(object runtime.Object, eventtype, reason, message string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Event", object, eventtype, reason, message) +} + +// Event indicates an expected call of Event. +func (mr *MockEventRecorderMockRecorder) Event(object, eventtype, reason, message any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Event", reflect.TypeOf((*MockEventRecorder)(nil).Event), object, eventtype, reason, message) +} + +// Eventf mocks base method. +func (m *MockEventRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...any) { + m.ctrl.T.Helper() + varargs := []any{object, eventtype, reason, messageFmt} + for _, a := range args { + varargs = append(varargs, a) + } + m.ctrl.Call(m, "Eventf", varargs...) +} + +// Eventf indicates an expected call of Eventf. +func (mr *MockEventRecorderMockRecorder) Eventf(object, eventtype, reason, messageFmt any, args ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{object, eventtype, reason, messageFmt}, args...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Eventf", reflect.TypeOf((*MockEventRecorder)(nil).Eventf), varargs...) +} diff --git a/internal/mocks/mock_cr_status_writer.go b/internal/mocks/mock_cr_status_writer.go new file mode 100644 index 0000000..71090e9 --- /dev/null +++ b/internal/mocks/mock_cr_status_writer.go @@ -0,0 +1,99 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: sigs.k8s.io/controller-runtime/pkg/client (interfaces: StatusWriter) +// +// Generated by this command: +// +// mockgen -destination ./mock_cr_status_writer.go -package mocks sigs.k8s.io/controller-runtime/pkg/client StatusWriter +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + client "sigs.k8s.io/controller-runtime/pkg/client" +) + +// MockStatusWriter is a mock of StatusWriter interface. +type MockStatusWriter struct { + ctrl *gomock.Controller + recorder *MockStatusWriterMockRecorder + isgomock struct{} +} + +// MockStatusWriterMockRecorder is the mock recorder for MockStatusWriter. +type MockStatusWriterMockRecorder struct { + mock *MockStatusWriter +} + +// NewMockStatusWriter creates a new mock instance. +func NewMockStatusWriter(ctrl *gomock.Controller) *MockStatusWriter { + mock := &MockStatusWriter{ctrl: ctrl} + mock.recorder = &MockStatusWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStatusWriter) EXPECT() *MockStatusWriterMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockStatusWriter) Create(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceCreateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, subResource} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockStatusWriterMockRecorder) Create(ctx, obj, subResource any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, subResource}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockStatusWriter)(nil).Create), varargs...) +} + +// Patch mocks base method. +func (m *MockStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj, patch} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Patch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockStatusWriterMockRecorder) Patch(ctx, obj, patch any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj, patch}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockStatusWriter)(nil).Patch), varargs...) +} + +// Update mocks base method. +func (m *MockStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error { + m.ctrl.T.Helper() + varargs := []any{ctx, obj} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockStatusWriterMockRecorder) Update(ctx, obj any, opts ...any) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]any{ctx, obj}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockStatusWriter)(nil).Update), varargs...) +} From 1cef435b392b6094bc50d2cacb1799bbb4cff5b9 Mon Sep 17 00:00:00 2001 From: DQ Date: Fri, 11 Apr 2025 14:45:20 +1000 Subject: [PATCH 2/3] Add Cache, Operation, and Requirement CRDs and Update Dependencies - Added CustomResourceDefinitions (CRDs) for Cache, Operation, and Requirement resources, including their schemas and metadata. - Updated RBAC roles to include permissions for managing Cache, Operation, and Requirement resources. - Added dependencies for logr and mock libraries in go.mod to enhance logging and testing capabilities. --- config/crd/bases/app.github.com_caches.yaml | 38 +++++++++++++++++++ .../crd/bases/app.github.com_operations.yaml | 38 +++++++++++++++++++ .../bases/app.github.com_requirements.yaml | 38 +++++++++++++++++++ config/rbac/role.yaml | 9 +++++ go.mod | 3 +- go.sum | 2 + 6 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 config/crd/bases/app.github.com_caches.yaml create mode 100644 config/crd/bases/app.github.com_operations.yaml create mode 100644 config/crd/bases/app.github.com_requirements.yaml diff --git a/config/crd/bases/app.github.com_caches.yaml b/config/crd/bases/app.github.com_caches.yaml new file mode 100644 index 0000000..90b7182 --- /dev/null +++ b/config/crd/bases/app.github.com_caches.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: caches.app.github.com +spec: + group: app.github.com + names: + kind: Cache + listKind: CacheList + plural: caches + singular: cache + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + foo: + type: string + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/app.github.com_operations.yaml b/config/crd/bases/app.github.com_operations.yaml new file mode 100644 index 0000000..cea74ba --- /dev/null +++ b/config/crd/bases/app.github.com_operations.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: operations.app.github.com +spec: + group: app.github.com + names: + kind: Operation + listKind: OperationList + plural: operations + singular: operation + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + foo: + type: string + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/app.github.com_requirements.yaml b/config/crd/bases/app.github.com_requirements.yaml new file mode 100644 index 0000000..93f8b6c --- /dev/null +++ b/config/crd/bases/app.github.com_requirements.yaml @@ -0,0 +1,38 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: requirements.app.github.com +spec: + group: app.github.com + names: + kind: Requirement + listKind: RequirementList + plural: requirements + singular: requirement + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + type: string + kind: + type: string + metadata: + type: object + spec: + properties: + foo: + type: string + type: object + status: + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 0970dfc..7e8af4c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -8,6 +8,9 @@ rules: - app.github.com resources: - appdeployments + - caches + - operations + - requirements verbs: - create - delete @@ -20,12 +23,18 @@ rules: - app.github.com resources: - appdeployments/finalizers + - caches/finalizers + - operations/finalizers + - requirements/finalizers verbs: - update - apiGroups: - app.github.com resources: - appdeployments/status + - caches/status + - operations/status + - requirements/status verbs: - get - patch diff --git a/go.mod b/go.mod index e321eba..4c992eb 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,11 @@ go 1.23.0 godebug default=go1.23 require ( + github.com/go-logr/logr v1.4.2 github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 github.com/stretchr/testify v1.9.0 + go.uber.org/mock v0.5.1 k8s.io/api v0.32.1 k8s.io/apimachinery v0.32.1 k8s.io/client-go v0.32.1 @@ -28,7 +30,6 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect diff --git a/go.sum b/go.sum index e71a42c..de2f8df 100644 --- a/go.sum +++ b/go.sum @@ -150,6 +150,8 @@ go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeX go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs= +go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= From 1a43f3e68346715e3425bc8c0ad350e302df5c11 Mon Sep 17 00:00:00 2001 From: DQ Date: Fri, 11 Apr 2025 14:51:02 +1000 Subject: [PATCH 3/3] Refactor OpId usage in AppDeployment tests to use a constant - Replaced hardcoded OpId string with a constant in the AppDeployment test file for improved maintainability and consistency across tests. - Updated multiple test cases to utilize the new constant instead of the previous hardcoded value. --- internal/controller/appdeployment_adapter_test.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/controller/appdeployment_adapter_test.go b/internal/controller/appdeployment_adapter_test.go index 4f4a96a..deca909 100644 --- a/internal/controller/appdeployment_adapter_test.go +++ b/internal/controller/appdeployment_adapter_test.go @@ -21,11 +21,13 @@ import ( "github.com/Azure/operation-cache-controller/internal/utils/reconciler" ) +const testOpId = "test-op-id" + var validAppDeployment = &appsv1.AppDeployment{ Spec: appsv1.AppDeploymentSpec{ Provision: newTestJobSpec(), Teardown: newTestJobSpec(), - OpId: "test-op-id", + OpId: testOpId, }, } @@ -249,7 +251,7 @@ func TestAppDeploymentAdapter_EnsureDependenciesReady(t *testing.T) { mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() appDeployment := validAppDeployment.DeepCopy() appDeployment.Status.Phase = apdutil.PhasePending - appDeployment.Spec.OpId = "test-op-id" + appDeployment.Spec.OpId = testOpId appDeployment.Spec.Dependencies = []string{ "test-app-1", } @@ -279,7 +281,7 @@ func TestAppDeploymentAdapter_EnsureDependenciesReady(t *testing.T) { mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() appDeployment := validAppDeployment.DeepCopy() appDeployment.Status.Phase = apdutil.PhasePending - appDeployment.Spec.OpId = "test-op-id" + appDeployment.Spec.OpId = testOpId appDeployment.Spec.Dependencies = []string{ "test-app-1", } @@ -297,7 +299,7 @@ func TestAppDeploymentAdapter_EnsureDependenciesReady(t *testing.T) { mockClient.EXPECT().Status().Return(mockStatusWriter).AnyTimes() appDeployment := validAppDeployment.DeepCopy() appDeployment.Status.Phase = apdutil.PhasePending - appDeployment.Spec.OpId = "test-op-id" + appDeployment.Spec.OpId = testOpId appDeployment.Spec.Dependencies = []string{ "test-app-1", }