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