Skip to content

Commit 3e742cb

Browse files
committed
add unit tests for multi-cluster support
Signed-off-by: kahirokunn <okinakahiro@gmail.com>
1 parent e871038 commit 3e742cb

2 files changed

Lines changed: 508 additions & 2 deletions

File tree

Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
/*
2+
Copyright 2026 The Knative 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 common
18+
19+
import (
20+
"context"
21+
"strings"
22+
"testing"
23+
24+
mf "github.com/manifestival/manifestival"
25+
corev1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/client-go/kubernetes/fake"
28+
29+
"knative.dev/operator/pkg/apis/operator/base"
30+
"knative.dev/operator/pkg/apis/operator/v1beta1"
31+
)
32+
33+
func TestResolveTargetCluster_NilRef(t *testing.T) {
34+
// ClusterProfileRef is nil => the stage should be a no-op.
35+
instance := &v1beta1.KnativeServing{
36+
ObjectMeta: metav1.ObjectMeta{
37+
Namespace: "default",
38+
Name: "test",
39+
},
40+
}
41+
42+
manifest, err := mf.ManifestFrom(mf.Slice{})
43+
if err != nil {
44+
t.Fatalf("Failed to create manifest: %v", err)
45+
}
46+
47+
origClient := manifest.Client
48+
49+
var anchorOwner mf.Owner
50+
stage := ResolveTargetCluster(nil, &anchorOwner)
51+
if err := stage(context.Background(), &manifest, instance); err != nil {
52+
t.Fatalf("Expected no error for nil ClusterProfileRef, got: %v", err)
53+
}
54+
55+
if manifest.Client != origClient {
56+
t.Fatal("Expected manifest.Client to remain unchanged when ClusterProfileRef is nil")
57+
}
58+
59+
if anchorOwner != nil {
60+
t.Fatal("Expected anchorOwner to remain nil when ClusterProfileRef is nil")
61+
}
62+
}
63+
64+
func TestResolveTargetCluster_MissingCredentialConfig(t *testing.T) {
65+
// Ensure the package variable is empty for this test.
66+
old := CredentialProvidersConfigPath
67+
CredentialProvidersConfigPath = ""
68+
defer func() { CredentialProvidersConfigPath = old }()
69+
70+
instance := &v1beta1.KnativeServing{
71+
ObjectMeta: metav1.ObjectMeta{
72+
Namespace: "default",
73+
Name: "test",
74+
},
75+
Spec: v1beta1.KnativeServingSpec{
76+
CommonSpec: base.CommonSpec{
77+
ClusterProfileRef: &base.ClusterProfileReference{
78+
Name: "test-cluster",
79+
Namespace: "fleet-system",
80+
},
81+
},
82+
},
83+
}
84+
85+
manifest, err := mf.ManifestFrom(mf.Slice{})
86+
if err != nil {
87+
t.Fatalf("Failed to create manifest: %v", err)
88+
}
89+
90+
var anchorOwner mf.Owner
91+
stage := ResolveTargetCluster(nil, &anchorOwner)
92+
err = stage(context.Background(), &manifest, instance)
93+
if err == nil {
94+
t.Fatal("Expected error when CredentialProvidersConfigPath is empty, got nil")
95+
}
96+
if !strings.Contains(err.Error(), "credential-providers-config") {
97+
t.Fatalf("Expected error message to contain 'credential-providers-config', got: %v", err)
98+
}
99+
}
100+
101+
func TestResolveTargetClusterConfig_NilRef(t *testing.T) {
102+
// ResolveTargetClusterConfig with nil ClusterProfileRef => (nil, nil).
103+
instance := &v1beta1.KnativeServing{
104+
ObjectMeta: metav1.ObjectMeta{
105+
Namespace: "default",
106+
Name: "test",
107+
},
108+
}
109+
110+
cfg, err := ResolveTargetClusterConfig(context.Background(), nil, instance)
111+
if err != nil {
112+
t.Fatalf("Expected no error for nil ClusterProfileRef, got: %v", err)
113+
}
114+
if cfg != nil {
115+
t.Fatal("Expected nil config for nil ClusterProfileRef")
116+
}
117+
}
118+
119+
func TestResolveTargetClusterConfig_MissingCredentialConfig(t *testing.T) {
120+
old := CredentialProvidersConfigPath
121+
CredentialProvidersConfigPath = ""
122+
defer func() { CredentialProvidersConfigPath = old }()
123+
124+
instance := &v1beta1.KnativeServing{
125+
ObjectMeta: metav1.ObjectMeta{
126+
Namespace: "default",
127+
Name: "test",
128+
},
129+
Spec: v1beta1.KnativeServingSpec{
130+
CommonSpec: base.CommonSpec{
131+
ClusterProfileRef: &base.ClusterProfileReference{
132+
Name: "test-cluster",
133+
Namespace: "fleet-system",
134+
},
135+
},
136+
},
137+
}
138+
139+
_, err := ResolveTargetClusterConfig(context.Background(), nil, instance)
140+
if err == nil {
141+
t.Fatal("Expected error when CredentialProvidersConfigPath is empty, got nil")
142+
}
143+
if !strings.Contains(err.Error(), "credential-providers-config") {
144+
t.Fatalf("Expected error message to contain 'credential-providers-config', got: %v", err)
145+
}
146+
}
147+
148+
func TestAnchorName(t *testing.T) {
149+
tests := []struct {
150+
name string
151+
instance base.KComponent
152+
want string
153+
}{
154+
{
155+
name: "KnativeServing",
156+
instance: &v1beta1.KnativeServing{
157+
ObjectMeta: metav1.ObjectMeta{
158+
Namespace: "knative-serving",
159+
Name: "knative-serving",
160+
},
161+
},
162+
want: "knativeserving-knative-serving-root-owner",
163+
},
164+
{
165+
name: "KnativeEventing",
166+
instance: &v1beta1.KnativeEventing{
167+
ObjectMeta: metav1.ObjectMeta{
168+
Namespace: "knative-eventing",
169+
Name: "my-eventing",
170+
},
171+
},
172+
want: "knativeeventing-my-eventing-root-owner",
173+
},
174+
}
175+
176+
for _, tt := range tests {
177+
t.Run(tt.name, func(t *testing.T) {
178+
got := AnchorName(tt.instance)
179+
if got != tt.want {
180+
t.Fatalf("AnchorName() = %q, want %q", got, tt.want)
181+
}
182+
})
183+
}
184+
}
185+
186+
func TestEnsureAnchorConfigMap_Create(t *testing.T) {
187+
// No pre-existing resources: namespace and anchor ConfigMap should be created.
188+
kubeClient := fake.NewSimpleClientset()
189+
instance := &v1beta1.KnativeServing{
190+
ObjectMeta: metav1.ObjectMeta{
191+
Namespace: "test-ns",
192+
Name: "test",
193+
},
194+
}
195+
196+
ctx := context.Background()
197+
anchor, err := EnsureAnchorConfigMap(ctx, kubeClient, instance)
198+
if err != nil {
199+
t.Fatalf("EnsureAnchorConfigMap() error: %v", err)
200+
}
201+
202+
expectedName := "knativeserving-test-root-owner"
203+
if anchor.Name != expectedName {
204+
t.Fatalf("anchor.Name = %q, want %q", anchor.Name, expectedName)
205+
}
206+
if anchor.Namespace != "test-ns" {
207+
t.Fatalf("anchor.Namespace = %q, want %q", anchor.Namespace, "test-ns")
208+
}
209+
210+
// Verify the anchor has the expected labels.
211+
if anchor.Labels["app.kubernetes.io/managed-by"] != "knative-operator" {
212+
t.Fatalf("Expected label app.kubernetes.io/managed-by=knative-operator, got %q",
213+
anchor.Labels["app.kubernetes.io/managed-by"])
214+
}
215+
if anchor.Labels["operator.knative.dev/cr-name"] != "test" {
216+
t.Fatalf("Expected label operator.knative.dev/cr-name=test, got %q",
217+
anchor.Labels["operator.knative.dev/cr-name"])
218+
}
219+
220+
// Verify the anchor has the expected annotations.
221+
if anchor.Annotations["operator.knative.dev/anchor"] != "true" {
222+
t.Fatalf("Expected annotation operator.knative.dev/anchor=true, got %q",
223+
anchor.Annotations["operator.knative.dev/anchor"])
224+
}
225+
226+
// Verify the namespace was created.
227+
ns, err := kubeClient.CoreV1().Namespaces().Get(ctx, "test-ns", metav1.GetOptions{})
228+
if err != nil {
229+
t.Fatalf("Expected namespace test-ns to exist, got error: %v", err)
230+
}
231+
if ns.Name != "test-ns" {
232+
t.Fatalf("namespace.Name = %q, want %q", ns.Name, "test-ns")
233+
}
234+
}
235+
236+
func TestEnsureAnchorConfigMap_AlreadyExists(t *testing.T) {
237+
// Pre-existing anchor ConfigMap should be returned as-is.
238+
existingAnchor := &corev1.ConfigMap{
239+
ObjectMeta: metav1.ObjectMeta{
240+
Name: "knativeserving-test-root-owner",
241+
Namespace: "test-ns",
242+
},
243+
}
244+
existingNS := &corev1.Namespace{
245+
ObjectMeta: metav1.ObjectMeta{Name: "test-ns"},
246+
}
247+
kubeClient := fake.NewSimpleClientset(existingNS, existingAnchor)
248+
249+
instance := &v1beta1.KnativeServing{
250+
ObjectMeta: metav1.ObjectMeta{
251+
Namespace: "test-ns",
252+
Name: "test",
253+
},
254+
}
255+
256+
ctx := context.Background()
257+
anchor, err := EnsureAnchorConfigMap(ctx, kubeClient, instance)
258+
if err != nil {
259+
t.Fatalf("EnsureAnchorConfigMap() error: %v", err)
260+
}
261+
262+
if anchor.Name != "knativeserving-test-root-owner" {
263+
t.Fatalf("anchor.Name = %q, want %q", anchor.Name, "knativeserving-test-root-owner")
264+
}
265+
if anchor.Namespace != "test-ns" {
266+
t.Fatalf("anchor.Namespace = %q, want %q", anchor.Namespace, "test-ns")
267+
}
268+
}
269+
270+
func TestEnsureAnchorConfigMap_NamespaceAlreadyExists(t *testing.T) {
271+
// Namespace exists but anchor ConfigMap does not.
272+
existingNS := &corev1.Namespace{
273+
ObjectMeta: metav1.ObjectMeta{Name: "test-ns"},
274+
}
275+
kubeClient := fake.NewSimpleClientset(existingNS)
276+
277+
instance := &v1beta1.KnativeServing{
278+
ObjectMeta: metav1.ObjectMeta{
279+
Namespace: "test-ns",
280+
Name: "test",
281+
},
282+
}
283+
284+
ctx := context.Background()
285+
anchor, err := EnsureAnchorConfigMap(ctx, kubeClient, instance)
286+
if err != nil {
287+
t.Fatalf("EnsureAnchorConfigMap() error: %v", err)
288+
}
289+
290+
if anchor.Name != "knativeserving-test-root-owner" {
291+
t.Fatalf("anchor.Name = %q, want %q", anchor.Name, "knativeserving-test-root-owner")
292+
}
293+
294+
// Verify only one namespace with that name (no duplicate creation attempt).
295+
nsList, err := kubeClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
296+
if err != nil {
297+
t.Fatalf("Failed to list namespaces: %v", err)
298+
}
299+
count := 0
300+
for _, ns := range nsList.Items {
301+
if ns.Name == "test-ns" {
302+
count++
303+
}
304+
}
305+
if count != 1 {
306+
t.Fatalf("Expected 1 namespace named test-ns, got %d", count)
307+
}
308+
}
309+
310+
func TestDeleteAnchorConfigMap_Success(t *testing.T) {
311+
existingAnchor := &corev1.ConfigMap{
312+
ObjectMeta: metav1.ObjectMeta{
313+
Name: "knativeserving-test-root-owner",
314+
Namespace: "test-ns",
315+
},
316+
}
317+
kubeClient := fake.NewSimpleClientset(existingAnchor)
318+
319+
instance := &v1beta1.KnativeServing{
320+
ObjectMeta: metav1.ObjectMeta{
321+
Namespace: "test-ns",
322+
Name: "test",
323+
},
324+
}
325+
326+
ctx := context.Background()
327+
if err := DeleteAnchorConfigMap(ctx, kubeClient, instance); err != nil {
328+
t.Fatalf("DeleteAnchorConfigMap() error: %v", err)
329+
}
330+
331+
// Verify the ConfigMap no longer exists.
332+
_, err := kubeClient.CoreV1().ConfigMaps("test-ns").Get(ctx, "knativeserving-test-root-owner", metav1.GetOptions{})
333+
if err == nil {
334+
t.Fatal("Expected anchor ConfigMap to be deleted, but it still exists")
335+
}
336+
}
337+
338+
func TestDeleteAnchorConfigMap_NotFound(t *testing.T) {
339+
// Deleting a non-existent anchor should not return an error.
340+
kubeClient := fake.NewSimpleClientset()
341+
342+
instance := &v1beta1.KnativeServing{
343+
ObjectMeta: metav1.ObjectMeta{
344+
Namespace: "test-ns",
345+
Name: "test",
346+
},
347+
}
348+
349+
ctx := context.Background()
350+
if err := DeleteAnchorConfigMap(ctx, kubeClient, instance); err != nil {
351+
t.Fatalf("Expected no error for deleting non-existent anchor, got: %v", err)
352+
}
353+
}

0 commit comments

Comments
 (0)