|  | // Copyright 2015 Google LLC | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package gensupport | 
|  |  | 
|  | import ( | 
|  | "encoding/json" | 
|  | "reflect" | 
|  | "testing" | 
|  |  | 
|  | "google.golang.org/api/googleapi" | 
|  | ) | 
|  |  | 
|  | type schema struct { | 
|  | // Basic types | 
|  | B    bool    `json:"b,omitempty"` | 
|  | F    float64 `json:"f,omitempty"` | 
|  | I    int64   `json:"i,omitempty"` | 
|  | Istr int64   `json:"istr,omitempty,string"` | 
|  | Str  string  `json:"str,omitempty"` | 
|  |  | 
|  | // Pointers to basic types | 
|  | PB    *bool    `json:"pb,omitempty"` | 
|  | PF    *float64 `json:"pf,omitempty"` | 
|  | PI    *int64   `json:"pi,omitempty"` | 
|  | PIStr *int64   `json:"pistr,omitempty,string"` | 
|  | PStr  *string  `json:"pstr,omitempty"` | 
|  |  | 
|  | // Other types | 
|  | Int64s        googleapi.Int64s         `json:"i64s,omitempty"` | 
|  | S             []int                    `json:"s,omitempty"` | 
|  | M             map[string]string        `json:"m,omitempty"` | 
|  | Any           interface{}              `json:"any,omitempty"` | 
|  | Child         *child                   `json:"child,omitempty"` | 
|  | MapToAnyArray map[string][]interface{} `json:"maptoanyarray,omitempty"` | 
|  |  | 
|  | ForceSendFields []string `json:"-"` | 
|  | NullFields      []string `json:"-"` | 
|  | } | 
|  |  | 
|  | type child struct { | 
|  | B bool `json:"childbool,omitempty"` | 
|  | } | 
|  |  | 
|  | type testCase struct { | 
|  | s    schema | 
|  | want string | 
|  | } | 
|  |  | 
|  | func TestBasics(t *testing.T) { | 
|  | for _, tc := range []testCase{ | 
|  | { | 
|  | s:    schema{}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | ForceSendFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"}, | 
|  | }, | 
|  | want: `{"b":false,"f":0.0,"i":0,"istr":"0","str":""}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | NullFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"}, | 
|  | }, | 
|  | want: `{"b":null,"f":null,"i":null,"istr":null,"str":null,"pb":null,"pf":null,"pi":null,"pistr":null,"pstr":null}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | B:     true, | 
|  | F:     1.2, | 
|  | I:     1, | 
|  | Istr:  2, | 
|  | Str:   "a", | 
|  | PB:    googleapi.Bool(true), | 
|  | PF:    googleapi.Float64(1.2), | 
|  | PI:    googleapi.Int64(int64(1)), | 
|  | PIStr: googleapi.Int64(int64(2)), | 
|  | PStr:  googleapi.String("a"), | 
|  | }, | 
|  | want: `{"b":true,"f":1.2,"i":1,"istr":"2","str":"a","pb":true,"pf":1.2,"pi":1,"pistr":"2","pstr":"a"}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | B:     false, | 
|  | F:     0.0, | 
|  | I:     0, | 
|  | Istr:  0, | 
|  | Str:   "", | 
|  | PB:    googleapi.Bool(false), | 
|  | PF:    googleapi.Float64(0.0), | 
|  | PI:    googleapi.Int64(int64(0)), | 
|  | PIStr: googleapi.Int64(int64(0)), | 
|  | PStr:  googleapi.String(""), | 
|  | }, | 
|  | want: `{"pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | B:               false, | 
|  | F:               0.0, | 
|  | I:               0, | 
|  | Istr:            0, | 
|  | Str:             "", | 
|  | PB:              googleapi.Bool(false), | 
|  | PF:              googleapi.Float64(0.0), | 
|  | PI:              googleapi.Int64(int64(0)), | 
|  | PIStr:           googleapi.Int64(int64(0)), | 
|  | PStr:            googleapi.String(""), | 
|  | ForceSendFields: []string{"B", "F", "I", "Istr", "Str", "PB", "PF", "PI", "PIStr", "PStr"}, | 
|  | }, | 
|  | want: `{"b":false,"f":0.0,"i":0,"istr":"0","str":"","pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | B:          false, | 
|  | F:          0.0, | 
|  | I:          0, | 
|  | Istr:       0, | 
|  | Str:        "", | 
|  | PB:         googleapi.Bool(false), | 
|  | PF:         googleapi.Float64(0.0), | 
|  | PI:         googleapi.Int64(int64(0)), | 
|  | PIStr:      googleapi.Int64(int64(0)), | 
|  | PStr:       googleapi.String(""), | 
|  | NullFields: []string{"B", "F", "I", "Istr", "Str"}, | 
|  | }, | 
|  | want: `{"b":null,"f":null,"i":null,"istr":null,"str":null,"pb":false,"pf":0.0,"pi":0,"pistr":"0","pstr":""}`, | 
|  | }, | 
|  | } { | 
|  | checkMarshalJSON(t, tc) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestSliceFields(t *testing.T) { | 
|  | for _, tc := range []testCase{ | 
|  | { | 
|  | s:    schema{}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{S: []int{}, Int64s: googleapi.Int64s{}}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{S: []int{1}, Int64s: googleapi.Int64s{1}}, | 
|  | want: `{"s":[1],"i64s":["1"]}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | ForceSendFields: []string{"S", "Int64s"}, | 
|  | }, | 
|  | want: `{"s":[],"i64s":[]}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | S:               []int{}, | 
|  | Int64s:          googleapi.Int64s{}, | 
|  | ForceSendFields: []string{"S", "Int64s"}, | 
|  | }, | 
|  | want: `{"s":[],"i64s":[]}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | S:               []int{1}, | 
|  | Int64s:          googleapi.Int64s{1}, | 
|  | ForceSendFields: []string{"S", "Int64s"}, | 
|  | }, | 
|  | want: `{"s":[1],"i64s":["1"]}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | NullFields: []string{"S", "Int64s"}, | 
|  | }, | 
|  | want: `{"s":null,"i64s":null}`, | 
|  | }, | 
|  | } { | 
|  | checkMarshalJSON(t, tc) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMapField(t *testing.T) { | 
|  | for _, tc := range []testCase{ | 
|  | { | 
|  | s:    schema{}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{M: make(map[string]string)}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{M: map[string]string{"a": "b"}}, | 
|  | want: `{"m":{"a":"b"}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | ForceSendFields: []string{"M"}, | 
|  | }, | 
|  | want: `{"m":{}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | NullFields: []string{"M"}, | 
|  | }, | 
|  | want: `{"m":null}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | M:               make(map[string]string), | 
|  | ForceSendFields: []string{"M"}, | 
|  | }, | 
|  | want: `{"m":{}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | M:          make(map[string]string), | 
|  | NullFields: []string{"M"}, | 
|  | }, | 
|  | want: `{"m":null}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | M:               map[string]string{"a": "b"}, | 
|  | ForceSendFields: []string{"M"}, | 
|  | }, | 
|  | want: `{"m":{"a":"b"}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | M:          map[string]string{"a": "b"}, | 
|  | NullFields: []string{"M.a", "M."}, | 
|  | }, | 
|  | want: `{"m": {"a": null, "":null}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | M:          map[string]string{"a": "b"}, | 
|  | NullFields: []string{"M.c"}, | 
|  | }, | 
|  | want: `{"m": {"a": "b", "c": null}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | NullFields:      []string{"M.a"}, | 
|  | ForceSendFields: []string{"M"}, | 
|  | }, | 
|  | want: `{"m": {"a": null}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | NullFields: []string{"M.a"}, | 
|  | }, | 
|  | want: `{}`, | 
|  | }, | 
|  | } { | 
|  | checkMarshalJSON(t, tc) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMapToAnyArray(t *testing.T) { | 
|  | for _, tc := range []testCase{ | 
|  | { | 
|  | s:    schema{}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{MapToAnyArray: make(map[string][]interface{})}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | MapToAnyArray: map[string][]interface{}{ | 
|  | "a": {2, "b"}, | 
|  | }, | 
|  | }, | 
|  | want: `{"maptoanyarray":{"a":[2, "b"]}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | MapToAnyArray: map[string][]interface{}{ | 
|  | "a": nil, | 
|  | }, | 
|  | }, | 
|  | want: `{"maptoanyarray":{"a": null}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | MapToAnyArray: map[string][]interface{}{ | 
|  | "a": {nil}, | 
|  | }, | 
|  | }, | 
|  | want: `{"maptoanyarray":{"a":[null]}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | ForceSendFields: []string{"MapToAnyArray"}, | 
|  | }, | 
|  | want: `{"maptoanyarray":{}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | NullFields: []string{"MapToAnyArray"}, | 
|  | }, | 
|  | want: `{"maptoanyarray":null}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | MapToAnyArray:   make(map[string][]interface{}), | 
|  | ForceSendFields: []string{"MapToAnyArray"}, | 
|  | }, | 
|  | want: `{"maptoanyarray":{}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | MapToAnyArray: map[string][]interface{}{ | 
|  | "a": {2, "b"}, | 
|  | }, | 
|  | ForceSendFields: []string{"MapToAnyArray"}, | 
|  | }, | 
|  | want: `{"maptoanyarray":{"a":[2, "b"]}}`, | 
|  | }, | 
|  | } { | 
|  | checkMarshalJSON(t, tc) | 
|  | } | 
|  | } | 
|  |  | 
|  | type anyType struct { | 
|  | Field int | 
|  | } | 
|  |  | 
|  | func (a anyType) MarshalJSON() ([]byte, error) { | 
|  | return []byte(`"anyType value"`), nil | 
|  | } | 
|  |  | 
|  | func TestAnyField(t *testing.T) { | 
|  | // ForceSendFields has no effect on nil interfaces and interfaces that contain nil pointers. | 
|  | var nilAny *anyType | 
|  | for _, tc := range []testCase{ | 
|  | { | 
|  | s:    schema{}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{Any: nilAny}, | 
|  | want: `{"any": null}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{Any: &anyType{}}, | 
|  | want: `{"any":"anyType value"}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{Any: anyType{}}, | 
|  | want: `{"any":"anyType value"}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | ForceSendFields: []string{"Any"}, | 
|  | }, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | NullFields: []string{"Any"}, | 
|  | }, | 
|  | want: `{"any":null}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | Any:             nilAny, | 
|  | ForceSendFields: []string{"Any"}, | 
|  | }, | 
|  | want: `{"any": null}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | Any:             &anyType{}, | 
|  | ForceSendFields: []string{"Any"}, | 
|  | }, | 
|  | want: `{"any":"anyType value"}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | Any:             anyType{}, | 
|  | ForceSendFields: []string{"Any"}, | 
|  | }, | 
|  | want: `{"any":"anyType value"}`, | 
|  | }, | 
|  | } { | 
|  | checkMarshalJSON(t, tc) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestSubschema(t *testing.T) { | 
|  | // Subschemas are always stored as pointers, so ForceSendFields has no effect on them. | 
|  | for _, tc := range []testCase{ | 
|  | { | 
|  | s:    schema{}, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | ForceSendFields: []string{"Child"}, | 
|  | }, | 
|  | want: `{}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | NullFields: []string{"Child"}, | 
|  | }, | 
|  | want: `{"child":null}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{Child: &child{}}, | 
|  | want: `{"child":{}}`, | 
|  | }, | 
|  | { | 
|  | s: schema{ | 
|  | Child:           &child{}, | 
|  | ForceSendFields: []string{"Child"}, | 
|  | }, | 
|  | want: `{"child":{}}`, | 
|  | }, | 
|  | { | 
|  | s:    schema{Child: &child{B: true}}, | 
|  | want: `{"child":{"childbool":true}}`, | 
|  | }, | 
|  |  | 
|  | { | 
|  | s: schema{ | 
|  | Child:           &child{B: true}, | 
|  | ForceSendFields: []string{"Child"}, | 
|  | }, | 
|  | want: `{"child":{"childbool":true}}`, | 
|  | }, | 
|  | } { | 
|  | checkMarshalJSON(t, tc) | 
|  | } | 
|  | } | 
|  |  | 
|  | // checkMarshalJSON verifies that calling schemaToMap on tc.s yields a result which is equivalent to tc.want. | 
|  | func checkMarshalJSON(t *testing.T, tc testCase) { | 
|  | doCheckMarshalJSON(t, tc.s, tc.s.ForceSendFields, tc.s.NullFields, tc.want) | 
|  | if len(tc.s.ForceSendFields) == 0 && len(tc.s.NullFields) == 0 { | 
|  | // verify that the code path used when ForceSendFields and NullFields | 
|  | // are non-empty produces the same output as the fast path that is used | 
|  | // when they are empty. | 
|  | doCheckMarshalJSON(t, tc.s, []string{"dummy"}, []string{"dummy"}, tc.want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func doCheckMarshalJSON(t *testing.T, s schema, forceSendFields, nullFields []string, wantJSON string) { | 
|  | encoded, err := MarshalJSON(s, forceSendFields, nullFields) | 
|  | if err != nil { | 
|  | t.Fatalf("encoding json:\n got err: %v", err) | 
|  | } | 
|  |  | 
|  | // The expected and obtained JSON can differ in field ordering, so unmarshal before comparing. | 
|  | var got interface{} | 
|  | var want interface{} | 
|  | err = json.Unmarshal(encoded, &got) | 
|  | if err != nil { | 
|  | t.Fatalf("decoding json:\n got err: %v", err) | 
|  | } | 
|  | err = json.Unmarshal([]byte(wantJSON), &want) | 
|  | if err != nil { | 
|  | t.Fatalf("decoding json:\n got err: %v", err) | 
|  | } | 
|  | if !reflect.DeepEqual(got, want) { | 
|  | t.Errorf("schemaToMap:\ngot :%v\nwant: %v", got, want) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestParseJSONTag(t *testing.T) { | 
|  | for _, tc := range []struct { | 
|  | tag  string | 
|  | want jsonTag | 
|  | }{ | 
|  | { | 
|  | tag:  "-", | 
|  | want: jsonTag{ignore: true}, | 
|  | }, { | 
|  | tag:  "name,omitempty", | 
|  | want: jsonTag{apiName: "name"}, | 
|  | }, { | 
|  | tag:  "name,omitempty,string", | 
|  | want: jsonTag{apiName: "name", stringFormat: true}, | 
|  | }, | 
|  | } { | 
|  | got, err := parseJSONTag(tc.tag) | 
|  | if err != nil { | 
|  | t.Fatalf("parsing json:\n got err: %v\ntag: %q", err, tc.tag) | 
|  | } | 
|  | if !reflect.DeepEqual(got, tc.want) { | 
|  | t.Errorf("parseJSONTage:\ngot :%v\nwant:%v", got, tc.want) | 
|  | } | 
|  | } | 
|  | } | 
|  | func TestParseMalformedJSONTag(t *testing.T) { | 
|  | for _, tag := range []string{ | 
|  | "", | 
|  | "name", | 
|  | "name,", | 
|  | "name,blah", | 
|  | "name,blah,string", | 
|  | ",omitempty", | 
|  | ",omitempty,string", | 
|  | "name,omitempty,string,blah", | 
|  | } { | 
|  | _, err := parseJSONTag(tag) | 
|  | if err == nil { | 
|  | t.Fatalf("parsing json: expected err, got nil for tag: %v", tag) | 
|  | } | 
|  | } | 
|  | } |