From 2d5b1f898039e94b1fb46af0be2913fb2157c72c Mon Sep 17 00:00:00 2001 From: Chunyi Lyu Date: Wed, 14 Apr 2021 13:18:02 +0100 Subject: [PATCH] Add integration tests for queue controller --- controllers/queue_controller_test.go | 218 +++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 controllers/queue_controller_test.go diff --git a/controllers/queue_controller_test.go b/controllers/queue_controller_test.go new file mode 100644 index 00000000..d4764fa0 --- /dev/null +++ b/controllers/queue_controller_test.go @@ -0,0 +1,218 @@ +package controllers_test + +import ( + "bytes" + "errors" + "io/ioutil" + "net/http" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + topology "github.com/rabbitmq/messaging-topology-operator/api/v1alpha2" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("queue-controller", func() { + var q topology.Queue + var qName string + + JustBeforeEach(func() { + q = topology.Queue{ + ObjectMeta: metav1.ObjectMeta{ + Name: qName, + Namespace: "default", + }, + Spec: topology.QueueSpec{ + RabbitmqClusterReference: topology.RabbitmqClusterReference{ + Name: "example-rabbit", + }, + }, + } + }) + + Context("creation", func() { + When("the RabbitMQ Client returns a HTTP error response", func() { + BeforeEach(func() { + qName = "test-http-error" + fakeRabbitMQClient.DeclareQueueReturns(&http.Response{ + Status: "418 I'm a teapot", + StatusCode: 418, + }, errors.New("a failure")) + }) + + It("sets the status condition", func() { + Expect(client.Create(ctx, &q)).To(Succeed()) + EventuallyWithOffset(1, func() []topology.Condition { + _ = client.Get( + ctx, + types.NamespacedName{Name: q.Name, Namespace: q.Namespace}, + &q, + ) + + return q.Status.Conditions + }, 10*time.Second, 1*time.Second).Should(ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(topology.ConditionType("Ready")), + "Reason": Equal("FailedCreateOrUpdate"), + "Status": Equal(corev1.ConditionFalse), + "Message": ContainSubstring("a failure"), + }))) + }) + }) + + When("the RabbitMQ Client returns a Go error response", func() { + BeforeEach(func() { + qName = "test-go-error" + fakeRabbitMQClient.DeclareQueueReturns(nil, errors.New("a go failure")) + }) + + It("sets the status condition to indicate a failure to reconcile", func() { + Expect(client.Create(ctx, &q)).To(Succeed()) + EventuallyWithOffset(1, func() []topology.Condition { + _ = client.Get( + ctx, + types.NamespacedName{Name: q.Name, Namespace: q.Namespace}, + &q, + ) + + return q.Status.Conditions + }, 10*time.Second, 1*time.Second).Should(ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(topology.ConditionType("Ready")), + "Reason": Equal("FailedCreateOrUpdate"), + "Status": Equal(corev1.ConditionFalse), + "Message": ContainSubstring("a go failure"), + }))) + }) + }) + + When("success", func() { + BeforeEach(func() { + qName = "test-create-success" + fakeRabbitMQClient.DeclareQueueReturns(&http.Response{ + Status: "201 Created", + StatusCode: http.StatusCreated, + }, nil) + }) + + It("sets the status condition 'Ready' to 'true' ", func() { + Expect(client.Create(ctx, &q)).To(Succeed()) + EventuallyWithOffset(1, func() []topology.Condition { + _ = client.Get( + ctx, + types.NamespacedName{Name: q.Name, Namespace: q.Namespace}, + &q, + ) + + return q.Status.Conditions + }, 10*time.Second, 1*time.Second).Should(ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(topology.ConditionType("Ready")), + "Reason": Equal("SuccessfulCreateOrUpdate"), + "Status": Equal(corev1.ConditionTrue), + }))) + }) + }) + }) + + Context("deletion", func() { + JustBeforeEach(func() { + fakeRabbitMQClient.DeclareQueueReturns(&http.Response{ + Status: "201 Created", + StatusCode: http.StatusCreated, + }, nil) + Expect(client.Create(ctx, &q)).To(Succeed()) + EventuallyWithOffset(1, func() []topology.Condition { + _ = client.Get( + ctx, + types.NamespacedName{Name: q.Name, Namespace: q.Namespace}, + &q, + ) + + return q.Status.Conditions + }, 10*time.Second, 1*time.Second).Should(ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(topology.ConditionType("Ready")), + "Reason": Equal("SuccessfulCreateOrUpdate"), + "Status": Equal(corev1.ConditionTrue), + }))) + }) + + When("the RabbitMQ Client returns a HTTP error response", func() { + BeforeEach(func() { + qName = "delete-q-http-error" + fakeRabbitMQClient.DeleteQueueReturns(&http.Response{ + Status: "502 Bad Gateway", + StatusCode: http.StatusBadGateway, + Body: ioutil.NopCloser(bytes.NewBufferString("Hello World")), + }, nil) + }) + + It("publishes a 'warning' event", func() { + Expect(client.Delete(ctx, &q)).To(Succeed()) + Consistently(func() bool { + err := client.Get(ctx, types.NamespacedName{Name: q.Name, Namespace: q.Namespace}, &topology.Queue{}) + return apierrors.IsNotFound(err) + }, 5).Should(BeFalse()) + Expect(observedEvents()).To(ContainElement("Warning FailedDelete failed to delete queue")) + }) + }) + + When("the RabbitMQ Client returns a Go error response", func() { + BeforeEach(func() { + qName = "delete-go-error" + fakeRabbitMQClient.DeleteQueueReturns(nil, errors.New("some error")) + }) + + It("publishes a 'warning' event", func() { + Expect(client.Delete(ctx, &q)).To(Succeed()) + Consistently(func() bool { + err := client.Get(ctx, types.NamespacedName{Name: q.Name, Namespace: q.Namespace}, &topology.Queue{}) + return apierrors.IsNotFound(err) + }, 5).Should(BeFalse()) + Expect(observedEvents()).To(ContainElement("Warning FailedDelete failed to delete queue")) + }) + }) + + When("the RabbitMQ cluster is nil", func() { + BeforeEach(func() { + qName = "delete-client-not-found-error" + }) + + JustBeforeEach(func() { + prepareNoSuchClusterError() + }) + + It("successfully deletes the queue regardless", func() { + Expect(client.Delete(ctx, &q)).To(Succeed()) + Eventually(func() bool { + err := client.Get(ctx, types.NamespacedName{Name: q.Name, Namespace: q.Namespace}, &topology.Queue{}) + return apierrors.IsNotFound(err) + }, 5).Should(BeTrue()) + Expect(observedEvents()).To(ContainElement("Normal SuccessfulDelete successfully deleted queue")) + }) + }) + + When("the RabbitMQ Client successfully deletes a q", func() { + BeforeEach(func() { + qName = "delete-q-success" + fakeRabbitMQClient.DeleteQueueReturns(&http.Response{ + Status: "204 No Content", + StatusCode: http.StatusNoContent, + }, nil) + }) + + It("publishes a normal event", func() { + Expect(client.Delete(ctx, &q)).To(Succeed()) + Eventually(func() bool { + err := client.Get(ctx, types.NamespacedName{Name: q.Name, Namespace: q.Namespace}, &topology.Queue{}) + return apierrors.IsNotFound(err) + }, 5).Should(BeTrue()) + observedEvents := observedEvents() + Expect(observedEvents).NotTo(ContainElement("Warning FailedDelete failed to delete queue")) + Expect(observedEvents).To(ContainElement("Normal SuccessfulDelete successfully deleted queue")) + }) + }) + }) +})