blob: 643f740229e9e9b0726e2a168eaa2ea2421c200b [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package structpb_test
import (
"encoding/json"
"math"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/testing/protocmp"
spb "google.golang.org/protobuf/types/known/structpb"
)
var equateJSON = cmpopts.AcyclicTransformer("UnmarshalJSON", func(in []byte) (out interface{}) {
if err := json.Unmarshal(in, &out); err != nil {
return in
}
return out
})
func TestToStruct(t *testing.T) {
tests := []struct {
in map[string]interface{}
wantPB *spb.Struct
wantErr error
}{{
in: nil,
wantPB: new(spb.Struct),
}, {
in: make(map[string]interface{}),
wantPB: new(spb.Struct),
}, {
in: map[string]interface{}{
"nil": nil,
"bool": bool(false),
"int": int(-123),
"int32": int32(math.MinInt32),
"int64": int64(math.MinInt64),
"uint": uint(123),
"uint32": uint32(math.MaxInt32),
"uint64": uint64(math.MaxInt64),
"float32": float32(123.456),
"float64": float64(123.456),
"string": string("hello, world!"),
"bytes": []byte("\xde\xad\xbe\xef"),
"map": map[string]interface{}{"k1": "v1", "k2": "v2"},
"slice": []interface{}{"one", "two", "three"},
},
wantPB: &spb.Struct{Fields: map[string]*spb.Value{
"nil": spb.NewNullValue(),
"bool": spb.NewBoolValue(false),
"int": spb.NewNumberValue(float64(-123)),
"int32": spb.NewNumberValue(float64(math.MinInt32)),
"int64": spb.NewNumberValue(float64(math.MinInt64)),
"uint": spb.NewNumberValue(float64(123)),
"uint32": spb.NewNumberValue(float64(math.MaxInt32)),
"uint64": spb.NewNumberValue(float64(math.MaxInt64)),
"float32": spb.NewNumberValue(float64(float32(123.456))),
"float64": spb.NewNumberValue(float64(float64(123.456))),
"string": spb.NewStringValue("hello, world!"),
"bytes": spb.NewStringValue("3q2+7w=="),
"map": spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
"k1": spb.NewStringValue("v1"),
"k2": spb.NewStringValue("v2"),
}}),
"slice": spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
spb.NewStringValue("one"),
spb.NewStringValue("two"),
spb.NewStringValue("three"),
}}),
}},
}, {
in: map[string]interface{}{"\xde\xad\xbe\xef": "<invalid UTF-8>"},
wantErr: cmpopts.AnyError,
}, {
in: map[string]interface{}{"<invalid UTF-8>": "\xde\xad\xbe\xef"},
wantErr: cmpopts.AnyError,
}, {
in: map[string]interface{}{"key": protoreflect.Name("named string")},
wantErr: cmpopts.AnyError,
}}
for _, tt := range tests {
gotPB, gotErr := spb.NewStruct(tt.in)
if diff := cmp.Diff(tt.wantPB, gotPB, protocmp.Transform()); diff != "" {
t.Errorf("NewStruct(%v) output mismatch (-want +got):\n%s", tt.in, diff)
}
if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
t.Errorf("NewStruct(%v) error mismatch (-want +got):\n%s", tt.in, diff)
}
}
}
func TestFromStruct(t *testing.T) {
tests := []struct {
in *spb.Struct
want map[string]interface{}
}{{
in: nil,
want: make(map[string]interface{}),
}, {
in: new(spb.Struct),
want: make(map[string]interface{}),
}, {
in: &spb.Struct{Fields: make(map[string]*spb.Value)},
want: make(map[string]interface{}),
}, {
in: &spb.Struct{Fields: map[string]*spb.Value{
"nil": spb.NewNullValue(),
"bool": spb.NewBoolValue(false),
"int": spb.NewNumberValue(float64(-123)),
"int32": spb.NewNumberValue(float64(math.MinInt32)),
"int64": spb.NewNumberValue(float64(math.MinInt64)),
"uint": spb.NewNumberValue(float64(123)),
"uint32": spb.NewNumberValue(float64(math.MaxInt32)),
"uint64": spb.NewNumberValue(float64(math.MaxInt64)),
"float32": spb.NewNumberValue(float64(float32(123.456))),
"float64": spb.NewNumberValue(float64(float64(123.456))),
"string": spb.NewStringValue("hello, world!"),
"bytes": spb.NewStringValue("3q2+7w=="),
"map": spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
"k1": spb.NewStringValue("v1"),
"k2": spb.NewStringValue("v2"),
}}),
"slice": spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
spb.NewStringValue("one"),
spb.NewStringValue("two"),
spb.NewStringValue("three"),
}}),
}},
want: map[string]interface{}{
"nil": nil,
"bool": bool(false),
"int": float64(-123),
"int32": float64(math.MinInt32),
"int64": float64(math.MinInt64),
"uint": float64(123),
"uint32": float64(math.MaxInt32),
"uint64": float64(math.MaxInt64),
"float32": float64(float32(123.456)),
"float64": float64(float64(123.456)),
"string": string("hello, world!"),
"bytes": string("3q2+7w=="),
"map": map[string]interface{}{"k1": "v1", "k2": "v2"},
"slice": []interface{}{"one", "two", "three"},
},
}}
for _, tt := range tests {
got := tt.in.AsMap()
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("AsMap(%v) mismatch (-want +got):\n%s", tt.in, diff)
}
gotJSON, err := json.Marshal(got)
if err != nil {
t.Errorf("Marshal error: %v", err)
}
wantJSON, err := tt.in.MarshalJSON()
if err != nil {
t.Errorf("Marshal error: %v", err)
}
if diff := cmp.Diff(wantJSON, gotJSON, equateJSON); diff != "" {
t.Errorf("MarshalJSON(%v) mismatch (-want +got):\n%s", tt.in, diff)
}
}
}
func TestToListValue(t *testing.T) {
tests := []struct {
in []interface{}
wantPB *spb.ListValue
wantErr error
}{{
in: nil,
wantPB: new(spb.ListValue),
}, {
in: make([]interface{}, 0),
wantPB: new(spb.ListValue),
}, {
in: []interface{}{
nil,
bool(false),
int(-123),
int32(math.MinInt32),
int64(math.MinInt64),
uint(123),
uint32(math.MaxInt32),
uint64(math.MaxInt64),
float32(123.456),
float64(123.456),
string("hello, world!"),
[]byte("\xde\xad\xbe\xef"),
map[string]interface{}{"k1": "v1", "k2": "v2"},
[]interface{}{"one", "two", "three"},
},
wantPB: &spb.ListValue{Values: []*spb.Value{
spb.NewNullValue(),
spb.NewBoolValue(false),
spb.NewNumberValue(float64(-123)),
spb.NewNumberValue(float64(math.MinInt32)),
spb.NewNumberValue(float64(math.MinInt64)),
spb.NewNumberValue(float64(123)),
spb.NewNumberValue(float64(math.MaxInt32)),
spb.NewNumberValue(float64(math.MaxInt64)),
spb.NewNumberValue(float64(float32(123.456))),
spb.NewNumberValue(float64(float64(123.456))),
spb.NewStringValue("hello, world!"),
spb.NewStringValue("3q2+7w=="),
spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
"k1": spb.NewStringValue("v1"),
"k2": spb.NewStringValue("v2"),
}}),
spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
spb.NewStringValue("one"),
spb.NewStringValue("two"),
spb.NewStringValue("three"),
}}),
}},
}, {
in: []interface{}{"\xde\xad\xbe\xef"},
wantErr: cmpopts.AnyError,
}, {
in: []interface{}{protoreflect.Name("named string")},
wantErr: cmpopts.AnyError,
}}
for _, tt := range tests {
gotPB, gotErr := spb.NewList(tt.in)
if diff := cmp.Diff(tt.wantPB, gotPB, protocmp.Transform()); diff != "" {
t.Errorf("NewListValue(%v) output mismatch (-want +got):\n%s", tt.in, diff)
}
if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
t.Errorf("NewListValue(%v) error mismatch (-want +got):\n%s", tt.in, diff)
}
}
}
func TestFromListValue(t *testing.T) {
tests := []struct {
in *spb.ListValue
want []interface{}
}{{
in: nil,
want: make([]interface{}, 0),
}, {
in: new(spb.ListValue),
want: make([]interface{}, 0),
}, {
in: &spb.ListValue{Values: make([]*spb.Value, 0)},
want: make([]interface{}, 0),
}, {
in: &spb.ListValue{Values: []*spb.Value{
spb.NewNullValue(),
spb.NewBoolValue(false),
spb.NewNumberValue(float64(-123)),
spb.NewNumberValue(float64(math.MinInt32)),
spb.NewNumberValue(float64(math.MinInt64)),
spb.NewNumberValue(float64(123)),
spb.NewNumberValue(float64(math.MaxInt32)),
spb.NewNumberValue(float64(math.MaxInt64)),
spb.NewNumberValue(float64(float32(123.456))),
spb.NewNumberValue(float64(float64(123.456))),
spb.NewStringValue("hello, world!"),
spb.NewStringValue("3q2+7w=="),
spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
"k1": spb.NewStringValue("v1"),
"k2": spb.NewStringValue("v2"),
}}),
spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
spb.NewStringValue("one"),
spb.NewStringValue("two"),
spb.NewStringValue("three"),
}}),
}},
want: []interface{}{
nil,
bool(false),
float64(-123),
float64(math.MinInt32),
float64(math.MinInt64),
float64(123),
float64(math.MaxInt32),
float64(math.MaxInt64),
float64(float32(123.456)),
float64(float64(123.456)),
string("hello, world!"),
string("3q2+7w=="),
map[string]interface{}{"k1": "v1", "k2": "v2"},
[]interface{}{"one", "two", "three"},
},
}}
for _, tt := range tests {
got := tt.in.AsSlice()
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("AsSlice(%v) mismatch (-want +got):\n%s", tt.in, diff)
}
gotJSON, err := json.Marshal(got)
if err != nil {
t.Errorf("Marshal error: %v", err)
}
wantJSON, err := tt.in.MarshalJSON()
if err != nil {
t.Errorf("Marshal error: %v", err)
}
if diff := cmp.Diff(wantJSON, gotJSON, equateJSON); diff != "" {
t.Errorf("MarshalJSON(%v) mismatch (-want +got):\n%s", tt.in, diff)
}
}
}
func TestToValue(t *testing.T) {
tests := []struct {
in interface{}
wantPB *spb.Value
wantErr error
}{{
in: nil,
wantPB: spb.NewNullValue(),
}, {
in: bool(false),
wantPB: spb.NewBoolValue(false),
}, {
in: int(-123),
wantPB: spb.NewNumberValue(float64(-123)),
}, {
in: int32(math.MinInt32),
wantPB: spb.NewNumberValue(float64(math.MinInt32)),
}, {
in: int64(math.MinInt64),
wantPB: spb.NewNumberValue(float64(math.MinInt64)),
}, {
in: uint(123),
wantPB: spb.NewNumberValue(float64(123)),
}, {
in: uint32(math.MaxInt32),
wantPB: spb.NewNumberValue(float64(math.MaxInt32)),
}, {
in: uint64(math.MaxInt64),
wantPB: spb.NewNumberValue(float64(math.MaxInt64)),
}, {
in: float32(123.456),
wantPB: spb.NewNumberValue(float64(float32(123.456))),
}, {
in: float64(123.456),
wantPB: spb.NewNumberValue(float64(float64(123.456))),
}, {
in: string("hello, world!"),
wantPB: spb.NewStringValue("hello, world!"),
}, {
in: []byte("\xde\xad\xbe\xef"),
wantPB: spb.NewStringValue("3q2+7w=="),
}, {
in: map[string]interface{}(nil),
wantPB: spb.NewStructValue(nil),
}, {
in: make(map[string]interface{}),
wantPB: spb.NewStructValue(nil),
}, {
in: map[string]interface{}{"k1": "v1", "k2": "v2"},
wantPB: spb.NewStructValue(&spb.Struct{Fields: map[string]*spb.Value{
"k1": spb.NewStringValue("v1"),
"k2": spb.NewStringValue("v2"),
}}),
}, {
in: []interface{}(nil),
wantPB: spb.NewListValue(nil),
}, {
in: make([]interface{}, 0),
wantPB: spb.NewListValue(nil),
}, {
in: []interface{}{"one", "two", "three"},
wantPB: spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
spb.NewStringValue("one"),
spb.NewStringValue("two"),
spb.NewStringValue("three"),
}}),
}, {
in: "\xde\xad\xbe\xef",
wantErr: cmpopts.AnyError,
}, {
in: protoreflect.Name("named string"),
wantErr: cmpopts.AnyError,
}}
for _, tt := range tests {
gotPB, gotErr := spb.NewValue(tt.in)
if diff := cmp.Diff(tt.wantPB, gotPB, protocmp.Transform()); diff != "" {
t.Errorf("NewValue(%v) output mismatch (-want +got):\n%s", tt.in, diff)
}
if diff := cmp.Diff(tt.wantErr, gotErr, cmpopts.EquateErrors()); diff != "" {
t.Errorf("NewValue(%v) error mismatch (-want +got):\n%s", tt.in, diff)
}
}
}
func TestFromValue(t *testing.T) {
tests := []struct {
in *spb.Value
want interface{}
}{{
in: nil,
want: nil,
}, {
in: new(spb.Value),
want: nil,
}, {
in: &spb.Value{Kind: (*spb.Value_NullValue)(nil)},
want: nil,
}, {
in: spb.NewNullValue(),
want: nil,
}, {
in: &spb.Value{Kind: &spb.Value_NullValue{NullValue: math.MinInt32}},
want: nil,
}, {
in: &spb.Value{Kind: (*spb.Value_BoolValue)(nil)},
want: nil,
}, {
in: spb.NewBoolValue(false),
want: bool(false),
}, {
in: &spb.Value{Kind: (*spb.Value_NumberValue)(nil)},
want: nil,
}, {
in: spb.NewNumberValue(float64(math.MinInt32)),
want: float64(math.MinInt32),
}, {
in: spb.NewNumberValue(float64(math.MinInt64)),
want: float64(math.MinInt64),
}, {
in: spb.NewNumberValue(float64(123)),
want: float64(123),
}, {
in: spb.NewNumberValue(float64(math.MaxInt32)),
want: float64(math.MaxInt32),
}, {
in: spb.NewNumberValue(float64(math.MaxInt64)),
want: float64(math.MaxInt64),
}, {
in: spb.NewNumberValue(float64(float32(123.456))),
want: float64(float32(123.456)),
}, {
in: spb.NewNumberValue(float64(float64(123.456))),
want: float64(float64(123.456)),
}, {
in: spb.NewNumberValue(math.NaN()),
want: string("NaN"),
}, {
in: spb.NewNumberValue(math.Inf(-1)),
want: string("-Infinity"),
}, {
in: spb.NewNumberValue(math.Inf(+1)),
want: string("Infinity"),
}, {
in: &spb.Value{Kind: (*spb.Value_StringValue)(nil)},
want: nil,
}, {
in: spb.NewStringValue("hello, world!"),
want: string("hello, world!"),
}, {
in: spb.NewStringValue("3q2+7w=="),
want: string("3q2+7w=="),
}, {
in: &spb.Value{Kind: (*spb.Value_StructValue)(nil)},
want: nil,
}, {
in: &spb.Value{Kind: &spb.Value_StructValue{}},
want: make(map[string]interface{}),
}, {
in: spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
spb.NewStringValue("one"),
spb.NewStringValue("two"),
spb.NewStringValue("three"),
}}),
want: []interface{}{"one", "two", "three"},
}, {
in: &spb.Value{Kind: (*spb.Value_ListValue)(nil)},
want: nil,
}, {
in: &spb.Value{Kind: &spb.Value_ListValue{}},
want: make([]interface{}, 0),
}, {
in: spb.NewListValue(&spb.ListValue{Values: []*spb.Value{
spb.NewStringValue("one"),
spb.NewStringValue("two"),
spb.NewStringValue("three"),
}}),
want: []interface{}{"one", "two", "three"},
}}
for _, tt := range tests {
got := tt.in.AsInterface()
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Errorf("AsInterface(%v) mismatch (-want +got):\n%s", tt.in, diff)
}
gotJSON, gotErr := json.Marshal(got)
if gotErr != nil {
t.Errorf("Marshal error: %v", gotErr)
}
wantJSON, wantErr := tt.in.MarshalJSON()
if diff := cmp.Diff(wantJSON, gotJSON, equateJSON); diff != "" && wantErr == nil {
t.Errorf("MarshalJSON(%v) mismatch (-want +got):\n%s", tt.in, diff)
}
}
}