1// Copyright 2015 Google LLC
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package gensupport
6
7import (
8	"encoding/json"
9	"reflect"
10	"testing"
11
12	"google.golang.org/api/googleapi"
13)
14
15type schema struct {
16	// Basic types
17	B    bool    `json:"b,omitempty"`
18	F    float64 `json:"f,omitempty"`
19	I    int64   `json:"i,omitempty"`
20	Istr int64   `json:"istr,omitempty,string"`
21	Str  string  `json:"str,omitempty"`
22
23	// Pointers to basic types
24	PB    *bool    `json:"pb,omitempty"`
25	PF    *float64 `json:"pf,omitempty"`
26	PI    *int64   `json:"pi,omitempty"`
27	PIStr *int64   `json:"pistr,omitempty,string"`
28	PStr  *string  `json:"pstr,omitempty"`
29
30	// Other types
31	Int64s        googleapi.Int64s         `json:"i64s,omitempty"`
32	S             []int                    `json:"s,omitempty"`
33	M             map[string]string        `json:"m,omitempty"`
34	Any           interface{}              `json:"any,omitempty"`
35	Child         *child                   `json:"child,omitempty"`
36	MapToAnyArray map[string][]interface{} `json:"maptoanyarray,omitempty"`
37
38	ForceSendFields []string `json:"-"`
39	NullFields      []string `json:"-"`
40}
41
42type child struct {
43	B bool `json:"childbool,omitempty"`
44}
45
46type testCase struct {
47	s    schema
48	want string
49}
50
51func TestBasics(t *testing.T) {
52	for _, tc := range []testCase{
53		{
54			s:    schema{},
55			want: `{}`,
56		},
57		{
58			s: schema{
59				ForceSendFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"},
60			},
61			want: `{"b":false,"f":0.0,"i":0,"istr":"0","str":""}`,
62		},
63		{
64			s: schema{
65				NullFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"},
66			},
67			want: `{"b":null,"f":null,"i":null,"istr":null,"str":null,"pb":null,"pf":null,"pi":null,"pistr":null,"pstr":null}`,
68		},
69		{
70			s: schema{
71				B:     true,
72				F:     1.2,
73				I:     1,
74				Istr:  2,
75				Str:   "a",
76				PB:    googleapi.Bool(true),
77				PF:    googleapi.Float64(1.2),
78				PI:    googleapi.Int64(int64(1)),
79				PIStr: googleapi.Int64(int64(2)),
80				PStr:  googleapi.String("a"),
81			},
82			want: `{"b":true,"f":1.2,"i":1,"istr":"2","str":"a","pb":true,"pf":1.2,"pi":1,"pistr":"2","pstr":"a"}`,
83		},
84		{
85			s: schema{
86				B:     false,
87				F:     0.0,
88				I:     0,
89				Istr:  0,
90				Str:   "",
91				PB:    googleapi.Bool(false),
92				PF:    googleapi.Float64(0.0),
93				PI:    googleapi.Int64(int64(0)),
94				PIStr: googleapi.Int64(int64(0)),
95				PStr:  googleapi.String(""),
96			},
97			want: `{"pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`,
98		},
99		{
100			s: schema{
101				B:               false,
102				F:               0.0,
103				I:               0,
104				Istr:            0,
105				Str:             "",
106				PB:              googleapi.Bool(false),
107				PF:              googleapi.Float64(0.0),
108				PI:              googleapi.Int64(int64(0)),
109				PIStr:           googleapi.Int64(int64(0)),
110				PStr:            googleapi.String(""),
111				ForceSendFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"},
112			},
113			want: `{"b":false,"f":0.0,"i":0,"istr":"0","str":"","pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`,
114		},
115		{
116			s: schema{
117				B:          false,
118				F:          0.0,
119				I:          0,
120				Istr:       0,
121				Str:        "",
122				PB:         googleapi.Bool(false),
123				PF:         googleapi.Float64(0.0),
124				PI:         googleapi.Int64(int64(0)),
125				PIStr:      googleapi.Int64(int64(0)),
126				PStr:       googleapi.String(""),
127				NullFields: []string{"B", "F", "I", "Istr", "Str"},
128			},
129			want: `{"b":null,"f":null,"i":null,"istr":null,"str":null,"pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`,
130		},
131	} {
132		checkMarshalJSON(t, tc)
133	}
134}
135
136func TestSliceFields(t *testing.T) {
137	for _, tc := range []testCase{
138		{
139			s:    schema{},
140			want: `{}`,
141		},
142		{
143			s:    schema{S: []int{}, Int64s: googleapi.Int64s{}},
144			want: `{}`,
145		},
146		{
147			s:    schema{S: []int{1}, Int64s: googleapi.Int64s{1}},
148			want: `{"s":[1],"i64s":["1"]}`,
149		},
150		{
151			s: schema{
152				ForceSendFields: []string{"S", "Int64s"},
153			},
154			want: `{"s":[],"i64s":[]}`,
155		},
156		{
157			s: schema{
158				S:               []int{},
159				Int64s:          googleapi.Int64s{},
160				ForceSendFields: []string{"S", "Int64s"},
161			},
162			want: `{"s":[],"i64s":[]}`,
163		},
164		{
165			s: schema{
166				S:               []int{1},
167				Int64s:          googleapi.Int64s{1},
168				ForceSendFields: []string{"S", "Int64s"},
169			},
170			want: `{"s":[1],"i64s":["1"]}`,
171		},
172		{
173			s: schema{
174				NullFields: []string{"S", "Int64s"},
175			},
176			want: `{"s":null,"i64s":null}`,
177		},
178	} {
179		checkMarshalJSON(t, tc)
180	}
181}
182
183func TestMapField(t *testing.T) {
184	for _, tc := range []testCase{
185		{
186			s:    schema{},
187			want: `{}`,
188		},
189		{
190			s:    schema{M: make(map[string]string)},
191			want: `{}`,
192		},
193		{
194			s:    schema{M: map[string]string{"a": "b"}},
195			want: `{"m":{"a":"b"}}`,
196		},
197		{
198			s: schema{
199				ForceSendFields: []string{"M"},
200			},
201			want: `{"m":{}}`,
202		},
203		{
204			s: schema{
205				NullFields: []string{"M"},
206			},
207			want: `{"m":null}`,
208		},
209		{
210			s: schema{
211				M:               make(map[string]string),
212				ForceSendFields: []string{"M"},
213			},
214			want: `{"m":{}}`,
215		},
216		{
217			s: schema{
218				M:          make(map[string]string),
219				NullFields: []string{"M"},
220			},
221			want: `{"m":null}`,
222		},
223		{
224			s: schema{
225				M:               map[string]string{"a": "b"},
226				ForceSendFields: []string{"M"},
227			},
228			want: `{"m":{"a":"b"}}`,
229		},
230		{
231			s: schema{
232				M:          map[string]string{"a": "b"},
233				NullFields: []string{"M.a", "M."},
234			},
235			want: `{"m": {"a": null, "":null}}`,
236		},
237		{
238			s: schema{
239				M:          map[string]string{"a": "b"},
240				NullFields: []string{"M.c"},
241			},
242			want: `{"m": {"a": "b", "c": null}}`,
243		},
244		{
245			s: schema{
246				NullFields:      []string{"M.a"},
247				ForceSendFields: []string{"M"},
248			},
249			want: `{"m": {"a": null}}`,
250		},
251		{
252			s: schema{
253				NullFields: []string{"M.a"},
254			},
255			want: `{}`,
256		},
257	} {
258		checkMarshalJSON(t, tc)
259	}
260}
261
262func TestMapToAnyArray(t *testing.T) {
263	for _, tc := range []testCase{
264		{
265			s:    schema{},
266			want: `{}`,
267		},
268		{
269			s:    schema{MapToAnyArray: make(map[string][]interface{})},
270			want: `{}`,
271		},
272		{
273			s: schema{
274				MapToAnyArray: map[string][]interface{}{
275					"a": {2, "b"},
276				},
277			},
278			want: `{"maptoanyarray":{"a":[2, "b"]}}`,
279		},
280		{
281			s: schema{
282				MapToAnyArray: map[string][]interface{}{
283					"a": nil,
284				},
285			},
286			want: `{"maptoanyarray":{"a": null}}`,
287		},
288		{
289			s: schema{
290				MapToAnyArray: map[string][]interface{}{
291					"a": {nil},
292				},
293			},
294			want: `{"maptoanyarray":{"a":[null]}}`,
295		},
296		{
297			s: schema{
298				ForceSendFields: []string{"MapToAnyArray"},
299			},
300			want: `{"maptoanyarray":{}}`,
301		},
302		{
303			s: schema{
304				NullFields: []string{"MapToAnyArray"},
305			},
306			want: `{"maptoanyarray":null}`,
307		},
308		{
309			s: schema{
310				MapToAnyArray:   make(map[string][]interface{}),
311				ForceSendFields: []string{"MapToAnyArray"},
312			},
313			want: `{"maptoanyarray":{}}`,
314		},
315		{
316			s: schema{
317				MapToAnyArray: map[string][]interface{}{
318					"a": {2, "b"},
319				},
320				ForceSendFields: []string{"MapToAnyArray"},
321			},
322			want: `{"maptoanyarray":{"a":[2, "b"]}}`,
323		},
324	} {
325		checkMarshalJSON(t, tc)
326	}
327}
328
329type anyType struct {
330	Field int
331}
332
333func (a anyType) MarshalJSON() ([]byte, error) {
334	return []byte(`"anyType value"`), nil
335}
336
337func TestAnyField(t *testing.T) {
338	// ForceSendFields has no effect on nil interfaces and interfaces that contain nil pointers.
339	var nilAny *anyType
340	for _, tc := range []testCase{
341		{
342			s:    schema{},
343			want: `{}`,
344		},
345		{
346			s:    schema{Any: nilAny},
347			want: `{"any": null}`,
348		},
349		{
350			s:    schema{Any: &anyType{}},
351			want: `{"any":"anyType value"}`,
352		},
353		{
354			s:    schema{Any: anyType{}},
355			want: `{"any":"anyType value"}`,
356		},
357		{
358			s: schema{
359				ForceSendFields: []string{"Any"},
360			},
361			want: `{}`,
362		},
363		{
364			s: schema{
365				NullFields: []string{"Any"},
366			},
367			want: `{"any":null}`,
368		},
369		{
370			s: schema{
371				Any:             nilAny,
372				ForceSendFields: []string{"Any"},
373			},
374			want: `{"any": null}`,
375		},
376		{
377			s: schema{
378				Any:             &anyType{},
379				ForceSendFields: []string{"Any"},
380			},
381			want: `{"any":"anyType value"}`,
382		},
383		{
384			s: schema{
385				Any:             anyType{},
386				ForceSendFields: []string{"Any"},
387			},
388			want: `{"any":"anyType value"}`,
389		},
390	} {
391		checkMarshalJSON(t, tc)
392	}
393}
394
395func TestSubschema(t *testing.T) {
396	// Subschemas are always stored as pointers, so ForceSendFields has no effect on them.
397	for _, tc := range []testCase{
398		{
399			s:    schema{},
400			want: `{}`,
401		},
402		{
403			s: schema{
404				ForceSendFields: []string{"Child"},
405			},
406			want: `{}`,
407		},
408		{
409			s: schema{
410				NullFields: []string{"Child"},
411			},
412			want: `{"child":null}`,
413		},
414		{
415			s:    schema{Child: &child{}},
416			want: `{"child":{}}`,
417		},
418		{
419			s: schema{
420				Child:           &child{},
421				ForceSendFields: []string{"Child"},
422			},
423			want: `{"child":{}}`,
424		},
425		{
426			s:    schema{Child: &child{B: true}},
427			want: `{"child":{"childbool":true}}`,
428		},
429
430		{
431			s: schema{
432				Child:           &child{B: true},
433				ForceSendFields: []string{"Child"},
434			},
435			want: `{"child":{"childbool":true}}`,
436		},
437	} {
438		checkMarshalJSON(t, tc)
439	}
440}
441
442// checkMarshalJSON verifies that calling schemaToMap on tc.s yields a result which is equivalent to tc.want.
443func checkMarshalJSON(t *testing.T, tc testCase) {
444	doCheckMarshalJSON(t, tc.s, tc.s.ForceSendFields, tc.s.NullFields, tc.want)
445	if len(tc.s.ForceSendFields) == 0 && len(tc.s.NullFields) == 0 {
446		// verify that the code path used when ForceSendFields and NullFields
447		// are non-empty produces the same output as the fast path that is used
448		// when they are empty.
449		doCheckMarshalJSON(t, tc.s, []string{"dummy"}, []string{"dummy"}, tc.want)
450	}
451}
452
453func doCheckMarshalJSON(t *testing.T, s schema, forceSendFields, nullFields []string, wantJSON string) {
454	encoded, err := MarshalJSON(s, forceSendFields, nullFields)
455	if err != nil {
456		t.Fatalf("encoding json:\n got err: %v", err)
457	}
458
459	// The expected and obtained JSON can differ in field ordering, so unmarshal before comparing.
460	var got interface{}
461	var want interface{}
462	err = json.Unmarshal(encoded, &got)
463	if err != nil {
464		t.Fatalf("decoding json:\n got err: %v", err)
465	}
466	err = json.Unmarshal([]byte(wantJSON), &want)
467	if err != nil {
468		t.Fatalf("decoding json:\n got err: %v", err)
469	}
470	if !reflect.DeepEqual(got, want) {
471		t.Errorf("schemaToMap:\ngot :%v\nwant: %v", got, want)
472	}
473}
474
475func TestParseJSONTag(t *testing.T) {
476	for _, tc := range []struct {
477		tag  string
478		want jsonTag
479	}{
480		{
481			tag:  "-",
482			want: jsonTag{ignore: true},
483		}, {
484			tag:  "name,omitempty",
485			want: jsonTag{apiName: "name"},
486		}, {
487			tag:  "name,omitempty,string",
488			want: jsonTag{apiName: "name", stringFormat: true},
489		},
490	} {
491		got, err := parseJSONTag(tc.tag)
492		if err != nil {
493			t.Fatalf("parsing json:\n got err: %v\ntag: %q", err, tc.tag)
494		}
495		if !reflect.DeepEqual(got, tc.want) {
496			t.Errorf("parseJSONTage:\ngot :%v\nwant:%v", got, tc.want)
497		}
498	}
499}
500func TestParseMalformedJSONTag(t *testing.T) {
501	for _, tag := range []string{
502		"",
503		"name",
504		"name,",
505		"name,blah",
506		"name,blah,string",
507		",omitempty",
508		",omitempty,string",
509		"name,omitempty,string,blah",
510	} {
511		_, err := parseJSONTag(tag)
512		if err == nil {
513			t.Fatalf("parsing json: expected err, got nil for tag: %v", tag)
514		}
515	}
516}
517