sigs.k8s.io/controller-runtime@v0.18.2/pkg/reconcile/reconcile_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package reconcile_test 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 . "github.com/onsi/ginkgo/v2" 25 . "github.com/onsi/gomega" 26 corev1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/types" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 "sigs.k8s.io/controller-runtime/pkg/envtest" 32 "sigs.k8s.io/controller-runtime/pkg/reconcile" 33 ) 34 35 type mockObjectReconciler struct { 36 reconcileFunc func(context.Context, *corev1.ConfigMap) (reconcile.Result, error) 37 } 38 39 func (r *mockObjectReconciler) Reconcile(ctx context.Context, cm *corev1.ConfigMap) (reconcile.Result, error) { 40 return r.reconcileFunc(ctx, cm) 41 } 42 43 var _ = Describe("reconcile", func() { 44 Describe("Result", func() { 45 It("IsZero should return true if empty", func() { 46 var res *reconcile.Result 47 Expect(res.IsZero()).To(BeTrue()) 48 res2 := &reconcile.Result{} 49 Expect(res2.IsZero()).To(BeTrue()) 50 res3 := reconcile.Result{} 51 Expect(res3.IsZero()).To(BeTrue()) 52 }) 53 54 It("IsZero should return false if Requeue is set to true", func() { 55 res := reconcile.Result{Requeue: true} 56 Expect(res.IsZero()).To(BeFalse()) 57 }) 58 59 It("IsZero should return false if RequeueAfter is set to true", func() { 60 res := reconcile.Result{RequeueAfter: 1 * time.Second} 61 Expect(res.IsZero()).To(BeFalse()) 62 }) 63 }) 64 65 Describe("Func", func() { 66 It("should call the function with the request and return a nil error.", func() { 67 request := reconcile.Request{ 68 NamespacedName: types.NamespacedName{Name: "foo", Namespace: "bar"}, 69 } 70 result := reconcile.Result{ 71 Requeue: true, 72 } 73 74 instance := reconcile.Func(func(_ context.Context, r reconcile.Request) (reconcile.Result, error) { 75 defer GinkgoRecover() 76 Expect(r).To(Equal(request)) 77 78 return result, nil 79 }) 80 actualResult, actualErr := instance.Reconcile(context.Background(), request) 81 Expect(actualResult).To(Equal(result)) 82 Expect(actualErr).NotTo(HaveOccurred()) 83 }) 84 85 It("should call the function with the request and return an error.", func() { 86 request := reconcile.Request{ 87 NamespacedName: types.NamespacedName{Name: "foo", Namespace: "bar"}, 88 } 89 result := reconcile.Result{ 90 Requeue: false, 91 } 92 err := fmt.Errorf("hello world") 93 94 instance := reconcile.Func(func(_ context.Context, r reconcile.Request) (reconcile.Result, error) { 95 defer GinkgoRecover() 96 Expect(r).To(Equal(request)) 97 98 return result, err 99 }) 100 actualResult, actualErr := instance.Reconcile(context.Background(), request) 101 Expect(actualResult).To(Equal(result)) 102 Expect(actualErr).To(Equal(err)) 103 }) 104 105 It("should allow unwrapping inner error from terminal error", func() { 106 inner := apierrors.NewGone("") 107 terminalError := reconcile.TerminalError(inner) 108 109 Expect(apierrors.IsGone(terminalError)).To(BeTrue()) 110 }) 111 112 It("should handle nil terminal errors properly", func() { 113 err := reconcile.TerminalError(nil) 114 Expect(err.Error()).To(Equal("nil terminal error")) 115 }) 116 }) 117 118 Describe("AsReconciler", func() { 119 var testenv *envtest.Environment 120 var testClient client.Client 121 122 BeforeEach(func() { 123 testenv = &envtest.Environment{} 124 125 cfg, err := testenv.Start() 126 Expect(err).NotTo(HaveOccurred()) 127 128 testClient, err = client.New(cfg, client.Options{}) 129 Expect(err).NotTo(HaveOccurred()) 130 }) 131 132 AfterEach(func() { 133 Expect(testenv.Stop()).NotTo(HaveOccurred()) 134 }) 135 136 Context("with an existing object", func() { 137 var key client.ObjectKey 138 139 BeforeEach(func() { 140 cm := &corev1.ConfigMap{ 141 ObjectMeta: metav1.ObjectMeta{ 142 Namespace: "default", 143 Name: "test", 144 }, 145 } 146 key = client.ObjectKeyFromObject(cm) 147 148 err := testClient.Create(context.Background(), cm) 149 Expect(err).NotTo(HaveOccurred()) 150 }) 151 152 It("should Get the object and call the ObjectReconciler", func() { 153 var actual *corev1.ConfigMap 154 reconciler := reconcile.AsReconciler(testClient, &mockObjectReconciler{ 155 reconcileFunc: func(ctx context.Context, cm *corev1.ConfigMap) (reconcile.Result, error) { 156 actual = cm 157 return reconcile.Result{}, nil 158 }, 159 }) 160 161 res, err := reconciler.Reconcile(context.Background(), reconcile.Request{NamespacedName: key}) 162 Expect(err).NotTo(HaveOccurred()) 163 Expect(res).To(BeZero()) 164 Expect(actual).NotTo(BeNil()) 165 Expect(actual.ObjectMeta.Name).To(Equal(key.Name)) 166 Expect(actual.ObjectMeta.Namespace).To(Equal(key.Namespace)) 167 }) 168 }) 169 170 Context("with an object that doesn't exist", func() { 171 It("should not call the ObjectReconciler", func() { 172 called := false 173 reconciler := reconcile.AsReconciler(testClient, &mockObjectReconciler{ 174 reconcileFunc: func(ctx context.Context, cm *corev1.ConfigMap) (reconcile.Result, error) { 175 called = true 176 return reconcile.Result{}, nil 177 }, 178 }) 179 180 key := types.NamespacedName{Namespace: "default", Name: "fake-obj"} 181 res, err := reconciler.Reconcile(context.Background(), reconcile.Request{NamespacedName: key}) 182 Expect(err).NotTo(HaveOccurred()) 183 Expect(res).To(BeZero()) 184 Expect(called).To(BeFalse()) 185 }) 186 }) 187 }) 188 })