Skip to content

Commit eeae4ca

Browse files
authored
chore: improve pkg common coverage (#10373)
1 parent b4b1523 commit eeae4ca

3 files changed

Lines changed: 293 additions & 0 deletions

File tree

pkg/common/env_resolve_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
Copyright (C) 2022-2026 ApeCloud Co., Ltd
3+
4+
This file is part of KubeBlocks project
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU Affero General Public License for more details.
15+
16+
You should have received a copy of the GNU Affero General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package common
21+
22+
import (
23+
"testing"
24+
25+
corev1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/types"
28+
)
29+
30+
func TestGetFieldRef(t *testing.T) {
31+
pod := &corev1.Pod{
32+
ObjectMeta: metav1.ObjectMeta{
33+
Name: "pod-0",
34+
Namespace: "default",
35+
UID: types.UID("uid-1"),
36+
Labels: map[string]string{"app": "mysql", "role": "leader"},
37+
Annotations: map[string]string{"config": "enabled"},
38+
},
39+
Spec: corev1.PodSpec{
40+
NodeName: "node-1",
41+
ServiceAccountName: "kb-sa",
42+
},
43+
Status: corev1.PodStatus{
44+
HostIP: "192.168.0.1",
45+
PodIP: "10.0.0.1",
46+
PodIPs: []corev1.PodIP{{IP: "10.0.0.1"}, {IP: "fd00::1"}},
47+
},
48+
}
49+
50+
cases := []struct {
51+
name string
52+
fieldPath string
53+
want string
54+
wantErr bool
55+
}{
56+
{name: "annotation subscript", fieldPath: "metadata.annotations['config']", want: "enabled"},
57+
{name: "label subscript", fieldPath: "metadata.labels['role']", want: "leader"},
58+
{name: "annotations map is sorted", fieldPath: "metadata.annotations", want: `config="enabled"`},
59+
{name: "labels map is sorted", fieldPath: "metadata.labels", want: "app=\"mysql\"\nrole=\"leader\""},
60+
{name: "name", fieldPath: "metadata.name", want: "pod-0"},
61+
{name: "namespace", fieldPath: "metadata.namespace", want: "default"},
62+
{name: "uid", fieldPath: "metadata.uid", want: "uid-1"},
63+
{name: "node name", fieldPath: "spec.nodeName", want: "node-1"},
64+
{name: "service account", fieldPath: "spec.serviceAccountName", want: "kb-sa"},
65+
{name: "host ip", fieldPath: "status.hostIP", want: "192.168.0.1"},
66+
{name: "pod ip", fieldPath: "status.podIP", want: "10.0.0.1"},
67+
{name: "pod ips", fieldPath: "status.podIPs", want: "10.0.0.1,fd00::1"},
68+
{name: "unsupported field", fieldPath: "spec.unsupported", wantErr: true},
69+
{name: "unsupported subscript", fieldPath: "spec.containers['main']", wantErr: true},
70+
{name: "invalid label key", fieldPath: "metadata.labels['bad/key/again']", wantErr: true},
71+
}
72+
73+
for _, tc := range cases {
74+
t.Run(tc.name, func(t *testing.T) {
75+
got, err := GetFieldRef(pod, &corev1.EnvVarSource{
76+
FieldRef: &corev1.ObjectFieldSelector{FieldPath: tc.fieldPath},
77+
})
78+
if tc.wantErr {
79+
if err == nil {
80+
t.Fatalf("expected error")
81+
}
82+
return
83+
}
84+
if err != nil {
85+
t.Fatalf("unexpected error: %v", err)
86+
}
87+
if got != tc.want {
88+
t.Fatalf("expected %q, got %q", tc.want, got)
89+
}
90+
})
91+
}
92+
}
93+
94+
func TestSplitMaybeSubscriptedPath(t *testing.T) {
95+
cases := []struct {
96+
input string
97+
wantPath string
98+
wantKey string
99+
wantFound bool
100+
}{
101+
{input: "metadata.annotations['myKey']", wantPath: "metadata.annotations", wantKey: "myKey", wantFound: true},
102+
{input: "metadata.annotations['a[b]c']", wantPath: "metadata.annotations", wantKey: "a[b]c", wantFound: true},
103+
{input: "metadata.labels", wantPath: "metadata.labels"},
104+
{input: "['missingPath']", wantPath: "['missingPath']"},
105+
{input: "metadata.labels['unterminated", wantPath: "metadata.labels['unterminated"},
106+
}
107+
108+
for _, tc := range cases {
109+
t.Run(tc.input, func(t *testing.T) {
110+
path, key, found := splitMaybeSubscriptedPath(tc.input)
111+
if path != tc.wantPath || key != tc.wantKey || found != tc.wantFound {
112+
t.Fatalf("expected (%q, %q, %v), got (%q, %q, %v)",
113+
tc.wantPath, tc.wantKey, tc.wantFound, path, key, found)
114+
}
115+
})
116+
}
117+
}

pkg/common/openapiv3schema_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
Copyright (C) 2022-2026 ApeCloud Co., Ltd
3+
4+
This file is part of KubeBlocks project
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU Affero General Public License for more details.
15+
16+
You should have received a copy of the GNU Affero General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package common
21+
22+
import (
23+
"reflect"
24+
"strings"
25+
"testing"
26+
27+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28+
)
29+
30+
func TestValidateDataWithSchema(t *testing.T) {
31+
schema := &apiextensionsv1.JSONSchemaProps{
32+
Type: "object",
33+
Required: []string{"replicas"},
34+
Properties: map[string]apiextensionsv1.JSONSchemaProps{
35+
"replicas": {Type: "integer", Minimum: ptrFloat64(1)},
36+
},
37+
}
38+
39+
if err := ValidateDataWithSchema(schema, map[string]interface{}{"replicas": int64(3)}); err != nil {
40+
t.Fatalf("expected valid data, got %v", err)
41+
}
42+
if err := ValidateDataWithSchema(nil, map[string]interface{}{}); err == nil {
43+
t.Fatalf("expected nil schema error")
44+
}
45+
if err := ValidateDataWithSchema(schema, map[string]interface{}{"replicas": int64(0)}); err == nil {
46+
t.Fatalf("expected validation error")
47+
}
48+
}
49+
50+
func TestConvertStringToInterfaceBySchemaType(t *testing.T) {
51+
schema := &apiextensionsv1.JSONSchemaProps{
52+
Properties: map[string]apiextensionsv1.JSONSchemaProps{
53+
"replicas": {Type: "integer"},
54+
"ratio": {Type: "number"},
55+
"enabled": {Type: "boolean"},
56+
"items": {Type: "array"},
57+
"name": {Type: "string"},
58+
},
59+
}
60+
61+
got, err := ConvertStringToInterfaceBySchemaType(schema, map[string]string{
62+
"replicas": "3",
63+
"ratio": "1.5",
64+
"enabled": "true",
65+
"items": "a,b,c",
66+
"name": "mysql",
67+
"ignored": "value",
68+
})
69+
if err != nil {
70+
t.Fatalf("unexpected error: %v", err)
71+
}
72+
73+
want := map[string]interface{}{
74+
"replicas": int64(3),
75+
"ratio": 1.5,
76+
"enabled": true,
77+
"items": []string{"a", "b", "c"},
78+
"name": "mysql",
79+
}
80+
if !reflect.DeepEqual(got, want) {
81+
t.Fatalf("expected %#v, got %#v", want, got)
82+
}
83+
84+
_, err = ConvertStringToInterfaceBySchemaType(schema, map[string]string{"replicas": "bad"})
85+
if err == nil || !strings.Contains(err.Error(), `convert "replicas" failed`) {
86+
t.Fatalf("expected conversion error for replicas, got %v", err)
87+
}
88+
}
89+
90+
func ptrFloat64(v float64) *float64 {
91+
return &v
92+
}

pkg/common/utils_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
Copyright (C) 2022-2026 ApeCloud Co., Ltd
3+
4+
This file is part of KubeBlocks project
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU Affero General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU Affero General Public License for more details.
15+
16+
You should have received a copy of the GNU Affero General Public License
17+
along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package common
21+
22+
import (
23+
"math"
24+
"testing"
25+
26+
"github.com/apecloud/kubeblocks/pkg/constant"
27+
)
28+
29+
func TestToCamelCase(t *testing.T) {
30+
cases := map[string]string{
31+
"make-food": "MakeFood",
32+
"make.food": "MakeFood",
33+
"alreadyCamel": "Alreadycamel",
34+
"multi-part.id": "MultiPartId",
35+
}
36+
for input, want := range cases {
37+
if got := ToCamelCase(input); got != want {
38+
t.Fatalf("ToCamelCase(%q) expected %q, got %q", input, want, got)
39+
}
40+
}
41+
}
42+
43+
func TestIsCompactMode(t *testing.T) {
44+
if IsCompactMode(nil) {
45+
t.Fatalf("nil annotations should not be compact mode")
46+
}
47+
if IsCompactMode(map[string]string{"other": "true"}) {
48+
t.Fatalf("unrelated annotations should not be compact mode")
49+
}
50+
if !IsCompactMode(map[string]string{constant.FeatureReconciliationInCompactModeAnnotationKey: "true"}) {
51+
t.Fatalf("compact mode annotation should be detected")
52+
}
53+
}
54+
55+
func TestSafeAddInt(t *testing.T) {
56+
if got := SafeAddInt(1, 2); got != 3 {
57+
t.Fatalf("expected 3, got %d", got)
58+
}
59+
if got := SafeAddInt(-3, 2); got != -1 {
60+
t.Fatalf("expected -1, got %d", got)
61+
}
62+
63+
assertPanic(t, func() { SafeAddInt(math.MaxInt, 1) })
64+
assertPanic(t, func() { SafeAddInt(math.MinInt, -1) })
65+
}
66+
67+
func TestCutString(t *testing.T) {
68+
if got := CutString("abcdef", 3); got != "abc" {
69+
t.Fatalf("expected abc, got %q", got)
70+
}
71+
if got := CutString("abc", 10); got != "abc" {
72+
t.Fatalf("expected abc, got %q", got)
73+
}
74+
}
75+
76+
func assertPanic(t *testing.T, f func()) {
77+
t.Helper()
78+
defer func() {
79+
if recover() == nil {
80+
t.Fatalf("expected panic")
81+
}
82+
}()
83+
f()
84+
}

0 commit comments

Comments
 (0)