Skip to content

Commit de63f04

Browse files
committed
test(core): RGDs all the way down
Adds integration tests that verify: - Nested `ResourceGraphDefinition` lifecycle including creation, status updates, and cleanup - Dynamic creation of RGDs with different schema field types (integer, string, boolean)
1 parent 45adaf5 commit de63f04

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed
+280
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
// Copyright 2025 The Kube Resource Orchestrator Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package core_test
15+
16+
import (
17+
"context"
18+
"encoding/json"
19+
"fmt"
20+
"time"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
"k8s.io/apimachinery/pkg/api/errors"
25+
"k8s.io/apimachinery/pkg/types"
26+
"k8s.io/apimachinery/pkg/util/rand"
27+
28+
corev1 "k8s.io/api/core/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31+
32+
krov1alpha1 "github.com/kro-run/kro/api/v1alpha1"
33+
"github.com/kro-run/kro/pkg/testutil/generator"
34+
)
35+
36+
var _ = Describe("Nested ResourceGraphDefinition", func() {
37+
var (
38+
ctx context.Context
39+
namespace string
40+
)
41+
42+
BeforeEach(func() {
43+
ctx = context.Background()
44+
namespace = fmt.Sprintf("test-%s", rand.String(5))
45+
// Create namespace
46+
Expect(env.Client.Create(ctx, &corev1.Namespace{
47+
ObjectMeta: metav1.ObjectMeta{
48+
Name: namespace,
49+
},
50+
})).To(Succeed())
51+
})
52+
53+
It("should handle nested ResourceGraphDefinition lifecycle", func() {
54+
ctx := context.Background()
55+
namespace := fmt.Sprintf("test-%s", rand.String(5))
56+
57+
// Create namespace
58+
ns := &corev1.Namespace{
59+
ObjectMeta: metav1.ObjectMeta{
60+
Name: namespace,
61+
},
62+
}
63+
Expect(env.Client.Create(ctx, ns)).To(Succeed())
64+
65+
// Create parent ResourceGraphDefinition
66+
rg, genInstance := nestedResourceGraphDefinition("test-nested-rg")
67+
Expect(env.Client.Create(ctx, rg)).To(Succeed())
68+
69+
// Wait for parent ResourceGraphDefinition to be ready
70+
Eventually(func(g Gomega) {
71+
err := env.Client.Get(ctx, types.NamespacedName{
72+
Name: rg.Name,
73+
}, rg)
74+
g.Expect(err).ToNot(HaveOccurred())
75+
g.Expect(rg.Status.State).To(Equal(krov1alpha1.ResourceGraphDefinitionStateActive))
76+
}, 30*time.Second, time.Second).Should(Succeed())
77+
78+
// Create instance
79+
instance := genInstance(namespace, "test-string", "string", "10")
80+
Expect(env.Client.Create(ctx, instance)).To(Succeed())
81+
82+
// Wait for nested ResourceGraphDefinition to be created and ready
83+
var nestedRG krov1alpha1.ResourceGraphDefinition
84+
Eventually(func(g Gomega) {
85+
err := env.Client.Get(ctx, types.NamespacedName{
86+
Name: "rg-string",
87+
}, &nestedRG)
88+
g.Expect(err).ToNot(HaveOccurred())
89+
g.Expect(nestedRG.Status.State).To(Equal(krov1alpha1.ResourceGraphDefinitionStateActive))
90+
}, 30*time.Second, time.Second).Should(Succeed())
91+
92+
// Verify instance status reflects nested ResourceGraphDefinition
93+
Eventually(func(g Gomega) {
94+
err := env.Client.Get(ctx, types.NamespacedName{
95+
Name: instance.GetName(),
96+
Namespace: namespace,
97+
}, instance)
98+
g.Expect(err).ToNot(HaveOccurred())
99+
}, 30*time.Second, time.Second).Should(Succeed())
100+
101+
// Check instance status.State
102+
instanceStatus, found, _ := unstructured.NestedMap(instance.Object, "status", "state")
103+
b, _ := json.Marshal(instance.Object)
104+
fmt.Println(string(b))
105+
Expect(found).To(BeTrue())
106+
Expect(instanceStatus).To(Equal("Active"))
107+
108+
// Delete instance
109+
Expect(env.Client.Delete(ctx, instance)).To(Succeed())
110+
111+
// Verify nested ResourceGraphDefinition is deleted
112+
Eventually(func() bool {
113+
err := env.Client.Get(ctx, types.NamespacedName{
114+
Name: "rg-integer",
115+
}, &nestedRG)
116+
return errors.IsNotFound(err)
117+
}, 30*time.Second, time.Second).Should(BeTrue())
118+
119+
// Delete parent ResourceGraphDefinition
120+
Expect(env.Client.Delete(ctx, rg)).To(Succeed())
121+
122+
// Cleanup namespace
123+
Expect(env.Client.Delete(ctx, ns)).To(Succeed())
124+
})
125+
126+
It("should dynamically create RGDs with different schema field types", func() {
127+
ctx := context.Background()
128+
namespace := fmt.Sprintf("test-%s", rand.String(5))
129+
130+
// Create namespace
131+
ns := &corev1.Namespace{
132+
ObjectMeta: metav1.ObjectMeta{
133+
Name: namespace,
134+
},
135+
}
136+
Expect(env.Client.Create(ctx, ns)).To(Succeed())
137+
138+
// Create parent ResourceGraphDefinition
139+
rg, genInstance := nestedResourceGraphDefinition("test-multi-rg")
140+
Expect(env.Client.Create(ctx, rg)).To(Succeed())
141+
142+
// Wait for parent ResourceGraphDefinition to be ready
143+
Eventually(func(g Gomega) {
144+
err := env.Client.Get(ctx, types.NamespacedName{
145+
Name: rg.Name,
146+
}, rg)
147+
g.Expect(err).ToNot(HaveOccurred())
148+
g.Expect(rg.Status.State).To(Equal(krov1alpha1.ResourceGraphDefinitionStateActive))
149+
}, 30*time.Second, time.Second).Should(Succeed())
150+
151+
// Create instances with different types
152+
testCases := []struct {
153+
name string
154+
typeVal string
155+
defaultVal string
156+
}{
157+
{"test-integer", "integer", "10"},
158+
{"test-string", "string", "default"},
159+
{"test-boolean", "boolean", "true"},
160+
}
161+
162+
// Create all instances
163+
for _, t := range testCases {
164+
instance := genInstance(namespace, t.name, t.typeVal, t.defaultVal)
165+
Expect(env.Client.Create(ctx, instance)).To(Succeed())
166+
}
167+
168+
// Wait for all nested ResourceGraphDefinitions and verify status
169+
for _, t := range testCases {
170+
// Wait for nested ResourceGraphDefinition
171+
var nestedRG krov1alpha1.ResourceGraphDefinition
172+
Eventually(func(g Gomega) {
173+
err := env.Client.Get(ctx, types.NamespacedName{
174+
Name: fmt.Sprintf("rg-%s", t.typeVal),
175+
}, &nestedRG)
176+
g.Expect(err).ToNot(HaveOccurred())
177+
g.Expect(nestedRG.Status.State).To(Equal(krov1alpha1.ResourceGraphDefinitionStateActive))
178+
}, 30*time.Second, time.Second).Should(Succeed())
179+
180+
// Verify instance status
181+
Eventually(func(g Gomega) {
182+
instance := &unstructured.Unstructured{
183+
Object: map[string]interface{}{
184+
"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
185+
"kind": "NestedRGD",
186+
},
187+
}
188+
err := env.Client.Get(ctx, types.NamespacedName{
189+
Name: t.name,
190+
Namespace: namespace,
191+
}, instance)
192+
g.Expect(err).ToNot(HaveOccurred())
193+
194+
// Check instance status.State
195+
instanceStatus, found, _ := unstructured.NestedMap(instance.Object, "status", "state")
196+
Expect(found).To(BeTrue())
197+
Expect(instanceStatus).To(Equal("Active"))
198+
199+
}, 30*time.Second, time.Second).Should(Succeed())
200+
}
201+
202+
// Delete all instances
203+
for _, t := range testCases {
204+
instance := genInstance(namespace, t.name, t.typeVal, t.defaultVal)
205+
Expect(env.Client.Delete(ctx, instance)).To(Succeed())
206+
}
207+
208+
// Verify all nested ResourceGraphDefinitions are deleted
209+
for _, t := range testCases {
210+
Eventually(func() bool {
211+
var nestedRG krov1alpha1.ResourceGraphDefinition
212+
err := env.Client.Get(ctx, types.NamespacedName{
213+
Name: fmt.Sprintf("rg-%s", t.typeVal),
214+
}, &nestedRG)
215+
return errors.IsNotFound(err)
216+
}, 30*time.Second, time.Second).Should(BeTrue())
217+
}
218+
219+
// Delete parent ResourceGraphDefinition
220+
Expect(env.Client.Delete(ctx, rg)).To(Succeed())
221+
222+
// Cleanup namespace
223+
Expect(env.Client.Delete(ctx, ns)).To(Succeed())
224+
})
225+
})
226+
227+
// nestedResourceGraphDefinition creates a ResourceGraphDefinition inception
228+
func nestedResourceGraphDefinition(name string) (
229+
*krov1alpha1.ResourceGraphDefinition,
230+
func(namespace, name string, typeVal string, defaultVal string) *unstructured.Unstructured,
231+
) {
232+
rg := generator.NewResourceGraphDefinition(name,
233+
generator.WithSchema(
234+
"NestedRGD", "v1alpha1",
235+
map[string]interface{}{
236+
"type": "string",
237+
"default": "string",
238+
},
239+
map[string]interface{}{},
240+
),
241+
generator.WithResource("nested", map[string]interface{}{
242+
"apiVersion": "kro.run/v1alpha1",
243+
"kind": "ResourceGraphDefinition",
244+
"metadata": map[string]interface{}{
245+
"name": "rg-${schema.spec.type}",
246+
},
247+
"spec": map[string]interface{}{
248+
"schema": map[string]interface{}{
249+
"apiVersion": "v1alpha1",
250+
"kind": "NestedRGD${schema.spec.type}",
251+
"spec": map[string]interface{}{
252+
"name": "string",
253+
"somefield": map[string]interface{}{
254+
"nested": "${schema.spec.type} | default=${schema.spec.default}",
255+
},
256+
},
257+
},
258+
},
259+
}, nil, nil),
260+
)
261+
262+
instanceGen := func(namespace, name string, typeVal string, defaultVal string) *unstructured.Unstructured {
263+
return &unstructured.Unstructured{
264+
Object: map[string]interface{}{
265+
"apiVersion": fmt.Sprintf("%s/%s", krov1alpha1.KroDomainName, "v1alpha1"),
266+
"kind": "NestedRGD",
267+
"metadata": map[string]interface{}{
268+
"name": name,
269+
"namespace": namespace,
270+
},
271+
"spec": map[string]interface{}{
272+
"type": typeVal,
273+
"default": defaultVal,
274+
},
275+
},
276+
}
277+
}
278+
279+
return rg, instanceGen
280+
}

0 commit comments

Comments
 (0)