1
16
17 package objectmeta
18
19 import (
20 "bytes"
21 "reflect"
22 "strings"
23 "testing"
24
25 "github.com/google/go-cmp/cmp"
26
27 "k8s.io/apimachinery/pkg/util/json"
28
29 structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
30 )
31
32 func TestCoerce(t *testing.T) {
33 tests := []struct {
34 name string
35 json string
36 includeRoot bool
37 dropInvalidFields bool
38 schema *structuralschema.Structural
39 expected string
40 expectedError bool
41 expectedUnknownFields []string
42 }{
43 {name: "empty", json: "null", schema: nil, expected: "null"},
44 {name: "scalar", json: "4", schema: &structuralschema.Structural{}, expected: "4"},
45 {name: "scalar array", json: "[1,2]", schema: &structuralschema.Structural{
46 Items: &structuralschema.Structural{},
47 }, expected: "[1,2]"},
48 {name: "x-kubernetes-embedded-resource", json: `
49 {
50 "apiVersion": "foo/v1",
51 "kind": "Foo",
52 "metadata": {
53 "name": "instance",
54 "unspecified": "bar"
55 },
56 "unspecified":"bar",
57 "pruned": {
58 "apiVersion": "foo/v1",
59 "kind": "Foo",
60 "unspecified": "bar",
61 "metadata": {
62 "name": "instance",
63 "unspecified": "bar"
64 },
65 "spec": {
66 "unspecified": "bar"
67 }
68 },
69 "preserving": {
70 "apiVersion": "foo/v1",
71 "kind": "Foo",
72 "unspecified": "bar",
73 "metadata": {
74 "name": "instance",
75 "unspecified": "bar"
76 },
77 "spec": {
78 "unspecified": "bar"
79 }
80 },
81 "nested": {
82 "apiVersion": "foo/v1",
83 "kind": "Foo",
84 "unspecified": "bar",
85 "metadata": {
86 "name": "instance",
87 "unspecified": "bar"
88 },
89 "spec": {
90 "unspecified": "bar",
91 "embedded": {
92 "apiVersion": "foo/v1",
93 "kind": "Foo",
94 "unspecified": "bar",
95 "metadata": {
96 "name": "instance",
97 "unspecified": "bar"
98 },
99 "spec": {
100 "unspecified": "bar"
101 }
102 }
103 }
104 }
105 }
106 `, schema: &structuralschema.Structural{
107 Generic: structuralschema.Generic{Type: "object"},
108 Properties: map[string]structuralschema.Structural{
109 "pruned": {
110 Generic: structuralschema.Generic{Type: "object"},
111 Extensions: structuralschema.Extensions{
112 XEmbeddedResource: true,
113 },
114 Properties: map[string]structuralschema.Structural{
115 "spec": {
116 Generic: structuralschema.Generic{Type: "object"},
117 },
118 },
119 },
120 "preserving": {
121 Generic: structuralschema.Generic{Type: "object"},
122 Extensions: structuralschema.Extensions{
123 XEmbeddedResource: true,
124 XPreserveUnknownFields: true,
125 },
126 },
127 "nested": {
128 Generic: structuralschema.Generic{Type: "object"},
129 Extensions: structuralschema.Extensions{
130 XEmbeddedResource: true,
131 },
132 Properties: map[string]structuralschema.Structural{
133 "spec": {
134 Generic: structuralschema.Generic{Type: "object"},
135 Properties: map[string]structuralschema.Structural{
136 "embedded": {
137 Generic: structuralschema.Generic{Type: "object"},
138 Extensions: structuralschema.Extensions{
139 XEmbeddedResource: true,
140 },
141 Properties: map[string]structuralschema.Structural{
142 "spec": {
143 Generic: structuralschema.Generic{Type: "object"},
144 },
145 },
146 },
147 },
148 },
149 },
150 },
151 },
152 }, expected: `
153 {
154 "apiVersion": "foo/v1",
155 "kind": "Foo",
156 "metadata": {
157 "name": "instance",
158 "unspecified": "bar"
159 },
160 "unspecified":"bar",
161 "pruned": {
162 "apiVersion": "foo/v1",
163 "kind": "Foo",
164 "unspecified": "bar",
165 "metadata": {
166 "name": "instance"
167 },
168 "spec": {
169 "unspecified": "bar"
170 }
171 },
172 "preserving": {
173 "apiVersion": "foo/v1",
174 "kind": "Foo",
175 "unspecified": "bar",
176 "metadata": {
177 "name": "instance"
178 },
179 "spec": {
180 "unspecified": "bar"
181 }
182 },
183 "nested": {
184 "apiVersion": "foo/v1",
185 "kind": "Foo",
186 "unspecified": "bar",
187 "metadata": {
188 "name": "instance"
189 },
190 "spec": {
191 "unspecified": "bar",
192 "embedded": {
193 "apiVersion": "foo/v1",
194 "kind": "Foo",
195 "unspecified": "bar",
196 "metadata": {
197 "name": "instance"
198 },
199 "spec": {
200 "unspecified": "bar"
201 }
202 }
203 }
204 }
205 }
206 `, expectedUnknownFields: []string{
207 "nested.metadata.unspecified",
208 "nested.spec.embedded.metadata.unspecified",
209 "preserving.metadata.unspecified",
210 "pruned.metadata.unspecified",
211 }},
212 {name: "x-kubernetes-embedded-resource, with includeRoot=true", json: `
213 {
214 "apiVersion": "foo/v1",
215 "kind": "Foo",
216 "metadata": {
217 "name": "instance",
218 "unspecified": "bar"
219 },
220 "unspecified":"bar",
221 "pruned": {
222 "apiVersion": "foo/v1",
223 "kind": "Foo",
224 "unspecified": "bar",
225 "metadata": {
226 "name": "instance",
227 "unspecified": "bar"
228 },
229 "spec": {
230 "unspecified": "bar"
231 }
232 },
233 "preserving": {
234 "apiVersion": "foo/v1",
235 "kind": "Foo",
236 "unspecified": "bar",
237 "metadata": {
238 "name": "instance",
239 "unspecified": "bar"
240 },
241 "spec": {
242 "unspecified": "bar"
243 }
244 },
245 "nested": {
246 "apiVersion": "foo/v1",
247 "kind": "Foo",
248 "unspecified": "bar",
249 "metadata": {
250 "name": "instance",
251 "unspecified": "bar"
252 },
253 "spec": {
254 "unspecified": "bar",
255 "embedded": {
256 "apiVersion": "foo/v1",
257 "kind": "Foo",
258 "unspecified": "bar",
259 "metadata": {
260 "name": "instance",
261 "unspecified": "bar"
262 },
263 "spec": {
264 "unspecified": "bar"
265 }
266 }
267 }
268 }
269 }
270 `, includeRoot: true, schema: &structuralschema.Structural{
271 Generic: structuralschema.Generic{Type: "object"},
272 Properties: map[string]structuralschema.Structural{
273 "pruned": {
274 Generic: structuralschema.Generic{Type: "object"},
275 Extensions: structuralschema.Extensions{
276 XEmbeddedResource: true,
277 },
278 Properties: map[string]structuralschema.Structural{
279 "spec": {
280 Generic: structuralschema.Generic{Type: "object"},
281 },
282 },
283 },
284 "preserving": {
285 Generic: structuralschema.Generic{Type: "object"},
286 Extensions: structuralschema.Extensions{
287 XEmbeddedResource: true,
288 XPreserveUnknownFields: true,
289 },
290 },
291 "nested": {
292 Generic: structuralschema.Generic{Type: "object"},
293 Extensions: structuralschema.Extensions{
294 XEmbeddedResource: true,
295 },
296 Properties: map[string]structuralschema.Structural{
297 "spec": {
298 Generic: structuralschema.Generic{Type: "object"},
299 Properties: map[string]structuralschema.Structural{
300 "embedded": {
301 Generic: structuralschema.Generic{Type: "object"},
302 Extensions: structuralschema.Extensions{
303 XEmbeddedResource: true,
304 },
305 Properties: map[string]structuralschema.Structural{
306 "spec": {
307 Generic: structuralschema.Generic{Type: "object"},
308 },
309 },
310 },
311 },
312 },
313 },
314 },
315 },
316 }, expected: `
317 {
318 "apiVersion": "foo/v1",
319 "kind": "Foo",
320 "metadata": {
321 "name": "instance"
322 },
323 "unspecified":"bar",
324 "pruned": {
325 "apiVersion": "foo/v1",
326 "kind": "Foo",
327 "unspecified": "bar",
328 "metadata": {
329 "name": "instance"
330 },
331 "spec": {
332 "unspecified": "bar"
333 }
334 },
335 "preserving": {
336 "apiVersion": "foo/v1",
337 "kind": "Foo",
338 "unspecified": "bar",
339 "metadata": {
340 "name": "instance"
341 },
342 "spec": {
343 "unspecified": "bar"
344 }
345 },
346 "nested": {
347 "apiVersion": "foo/v1",
348 "kind": "Foo",
349 "unspecified": "bar",
350 "metadata": {
351 "name": "instance"
352 },
353 "spec": {
354 "unspecified": "bar",
355 "embedded": {
356 "apiVersion": "foo/v1",
357 "kind": "Foo",
358 "unspecified": "bar",
359 "metadata": {
360 "name": "instance"
361 },
362 "spec": {
363 "unspecified": "bar"
364 }
365 }
366 }
367 }
368 }`, expectedUnknownFields: []string{
369 "metadata.unspecified",
370 "nested.metadata.unspecified",
371 "nested.spec.embedded.metadata.unspecified",
372 "preserving.metadata.unspecified",
373 "pruned.metadata.unspecified",
374 }},
375 {name: "without name", json: `
376 {
377 "apiVersion": "foo/v1",
378 "kind": "Foo",
379 "metadata": {
380 "name": "instance"
381 },
382 "pruned": {
383 "apiVersion": "foo/v1",
384 "kind": "Foo",
385 "metadata": {
386 "namespace": "kube-system"
387 }
388 }
389 }
390 `, schema: &structuralschema.Structural{
391 Generic: structuralschema.Generic{Type: "object"},
392 Properties: map[string]structuralschema.Structural{
393 "pruned": {
394 Generic: structuralschema.Generic{Type: "object"},
395 Extensions: structuralschema.Extensions{
396 XEmbeddedResource: true,
397 },
398 },
399 },
400 }, expected: `
401 {
402 "apiVersion": "foo/v1",
403 "kind": "Foo",
404 "metadata": {
405 "name": "instance"
406 },
407 "pruned": {
408 "apiVersion": "foo/v1",
409 "kind": "Foo",
410 "metadata": {
411 "namespace": "kube-system"
412 }
413 }
414 }
415 `},
416 {name: "x-kubernetes-embedded-resource, with dropInvalidFields=true", json: `
417 {
418 "apiVersion": "foo/v1",
419 "kind": "Foo",
420 "metadata": {
421 "name": "instance"
422 },
423 "pruned": {
424 "apiVersion": 42,
425 "kind": 42,
426 "metadata": {
427 "name": "instance",
428 "namespace": ["abc"],
429 "labels": {
430 "foo": 42
431 }
432 }
433 }
434 }
435 `, dropInvalidFields: true, schema: &structuralschema.Structural{
436 Generic: structuralschema.Generic{Type: "object"},
437 Properties: map[string]structuralschema.Structural{
438 "pruned": {
439 Generic: structuralschema.Generic{Type: "object"},
440 Extensions: structuralschema.Extensions{
441 XEmbeddedResource: true,
442 },
443 Properties: map[string]structuralschema.Structural{
444 "spec": {
445 Generic: structuralschema.Generic{Type: "object"},
446 },
447 },
448 },
449 },
450 }, expected: `
451 {
452 "apiVersion": "foo/v1",
453 "kind": "Foo",
454 "metadata": {
455 "name": "instance"
456 },
457 "pruned": {
458 "metadata": {
459 "name": "instance"
460 }
461 }
462 }
463 `},
464 {name: "invalid metadata type, with dropInvalidFields=true", json: `
465 {
466 "apiVersion": "foo/v1",
467 "kind": "Foo",
468 "metadata": {
469 "name": "instance"
470 },
471 "pruned": {
472 "apiVersion": 42,
473 "kind": 42,
474 "metadata": [42]
475 }
476 }
477 `, dropInvalidFields: true, schema: &structuralschema.Structural{
478 Generic: structuralschema.Generic{Type: "object"},
479 Properties: map[string]structuralschema.Structural{
480 "pruned": {
481 Generic: structuralschema.Generic{Type: "object"},
482 Extensions: structuralschema.Extensions{
483 XEmbeddedResource: true,
484 },
485 },
486 },
487 }, expected: `
488 {
489 "apiVersion": "foo/v1",
490 "kind": "Foo",
491 "metadata": {
492 "name": "instance"
493 },
494 "pruned": {
495 "metadata": [42]
496 }
497 }
498 `},
499 }
500 for _, tt := range tests {
501 t.Run(tt.name, func(t *testing.T) {
502 var in interface{}
503 if err := json.Unmarshal([]byte(tt.json), &in); err != nil {
504 t.Fatal(err)
505 }
506
507 var expected interface{}
508 if err := json.Unmarshal([]byte(tt.expected), &expected); err != nil {
509 t.Fatal(err)
510 }
511
512 err, unknownFields := CoerceWithOptions(nil, in, tt.schema, tt.includeRoot, CoerceOptions{
513 DropInvalidFields: tt.dropInvalidFields,
514 ReturnUnknownFieldPaths: true,
515 })
516 if tt.expectedError && err == nil {
517 t.Error("expected error, but did not get any")
518 } else if !tt.expectedError && err != nil {
519 t.Errorf("expected no error, but got: %v", err)
520 } else if !reflect.DeepEqual(in, expected) {
521 var buf bytes.Buffer
522 enc := json.NewEncoder(&buf)
523 enc.SetIndent("", " ")
524 err := enc.Encode(in)
525 if err != nil {
526 t.Fatalf("unexpected result mashalling error: %v", err)
527 }
528 t.Errorf("expected: %s\ngot: %s\ndiff: %s", tt.expected, buf.String(), cmp.Diff(expected, in))
529 }
530 if !reflect.DeepEqual(unknownFields, tt.expectedUnknownFields) {
531 t.Errorf("expected unknown fields:\n\t%v\ngot:\n\t%v\n", strings.Join(tt.expectedUnknownFields, "\n\t"), strings.Join(unknownFields, "\n\t"))
532 }
533 })
534 }
535 }
536
View as plain text