Skip to content

Commit 57744ef

Browse files
authored
test(cache/client): improve unit test coverage from 5.6% to 69.4% (#12986)
* test(cache/client): add unit tests for kubernetes core and fake clients Add comprehensive tests for the cache/client package to improve coverage from 5.6% to 69.4%. Tests cover KubernetesCore.PodClient with fake clientset, namespace isolation, CRUD operations, all FakePodClient methods, FakeBadPodClient error behavior, empty namespace panic, and additional MySQL config edge cases. Signed-off-by: Jaison Paul <paul.jaison@gmail.com> * fix: update copyright year and consolidate stub tests Use 2026 copyright for new files. Consolidate repetitive FakePodClient stub method tests into a single table-driven TestFakePodClientStubMethods. Remove tests that only exercise k8s fake clientset internals. Signed-off-by: Jaison Paul <paul.jaison@gmail.com> * fix: address Copilot review feedback Use types.MergePatchType constant instead of raw string literal for Patch test. Replace nil arguments with minimal valid objects (corev1.Pod, corev1.Binding) in Create, Update, UpdateStatus, UpdateEphemeralContainers, UpdateResize, and Bind stub tests. Signed-off-by: Jaison Paul <paul.jaison@gmail.com> * fix: resolve golangci-lint staticcheck and typecheck issues Remove redundant type declaration flagged by QF1011 and unused import flagged by typecheck. Signed-off-by: Jaison Paul <paul.jaison@gmail.com> --------- Signed-off-by: Jaison Paul <paul.jaison@gmail.com>
1 parent eb33caf commit 57744ef

File tree

3 files changed

+317
-0
lines changed

3 files changed

+317
-0
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// Copyright 2026 The Kubeflow Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package client
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
corev1 "k8s.io/api/core/v1"
23+
policyv1 "k8s.io/api/policy/v1"
24+
policyv1beta1 "k8s.io/api/policy/v1beta1"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/types"
27+
)
28+
29+
func TestNewFakeKuberneteCoresClient(t *testing.T) {
30+
fakeClient := NewFakeKuberneteCoresClient()
31+
assert.NotNil(t, fakeClient)
32+
assert.NotNil(t, fakeClient.podClientFake)
33+
}
34+
35+
func TestFakeKuberneteCoreClientImplementsInterface(t *testing.T) {
36+
var _ KubernetesCoreInterface = NewFakeKuberneteCoresClient()
37+
}
38+
39+
func TestFakeKuberneteCoreClientPodClientReturnsClient(t *testing.T) {
40+
fakeClient := NewFakeKuberneteCoresClient()
41+
podClient := fakeClient.PodClient("test-namespace")
42+
assert.NotNil(t, podClient)
43+
}
44+
45+
func TestFakeKuberneteCoreClientPodClientEmptyNamespacePanics(t *testing.T) {
46+
fakeClient := NewFakeKuberneteCoresClient()
47+
assert.Panics(t, func() {
48+
fakeClient.PodClient("")
49+
})
50+
}
51+
52+
func TestNewFakeKubernetesCoreClientWithBadPodClient(t *testing.T) {
53+
fakeClient := NewFakeKubernetesCoreClientWithBadPodClient()
54+
assert.NotNil(t, fakeClient)
55+
assert.NotNil(t, fakeClient.podClientFake)
56+
}
57+
58+
func TestFakeKubernetesCoreClientWithBadPodClientImplementsInterface(t *testing.T) {
59+
var _ KubernetesCoreInterface = NewFakeKubernetesCoreClientWithBadPodClient()
60+
}
61+
62+
func TestFakeKubernetesCoreClientWithBadPodClientReturnsClient(t *testing.T) {
63+
fakeClient := NewFakeKubernetesCoreClientWithBadPodClient()
64+
podClient := fakeClient.PodClient("test-namespace")
65+
assert.NotNil(t, podClient)
66+
}
67+
68+
func TestFakeBadPodClientDeleteFails(t *testing.T) {
69+
fakeClient := NewFakeKubernetesCoreClientWithBadPodClient()
70+
podClient := fakeClient.PodClient("test-namespace")
71+
err := podClient.Delete(context.Background(), "some-pod", metav1.DeleteOptions{})
72+
assert.Error(t, err)
73+
assert.Equal(t, "failed to delete pod", err.Error())
74+
}
75+
76+
func TestFakePodClientDeleteSucceeds(t *testing.T) {
77+
fakeClient := NewFakeKuberneteCoresClient()
78+
podClient := fakeClient.PodClient("test-namespace")
79+
err := podClient.Delete(context.Background(), "some-pod", metav1.DeleteOptions{})
80+
assert.NoError(t, err)
81+
}
82+
83+
func TestFakePodClientEvictV1(t *testing.T) {
84+
fakePodClient := &FakePodClient{}
85+
err := fakePodClient.EvictV1(context.Background(), &policyv1.Eviction{})
86+
assert.NoError(t, err)
87+
}
88+
89+
func TestFakePodClientEvictV1beta1(t *testing.T) {
90+
fakePodClient := &FakePodClient{}
91+
err := fakePodClient.EvictV1beta1(context.Background(), &policyv1beta1.Eviction{})
92+
assert.NoError(t, err)
93+
}
94+
95+
func TestFakePodClientWatch(t *testing.T) {
96+
fakePodClient := &FakePodClient{}
97+
watcher, err := fakePodClient.Watch(context.Background(), metav1.ListOptions{})
98+
assert.Nil(t, watcher)
99+
assert.NoError(t, err)
100+
}
101+
102+
func TestFakePodClientPatch(t *testing.T) {
103+
fakePodClient := &FakePodClient{}
104+
result, err := fakePodClient.Patch(context.Background(), "test-pod", types.MergePatchType, []byte(`{}`), metav1.PatchOptions{})
105+
assert.Nil(t, result)
106+
assert.NoError(t, err)
107+
}
108+
109+
// TestFakePodClientStubMethods verifies all unimplemented stub methods return nil values.
110+
func TestFakePodClientStubMethods(t *testing.T) {
111+
fakePodClient := &FakePodClient{}
112+
ctx := context.Background()
113+
minimalPod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "default"}}
114+
115+
t.Run("Get", func(t *testing.T) {
116+
pod, err := fakePodClient.Get(ctx, "test-pod", metav1.GetOptions{})
117+
assert.Nil(t, pod)
118+
assert.NoError(t, err)
119+
})
120+
121+
t.Run("List", func(t *testing.T) {
122+
podList, err := fakePodClient.List(ctx, metav1.ListOptions{})
123+
assert.Nil(t, podList)
124+
assert.NoError(t, err)
125+
})
126+
127+
t.Run("Create", func(t *testing.T) {
128+
pod, err := fakePodClient.Create(ctx, minimalPod, metav1.CreateOptions{})
129+
assert.Nil(t, pod)
130+
assert.NoError(t, err)
131+
})
132+
133+
t.Run("Update", func(t *testing.T) {
134+
pod, err := fakePodClient.Update(ctx, minimalPod, metav1.UpdateOptions{})
135+
assert.Nil(t, pod)
136+
assert.NoError(t, err)
137+
})
138+
139+
t.Run("UpdateStatus", func(t *testing.T) {
140+
pod, err := fakePodClient.UpdateStatus(ctx, minimalPod, metav1.UpdateOptions{})
141+
assert.Nil(t, pod)
142+
assert.NoError(t, err)
143+
})
144+
145+
t.Run("DeleteCollection", func(t *testing.T) {
146+
err := fakePodClient.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{})
147+
assert.NoError(t, err)
148+
})
149+
150+
t.Run("Bind", func(t *testing.T) {
151+
binding := &corev1.Binding{
152+
ObjectMeta: metav1.ObjectMeta{Name: "test-pod"},
153+
Target: corev1.ObjectReference{Name: "test-node"},
154+
}
155+
err := fakePodClient.Bind(ctx, binding, metav1.CreateOptions{})
156+
assert.NoError(t, err)
157+
})
158+
159+
t.Run("Evict", func(t *testing.T) {
160+
err := fakePodClient.Evict(ctx, nil)
161+
assert.NoError(t, err)
162+
})
163+
164+
t.Run("GetLogs", func(t *testing.T) {
165+
result := fakePodClient.GetLogs("test-pod", nil)
166+
assert.Nil(t, result)
167+
})
168+
169+
t.Run("ProxyGet", func(t *testing.T) {
170+
result := fakePodClient.ProxyGet("http", "test-pod", "8080", "/healthz", nil)
171+
assert.Nil(t, result)
172+
})
173+
174+
t.Run("UpdateEphemeralContainers", func(t *testing.T) {
175+
pod, err := fakePodClient.UpdateEphemeralContainers(ctx, "test-pod", minimalPod, metav1.UpdateOptions{})
176+
assert.Nil(t, pod)
177+
assert.NoError(t, err)
178+
})
179+
180+
t.Run("Apply", func(t *testing.T) {
181+
pod, err := fakePodClient.Apply(ctx, nil, metav1.ApplyOptions{})
182+
assert.Nil(t, pod)
183+
assert.NoError(t, err)
184+
})
185+
186+
t.Run("ApplyStatus", func(t *testing.T) {
187+
pod, err := fakePodClient.ApplyStatus(ctx, nil, metav1.ApplyOptions{})
188+
assert.Nil(t, pod)
189+
assert.NoError(t, err)
190+
})
191+
192+
t.Run("UpdateResize", func(t *testing.T) {
193+
pod, err := fakePodClient.UpdateResize(ctx, "test-pod", minimalPod, metav1.UpdateOptions{})
194+
assert.Nil(t, pod)
195+
assert.NoError(t, err)
196+
})
197+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2026 The Kubeflow Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package client
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
fakeclientset "k8s.io/client-go/kubernetes/fake"
22+
)
23+
24+
func TestKubernetesCorePodClient(t *testing.T) {
25+
fakeClientset := fakeclientset.NewSimpleClientset()
26+
kubernetesCore := &KubernetesCore{coreV1Client: fakeClientset.CoreV1()}
27+
28+
podClient := kubernetesCore.PodClient("test-namespace")
29+
assert.NotNil(t, podClient)
30+
}
31+
32+
func TestKubernetesCorePodClientDifferentNamespaces(t *testing.T) {
33+
fakeClientset := fakeclientset.NewSimpleClientset()
34+
kubernetesCore := &KubernetesCore{coreV1Client: fakeClientset.CoreV1()}
35+
36+
clientA := kubernetesCore.PodClient("namespace-a")
37+
clientB := kubernetesCore.PodClient("namespace-b")
38+
39+
assert.NotNil(t, clientA)
40+
assert.NotNil(t, clientB)
41+
}
42+
43+
func TestKubernetesCoreImplementsInterface(t *testing.T) {
44+
fakeClientset := fakeclientset.NewSimpleClientset()
45+
kubernetesCore := &KubernetesCore{coreV1Client: fakeClientset.CoreV1()}
46+
47+
var _ KubernetesCoreInterface = kubernetesCore
48+
}

backend/src/cache/client/sql_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,78 @@ func TestCreateMySQLConfig(t *testing.T) {
7070
AllowNativePasswords: true,
7171
},
7272
},
73+
{
74+
name: "with password and database name",
75+
args: args{
76+
user: "pipeline_user",
77+
password: "secretpassword",
78+
host: "mysql-service.kubeflow",
79+
port: "3307",
80+
dbName: "cachedb",
81+
mysqlGroupConcatMaxLen: "4096",
82+
mysqlExtraParams: nil,
83+
},
84+
want: &mysql.Config{
85+
User: "pipeline_user",
86+
Passwd: "secretpassword",
87+
Net: "tcp",
88+
Addr: "mysql-service.kubeflow:3307",
89+
DBName: "cachedb",
90+
Params: map[string]string{"charset": "utf8", "parseTime": "True", "loc": "Local", "group_concat_max_len": "4096"},
91+
AllowNativePasswords: true,
92+
},
93+
},
94+
{
95+
name: "extra params override defaults",
96+
args: args{
97+
user: "root",
98+
host: "mysql",
99+
port: "3306",
100+
mysqlGroupConcatMaxLen: "1024",
101+
mysqlExtraParams: map[string]string{"charset": "utf8mb4"},
102+
},
103+
want: &mysql.Config{
104+
User: "root",
105+
Net: "tcp",
106+
Addr: "mysql:3306",
107+
Params: map[string]string{"charset": "utf8mb4", "parseTime": "True", "loc": "Local", "group_concat_max_len": "1024"},
108+
AllowNativePasswords: true,
109+
},
110+
},
111+
{
112+
name: "multiple extra parameters",
113+
args: args{
114+
user: "root",
115+
host: "mysql",
116+
port: "3306",
117+
mysqlGroupConcatMaxLen: "1024",
118+
mysqlExtraParams: map[string]string{"tls": "skip-verify", "timeout": "30s", "readTimeout": "30s"},
119+
},
120+
want: &mysql.Config{
121+
User: "root",
122+
Net: "tcp",
123+
Addr: "mysql:3306",
124+
Params: map[string]string{"charset": "utf8", "parseTime": "True", "loc": "Local", "group_concat_max_len": "1024", "tls": "skip-verify", "timeout": "30s", "readTimeout": "30s"},
125+
AllowNativePasswords: true,
126+
},
127+
},
128+
{
129+
name: "empty extra params map",
130+
args: args{
131+
user: "root",
132+
host: "localhost",
133+
port: "3306",
134+
mysqlGroupConcatMaxLen: "1024",
135+
mysqlExtraParams: map[string]string{},
136+
},
137+
want: &mysql.Config{
138+
User: "root",
139+
Net: "tcp",
140+
Addr: "localhost:3306",
141+
Params: map[string]string{"charset": "utf8", "parseTime": "True", "loc": "Local", "group_concat_max_len": "1024"},
142+
AllowNativePasswords: true,
143+
},
144+
},
73145
}
74146
for _, tt := range tests {
75147
t.Run(tt.name, func(t *testing.T) {

0 commit comments

Comments
 (0)