Skip to content

Commit 3e2c729

Browse files
pkg: test ResourceDeletionProtection valid webhook
In this commit, we add unit tests for the `ValidatingAdmission` validation webhook specifically for the resource deletion protection feature. These tests ensure that the webhook correctly handles various admission scenarios: - Tests the behavior when decoding the request object fails, verifying that admission is denied with an appropriate error message. - Ensures that resources labeled with `DeletionProtectionAlways` are denied deletion, validating that the error message is returned. - Confirms that resources without the deletion protection label are allowed to be deleted without errors. - Validates that resources with other labels are not impacted and are allowed to be deleted. - Confirms that non-delete operations (e.g., create) are allowed by default. Signed-off-by: Mohamed Awnallah <[email protected]>
1 parent 32c2ef7 commit 3e2c729

File tree

1 file changed

+166
-0
lines changed

1 file changed

+166
-0
lines changed
+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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 resourcedeletionprotection
18+
19+
import (
20+
"context"
21+
"errors"
22+
"fmt"
23+
"net/http"
24+
"reflect"
25+
"testing"
26+
27+
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
28+
v1 "k8s.io/api/admission/v1"
29+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30+
"k8s.io/apimachinery/pkg/runtime"
31+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
32+
)
33+
34+
type fakeValidationDecoder struct {
35+
err error
36+
obj runtime.Object
37+
}
38+
39+
// Decode mocks the Decode method of admission.Decoder.
40+
func (f *fakeValidationDecoder) Decode(_ admission.Request, obj runtime.Object) error {
41+
if f.err != nil {
42+
return f.err
43+
}
44+
if f.obj != nil {
45+
reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(f.obj).Elem())
46+
}
47+
return nil
48+
}
49+
50+
// DecodeRaw mocks the DecodeRaw method of admission.Decoder.
51+
func (f *fakeValidationDecoder) DecodeRaw(rawObj runtime.RawExtension, obj runtime.Object) error {
52+
if f.err != nil {
53+
return f.err
54+
}
55+
if rawObj.Object != nil {
56+
reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(rawObj.Object).Elem())
57+
}
58+
return nil
59+
}
60+
61+
func TestValidatingAdmission_Handle(t *testing.T) {
62+
tests := []struct {
63+
name string
64+
decoder admission.Decoder
65+
req admission.Request
66+
want admission.Response
67+
}{
68+
{
69+
name: "Handle_DecodeRawError_DeniesAdmission",
70+
decoder: &fakeValidationDecoder{
71+
err: errors.New("decode error"),
72+
},
73+
req: admission.Request{
74+
AdmissionRequest: v1.AdmissionRequest{
75+
Operation: v1.Delete,
76+
},
77+
},
78+
want: admission.Errored(http.StatusBadRequest, errors.New("decode error")),
79+
},
80+
{
81+
name: "Handle_DeleteWithProtectionLabel_DeniesAdmission",
82+
decoder: &fakeValidationDecoder{},
83+
req: admission.Request{
84+
AdmissionRequest: v1.AdmissionRequest{
85+
Operation: v1.Delete,
86+
OldObject: runtime.RawExtension{
87+
Object: &unstructured.Unstructured{
88+
Object: map[string]interface{}{
89+
"metadata": map[string]interface{}{
90+
"labels": map[string]interface{}{
91+
workv1alpha2.DeletionProtectionLabelKey: workv1alpha2.DeletionProtectionAlways,
92+
},
93+
},
94+
},
95+
},
96+
},
97+
},
98+
},
99+
want: admission.Denied(fmt.Sprintf("This resource is protected, please make sure to remove the label: %s", workv1alpha2.DeletionProtectionLabelKey)),
100+
},
101+
{
102+
name: "Handle_DeleteWithoutLabel_AllowsAdmission",
103+
decoder: &fakeValidationDecoder{},
104+
req: admission.Request{
105+
AdmissionRequest: v1.AdmissionRequest{
106+
Operation: v1.Delete,
107+
OldObject: runtime.RawExtension{
108+
Object: &unstructured.Unstructured{
109+
Object: map[string]interface{}{
110+
"metadata": map[string]interface{}{
111+
"labels": map[string]interface{}{},
112+
},
113+
},
114+
},
115+
},
116+
},
117+
},
118+
want: admission.Allowed(""),
119+
},
120+
{
121+
name: "Handle_DeleteWithDifferentLabel_AllowsAdmission",
122+
decoder: &fakeValidationDecoder{},
123+
req: admission.Request{
124+
AdmissionRequest: v1.AdmissionRequest{
125+
Operation: v1.Delete,
126+
OldObject: runtime.RawExtension{
127+
Object: &unstructured.Unstructured{
128+
Object: map[string]interface{}{
129+
"metadata": map[string]interface{}{
130+
"labels": map[string]interface{}{
131+
"some-other-label": "some-value",
132+
},
133+
},
134+
},
135+
},
136+
},
137+
},
138+
},
139+
want: admission.Allowed(""),
140+
},
141+
{
142+
name: "Handle_CreateOperation_AllowsAdmission",
143+
decoder: &fakeValidationDecoder{
144+
err: errors.New("decode error"),
145+
},
146+
req: admission.Request{
147+
AdmissionRequest: v1.AdmissionRequest{
148+
Operation: v1.Create,
149+
},
150+
},
151+
want: admission.Allowed(""),
152+
},
153+
}
154+
155+
for _, tt := range tests {
156+
t.Run(tt.name, func(t *testing.T) {
157+
v := &ValidatingAdmission{
158+
Decoder: tt.decoder,
159+
}
160+
got := v.Handle(context.Background(), tt.req)
161+
if !reflect.DeepEqual(got, tt.want) {
162+
t.Errorf("Handle() = %v, want %v", got, tt.want)
163+
}
164+
})
165+
}
166+
}

0 commit comments

Comments
 (0)