rclone/vendor/cloud.google.com/go/firestore/docref_test.go
2018-01-16 13:20:59 +00:00

307 lines
7.5 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 (
"reflect"
"sort"
"testing"
"time"
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
"golang.org/x/net/context"
"google.golang.org/genproto/googleapis/type/latlng"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
var (
writeResultForSet = &WriteResult{UpdateTime: aTime}
commitResponseForSet = &pb.CommitResponse{
WriteResults: []*pb.WriteResult{{UpdateTime: aTimestamp}},
}
)
func TestDocGet(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
path := "projects/projectID/databases/(default)/documents/C/a"
pdoc := &pb.Document{
Name: path,
CreateTime: aTimestamp,
UpdateTime: aTimestamp,
Fields: map[string]*pb.Value{"f": intval(1)},
}
srv.addRPC(&pb.GetDocumentRequest{Name: path}, pdoc)
ref := c.Collection("C").Doc("a")
gotDoc, err := ref.Get(ctx)
if err != nil {
t.Fatal(err)
}
wantDoc := &DocumentSnapshot{
Ref: ref,
CreateTime: aTime,
UpdateTime: aTime,
proto: pdoc,
c: c,
}
if !testEqual(gotDoc, wantDoc) {
t.Fatalf("\ngot %+v\nwant %+v", gotDoc, wantDoc)
}
srv.addRPC(
&pb.GetDocumentRequest{
Name: "projects/projectID/databases/(default)/documents/C/b",
},
grpc.Errorf(codes.NotFound, "not found"),
)
_, err = c.Collection("C").Doc("b").Get(ctx)
if grpc.Code(err) != codes.NotFound {
t.Errorf("got %v, want NotFound", err)
}
}
func TestDocSet(t *testing.T) {
// Most tests for Set are in the cross-language tests.
ctx := context.Background()
c, srv := newMock(t)
doc := c.Collection("C").Doc("d")
// Merge with a struct and FieldPaths.
srv.addRPC(&pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{
{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: map[string]*pb.Value{
"*": mapval(map[string]*pb.Value{
"~": boolval(true),
}),
},
},
},
UpdateMask: &pb.DocumentMask{FieldPaths: []string{"`*`.`~`"}},
},
},
}, commitResponseForSet)
data := struct {
A map[string]bool `firestore:"*"`
}{A: map[string]bool{"~": true}}
wr, err := doc.Set(ctx, data, Merge([]string{"*", "~"}))
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, writeResultForSet) {
t.Errorf("got %v, want %v", wr, writeResultForSet)
}
// MergeAll cannot be used with structs.
_, err = doc.Set(ctx, data, MergeAll)
if err == nil {
t.Errorf("got nil, want error")
}
}
func TestDocCreate(t *testing.T) {
// Verify creation with structs. In particular, make sure zero values
// are handled well.
// Other tests for Create are handled by the cross-language tests.
ctx := context.Background()
c, srv := newMock(t)
type create struct {
Time time.Time
Bytes []byte
Geo *latlng.LatLng
}
srv.addRPC(
&pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{
{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: map[string]*pb.Value{
"Time": tsval(time.Time{}),
"Bytes": bytesval(nil),
"Geo": nullValue,
},
},
},
CurrentDocument: &pb.Precondition{
ConditionType: &pb.Precondition_Exists{false},
},
},
},
},
commitResponseForSet,
)
_, err := c.Collection("C").Doc("d").Create(ctx, &create{})
if err != nil {
t.Fatal(err)
}
}
func TestDocDelete(t *testing.T) {
ctx := context.Background()
c, srv := newMock(t)
srv.addRPC(
&pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{
{Operation: &pb.Write_Delete{"projects/projectID/databases/(default)/documents/C/d"}},
},
},
&pb.CommitResponse{
WriteResults: []*pb.WriteResult{{}},
})
wr, err := c.Collection("C").Doc("d").Delete(ctx)
if err != nil {
t.Fatal(err)
}
if !testEqual(wr, &WriteResult{}) {
t.Errorf("got %+v, want %+v", wr, writeResultForSet)
}
}
var (
testData = map[string]interface{}{"a": 1}
testFields = map[string]*pb.Value{"a": intval(1)}
)
// Update is tested by the cross-language tests.
func TestApplyFieldPaths(t *testing.T) {
submap := mapval(map[string]*pb.Value{
"b": intval(1),
"c": intval(2),
})
fields := map[string]*pb.Value{
"a": submap,
"d": intval(3),
}
for _, test := range []struct {
fps []FieldPath
want map[string]*pb.Value
}{
{nil, nil},
{[]FieldPath{[]string{"z"}}, nil},
{[]FieldPath{[]string{"a"}}, map[string]*pb.Value{"a": submap}},
{[]FieldPath{[]string{"a", "b", "c"}}, nil},
{[]FieldPath{[]string{"d"}}, map[string]*pb.Value{"d": intval(3)}},
{
[]FieldPath{[]string{"d"}, []string{"a", "c"}},
map[string]*pb.Value{
"a": mapval(map[string]*pb.Value{"c": intval(2)}),
"d": intval(3),
},
},
} {
got := applyFieldPaths(fields, test.fps, nil)
if !testEqual(got, test.want) {
t.Errorf("%v:\ngot %v\nwant \n%v", test.fps, got, test.want)
}
}
}
func TestFieldPathsFromMap(t *testing.T) {
for _, test := range []struct {
in map[string]interface{}
want []string
}{
{nil, nil},
{map[string]interface{}{"a": 1}, []string{"a"}},
{map[string]interface{}{
"a": 1,
"b": map[string]interface{}{"c": 2},
}, []string{"a", "b.c"}},
} {
fps := fieldPathsFromMap(reflect.ValueOf(test.in), nil)
got := toServiceFieldPaths(fps)
sort.Strings(got)
if !testEqual(got, test.want) {
t.Errorf("%+v: got %v, want %v", test.in, got, test.want)
}
}
}
func commitRequestForSet() *pb.CommitRequest {
return &pb.CommitRequest{
Database: "projects/projectID/databases/(default)",
Writes: []*pb.Write{
{
Operation: &pb.Write_Update{
Update: &pb.Document{
Name: "projects/projectID/databases/(default)/documents/C/d",
Fields: testFields,
},
},
},
},
}
}
func TestUpdateProcess(t *testing.T) {
for _, test := range []struct {
in Update
want fpv
wantErr bool
}{
{
in: Update{Path: "a", Value: 1},
want: fpv{fieldPath: []string{"a"}, value: 1},
},
{
in: Update{Path: "c.d", Value: Delete},
want: fpv{fieldPath: []string{"c", "d"}, value: Delete},
},
{
in: Update{FieldPath: []string{"*", "~"}, Value: ServerTimestamp},
want: fpv{fieldPath: []string{"*", "~"}, value: ServerTimestamp},
},
{
in: Update{Path: "*"},
wantErr: true, // bad rune in path
},
{
in: Update{Path: "a", FieldPath: []string{"b"}},
wantErr: true, // both Path and FieldPath
},
{
in: Update{Value: 1},
wantErr: true, // neither Path nor FieldPath
},
{
in: Update{FieldPath: []string{"", "a"}},
wantErr: true, // empty FieldPath component
},
} {
got, err := test.in.process()
if test.wantErr {
if err == nil {
t.Errorf("%+v: got nil, want error", test.in)
}
} else if err != nil {
t.Errorf("%+v: got error %v, want nil", test.in, err)
} else if !testEqual(got, test.want) {
t.Errorf("%+v: got %+v, want %+v", test.in, got, test.want)
}
}
}