| // 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) |
| } |
| } |
| } |