1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17// These tests are in a separate package to break cyclic dependency in tests.
18// Unstructured type depends on unstructured converter package but we want to test how the converter handles
19// the Unstructured type so we need to import both.
20
21package runtime_test
22
23import (
24	encodingjson "encoding/json"
25	"fmt"
26	"reflect"
27	"strconv"
28	"testing"
29	"time"
30
31	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
32	"k8s.io/apimachinery/pkg/conversion"
33	"k8s.io/apimachinery/pkg/runtime"
34	"k8s.io/apimachinery/pkg/util/diff"
35	"k8s.io/apimachinery/pkg/util/json"
36
37	"github.com/stretchr/testify/assert"
38	"github.com/stretchr/testify/require"
39)
40
41var simpleEquality = conversion.EqualitiesOrDie(
42	func(a, b time.Time) bool {
43		return a.UTC() == b.UTC()
44	},
45)
46
47// Define a number of test types.
48type A struct {
49	A int    `json:"aa,omitempty"`
50	B string `json:"ab,omitempty"`
51	C bool   `json:"ac,omitempty"`
52}
53
54type B struct {
55	A A                 `json:"ba"`
56	B string            `json:"bb"`
57	C map[string]string `json:"bc"`
58	D []string          `json:"bd"`
59}
60
61type C struct {
62	A []A `json:"ca"`
63	B `json:",inline"`
64	C string         `json:"cc"`
65	D *int64         `json:"cd"`
66	E map[string]int `json:"ce"`
67	F []bool         `json:"cf"`
68	G []int          `json:"cg"`
69	H float32        `json:"ch"`
70	I []interface{}  `json:"ci"`
71}
72
73type D struct {
74	A []interface{} `json:"da"`
75}
76
77type E struct {
78	A interface{} `json:"ea"`
79}
80
81type F struct {
82	A string            `json:"fa"`
83	B map[string]string `json:"fb"`
84	C []A               `json:"fc"`
85	D int               `json:"fd"`
86	E float32           `json:"fe"`
87	F []string          `json:"ff"`
88	G []int             `json:"fg"`
89	H []bool            `json:"fh"`
90	I []float32         `json:"fi"`
91}
92
93type G struct {
94	CustomValue1   CustomValue    `json:"customValue1"`
95	CustomValue2   *CustomValue   `json:"customValue2"`
96	CustomPointer1 CustomPointer  `json:"customPointer1"`
97	CustomPointer2 *CustomPointer `json:"customPointer2"`
98}
99
100type CustomValue struct {
101	data []byte
102}
103
104// MarshalJSON has a value receiver on this type.
105func (c CustomValue) MarshalJSON() ([]byte, error) {
106	return c.data, nil
107}
108
109type CustomPointer struct {
110	data []byte
111}
112
113// MarshalJSON has a pointer receiver on this type.
114func (c *CustomPointer) MarshalJSON() ([]byte, error) {
115	return c.data, nil
116}
117
118func doRoundTrip(t *testing.T, item interface{}) {
119	data, err := json.Marshal(item)
120	if err != nil {
121		t.Errorf("Error when marshaling object: %v", err)
122		return
123	}
124
125	unstr := make(map[string]interface{})
126	err = json.Unmarshal(data, &unstr)
127	if err != nil {
128		t.Errorf("Error when unmarshaling to unstructured: %v", err)
129		return
130	}
131
132	data, err = json.Marshal(unstr)
133	if err != nil {
134		t.Errorf("Error when marshaling unstructured: %v", err)
135		return
136	}
137	unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
138	err = json.Unmarshal(data, unmarshalledObj)
139	if err != nil {
140		t.Errorf("Error when unmarshaling to object: %v", err)
141		return
142	}
143	if !reflect.DeepEqual(item, unmarshalledObj) {
144		t.Errorf("Object changed during JSON operations, diff: %v", diff.ObjectReflectDiff(item, unmarshalledObj))
145		return
146	}
147
148	// TODO: should be using mismatch detection but fails due to another error
149	newUnstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(item)
150	if err != nil {
151		t.Errorf("ToUnstructured failed: %v", err)
152		return
153	}
154
155	newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
156	err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(newUnstr, newObj)
157	if err != nil {
158		t.Errorf("FromUnstructured failed: %v", err)
159		return
160	}
161
162	if !reflect.DeepEqual(item, newObj) {
163		t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(item, newObj))
164	}
165}
166
167func TestRoundTrip(t *testing.T) {
168	intVal := int64(42)
169	testCases := []struct {
170		obj interface{}
171	}{
172		{
173			obj: &unstructured.UnstructuredList{
174				Object: map[string]interface{}{
175					"kind": "List",
176				},
177				// Not testing a list with nil Items because items is a non-optional field and hence
178				// is always marshaled into an empty array which is not equal to nil when unmarshalled and will fail.
179				// That is expected.
180				Items: []unstructured.Unstructured{},
181			},
182		},
183		{
184			obj: &unstructured.UnstructuredList{
185				Object: map[string]interface{}{
186					"kind": "List",
187				},
188				Items: []unstructured.Unstructured{
189					{
190						Object: map[string]interface{}{
191							"kind": "Pod",
192						},
193					},
194				},
195			},
196		},
197		{
198			obj: &unstructured.Unstructured{
199				Object: map[string]interface{}{
200					"kind": "Pod",
201				},
202			},
203		},
204		{
205			obj: &unstructured.Unstructured{
206				Object: map[string]interface{}{
207					"apiVersion": "v1",
208					"kind":       "Foo",
209					"metadata": map[string]interface{}{
210						"name": "foo1",
211					},
212				},
213			},
214		},
215		{
216			// This (among others) tests nil map, slice and pointer.
217			obj: &C{
218				C: "ccc",
219			},
220		},
221		{
222			// This (among others) tests empty map and slice.
223			obj: &C{
224				A: []A{},
225				C: "ccc",
226				E: map[string]int{},
227				I: []interface{}{},
228			},
229		},
230		{
231			obj: &C{
232				A: []A{
233					{
234						A: 1,
235						B: "11",
236						C: true,
237					},
238					{
239						A: 2,
240						B: "22",
241						C: false,
242					},
243				},
244				B: B{
245					A: A{
246						A: 3,
247						B: "33",
248					},
249					B: "bbb",
250					C: map[string]string{
251						"k1": "v1",
252						"k2": "v2",
253					},
254					D: []string{"s1", "s2"},
255				},
256				C: "ccc",
257				D: &intVal,
258				E: map[string]int{
259					"k1": 1,
260					"k2": 2,
261				},
262				F: []bool{true, false, false},
263				G: []int{1, 2, 5},
264				H: 3.3,
265				I: []interface{}{nil, nil, nil},
266			},
267		},
268		{
269			// Test slice of interface{} with empty slices.
270			obj: &D{
271				A: []interface{}{[]interface{}{}, []interface{}{}},
272			},
273		},
274		{
275			// Test slice of interface{} with different values.
276			obj: &D{
277				A: []interface{}{3.0, "3.0", nil},
278			},
279		},
280	}
281
282	for i := range testCases {
283		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
284			doRoundTrip(t, testCases[i].obj)
285		})
286	}
287}
288
289// Verifies that:
290// 1) serialized json -> object
291// 2) serialized json -> map[string]interface{} -> object
292// produces the same object.
293func doUnrecognized(t *testing.T, jsonData string, item interface{}, expectedErr error) {
294	unmarshalledObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
295	err := json.Unmarshal([]byte(jsonData), unmarshalledObj)
296	if (err != nil) != (expectedErr != nil) {
297		t.Errorf("Unexpected error when unmarshaling to object: %v, expected: %v", err, expectedErr)
298		return
299	}
300
301	unstr := make(map[string]interface{})
302	err = json.Unmarshal([]byte(jsonData), &unstr)
303	if err != nil {
304		t.Errorf("Error when unmarshaling to unstructured: %v", err)
305		return
306	}
307	newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface()
308	err = runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, newObj)
309	if (err != nil) != (expectedErr != nil) {
310		t.Errorf("Unexpected error in FromUnstructured: %v, expected: %v", err, expectedErr)
311	}
312
313	if expectedErr == nil && !reflect.DeepEqual(unmarshalledObj, newObj) {
314		t.Errorf("Object changed, diff: %v", diff.ObjectReflectDiff(unmarshalledObj, newObj))
315	}
316}
317
318func TestUnrecognized(t *testing.T) {
319	testCases := []struct {
320		data string
321		obj  interface{}
322		err  error
323	}{
324		{
325			data: "{\"da\":[3.0,\"3.0\",null]}",
326			obj:  &D{},
327		},
328		{
329			data: "{\"ea\":[3.0,\"3.0\",null]}",
330			obj:  &E{},
331		},
332		{
333			data: "{\"ea\":[null,null,null]}",
334			obj:  &E{},
335		},
336		{
337			data: "{\"ea\":[[],[null]]}",
338			obj:  &E{},
339		},
340		{
341			data: "{\"ea\":{\"a\":[],\"b\":null}}",
342			obj:  &E{},
343		},
344		{
345			data: "{\"fa\":\"fa\",\"fb\":{\"a\":\"a\"}}",
346			obj:  &F{},
347		},
348		{
349			data: "{\"fa\":\"fa\",\"fb\":{\"a\":null}}",
350			obj:  &F{},
351		},
352		{
353			data: "{\"fc\":[null]}",
354			obj:  &F{},
355		},
356		{
357			data: "{\"fc\":[{\"aa\":123,\"ab\":\"bbb\"}]}",
358			obj:  &F{},
359		},
360		{
361			// Only unknown fields
362			data: "{\"fx\":[{\"aa\":123,\"ab\":\"bbb\"}],\"fz\":123}",
363			obj:  &F{},
364		},
365		{
366			data: "{\"fc\":[{\"aa\":\"aaa\",\"ab\":\"bbb\"}]}",
367			obj:  &F{},
368			err:  fmt.Errorf("json: cannot unmarshal string into Go value of type int"),
369		},
370		{
371			data: "{\"fd\":123,\"fe\":3.5}",
372			obj:  &F{},
373		},
374		{
375			data: "{\"ff\":[\"abc\"],\"fg\":[123],\"fh\":[true,false]}",
376			obj:  &F{},
377		},
378		{
379			// Invalid string data
380			data: "{\"fa\":123}",
381			obj:  &F{},
382			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
383		},
384		{
385			// Invalid string data
386			data: "{\"fa\":13.5}",
387			obj:  &F{},
388			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
389		},
390		{
391			// Invalid string data
392			data: "{\"fa\":true}",
393			obj:  &F{},
394			err:  fmt.Errorf("json: cannot unmarshal bool into Go value of type string"),
395		},
396		{
397			// Invalid []string data
398			data: "{\"ff\":123}",
399			obj:  &F{},
400			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type []string"),
401		},
402		{
403			// Invalid []string data
404			data: "{\"ff\":3.5}",
405			obj:  &F{},
406			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type []string"),
407		},
408		{
409			// Invalid []string data
410			data: "{\"ff\":[123,345]}",
411			obj:  &F{},
412			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type string"),
413		},
414		{
415			// Invalid []int data
416			data: "{\"fg\":123}",
417			obj:  &F{},
418			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type []int"),
419		},
420		{
421			// Invalid []int data
422			data: "{\"fg\":\"abc\"}",
423			obj:  &F{},
424			err:  fmt.Errorf("json: cannot unmarshal string into Go value of type []int"),
425		},
426		{
427			// Invalid []int data
428			data: "{\"fg\":[\"abc\"]}",
429			obj:  &F{},
430			err:  fmt.Errorf("json: cannot unmarshal string into Go value of type int"),
431		},
432		{
433			// Invalid []int data
434			data: "{\"fg\":[3.5]}",
435			obj:  &F{},
436			err:  fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"),
437		},
438		{
439			// Invalid []int data
440			data: "{\"fg\":[true,false]}",
441			obj:  &F{},
442			err:  fmt.Errorf("json: cannot unmarshal number 3.5 into Go value of type int"),
443		},
444		{
445			// Invalid []bool data
446			data: "{\"fh\":123}",
447			obj:  &F{},
448			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type []bool"),
449		},
450		{
451			// Invalid []bool data
452			data: "{\"fh\":\"abc\"}",
453			obj:  &F{},
454			err:  fmt.Errorf("json: cannot unmarshal string into Go value of type []bool"),
455		},
456		{
457			// Invalid []bool data
458			data: "{\"fh\":[\"abc\"]}",
459			obj:  &F{},
460			err:  fmt.Errorf("json: cannot unmarshal string into Go value of type bool"),
461		},
462		{
463			// Invalid []bool data
464			data: "{\"fh\":[3.5]}",
465			obj:  &F{},
466			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type bool"),
467		},
468		{
469			// Invalid []bool data
470			data: "{\"fh\":[123]}",
471			obj:  &F{},
472			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type bool"),
473		},
474		{
475			// Invalid []float data
476			data: "{\"fi\":123}",
477			obj:  &F{},
478			err:  fmt.Errorf("json: cannot unmarshal number into Go value of type []float32"),
479		},
480		{
481			// Invalid []float data
482			data: "{\"fi\":\"abc\"}",
483			obj:  &F{},
484			err:  fmt.Errorf("json: cannot unmarshal string into Go value of type []float32"),
485		},
486		{
487			// Invalid []float data
488			data: "{\"fi\":[\"abc\"]}",
489			obj:  &F{},
490			err:  fmt.Errorf("json: cannot unmarshal string into Go value of type float32"),
491		},
492		{
493			// Invalid []float data
494			data: "{\"fi\":[true]}",
495			obj:  &F{},
496			err:  fmt.Errorf("json: cannot unmarshal bool into Go value of type float32"),
497		},
498	}
499
500	for _, tc := range testCases {
501		t.Run(tc.data, func(t *testing.T) {
502			doUnrecognized(t, tc.data, tc.obj, tc.err)
503		})
504	}
505}
506
507func TestDeepCopyJSON(t *testing.T) {
508	src := map[string]interface{}{
509		"a": nil,
510		"b": int64(123),
511		"c": map[string]interface{}{
512			"a": "b",
513		},
514		"d": []interface{}{
515			int64(1), int64(2),
516		},
517		"e": "estr",
518		"f": true,
519		"g": encodingjson.Number("123"),
520	}
521	deepCopy := runtime.DeepCopyJSON(src)
522	assert.Equal(t, src, deepCopy)
523}
524
525func TestFloatIntConversion(t *testing.T) {
526	unstr := map[string]interface{}{"fd": float64(3)}
527
528	var obj F
529	if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil {
530		t.Errorf("Unexpected error in FromUnstructured: %v", err)
531	}
532
533	data, err := json.Marshal(unstr)
534	if err != nil {
535		t.Fatalf("Error when marshaling unstructured: %v", err)
536	}
537	var unmarshalled F
538	if err := json.Unmarshal(data, &unmarshalled); err != nil {
539		t.Fatalf("Error when unmarshaling to object: %v", err)
540	}
541
542	if !reflect.DeepEqual(obj, unmarshalled) {
543		t.Errorf("Incorrect conversion, diff: %v", diff.ObjectReflectDiff(obj, unmarshalled))
544	}
545}
546
547func TestIntFloatConversion(t *testing.T) {
548	unstr := map[string]interface{}{"ch": int64(3)}
549
550	var obj C
551	if err := runtime.NewTestUnstructuredConverter(simpleEquality).FromUnstructured(unstr, &obj); err != nil {
552		t.Errorf("Unexpected error in FromUnstructured: %v", err)
553	}
554
555	data, err := json.Marshal(unstr)
556	if err != nil {
557		t.Fatalf("Error when marshaling unstructured: %v", err)
558	}
559	var unmarshalled C
560	if err := json.Unmarshal(data, &unmarshalled); err != nil {
561		t.Fatalf("Error when unmarshaling to object: %v", err)
562	}
563
564	if !reflect.DeepEqual(obj, unmarshalled) {
565		t.Errorf("Incorrect conversion, diff: %v", diff.ObjectReflectDiff(obj, unmarshalled))
566	}
567}
568
569func TestCustomToUnstructured(t *testing.T) {
570	testcases := []struct {
571		Data     string
572		Expected interface{}
573	}{
574		{Data: `null`, Expected: nil},
575		{Data: `true`, Expected: true},
576		{Data: `false`, Expected: false},
577		{Data: `[]`, Expected: []interface{}{}},
578		{Data: `[1]`, Expected: []interface{}{int64(1)}},
579		{Data: `{}`, Expected: map[string]interface{}{}},
580		{Data: `{"a":1}`, Expected: map[string]interface{}{"a": int64(1)}},
581		{Data: `0`, Expected: int64(0)},
582		{Data: `0.0`, Expected: float64(0)},
583	}
584
585	for _, tc := range testcases {
586		tc := tc
587		t.Run(tc.Data, func(t *testing.T) {
588			t.Parallel()
589			result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(&G{
590				CustomValue1:   CustomValue{data: []byte(tc.Data)},
591				CustomValue2:   &CustomValue{data: []byte(tc.Data)},
592				CustomPointer1: CustomPointer{data: []byte(tc.Data)},
593				CustomPointer2: &CustomPointer{data: []byte(tc.Data)},
594			})
595			require.NoError(t, err)
596			for field, fieldResult := range result {
597				assert.Equal(t, tc.Expected, fieldResult, field)
598			}
599		})
600	}
601}
602
603func TestCustomToUnstructuredTopLevel(t *testing.T) {
604	// Only objects are supported at the top level
605	topLevelCases := []interface{}{
606		&CustomValue{data: []byte(`{"a":1}`)},
607		&CustomPointer{data: []byte(`{"a":1}`)},
608	}
609	expected := map[string]interface{}{"a": int64(1)}
610	for i, obj := range topLevelCases {
611		obj := obj
612		t.Run(strconv.Itoa(i), func(t *testing.T) {
613			t.Parallel()
614			result, err := runtime.NewTestUnstructuredConverter(simpleEquality).ToUnstructured(obj)
615			require.NoError(t, err)
616			assert.Equal(t, expected, result)
617		})
618	}
619}
620