knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/json/decode_test.go (about) 1 /* 2 Copyright 2021 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 json 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "knative.dev/pkg/apis" 29 "knative.dev/pkg/webhook/resourcesemantics" 30 ) 31 32 func TestDecode_KnownFields(t *testing.T) { 33 // We ignore type meta since inline only works with k8s yaml parsers 34 cases := []struct { 35 name string 36 input string 37 38 want fixture 39 wantErr bool 40 }{{ 41 name: "no metadata", 42 input: `{}`, 43 want: fixture{}, 44 }, { 45 name: "known fields", 46 input: `{ "metadata":{"name":"some-name", "namespace":"some-namespace"} }`, 47 want: fixture{ 48 ObjectMeta: metav1.ObjectMeta{ 49 Name: "some-name", 50 Namespace: "some-namespace", 51 }, 52 }, 53 }, { 54 name: "with spec", 55 input: `{ 56 "metadata":{"name":"some-name", "namespace":"some-namespace"}, 57 "spec":{"key":"value"} 58 }`, 59 want: fixture{ 60 ObjectMeta: metav1.ObjectMeta{ 61 Name: "some-name", 62 Namespace: "some-namespace", 63 }, 64 Spec: map[string]string{"key": "value"}, 65 }, 66 }, { 67 name: "unknown metadata field", 68 input: `{ 69 "metadata":{"name":"some-name", "namespace":"some-namespace", "bomba":"boom"}, 70 "spec":{"key":"value"} 71 }`, 72 want: fixture{ 73 ObjectMeta: metav1.ObjectMeta{ 74 Name: "some-name", 75 Namespace: "some-namespace", 76 }, 77 Spec: map[string]string{"key": "value"}, 78 }, 79 }, { 80 name: "unknown field top level", 81 input: `{ 82 "metadata":{"name":"some-name", "namespace":"some-namespace"}, 83 "bomba": "boom", 84 "spec":{"key":"value"} 85 }`, 86 wantErr: true, 87 }, { 88 name: "multiple metadata keys in the JSON", 89 input: `{ 90 "metadata":{"name":"some-name", "namespace":"some-namespace", "bomba":"boom"}, 91 "spec":{"metadata":"value"} 92 }`, 93 want: fixture{ 94 ObjectMeta: metav1.ObjectMeta{ 95 Name: "some-name", 96 Namespace: "some-namespace", 97 }, 98 Spec: map[string]string{"metadata": "value"}, 99 }, 100 }, { 101 name: "nested object in metadata", 102 input: `{ 103 "metadata":{"name":"some-name", "namespace":"some-namespace", "labels":{"key":"value"}} 104 }`, 105 want: fixture{ 106 ObjectMeta: metav1.ObjectMeta{ 107 Name: "some-name", 108 Namespace: "some-namespace", 109 Labels: map[string]string{ 110 "key": "value", 111 }, 112 }, 113 }, 114 }, { 115 name: "nested array in metadata", 116 input: `{ 117 "metadata":{"name":"some-name", "namespace":"some-namespace", "finalizers":["first","second"]} 118 }`, 119 want: fixture{ 120 ObjectMeta: metav1.ObjectMeta{ 121 Name: "some-name", 122 Namespace: "some-namespace", 123 Finalizers: []string{"first", "second"}, 124 }, 125 }, 126 }, { 127 name: "bad input", 128 // note use two characters so our decoder fails on the second token lookup 129 input: "{{", 130 wantErr: true, 131 }} 132 133 for _, tc := range cases { 134 t.Run(tc.name, func(t *testing.T) { 135 got := &fixture{} 136 err := Decode([]byte(tc.input), got, true) 137 138 if tc.wantErr && err == nil { 139 t.Fatal("DecodeTo() expected an error") 140 } else if !tc.wantErr && err != nil { 141 t.Fatal("unexpected error", err) 142 } 143 144 // Don't bother checking against the fixture if 145 // we expected an error 146 if tc.wantErr { 147 return 148 } 149 150 if diff := cmp.Diff(&tc.want, got); diff != "" { 151 t.Error("unexpected diff", diff) 152 } 153 }) 154 } 155 } 156 157 func TestDecode_AllowUnknownFields(t *testing.T) { 158 input := `{ 159 "metadata":{"name":"some-name", "namespace":"some-namespace"}, 160 "bomba": "boom", 161 "spec": {"some":"value"} 162 }` 163 164 got := &fixture{} 165 want := &fixture{ 166 ObjectMeta: metav1.ObjectMeta{ 167 Name: "some-name", 168 Namespace: "some-namespace", 169 }, 170 Spec: map[string]string{"some": "value"}, 171 } 172 173 err := Decode([]byte(input), got, false) 174 if err != nil { 175 t.Fatal("unexpected error", err) 176 } 177 178 if diff := cmp.Diff(want, got); diff != "" { 179 t.Error("unexpected diff", diff) 180 } 181 } 182 183 // Note: this test is paired with the failingFixture and knows 184 // that the implementation of Decode parses the json in two passes 185 // the first being with an empty metadata '{}' - the second being the real 186 // input 187 func TestDecode_UnmarshalMetadataFailed(t *testing.T) { 188 input := `{ "metadata":{"name":"some-name", "namespace":"some-namespace"} }` 189 err := Decode([]byte(input), &failingFixture{}, true) 190 if err == nil { 191 t.Fatal("expected error") 192 } 193 } 194 195 type fixture struct { 196 // Our decoder doesn't support `inline` that's a sig.k8s.io/yaml feature 197 // So we skip parsing this property 198 metav1.TypeMeta `json:"-"` 199 metav1.ObjectMeta `json:"metadata"` 200 Spec map[string]string `json:"spec"` 201 } 202 203 var _ resourcesemantics.GenericCRD = (*fixture)(nil) 204 205 func (f *fixture) DeepCopyObject() runtime.Object { 206 panic("not implemented") 207 } 208 209 func (f *fixture) SetDefaults(context.Context) { 210 panic("not implemented") 211 } 212 213 func (f *fixture) Validate(context.Context) *apis.FieldError { 214 panic("not implemented") 215 } 216 217 type failingFixture struct { 218 fixture 219 Meta failingMeta `json:"metadata"` 220 } 221 222 type failingMeta struct { 223 metav1.ObjectMeta 224 } 225 226 func (f *failingMeta) UnmarshalJSON(bites []byte) error { 227 if bytes.Equal([]byte("{}"), bites) { 228 return nil 229 } 230 return errors.New("bomba") 231 }