blob: 2cc5dfbbb909c55dbb9d42cc24ac94de23472657 [file] [log] [blame]
// 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)
}
}
}