Skip to content

Commit cad9cfa

Browse files
committed
test(goosefs): add Ginkgo v2 controller tests
Signed-off-by: Harsh <harshmastic@gmail.com>
1 parent e7ec384 commit cad9cfa

1 file changed

Lines changed: 276 additions & 0 deletions

File tree

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
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 goosefs
18+
19+
import (
20+
"context"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
25+
datav1alpha1 "github.com/fluid-cloudnative/fluid/api/v1alpha1"
26+
cruntime "github.com/fluid-cloudnative/fluid/pkg/runtime"
27+
"github.com/fluid-cloudnative/fluid/pkg/utils/fake"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
"k8s.io/apimachinery/pkg/types"
31+
"k8s.io/client-go/tools/record"
32+
ctrl "sigs.k8s.io/controller-runtime"
33+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
34+
)
35+
36+
// buildTestReconcileCtx builds a minimal ReconcileRequestContext for unit tests.
37+
func buildTestReconcileCtx(req ctrl.Request) cruntime.ReconcileRequestContext {
38+
return cruntime.ReconcileRequestContext{
39+
Context: context.Background(),
40+
NamespacedName: req.NamespacedName,
41+
Log: zap.New(zap.UseDevMode(true)),
42+
}
43+
}
44+
45+
const (
46+
testRuntimeName = "test-goosefs-runtime"
47+
testRuntimeNamespace = "default"
48+
)
49+
50+
var _ = Describe("RuntimeReconciler", func() {
51+
var (
52+
testScheme *runtime.Scheme
53+
)
54+
55+
BeforeEach(func() {
56+
testScheme = runtime.NewScheme()
57+
Expect(datav1alpha1.AddToScheme(testScheme)).To(Succeed())
58+
})
59+
60+
Describe("NewRuntimeReconciler", func() {
61+
It("should create a RuntimeReconciler with non-nil fields", func() {
62+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
63+
fakeRecorder := record.NewFakeRecorder(10)
64+
log := zap.New(zap.UseDevMode(true))
65+
66+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
67+
68+
Expect(reconciler).NotTo(BeNil())
69+
Expect(reconciler.Scheme).NotTo(BeNil())
70+
Expect(reconciler.RuntimeReconciler).NotTo(BeNil())
71+
Expect(reconciler.engines).NotTo(BeNil())
72+
Expect(reconciler.mutex).NotTo(BeNil())
73+
})
74+
})
75+
76+
Describe("ControllerName", func() {
77+
It("should return the expected controller name", func() {
78+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
79+
fakeRecorder := record.NewFakeRecorder(10)
80+
log := zap.New(zap.UseDevMode(true))
81+
82+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
83+
Expect(reconciler.ControllerName()).To(Equal(controllerName))
84+
})
85+
})
86+
87+
Describe("Reconcile", func() {
88+
Context("when the GooseFSRuntime does not exist", func() {
89+
It("should return empty result and no error (not-found is ignored)", func() {
90+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
91+
fakeRecorder := record.NewFakeRecorder(10)
92+
log := zap.New(zap.UseDevMode(true))
93+
94+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
95+
96+
req := ctrl.Request{
97+
NamespacedName: types.NamespacedName{
98+
Name: "nonexistent-runtime",
99+
Namespace: testRuntimeNamespace,
100+
},
101+
}
102+
result, err := reconciler.Reconcile(context.Background(), req)
103+
104+
Expect(err).NotTo(HaveOccurred())
105+
Expect(result).To(Equal(ctrl.Result{}))
106+
})
107+
})
108+
109+
Context("when a GooseFSRuntime exists", func() {
110+
It("should attempt to reconcile the runtime without crashing", func() {
111+
runtime := &datav1alpha1.GooseFSRuntime{
112+
ObjectMeta: metav1.ObjectMeta{
113+
Name: testRuntimeName,
114+
Namespace: testRuntimeNamespace,
115+
},
116+
Spec: datav1alpha1.GooseFSRuntimeSpec{},
117+
}
118+
119+
fakeClient := fake.NewFakeClientWithScheme(testScheme, runtime)
120+
fakeRecorder := record.NewFakeRecorder(10)
121+
log := zap.New(zap.UseDevMode(true))
122+
123+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
124+
125+
req := ctrl.Request{
126+
NamespacedName: types.NamespacedName{
127+
Name: testRuntimeName,
128+
Namespace: testRuntimeNamespace,
129+
},
130+
}
131+
// Reconcile will call ReconcileInternal which requires a full engine;
132+
// the result may be an error from engine creation but must not panic.
133+
_, _ = reconciler.Reconcile(context.Background(), req)
134+
// Primary assertion: reconciler does not panic and returns
135+
})
136+
})
137+
})
138+
139+
Describe("GetOrCreateEngine", func() {
140+
It("should return an error when EngineImpl is unset (no engine created)", func() {
141+
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
142+
ObjectMeta: metav1.ObjectMeta{
143+
Name: testRuntimeName,
144+
Namespace: testRuntimeNamespace,
145+
},
146+
}
147+
148+
fakeClient := fake.NewFakeClientWithScheme(testScheme, gooseFSRuntime)
149+
fakeRecorder := record.NewFakeRecorder(10)
150+
log := zap.New(zap.UseDevMode(true))
151+
152+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
153+
Expect(reconciler.engines).To(BeEmpty())
154+
155+
ctx := cruntime.ReconcileRequestContext{
156+
NamespacedName: types.NamespacedName{
157+
Name: testRuntimeName,
158+
Namespace: testRuntimeNamespace,
159+
},
160+
Client: fakeClient,
161+
Log: log,
162+
Recorder: fakeRecorder,
163+
Runtime: gooseFSRuntime,
164+
// EngineImpl intentionally left empty — simulates unknown impl
165+
}
166+
167+
engine, err := reconciler.GetOrCreateEngine(ctx)
168+
// An empty EngineImpl causes CreateEngine to return an error; no engine stored.
169+
Expect(err).To(HaveOccurred())
170+
Expect(engine).To(BeNil())
171+
Expect(reconciler.engines).To(BeEmpty())
172+
})
173+
})
174+
175+
Describe("RemoveEngine", func() {
176+
It("should not panic when removing a non-existent engine", func() {
177+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
178+
fakeRecorder := record.NewFakeRecorder(10)
179+
log := zap.New(zap.UseDevMode(true))
180+
181+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
182+
183+
ctx := cruntime.ReconcileRequestContext{
184+
NamespacedName: types.NamespacedName{
185+
Name: testRuntimeName,
186+
Namespace: testRuntimeNamespace,
187+
},
188+
Client: fakeClient,
189+
Log: log,
190+
Recorder: fakeRecorder,
191+
}
192+
193+
// RemoveEngine on an empty map must not panic.
194+
Expect(func() {
195+
reconciler.RemoveEngine(ctx)
196+
}).NotTo(Panic())
197+
Expect(reconciler.engines).To(BeEmpty())
198+
})
199+
})
200+
201+
Describe("getRuntime", func() {
202+
It("should return error when runtime does not exist", func() {
203+
fakeClient := fake.NewFakeClientWithScheme(testScheme)
204+
fakeRecorder := record.NewFakeRecorder(10)
205+
log := zap.New(zap.UseDevMode(true))
206+
207+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
208+
209+
req := ctrl.Request{
210+
NamespacedName: types.NamespacedName{
211+
Name: "missing",
212+
Namespace: testRuntimeNamespace,
213+
},
214+
}
215+
ctx := buildTestReconcileCtx(req)
216+
rt, err := reconciler.getRuntime(ctx)
217+
Expect(err).To(HaveOccurred())
218+
Expect(rt).To(BeNil())
219+
})
220+
221+
It("should return the runtime when it exists", func() {
222+
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
223+
ObjectMeta: metav1.ObjectMeta{
224+
Name: testRuntimeName,
225+
Namespace: testRuntimeNamespace,
226+
},
227+
Spec: datav1alpha1.GooseFSRuntimeSpec{},
228+
}
229+
230+
fakeClient := fake.NewFakeClientWithScheme(testScheme, gooseFSRuntime)
231+
fakeRecorder := record.NewFakeRecorder(10)
232+
log := zap.New(zap.UseDevMode(true))
233+
234+
reconciler := NewRuntimeReconciler(fakeClient, log, testScheme, fakeRecorder)
235+
236+
req := ctrl.Request{
237+
NamespacedName: types.NamespacedName{
238+
Name: testRuntimeName,
239+
Namespace: testRuntimeNamespace,
240+
},
241+
}
242+
ctx := buildTestReconcileCtx(req)
243+
rt, err := reconciler.getRuntime(ctx)
244+
Expect(err).NotTo(HaveOccurred())
245+
Expect(rt).NotTo(BeNil())
246+
Expect(rt.Name).To(Equal(testRuntimeName))
247+
Expect(rt.Namespace).To(Equal(testRuntimeNamespace))
248+
})
249+
})
250+
251+
Describe("GooseFSRuntime CRD via envtest", func() {
252+
It("should create and retrieve a GooseFSRuntime via the k8sClient", func() {
253+
gooseFSRuntime := &datav1alpha1.GooseFSRuntime{
254+
ObjectMeta: metav1.ObjectMeta{
255+
Name: "envtest-runtime",
256+
Namespace: "default",
257+
},
258+
Spec: datav1alpha1.GooseFSRuntimeSpec{},
259+
}
260+
261+
By("creating the GooseFSRuntime")
262+
Expect(k8sClient.Create(context.Background(), gooseFSRuntime)).To(Succeed())
263+
264+
By("retrieving the GooseFSRuntime")
265+
var fetched datav1alpha1.GooseFSRuntime
266+
Expect(k8sClient.Get(context.Background(), types.NamespacedName{
267+
Name: "envtest-runtime",
268+
Namespace: "default",
269+
}, &fetched)).To(Succeed())
270+
Expect(fetched.Name).To(Equal("envtest-runtime"))
271+
272+
By("deleting the GooseFSRuntime")
273+
Expect(k8sClient.Delete(context.Background(), gooseFSRuntime)).To(Succeed())
274+
})
275+
})
276+
})

0 commit comments

Comments
 (0)