269 lines
6.6 KiB
Go
269 lines
6.6 KiB
Go
|
// Copyright 2017 Google Inc. All Rights Reserved.
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package firestore
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
|
||
|
|
||
|
"google.golang.org/genproto/googleapis/type/latlng"
|
||
|
)
|
||
|
|
||
|
type testStruct1 struct {
|
||
|
B bool
|
||
|
I int
|
||
|
U uint32
|
||
|
F float64
|
||
|
S string
|
||
|
Y []byte
|
||
|
T time.Time
|
||
|
G *latlng.LatLng
|
||
|
L []int
|
||
|
M map[string]int
|
||
|
P *int
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
p = new(int)
|
||
|
|
||
|
testVal1 = testStruct1{
|
||
|
B: true,
|
||
|
I: 1,
|
||
|
U: 2,
|
||
|
F: 3.0,
|
||
|
S: "four",
|
||
|
Y: []byte{5},
|
||
|
T: tm,
|
||
|
G: ll,
|
||
|
L: []int{6},
|
||
|
M: map[string]int{"a": 7},
|
||
|
P: p,
|
||
|
}
|
||
|
|
||
|
mapVal1 = mapval(map[string]*pb.Value{
|
||
|
"B": boolval(true),
|
||
|
"I": intval(1),
|
||
|
"U": intval(2),
|
||
|
"F": floatval(3),
|
||
|
"S": &pb.Value{&pb.Value_StringValue{"four"}},
|
||
|
"Y": bytesval([]byte{5}),
|
||
|
"T": tsval(tm),
|
||
|
"G": geoval(ll),
|
||
|
"L": arrayval(intval(6)),
|
||
|
"M": mapval(map[string]*pb.Value{"a": intval(7)}),
|
||
|
"P": intval(8),
|
||
|
})
|
||
|
)
|
||
|
|
||
|
func TestToProtoValue(t *testing.T) {
|
||
|
*p = 8
|
||
|
for _, test := range []struct {
|
||
|
in interface{}
|
||
|
want *pb.Value
|
||
|
}{
|
||
|
{nil, nullValue},
|
||
|
{[]int(nil), nullValue},
|
||
|
{map[string]int(nil), nullValue},
|
||
|
{(*testStruct1)(nil), nullValue},
|
||
|
{(*latlng.LatLng)(nil), nullValue},
|
||
|
{(*DocumentRef)(nil), nullValue},
|
||
|
{true, boolval(true)},
|
||
|
{3, intval(3)},
|
||
|
{uint32(3), intval(3)},
|
||
|
{1.5, floatval(1.5)},
|
||
|
{"str", strval("str")},
|
||
|
{[]byte{1, 2}, bytesval([]byte{1, 2})},
|
||
|
{tm, tsval(tm)},
|
||
|
{ll, geoval(ll)},
|
||
|
{[]int{1, 2}, arrayval(intval(1), intval(2))},
|
||
|
{&[]int{1, 2}, arrayval(intval(1), intval(2))},
|
||
|
{[]int{}, arrayval()},
|
||
|
{map[string]int{"a": 1, "b": 2},
|
||
|
mapval(map[string]*pb.Value{"a": intval(1), "b": intval(2)})},
|
||
|
{map[string]int{}, mapval(map[string]*pb.Value{})},
|
||
|
{p, intval(8)},
|
||
|
{&p, intval(8)},
|
||
|
{map[string]interface{}{"a": 1, "p": p, "s": "str"},
|
||
|
mapval(map[string]*pb.Value{"a": intval(1), "p": intval(8), "s": strval("str")})},
|
||
|
{map[string]fmt.Stringer{"a": tm},
|
||
|
mapval(map[string]*pb.Value{"a": tsval(tm)})},
|
||
|
{testVal1, mapVal1},
|
||
|
{
|
||
|
&DocumentRef{
|
||
|
ID: "d",
|
||
|
Path: "projects/P/databases/D/documents/c/d",
|
||
|
Parent: &CollectionRef{
|
||
|
ID: "c",
|
||
|
parentPath: "projects/P/databases/D",
|
||
|
Path: "projects/P/databases/D/documents/c",
|
||
|
Query: Query{collectionID: "c", parentPath: "projects/P/databases/D"},
|
||
|
},
|
||
|
},
|
||
|
refval("projects/P/databases/D/documents/c/d"),
|
||
|
},
|
||
|
// ServerTimestamps are removed, possibly leaving nil.
|
||
|
{map[string]interface{}{"a": ServerTimestamp}, nil},
|
||
|
{
|
||
|
map[string]interface{}{
|
||
|
"a": map[string]interface{}{
|
||
|
"b": map[string]interface{}{
|
||
|
"c": ServerTimestamp,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
nil,
|
||
|
},
|
||
|
{
|
||
|
map[string]interface{}{
|
||
|
"a": map[string]interface{}{
|
||
|
"b": map[string]interface{}{
|
||
|
"c": ServerTimestamp,
|
||
|
"d": ServerTimestamp,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
nil,
|
||
|
},
|
||
|
{
|
||
|
map[string]interface{}{
|
||
|
"a": map[string]interface{}{
|
||
|
"b": map[string]interface{}{
|
||
|
"c": ServerTimestamp,
|
||
|
"d": ServerTimestamp,
|
||
|
"e": 1,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
mapval(map[string]*pb.Value{
|
||
|
"a": mapval(map[string]*pb.Value{
|
||
|
"b": mapval(map[string]*pb.Value{"e": intval(1)}),
|
||
|
}),
|
||
|
}),
|
||
|
},
|
||
|
} {
|
||
|
got, _, err := toProtoValue(reflect.ValueOf(test.in))
|
||
|
if err != nil {
|
||
|
t.Errorf("%v (%T): %v", test.in, test.in, err)
|
||
|
continue
|
||
|
}
|
||
|
if !testEqual(got, test.want) {
|
||
|
t.Errorf("%+v (%T):\ngot\n%+v\nwant\n%+v", test.in, test.in, got, test.want)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type stringy struct{}
|
||
|
|
||
|
func (stringy) String() string { return "stringy" }
|
||
|
|
||
|
func TestToProtoValueErrors(t *testing.T) {
|
||
|
for _, in := range []interface{}{
|
||
|
uint64(0), // a bad fit for int64
|
||
|
map[int]bool{}, // map key type is not string
|
||
|
make(chan int), // can't handle type
|
||
|
map[string]fmt.Stringer{"a": stringy{}}, // only empty interfaces
|
||
|
ServerTimestamp, // ServerTimestamp can only be a field value
|
||
|
[]interface{}{ServerTimestamp},
|
||
|
map[string]interface{}{"a": []interface{}{ServerTimestamp}},
|
||
|
map[string]interface{}{"a": []interface{}{
|
||
|
map[string]interface{}{"b": ServerTimestamp},
|
||
|
}},
|
||
|
Delete, // Delete should never appear
|
||
|
[]interface{}{Delete},
|
||
|
map[string]interface{}{"a": Delete},
|
||
|
map[string]interface{}{"a": []interface{}{Delete}},
|
||
|
} {
|
||
|
_, _, err := toProtoValue(reflect.ValueOf(in))
|
||
|
if err == nil {
|
||
|
t.Errorf("%v: got nil, want error", in)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type testStruct2 struct {
|
||
|
Ignore int `firestore:"-"`
|
||
|
Rename int `firestore:"a"`
|
||
|
OmitEmpty int `firestore:",omitempty"`
|
||
|
OmitEmptyTime time.Time `firestore:",omitempty"`
|
||
|
}
|
||
|
|
||
|
func TestToProtoValueTags(t *testing.T) {
|
||
|
in := &testStruct2{
|
||
|
Ignore: 1,
|
||
|
Rename: 2,
|
||
|
OmitEmpty: 3,
|
||
|
OmitEmptyTime: aTime,
|
||
|
}
|
||
|
got, _, err := toProtoValue(reflect.ValueOf(in))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
want := mapval(map[string]*pb.Value{
|
||
|
"a": intval(2),
|
||
|
"OmitEmpty": intval(3),
|
||
|
"OmitEmptyTime": tsval(aTime),
|
||
|
})
|
||
|
if !testEqual(got, want) {
|
||
|
t.Errorf("got %+v, want %+v", got, want)
|
||
|
}
|
||
|
|
||
|
got, _, err = toProtoValue(reflect.ValueOf(testStruct2{}))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
want = mapval(map[string]*pb.Value{"a": intval(0)})
|
||
|
if !testEqual(got, want) {
|
||
|
t.Errorf("got\n%+v\nwant\n%+v", got, want)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestToProtoValueEmbedded(t *testing.T) {
|
||
|
// Embedded time.Time or LatLng should behave like non-embedded.
|
||
|
type embed struct {
|
||
|
time.Time
|
||
|
*latlng.LatLng
|
||
|
}
|
||
|
|
||
|
got, _, err := toProtoValue(reflect.ValueOf(embed{tm, ll}))
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
want := mapval(map[string]*pb.Value{
|
||
|
"Time": tsval(tm),
|
||
|
"LatLng": geoval(ll),
|
||
|
})
|
||
|
if !testEqual(got, want) {
|
||
|
t.Errorf("got %+v, want %+v", got, want)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestIsEmpty(t *testing.T) {
|
||
|
for _, e := range []interface{}{int(0), float32(0), false, "", []int{}, []int(nil), (*int)(nil)} {
|
||
|
if !isEmptyValue(reflect.ValueOf(e)) {
|
||
|
t.Errorf("%v (%T): want true, got false", e, e)
|
||
|
}
|
||
|
}
|
||
|
i := 3
|
||
|
for _, n := range []interface{}{int(1), float32(1), true, "x", []int{1}, &i} {
|
||
|
if isEmptyValue(reflect.ValueOf(n)) {
|
||
|
t.Errorf("%v (%T): want false, got true", n, n)
|
||
|
}
|
||
|
}
|
||
|
}
|