Skip to content

Commit 1e73916

Browse files
pkg/webhook: test Interpreter
In this commit, we introduce unit tests for the `Interpreter` webhook across `Decoder`, `HTTP`, `Injection`, `Response`, and `Webhook` components. Signed-off-by: Mohamed Awnallah <[email protected]>
1 parent 2e60a6e commit 1e73916

File tree

6 files changed

+1051
-1
lines changed

6 files changed

+1051
-1
lines changed

pkg/webhook/interpreter/decode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func NewDecoder(scheme *runtime.Scheme) *Decoder {
4343
// It errors out if req.Object.Raw is empty i.e. containing 0 raw bytes.
4444
func (d *Decoder) Decode(req Request, into runtime.Object) error {
4545
if len(req.Object.Raw) == 0 {
46-
return fmt.Errorf("there is no context to decode")
46+
return fmt.Errorf("there is no content to decode")
4747
}
4848
return d.DecodeRaw(req.Object, into)
4949
}
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
Copyright 2024 The Karmada 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 interpreter
18+
19+
import (
20+
"fmt"
21+
"strings"
22+
"testing"
23+
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26+
"k8s.io/apimachinery/pkg/runtime"
27+
schema "k8s.io/apimachinery/pkg/runtime/schema"
28+
29+
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
30+
)
31+
32+
type Interface interface {
33+
GetAPIVersion() string
34+
GetKind() string
35+
GetName() string
36+
}
37+
38+
// MyTestPod represents a simplified version of a Kubernetes Pod for testing purposes.
39+
// It includes basic fields such as API version, kind, and metadata.
40+
type MyTestPod struct {
41+
APIVersion string `json:"apiVersion"`
42+
Kind string `json:"kind"`
43+
Metadata struct {
44+
Name string `json:"name"`
45+
} `json:"metadata"`
46+
}
47+
48+
// DeepCopyObject creates a deep copy of the MyTestPod instance.
49+
// This method is part of the runtime.Object interface and ensures that modifications
50+
// to the copy do not affect the original object.
51+
func (p *MyTestPod) DeepCopyObject() runtime.Object {
52+
return &MyTestPod{
53+
APIVersion: p.APIVersion,
54+
Kind: p.Kind,
55+
Metadata: p.Metadata,
56+
}
57+
}
58+
59+
// GetObjectKind returns the schema.ObjectKind for the MyTestPod instance.
60+
// This method is part of the runtime.Object interface and provides the API version
61+
// and kind of the object, which is used for object identification in Kubernetes.
62+
func (p *MyTestPod) GetObjectKind() schema.ObjectKind {
63+
return &metav1.TypeMeta{
64+
APIVersion: p.APIVersion,
65+
Kind: p.Kind,
66+
}
67+
}
68+
69+
// GetAPIVersion returns the API version of the MyTestPod.
70+
func (p *MyTestPod) GetAPIVersion() string {
71+
return p.APIVersion
72+
}
73+
74+
// GetKind returns the kind of the MyTestPod.
75+
func (p *MyTestPod) GetKind() string {
76+
return p.Kind
77+
}
78+
79+
// GetName returns the name of the MyTestPod.
80+
func (p *MyTestPod) GetName() string {
81+
return p.Metadata.Name
82+
}
83+
84+
func TestNewDecoder(t *testing.T) {
85+
tests := []struct {
86+
name string
87+
}{
88+
{
89+
name: "NewDecoder_ValidDecoder_DecoderIsValid",
90+
},
91+
}
92+
for _, tt := range tests {
93+
t.Run(tt.name, func(t *testing.T) {
94+
scheme := runtime.NewScheme()
95+
decoder := NewDecoder(scheme)
96+
if decoder == nil {
97+
t.Errorf("expected decoder to not be nil")
98+
}
99+
})
100+
}
101+
}
102+
103+
func TestDecodeRaw(t *testing.T) {
104+
tests := []struct {
105+
name string
106+
apiVersion string
107+
kind string
108+
objName string
109+
rawObj *runtime.RawExtension
110+
into Interface
111+
prep func(re *runtime.RawExtension, apiVersion, kind, name string) error
112+
verify func(into Interface, apiVersion, kind, name string) error
113+
wantErr bool
114+
errMsg string
115+
}{
116+
{
117+
name: "DecodeRaw_ValidRaw_DecodeRawIsSuccessful",
118+
objName: "test-pod",
119+
kind: "Pod",
120+
apiVersion: "v1",
121+
rawObj: &runtime.RawExtension{
122+
Raw: []byte{},
123+
},
124+
into: &unstructured.Unstructured{},
125+
prep: func(re *runtime.RawExtension, apiVersion, kind, name string) error {
126+
re.Raw = []byte(fmt.Sprintf(`{"apiVersion": "%s", "kind": "%s", "metadata": {"name": "%s"}}`, apiVersion, kind, name))
127+
return nil
128+
},
129+
verify: verifyRuntimeObject,
130+
wantErr: false,
131+
},
132+
{
133+
name: "DecodeRaw_IntoNonUnstructuredType_RawDecoded",
134+
objName: "test-pod",
135+
kind: "Pod",
136+
apiVersion: "v1",
137+
rawObj: &runtime.RawExtension{
138+
Raw: []byte{},
139+
},
140+
into: &MyTestPod{},
141+
prep: func(re *runtime.RawExtension, apiVersion, kind, name string) error {
142+
re.Raw = []byte(fmt.Sprintf(`{"apiVersion": "%s", "kind": "%s", "metadata": {"name": "%s"}}`, apiVersion, kind, name))
143+
return nil
144+
},
145+
verify: verifyRuntimeObject,
146+
wantErr: false,
147+
},
148+
{
149+
name: "DecodeRaw_EmptyRaw_NoContentToDecode",
150+
rawObj: &runtime.RawExtension{
151+
Raw: []byte{},
152+
},
153+
into: &unstructured.Unstructured{},
154+
prep: func(*runtime.RawExtension, string, string, string) error { return nil },
155+
verify: func(Interface, string, string, string) error { return nil },
156+
wantErr: true,
157+
errMsg: "there is no content to decode",
158+
},
159+
}
160+
for _, test := range tests {
161+
t.Run(test.name, func(t *testing.T) {
162+
if err := test.prep(test.rawObj, test.apiVersion, test.kind, test.objName); err != nil {
163+
t.Errorf("failed to prep the runtime raw extension object: %v", err)
164+
}
165+
scheme := runtime.NewScheme()
166+
decoder := NewDecoder(scheme)
167+
intoObj, ok := test.into.(runtime.Object)
168+
if !ok {
169+
t.Errorf("failed to type assert into object into runtime rawextension")
170+
}
171+
err := decoder.DecodeRaw(*test.rawObj, intoObj)
172+
if err != nil && !test.wantErr {
173+
t.Errorf("unexpected error while decoding the raw: %v", err)
174+
}
175+
if err == nil && test.wantErr {
176+
t.Errorf("expected an error, but got none")
177+
}
178+
if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
179+
t.Errorf("expected %s error msg to contain %s", err.Error(), test.errMsg)
180+
}
181+
if err := test.verify(test.into, test.apiVersion, test.kind, test.objName); err != nil {
182+
t.Errorf("failed to verify decoding the raw: %v", err)
183+
}
184+
})
185+
}
186+
}
187+
188+
func TestDecode(t *testing.T) {
189+
tests := []struct {
190+
name string
191+
apiVersion string
192+
kind string
193+
objName string
194+
req *Request
195+
into Interface
196+
prep func(re *Request, apiVersion, kind, name string) error
197+
verify func(into Interface, apiVersion, kind, name string) error
198+
wantErr bool
199+
errMsg string
200+
}{
201+
{
202+
name: "Decode_ValidRequest_DecodeRequestIsSuccessful",
203+
objName: "test-pod",
204+
kind: "Pod",
205+
apiVersion: "v1",
206+
req: &Request{
207+
ResourceInterpreterRequest: configv1alpha1.ResourceInterpreterRequest{
208+
Object: runtime.RawExtension{},
209+
},
210+
},
211+
into: &unstructured.Unstructured{},
212+
prep: func(re *Request, apiVersion, kind, name string) error {
213+
re.ResourceInterpreterRequest.Object.Raw = []byte(fmt.Sprintf(`{"apiVersion": "%s", "kind": "%s", "metadata": {"name": "%s"}}`, apiVersion, kind, name))
214+
return nil
215+
},
216+
verify: verifyRuntimeObject,
217+
wantErr: false,
218+
},
219+
{
220+
name: "Decode_EmptyRaw_NoContentToDecode",
221+
req: &Request{
222+
ResourceInterpreterRequest: configv1alpha1.ResourceInterpreterRequest{
223+
Object: runtime.RawExtension{},
224+
},
225+
},
226+
into: &unstructured.Unstructured{},
227+
prep: func(*Request, string, string, string) error { return nil },
228+
verify: func(Interface, string, string, string) error { return nil },
229+
wantErr: true,
230+
errMsg: "there is no content to decode",
231+
},
232+
}
233+
for _, test := range tests {
234+
t.Run(test.name, func(t *testing.T) {
235+
if err := test.prep(test.req, test.apiVersion, test.kind, test.objName); err != nil {
236+
t.Errorf("failed to prep the runtime raw extension object: %v", err)
237+
}
238+
scheme := runtime.NewScheme()
239+
decoder := NewDecoder(scheme)
240+
if decoder == nil {
241+
t.Errorf("expected decoder to not be nil")
242+
}
243+
intoObj, ok := test.into.(runtime.Object)
244+
if !ok {
245+
t.Errorf("failed to type assert into object into runtime rawextension")
246+
}
247+
err := decoder.Decode(*test.req, intoObj)
248+
if err != nil && !test.wantErr {
249+
t.Errorf("unexpected error while decoding the raw: %v", err)
250+
}
251+
if err == nil && test.wantErr {
252+
t.Errorf("expected an error, but got none")
253+
}
254+
if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) {
255+
t.Errorf("expected %s error msg to contain %s", err.Error(), test.errMsg)
256+
}
257+
if err := test.verify(test.into, test.apiVersion, test.kind, test.objName); err != nil {
258+
t.Errorf("failed to verify decoding the raw: %v", err)
259+
}
260+
})
261+
}
262+
}
263+
264+
// verifyRuntimeObject checks if the runtime object (`into`) matches the given
265+
// `apiVersion`, `kind`, and `name`. It returns an error if any field doesn't match.
266+
func verifyRuntimeObject(into Interface, apiVersion, kind, name string) error {
267+
if got := into.GetAPIVersion(); got != apiVersion {
268+
return fmt.Errorf("expected API version '%s', got '%s'", apiVersion, got)
269+
}
270+
if got := into.GetKind(); got != kind {
271+
return fmt.Errorf("expected kind '%s', got '%s'", kind, got)
272+
}
273+
if got := into.GetName(); got != name {
274+
return fmt.Errorf("expected name '%s', got '%s'", name, got)
275+
}
276+
return nil
277+
}

0 commit comments

Comments
 (0)