Skip to content

Commit 3369eb9

Browse files
committed
add tests to ensure reference semantics
On-behalf-of: @SAP [email protected]
1 parent 29cf649 commit 3369eb9

File tree

3 files changed

+239
-1
lines changed

3 files changed

+239
-1
lines changed

internal/sync/init_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
dummyv1alpha1 "github.com/kcp-dev/api-syncagent/internal/sync/apis/dummy/v1alpha1"
2323

24+
corev1 "k8s.io/api/core/v1"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/apimachinery/pkg/runtime"
2627
)
@@ -32,6 +33,9 @@ func init() {
3233
if err := dummyv1alpha1.AddToScheme(testScheme); err != nil {
3334
panic(err)
3435
}
36+
if err := corev1.AddToScheme(testScheme); err != nil {
37+
panic(err)
38+
}
3539
}
3640

3741
var nonEmptyTime = metav1.Time{

internal/sync/syncer_related.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ func resolveObjectReference(object *unstructured.Unstructured, ref syncagentv1al
457457
func resolveReference(jsonData []byte, ref syncagentv1alpha1.RelatedResourceObjectReference) (string, error) {
458458
gval := gjson.Get(string(jsonData), ref.Path)
459459
if !gval.Exists() {
460-
return "", fmt.Errorf("cannot find %s in document", ref.Path)
460+
return "", nil
461461
}
462462

463463
// this does apply some coalescing, like turning numbers into strings

internal/sync/syncer_related_test.go

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/*
2+
Copyright 2025 The KCP 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 sync
18+
19+
import (
20+
"testing"
21+
22+
dummyv1alpha1 "github.com/kcp-dev/api-syncagent/internal/sync/apis/dummy/v1alpha1"
23+
syncagentv1alpha1 "github.com/kcp-dev/api-syncagent/sdk/apis/syncagent/v1alpha1"
24+
25+
corev1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
)
28+
29+
func TestResolveRelatedResourceObjects(t *testing.T) {
30+
// in kcp
31+
primaryObject := newUnstructured(&dummyv1alpha1.Thing{
32+
ObjectMeta: metav1.ObjectMeta{
33+
Name: "my-test-thing",
34+
},
35+
Spec: dummyv1alpha1.ThingSpec{
36+
Username: "original-value",
37+
Kink: "taxreturns",
38+
},
39+
}, withKind("RemoteThing"))
40+
41+
// on the service cluster
42+
primaryObjectCopy := newUnstructured(&dummyv1alpha1.Thing{
43+
ObjectMeta: metav1.ObjectMeta{
44+
Name: "my-test-thing",
45+
},
46+
Spec: dummyv1alpha1.ThingSpec{
47+
Username: "mutated-value",
48+
Kink: "",
49+
},
50+
})
51+
52+
// Create a secret that can be found by using a good reference, so we can ensure that references
53+
// do indeed work; all other subtests here ensure that reference support can deal with broken refs.
54+
dummySecret := newUnstructured(&corev1.Secret{
55+
ObjectMeta: metav1.ObjectMeta{
56+
Namespace: "dummy-namespace",
57+
Name: "mutated-value",
58+
},
59+
})
60+
61+
kcpClient := buildFakeClient(primaryObject)
62+
serviceClusterClient := buildFakeClient(primaryObjectCopy, dummySecret)
63+
ctx := t.Context()
64+
65+
// Now we configure origin/dest as if we're syncing a Secret up from the service cluster to kcp,
66+
// i.e. origin=service.
67+
68+
originSide := syncSide{
69+
ctx: ctx,
70+
client: serviceClusterClient,
71+
object: primaryObjectCopy,
72+
}
73+
74+
destSide := syncSide{
75+
ctx: ctx,
76+
client: kcpClient,
77+
object: primaryObject,
78+
// Since this is a just a regular kube client, we do not need to set clusterName/clusterPath.
79+
}
80+
81+
testcases := []struct {
82+
name string
83+
objectSpec syncagentv1alpha1.RelatedResourceObject
84+
expectedSecrets int
85+
}{
86+
{
87+
name: "valid reference to an existing object",
88+
objectSpec: syncagentv1alpha1.RelatedResourceObject{
89+
RelatedResourceObjectSpec: syncagentv1alpha1.RelatedResourceObjectSpec{
90+
Reference: &syncagentv1alpha1.RelatedResourceObjectReference{
91+
Path: "spec.username",
92+
},
93+
},
94+
Namespace: &syncagentv1alpha1.RelatedResourceObjectSpec{
95+
Template: &syncagentv1alpha1.TemplateExpression{
96+
Template: "dummy-namespace",
97+
},
98+
},
99+
},
100+
expectedSecrets: 1,
101+
},
102+
{
103+
name: "valid template to an existing object",
104+
objectSpec: syncagentv1alpha1.RelatedResourceObject{
105+
RelatedResourceObjectSpec: syncagentv1alpha1.RelatedResourceObjectSpec{
106+
Template: &syncagentv1alpha1.TemplateExpression{
107+
Template: "{{ .Object.spec.username }}",
108+
},
109+
},
110+
Namespace: &syncagentv1alpha1.RelatedResourceObjectSpec{
111+
Template: &syncagentv1alpha1.TemplateExpression{
112+
Template: "dummy-namespace",
113+
},
114+
},
115+
},
116+
expectedSecrets: 1,
117+
},
118+
{
119+
name: "valid reference but target object doesn't exist [yet?]",
120+
objectSpec: syncagentv1alpha1.RelatedResourceObject{
121+
RelatedResourceObjectSpec: syncagentv1alpha1.RelatedResourceObjectSpec{
122+
Reference: &syncagentv1alpha1.RelatedResourceObjectReference{
123+
Path: "spec.username",
124+
},
125+
},
126+
Namespace: &syncagentv1alpha1.RelatedResourceObjectSpec{
127+
Template: &syncagentv1alpha1.TemplateExpression{
128+
Template: "nonexisting-namespace",
129+
},
130+
},
131+
},
132+
expectedSecrets: 0,
133+
},
134+
{
135+
name: "valid template but target object doesn't exist [yet?]",
136+
objectSpec: syncagentv1alpha1.RelatedResourceObject{
137+
RelatedResourceObjectSpec: syncagentv1alpha1.RelatedResourceObjectSpec{
138+
Template: &syncagentv1alpha1.TemplateExpression{
139+
Template: "{{ .Object.spec.username }}",
140+
},
141+
},
142+
Namespace: &syncagentv1alpha1.RelatedResourceObjectSpec{
143+
Template: &syncagentv1alpha1.TemplateExpression{
144+
Template: "nonexisting-namespace",
145+
},
146+
},
147+
},
148+
expectedSecrets: 0,
149+
},
150+
{
151+
name: "valid reference to an empty field",
152+
objectSpec: syncagentv1alpha1.RelatedResourceObject{
153+
RelatedResourceObjectSpec: syncagentv1alpha1.RelatedResourceObjectSpec{
154+
Reference: &syncagentv1alpha1.RelatedResourceObjectReference{
155+
Path: "spec.kink",
156+
},
157+
},
158+
Namespace: &syncagentv1alpha1.RelatedResourceObjectSpec{
159+
Template: &syncagentv1alpha1.TemplateExpression{
160+
Template: "dummy-namespace",
161+
},
162+
},
163+
},
164+
expectedSecrets: 0,
165+
},
166+
{
167+
name: "valid template to an empty field",
168+
objectSpec: syncagentv1alpha1.RelatedResourceObject{
169+
RelatedResourceObjectSpec: syncagentv1alpha1.RelatedResourceObjectSpec{
170+
Template: &syncagentv1alpha1.TemplateExpression{
171+
Template: "{{ .Object.spec.kink }}",
172+
},
173+
},
174+
Namespace: &syncagentv1alpha1.RelatedResourceObjectSpec{
175+
Template: &syncagentv1alpha1.TemplateExpression{
176+
Template: "dummy-namespace",
177+
},
178+
},
179+
},
180+
expectedSecrets: 0,
181+
},
182+
{
183+
name: "referring to an omitempty field",
184+
objectSpec: syncagentv1alpha1.RelatedResourceObject{
185+
RelatedResourceObjectSpec: syncagentv1alpha1.RelatedResourceObjectSpec{
186+
Reference: &syncagentv1alpha1.RelatedResourceObjectReference{
187+
Path: "spec.address",
188+
},
189+
},
190+
Namespace: &syncagentv1alpha1.RelatedResourceObjectSpec{
191+
Template: &syncagentv1alpha1.TemplateExpression{
192+
Template: "dummy-namespace",
193+
},
194+
},
195+
},
196+
expectedSecrets: 0,
197+
},
198+
{
199+
name: "templating an omitempty field",
200+
objectSpec: syncagentv1alpha1.RelatedResourceObject{
201+
RelatedResourceObjectSpec: syncagentv1alpha1.RelatedResourceObjectSpec{
202+
Template: &syncagentv1alpha1.TemplateExpression{
203+
Template: "{{ .Object.spec.address }}",
204+
},
205+
},
206+
Namespace: &syncagentv1alpha1.RelatedResourceObjectSpec{
207+
Template: &syncagentv1alpha1.TemplateExpression{
208+
Template: "dummy-namespace",
209+
},
210+
},
211+
},
212+
expectedSecrets: 0,
213+
},
214+
}
215+
216+
for _, testcase := range testcases {
217+
t.Run(testcase.name, func(t *testing.T) {
218+
pubRes := syncagentv1alpha1.RelatedResourceSpec{
219+
Identifier: "test",
220+
Origin: syncagentv1alpha1.RelatedResourceOriginService,
221+
Kind: "Secret",
222+
Object: testcase.objectSpec,
223+
}
224+
225+
foundObjects, err := resolveRelatedResourceObjects(originSide, destSide, pubRes)
226+
if err != nil {
227+
t.Fatalf("Failed to resolve related objects: %v", err)
228+
}
229+
if len(foundObjects) != testcase.expectedSecrets {
230+
t.Fatalf("Expected %d related object (Secret) to be found, but found %d.", testcase.expectedSecrets, len(foundObjects))
231+
}
232+
})
233+
}
234+
}

0 commit comments

Comments
 (0)