From 6163c551605f2bbcd31a10a588c4e6390d61c4aa Mon Sep 17 00:00:00 2001 From: Lukasz Zajaczkowski Date: Fri, 12 Apr 2024 15:53:51 +0200 Subject: [PATCH] feat: add unit tests (#832) --- .../controller/managenamespace_test.go | 185 ++++++++++++++++++ .../controller/notificationrouter_test.go | 164 ++++++++++++++++ .../controller/notificationsink_test.go | 165 ++++++++++++++++ 3 files changed, 514 insertions(+) create mode 100644 controller/internal/controller/managenamespace_test.go create mode 100644 controller/internal/controller/notificationrouter_test.go create mode 100644 controller/internal/controller/notificationsink_test.go diff --git a/controller/internal/controller/managenamespace_test.go b/controller/internal/controller/managenamespace_test.go new file mode 100644 index 0000000000..ed23990c6b --- /dev/null +++ b/controller/internal/controller/managenamespace_test.go @@ -0,0 +1,185 @@ +package controller_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + gqlclient "github.com/pluralsh/console-client-go" + "github.com/samber/lo" + "github.com/stretchr/testify/mock" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/pluralsh/console/controller/api/v1alpha1" + "github.com/pluralsh/console/controller/internal/controller" + common "github.com/pluralsh/console/controller/internal/test/common" + "github.com/pluralsh/console/controller/internal/test/mocks" +) + +var _ = Describe("ManagedNamespace Service Controller", Ordered, func() { + Context("When reconciling a resource", func() { + const ( + managedNamespaceName = "ns-test" + clusterName = "cluster-test" + namespace = "default" + id = "123" + ) + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: managedNamespaceName, + Namespace: namespace, + } + + BeforeAll(func() { + By("creating the custom resource for the Kind Cluster") + Expect(common.MaybeCreate(k8sClient, &v1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Name: clusterName, Namespace: namespace}, + Spec: v1alpha1.ClusterSpec{ + Cloud: "aws", + }, + }, func(p *v1alpha1.Cluster) { + p.Status.ID = lo.ToPtr(id) + })).To(Succeed()) + By("creating the custom resource for the Kind ManagedNamespace") + ns := &v1alpha1.ManagedNamespace{} + err := k8sClient.Get(ctx, typeNamespacedName, ns) + if err != nil && errors.IsNotFound(err) { + resource := &v1alpha1.ManagedNamespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: managedNamespaceName, + Namespace: namespace, + }, + Spec: v1alpha1.ManagedNamespaceSpec{ + Name: lo.ToPtr(managedNamespaceName), + Description: lo.ToPtr("test"), + Labels: map[string]string{"a": "a"}, + Annotations: map[string]string{"b": "b"}, + Target: &v1alpha1.ClusterTarget{ + Distro: lo.ToPtr(gqlclient.ClusterDistroGeneric), + ClusterRefs: []corev1.ObjectReference{ + { + Namespace: clusterName, + Name: namespace, + }, + }, + }, + }, + } + Expect(common.MaybeCreate(k8sClient, resource, nil)).To(Succeed()) + } + }) + + AfterAll(func() { + resource := &v1alpha1.Cluster{} + err := k8sClient.Get(ctx, types.NamespacedName{Name: clusterName, Namespace: namespace}, resource) + Expect(err).NotTo(HaveOccurred()) + By("Cleanup the specific resource instance Cluster") + Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) + ns := &v1alpha1.ManagedNamespace{} + if err := k8sClient.Get(ctx, typeNamespacedName, ns); err == nil { + By("Cleanup the specific resource instance ManagedNamespace") + Expect(k8sClient.Delete(ctx, ns)).To(Succeed()) + } + }) + + It("should successfully reconcile the resource", func() { + By("Create resource") + test := struct { + returnCreateNamespace *gqlclient.ManagedNamespaceFragment + expectedStatus v1alpha1.Status + }{ + expectedStatus: v1alpha1.Status{ + ID: lo.ToPtr("123"), + SHA: lo.ToPtr("DJCZHRJXVA2HKOIRZQKKLVCPP7TJ5MZMMEPDD2YANTAAEGDZMXNQ===="), + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ReadyConditionType.String(), + Status: metav1.ConditionTrue, + Reason: v1alpha1.ReadyConditionReason.String(), + Message: "", + }, + { + Type: v1alpha1.SynchronizedConditionType.String(), + Status: metav1.ConditionTrue, + Reason: v1alpha1.SynchronizedConditionReason.String(), + }, + }, + }, + returnCreateNamespace: &gqlclient.ManagedNamespaceFragment{ + ID: "123", + }, + } + + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("CreateNamespace", mock.Anything, mock.Anything).Return(test.returnCreateNamespace, nil) + namespaceReconciler := &controller.ManagedNamespaceReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + } + + _, err := namespaceReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + + Expect(err).NotTo(HaveOccurred()) + + mns := &v1alpha1.ManagedNamespace{} + err = k8sClient.Get(ctx, typeNamespacedName, mns) + + Expect(err).NotTo(HaveOccurred()) + Expect(common.SanitizeStatusConditions(mns.Status)).To(Equal(common.SanitizeStatusConditions(test.expectedStatus))) + }) + + It("should successfully reconcile the resource", func() { + By("Delete resource") + test := struct { + returnGetNs *gqlclient.ManagedNamespaceFragment + }{ + returnGetNs: &gqlclient.ManagedNamespaceFragment{ + ID: "123", + }, + } + + Expect(common.MaybePatch(k8sClient, &v1alpha1.ManagedNamespace{ + ObjectMeta: metav1.ObjectMeta{Name: managedNamespaceName, Namespace: namespace}, + }, func(p *v1alpha1.ManagedNamespace) { + p.Status.ID = lo.ToPtr(id) + p.Status.SHA = lo.ToPtr("WAXTBLTM6PFWW6BBRLCPV2ILX2J4EOHQKDISWH4QAM5IODNRMBJQ====") + })).To(Succeed()) + resource := &v1alpha1.ManagedNamespace{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Delete(ctx, resource) + Expect(err).NotTo(HaveOccurred()) + + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("GetNamespace", mock.Anything, mock.Anything).Return(test.returnGetNs, nil) + fakeConsoleClient.On("DeleteNamespace", mock.Anything, mock.Anything).Return(nil) + nsReconciler := &controller.ManagedNamespaceReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + } + + _, err = nsReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + + Expect(err).NotTo(HaveOccurred()) + + service := &v1alpha1.ManagedNamespace{} + err = k8sClient.Get(ctx, typeNamespacedName, service) + + Expect(err.Error()).To(Equal("managednamespaces.deployments.plural.sh \"ns-test\" not found")) + }) + + }) + +}) diff --git a/controller/internal/controller/notificationrouter_test.go b/controller/internal/controller/notificationrouter_test.go new file mode 100644 index 0000000000..dba22a89bd --- /dev/null +++ b/controller/internal/controller/notificationrouter_test.go @@ -0,0 +1,164 @@ +package controller_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + gqlclient "github.com/pluralsh/console-client-go" + "github.com/pluralsh/console/controller/api/v1alpha1" + "github.com/pluralsh/console/controller/internal/controller" + common "github.com/pluralsh/console/controller/internal/test/common" + "github.com/pluralsh/console/controller/internal/test/mocks" + "github.com/samber/lo" + "github.com/stretchr/testify/mock" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var _ = Describe("NotificationRouter Service Controller", Ordered, func() { + Context("When reconciling a resource", func() { + const ( + routerName = "test" + namespace = "default" + id = "123" + ) + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: routerName, + Namespace: namespace, + } + + BeforeAll(func() { + By("creating the custom resource for the Kind NotificationRouter") + ns := &v1alpha1.NotificationRouter{} + err := k8sClient.Get(ctx, typeNamespacedName, ns) + if err != nil && errors.IsNotFound(err) { + resource := &v1alpha1.NotificationRouter{ + ObjectMeta: metav1.ObjectMeta{ + Name: routerName, + Namespace: namespace, + }, + Spec: v1alpha1.NotificationRouterSpec{ + Name: lo.ToPtr(routerName), + }, + } + Expect(common.MaybeCreate(k8sClient, resource, nil)).To(Succeed()) + } + }) + + AfterAll(func() { + ns := &v1alpha1.NotificationRouter{} + if err := k8sClient.Get(ctx, typeNamespacedName, ns); err == nil { + By("Cleanup the specific resource instance NotificationRouter") + Expect(k8sClient.Delete(ctx, ns)).To(Succeed()) + } + }) + + It("should successfully reconcile the resource", func() { + By("Create resource") + test := struct { + notificationRouterFragment *gqlclient.NotificationRouterFragment + expectedStatus v1alpha1.Status + }{ + expectedStatus: v1alpha1.Status{ + ID: lo.ToPtr("123"), + SHA: lo.ToPtr("PWP5EBI7YMVTF7VLCCKG7K3LXEKCNK36HGVFIOJIT3MJFBSKVEOQ===="), + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ReadonlyConditionType.String(), + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReadonlyConditionReason.String(), + Message: "", + }, + { + Type: v1alpha1.ReadyConditionType.String(), + Status: metav1.ConditionTrue, + Reason: v1alpha1.ReadyConditionReason.String(), + Message: "", + }, + { + Type: v1alpha1.SynchronizedConditionType.String(), + Status: metav1.ConditionTrue, + Reason: v1alpha1.SynchronizedConditionReason.String(), + }, + }, + }, + notificationRouterFragment: &gqlclient.NotificationRouterFragment{ + ID: "123", + }, + } + + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("GetNotificationRouterByName", mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, id)) + fakeConsoleClient.On("UpsertNotificationRouter", mock.Anything, mock.Anything).Return(test.notificationRouterFragment, nil) + nr := &controller.NotificationRouterReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + } + + _, err := nr.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + + Expect(err).NotTo(HaveOccurred()) + + mns := &v1alpha1.NotificationRouter{} + err = k8sClient.Get(ctx, typeNamespacedName, mns) + + Expect(err).NotTo(HaveOccurred()) + Expect(common.SanitizeStatusConditions(mns.Status)).To(Equal(common.SanitizeStatusConditions(test.expectedStatus))) + }) + + It("should successfully reconcile the resource", func() { + By("Delete resource") + test := struct { + returnGetNs *gqlclient.NotificationRouterFragment + }{ + returnGetNs: &gqlclient.NotificationRouterFragment{ + ID: "123", + }, + } + + Expect(common.MaybePatch(k8sClient, &v1alpha1.NotificationRouter{ + ObjectMeta: metav1.ObjectMeta{Name: routerName, Namespace: namespace}, + }, func(p *v1alpha1.NotificationRouter) { + p.Status.ID = lo.ToPtr(id) + p.Status.SHA = lo.ToPtr("WAXTBLTM6PFWW6BBRLCPV2ILX2J4EOHQKDISWH4QAM5IODNRMBJQ====") + })).To(Succeed()) + resource := &v1alpha1.NotificationRouter{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Delete(ctx, resource) + Expect(err).NotTo(HaveOccurred()) + + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("GetNotificationRouter", mock.Anything, mock.Anything).Return(test.returnGetNs, nil) + fakeConsoleClient.On("DeleteNotificationRouter", mock.Anything, mock.Anything).Return(nil) + nsReconciler := &controller.NotificationRouterReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + } + + _, err = nsReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + + Expect(err).NotTo(HaveOccurred()) + + service := &v1alpha1.NotificationRouter{} + err = k8sClient.Get(ctx, typeNamespacedName, service) + + Expect(err.Error()).To(Equal("notificationrouters.deployments.plural.sh \"test\" not found")) + }) + + }) + +}) diff --git a/controller/internal/controller/notificationsink_test.go b/controller/internal/controller/notificationsink_test.go new file mode 100644 index 0000000000..536729d18f --- /dev/null +++ b/controller/internal/controller/notificationsink_test.go @@ -0,0 +1,165 @@ +package controller_test + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + gqlclient "github.com/pluralsh/console-client-go" + "github.com/pluralsh/console/controller/api/v1alpha1" + "github.com/pluralsh/console/controller/internal/controller" + common "github.com/pluralsh/console/controller/internal/test/common" + "github.com/pluralsh/console/controller/internal/test/mocks" + "github.com/samber/lo" + "github.com/stretchr/testify/mock" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +var _ = Describe("NotificationSink Service Controller", Ordered, func() { + Context("When reconciling a resource", func() { + const ( + sinkName = "test" + namespace = "default" + id = "123" + ) + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: sinkName, + Namespace: namespace, + } + + BeforeAll(func() { + By("creating the custom resource for the Kind NotificationSink") + ns := &v1alpha1.NotificationSink{} + err := k8sClient.Get(ctx, typeNamespacedName, ns) + if err != nil && errors.IsNotFound(err) { + resource := &v1alpha1.NotificationSink{ + ObjectMeta: metav1.ObjectMeta{ + Name: sinkName, + Namespace: namespace, + }, + Spec: v1alpha1.NotificationSinkSpec{ + Name: lo.ToPtr(sinkName), + Type: gqlclient.SinkTypeSLACk, + }, + } + Expect(common.MaybeCreate(k8sClient, resource, nil)).To(Succeed()) + } + }) + + AfterAll(func() { + ns := &v1alpha1.NotificationSink{} + if err := k8sClient.Get(ctx, typeNamespacedName, ns); err == nil { + By("Cleanup the specific resource instance NotificationSink") + Expect(k8sClient.Delete(ctx, ns)).To(Succeed()) + } + }) + + It("should successfully reconcile the resource", func() { + By("Create resource") + test := struct { + notificationSinkFragment *gqlclient.NotificationSinkFragment + expectedStatus v1alpha1.Status + }{ + expectedStatus: v1alpha1.Status{ + ID: lo.ToPtr("123"), + SHA: lo.ToPtr("RC5AMSF4VJD2XA4WXDNJBGE2MJFAFV67WD245WMWSQDOTQTVHOWA===="), + Conditions: []metav1.Condition{ + { + Type: v1alpha1.ReadonlyConditionType.String(), + Status: metav1.ConditionFalse, + Reason: v1alpha1.ReadonlyConditionReason.String(), + Message: "", + }, + { + Type: v1alpha1.ReadyConditionType.String(), + Status: metav1.ConditionTrue, + Reason: v1alpha1.ReadyConditionReason.String(), + Message: "", + }, + { + Type: v1alpha1.SynchronizedConditionType.String(), + Status: metav1.ConditionTrue, + Reason: v1alpha1.SynchronizedConditionReason.String(), + }, + }, + }, + notificationSinkFragment: &gqlclient.NotificationSinkFragment{ + ID: "123", + }, + } + + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("GetNotificationSinkByName", mock.Anything, mock.Anything).Return(nil, errors.NewNotFound(schema.GroupResource{}, id)) + fakeConsoleClient.On("UpsertNotificationSink", mock.Anything, mock.Anything).Return(test.notificationSinkFragment, nil) + nr := &controller.NotificationSinkReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + } + + _, err := nr.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + + Expect(err).NotTo(HaveOccurred()) + + mns := &v1alpha1.NotificationSink{} + err = k8sClient.Get(ctx, typeNamespacedName, mns) + + Expect(err).NotTo(HaveOccurred()) + Expect(common.SanitizeStatusConditions(mns.Status)).To(Equal(common.SanitizeStatusConditions(test.expectedStatus))) + }) + + It("should successfully reconcile the resource", func() { + By("Delete resource") + test := struct { + returnGetNs *gqlclient.NotificationSinkFragment + }{ + returnGetNs: &gqlclient.NotificationSinkFragment{ + ID: "123", + }, + } + + Expect(common.MaybePatch(k8sClient, &v1alpha1.NotificationSink{ + ObjectMeta: metav1.ObjectMeta{Name: sinkName, Namespace: namespace}, + }, func(p *v1alpha1.NotificationSink) { + p.Status.ID = lo.ToPtr(id) + p.Status.SHA = lo.ToPtr("WAXTBLTM6PFWW6BBRLCPV2ILX2J4EOHQKDISWH4QAM5IODNRMBJQ====") + })).To(Succeed()) + resource := &v1alpha1.NotificationSink{} + err := k8sClient.Get(ctx, typeNamespacedName, resource) + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Delete(ctx, resource) + Expect(err).NotTo(HaveOccurred()) + + fakeConsoleClient := mocks.NewConsoleClientMock(mocks.TestingT) + fakeConsoleClient.On("GetNotificationSink", mock.Anything, mock.Anything).Return(test.returnGetNs, nil) + fakeConsoleClient.On("DeleteNotificationSink", mock.Anything, mock.Anything).Return(nil) + nsReconciler := &controller.NotificationSinkReconciler{ + Client: k8sClient, + Scheme: k8sClient.Scheme(), + ConsoleClient: fakeConsoleClient, + } + + _, err = nsReconciler.Reconcile(ctx, reconcile.Request{ + NamespacedName: typeNamespacedName, + }) + + Expect(err).NotTo(HaveOccurred()) + + service := &v1alpha1.NotificationSink{} + err = k8sClient.Get(ctx, typeNamespacedName, service) + + Expect(err.Error()).To(Equal("notificationsinks.deployments.plural.sh \"test\" not found")) + }) + + }) + +})