1
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 })
189
View as plain text