Skip to content

Commit 1d3932c

Browse files
authored
test(fluidapp): add Ginkgo controller unit coverage (#5828)
* test(fluidapp): add ginkgo controller unit coverage Signed-off-by: Harsh <harshmastic@gmail.com> * test(fluidapp): tighten reconciler coverage gaps Signed-off-by: Harsh <harshmastic@gmail.com> --------- Signed-off-by: Harsh <harshmastic@gmail.com>
1 parent 17f3793 commit 1d3932c

3 files changed

Lines changed: 391 additions & 132 deletions

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
Copyright 2026 The Fluid 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 fluidapp
18+
19+
import (
20+
"context"
21+
"errors"
22+
23+
"github.com/agiledragon/gomonkey/v2"
24+
. "github.com/onsi/ginkgo/v2"
25+
. "github.com/onsi/gomega"
26+
corev1 "k8s.io/api/core/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
"k8s.io/apimachinery/pkg/types"
30+
"k8s.io/client-go/tools/record"
31+
ctrl "sigs.k8s.io/controller-runtime"
32+
"sigs.k8s.io/controller-runtime/pkg/client"
33+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
34+
35+
datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
36+
"github.com/fluid-cloudnative/fluid/pkg/common"
37+
"github.com/fluid-cloudnative/fluid/pkg/utils/fake"
38+
"github.com/fluid-cloudnative/fluid/pkg/utils/kubeclient"
39+
)
40+
41+
var _ = Describe("FluidAppReconciler", func() {
42+
const (
43+
testNamespace = "default"
44+
testPodName = "fluidapp-pod"
45+
)
46+
47+
var scheme *runtime.Scheme
48+
var patches *gomonkey.Patches
49+
50+
BeforeEach(func() {
51+
scheme = runtime.NewScheme()
52+
Expect(corev1.AddToScheme(scheme)).To(Succeed())
53+
Expect(datav1alpha1.AddToScheme(scheme)).To(Succeed())
54+
})
55+
56+
AfterEach(func() {
57+
if patches != nil {
58+
patches.Reset()
59+
patches = nil
60+
}
61+
})
62+
63+
Describe("ControllerName", func() {
64+
It("returns the controller name constant", func() {
65+
r := &FluidAppReconciler{}
66+
Expect(r.ControllerName()).To(Equal(controllerName))
67+
})
68+
})
69+
70+
Describe("ManagedResource", func() {
71+
It("returns a pod object", func() {
72+
r := &FluidAppReconciler{}
73+
Expect(r.ManagedResource()).To(BeAssignableToTypeOf(&corev1.Pod{}))
74+
})
75+
})
76+
77+
Describe("NewFluidAppReconciler", func() {
78+
It("constructs a reconciler with the provided dependencies", func() {
79+
fakeClient := fake.NewFakeClientWithScheme(scheme)
80+
recorder := record.NewFakeRecorder(10)
81+
82+
r := NewFluidAppReconciler(fakeClient, fake.NullLogger(), recorder)
83+
84+
Expect(r).NotTo(BeNil())
85+
Expect(r.Client).To(Equal(fakeClient))
86+
Expect(r.Recorder).To(Equal(recorder))
87+
Expect(r.FluidAppReconcilerImplement).NotTo(BeNil())
88+
Expect(r.FluidAppReconcilerImplement.Client).To(Equal(fakeClient))
89+
})
90+
})
91+
92+
Describe("Reconcile", func() {
93+
It("returns the pod lookup error", func() {
94+
expectedErr := errors.New("get pod failed")
95+
patches = gomonkey.ApplyFunc(kubeclient.GetPodByName, func(_ client.Client, name, namespace string) (*corev1.Pod, error) {
96+
Expect(name).To(Equal(testPodName))
97+
Expect(namespace).To(Equal(testNamespace))
98+
return nil, expectedErr
99+
})
100+
101+
r := NewFluidAppReconciler(fake.NewFakeClientWithScheme(scheme), fake.NullLogger(), record.NewFakeRecorder(10))
102+
103+
result, err := r.Reconcile(context.Background(), reconcile.Request{
104+
NamespacedName: types.NamespacedName{Name: testPodName, Namespace: testNamespace},
105+
})
106+
107+
Expect(err).To(MatchError(expectedErr))
108+
Expect(result).To(Equal(ctrl.Result{}))
109+
})
110+
111+
It("returns no requeue when the pod does not exist", func() {
112+
fakeClient := fake.NewFakeClientWithScheme(scheme)
113+
r := NewFluidAppReconciler(fakeClient, fake.NullLogger(), record.NewFakeRecorder(10))
114+
115+
result, err := r.Reconcile(context.Background(), reconcile.Request{
116+
NamespacedName: types.NamespacedName{Name: testPodName, Namespace: testNamespace},
117+
})
118+
119+
Expect(err).NotTo(HaveOccurred())
120+
Expect(result).To(Equal(ctrl.Result{}))
121+
})
122+
123+
It("returns no requeue when the pod should not enter the queue", func() {
124+
pod := &corev1.Pod{
125+
ObjectMeta: metav1.ObjectMeta{
126+
Name: testPodName,
127+
Namespace: testNamespace,
128+
},
129+
}
130+
fakeClient := fake.NewFakeClientWithScheme(scheme, pod)
131+
r := NewFluidAppReconciler(fakeClient, fake.NullLogger(), record.NewFakeRecorder(10))
132+
133+
result, err := r.Reconcile(context.Background(), reconcile.Request{
134+
NamespacedName: types.NamespacedName{Name: testPodName, Namespace: testNamespace},
135+
})
136+
137+
Expect(err).NotTo(HaveOccurred())
138+
Expect(result).To(Equal(ctrl.Result{}))
139+
})
140+
141+
It("reconciles queueable pods and returns no requeue on successful fuse unmount", func() {
142+
pod := &corev1.Pod{
143+
ObjectMeta: metav1.ObjectMeta{
144+
Name: testPodName,
145+
Namespace: testNamespace,
146+
Labels: map[string]string{
147+
common.InjectServerless: common.True,
148+
common.InjectSidecarDone: common.True,
149+
},
150+
},
151+
Spec: corev1.PodSpec{
152+
RestartPolicy: corev1.RestartPolicyNever,
153+
Containers: []corev1.Container{
154+
{Name: "app"},
155+
{
156+
Name: common.FuseContainerName + "-0",
157+
Lifecycle: &corev1.Lifecycle{
158+
PreStop: &corev1.LifecycleHandler{
159+
Exec: &corev1.ExecAction{Command: []string{"umount", "/mnt/fuse"}},
160+
},
161+
},
162+
},
163+
},
164+
},
165+
Status: corev1.PodStatus{
166+
Phase: corev1.PodRunning,
167+
ContainerStatuses: []corev1.ContainerStatus{
168+
{
169+
Name: "app",
170+
State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{ExitCode: 0}},
171+
},
172+
{
173+
Name: common.FuseContainerName + "-0",
174+
State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
175+
},
176+
},
177+
},
178+
}
179+
180+
fakeClient := fake.NewFakeClientWithScheme(scheme, pod)
181+
r := NewFluidAppReconciler(fakeClient, fake.NullLogger(), record.NewFakeRecorder(10))
182+
183+
patches = gomonkey.ApplyFunc((*FluidAppReconcilerImplement).umountFuseSidecars, func(_ *FluidAppReconcilerImplement, gotPod *corev1.Pod) error {
184+
Expect(gotPod.Name).To(Equal(testPodName))
185+
return nil
186+
})
187+
188+
result, err := r.Reconcile(context.Background(), reconcile.Request{
189+
NamespacedName: types.NamespacedName{Name: testPodName, Namespace: testNamespace},
190+
})
191+
192+
Expect(err).NotTo(HaveOccurred())
193+
Expect(result).To(Equal(ctrl.Result{}))
194+
})
195+
})
196+
197+
Describe("internalReconcile", func() {
198+
It("requeues when unmounting fuse sidecars returns an error", func() {
199+
r := NewFluidAppReconciler(fake.NewFakeClientWithScheme(scheme), fake.NullLogger(), record.NewFakeRecorder(10))
200+
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: testPodName, Namespace: testNamespace}}
201+
expectedErr := errors.New("umount failed")
202+
203+
patches = gomonkey.ApplyFunc((*FluidAppReconcilerImplement).umountFuseSidecars, func(_ *FluidAppReconcilerImplement, gotPod *corev1.Pod) error {
204+
Expect(gotPod).To(Equal(pod))
205+
return expectedErr
206+
})
207+
208+
result, err := r.internalReconcile(reconcileRequestContext{
209+
Context: context.Background(),
210+
Log: fake.NullLogger(),
211+
pod: pod,
212+
})
213+
214+
Expect(err).To(MatchError(expectedErr))
215+
Expect(result).To(Equal(ctrl.Result{}))
216+
})
217+
218+
It("returns no requeue when unmounting fuse sidecars succeeds", func() {
219+
r := NewFluidAppReconciler(fake.NewFakeClientWithScheme(scheme), fake.NullLogger(), record.NewFakeRecorder(10))
220+
pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: testPodName, Namespace: testNamespace}}
221+
222+
patches = gomonkey.ApplyFunc((*FluidAppReconcilerImplement).umountFuseSidecars, func(_ *FluidAppReconcilerImplement, gotPod *corev1.Pod) error {
223+
Expect(gotPod).To(Equal(pod))
224+
return nil
225+
})
226+
227+
result, err := r.internalReconcile(reconcileRequestContext{
228+
Context: context.Background(),
229+
Log: fake.NullLogger(),
230+
pod: pod,
231+
})
232+
233+
Expect(err).NotTo(HaveOccurred())
234+
Expect(result).To(Equal(ctrl.Result{}))
235+
})
236+
})
237+
})

0 commit comments

Comments
 (0)