mirror of
https://github.com/restic/restic.git
synced 2026-09-19 02:37:58 +00:00
Update dependencies
Among others, this updates minio-go, so that the new "eu-west-3" zone for AWS is supported.
This commit is contained in:
+118
-563
@@ -22,7 +22,6 @@ import (
|
||||
|
||||
pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/genproto/googleapis/type/latlng"
|
||||
"google.golang.org/grpc"
|
||||
@@ -76,246 +75,34 @@ func TestDocGet(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDocSet(t *testing.T) {
|
||||
// Most tests for Set are in the cross-language tests.
|
||||
ctx := context.Background()
|
||||
c, srv := newMock(t)
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
data interface{}
|
||||
opt SetOption
|
||||
write map[string]*pb.Value
|
||||
mask []string
|
||||
transform []string
|
||||
isErr bool
|
||||
}{
|
||||
{
|
||||
desc: "Set with no options",
|
||||
data: map[string]interface{}{"a": 1},
|
||||
write: map[string]*pb.Value{"a": intval(1)},
|
||||
},
|
||||
{
|
||||
desc: "Merge with a field",
|
||||
data: map[string]interface{}{"a": 1, "b": 2},
|
||||
opt: Merge("a"),
|
||||
write: map[string]*pb.Value{"a": intval(1)},
|
||||
mask: []string{"a"},
|
||||
},
|
||||
{
|
||||
desc: "Merge field is not a leaf",
|
||||
data: map[string]interface{}{
|
||||
"a": map[string]interface{}{"b": 1, "c": 2},
|
||||
"d": 3,
|
||||
},
|
||||
opt: Merge("a"),
|
||||
write: map[string]*pb.Value{"a": mapval(map[string]*pb.Value{
|
||||
"b": intval(1),
|
||||
"c": intval(2),
|
||||
})},
|
||||
mask: []string{"a"},
|
||||
},
|
||||
{
|
||||
desc: "MergeAll",
|
||||
data: map[string]interface{}{"a": 1, "b": 2},
|
||||
opt: MergeAll,
|
||||
write: map[string]*pb.Value{"a": intval(1), "b": intval(2)},
|
||||
mask: []string{"a", "b"},
|
||||
},
|
||||
{
|
||||
desc: "MergeAll with nested fields",
|
||||
data: map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": map[string]interface{}{"c": 2},
|
||||
},
|
||||
opt: MergeAll,
|
||||
write: map[string]*pb.Value{
|
||||
"a": intval(1),
|
||||
"b": mapval(map[string]*pb.Value{"c": intval(2)}),
|
||||
},
|
||||
mask: []string{"a", "b.c"},
|
||||
},
|
||||
{
|
||||
desc: "Merge with FieldPaths",
|
||||
data: map[string]interface{}{"*": map[string]interface{}{"~": true}},
|
||||
opt: MergePaths([]string{"*", "~"}),
|
||||
write: map[string]*pb.Value{
|
||||
"*": mapval(map[string]*pb.Value{
|
||||
"~": boolval(true),
|
||||
}),
|
||||
},
|
||||
mask: []string{"`*`.`~`"},
|
||||
},
|
||||
{
|
||||
desc: "Merge with a struct and FieldPaths",
|
||||
data: struct {
|
||||
A map[string]bool `firestore:"*"`
|
||||
}{A: map[string]bool{"~": true}},
|
||||
opt: MergePaths([]string{"*", "~"}),
|
||||
write: map[string]*pb.Value{
|
||||
"*": mapval(map[string]*pb.Value{
|
||||
"~": boolval(true),
|
||||
}),
|
||||
},
|
||||
mask: []string{"`*`.`~`"},
|
||||
},
|
||||
{
|
||||
desc: "a ServerTimestamp field becomes a transform",
|
||||
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
|
||||
write: map[string]*pb.Value{"a": intval(1)},
|
||||
transform: []string{"b"},
|
||||
},
|
||||
{
|
||||
desc: "a ServerTimestamp alone",
|
||||
data: map[string]interface{}{"b": ServerTimestamp},
|
||||
write: nil,
|
||||
transform: []string{"b"},
|
||||
},
|
||||
{
|
||||
desc: "a ServerTimestamp alone with a path",
|
||||
data: map[string]interface{}{"b": ServerTimestamp},
|
||||
opt: MergePaths([]string{"b"}),
|
||||
write: nil,
|
||||
transform: []string{"b"},
|
||||
},
|
||||
{
|
||||
desc: "nested ServerTimestamp field",
|
||||
data: map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": map[string]interface{}{"c": ServerTimestamp},
|
||||
},
|
||||
write: map[string]*pb.Value{"a": intval(1)},
|
||||
transform: []string{"b.c"},
|
||||
},
|
||||
{
|
||||
desc: "multiple ServerTimestamp fields",
|
||||
data: map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": ServerTimestamp,
|
||||
"c": map[string]interface{}{"d": ServerTimestamp},
|
||||
},
|
||||
write: map[string]*pb.Value{"a": intval(1)},
|
||||
transform: []string{"b", "c.d"},
|
||||
},
|
||||
{
|
||||
desc: "ServerTimestamp with MergeAll",
|
||||
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
|
||||
opt: MergeAll,
|
||||
write: map[string]*pb.Value{"a": intval(1)},
|
||||
mask: []string{"a"},
|
||||
transform: []string{"b"},
|
||||
},
|
||||
{
|
||||
desc: "ServerTimestamp with Merge of both fields",
|
||||
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
|
||||
opt: Merge("a", "b"),
|
||||
write: map[string]*pb.Value{"a": intval(1)},
|
||||
mask: []string{"a"},
|
||||
transform: []string{"b"},
|
||||
},
|
||||
{
|
||||
desc: "If is ServerTimestamp not in Merge, no transform",
|
||||
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
|
||||
opt: Merge("a"),
|
||||
write: map[string]*pb.Value{"a": intval(1)},
|
||||
mask: []string{"a"},
|
||||
},
|
||||
{
|
||||
desc: "If no ordinary values in Merge, no write",
|
||||
data: map[string]interface{}{"a": 1, "b": ServerTimestamp},
|
||||
opt: Merge("b"),
|
||||
transform: []string{"b"},
|
||||
},
|
||||
{
|
||||
desc: "Merge fields must all be present in data.",
|
||||
data: map[string]interface{}{"a": 1},
|
||||
opt: Merge("b", "a"),
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
desc: "MergeAll cannot be used with structs",
|
||||
data: struct{ A int }{A: 1},
|
||||
opt: MergeAll,
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Delete cannot appear in data",
|
||||
data: map[string]interface{}{"a": 1, "b": Delete},
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
desc: "Delete cannot even appear in an unmerged field (allow?)",
|
||||
data: map[string]interface{}{"a": 1, "b": Delete},
|
||||
opt: Merge("a"),
|
||||
isErr: true,
|
||||
},
|
||||
} {
|
||||
srv.reset()
|
||||
if !test.isErr {
|
||||
var writes []*pb.Write
|
||||
if test.write != nil || test.mask != nil {
|
||||
w := &pb.Write{}
|
||||
if test.write != nil {
|
||||
w.Operation = &pb.Write_Update{
|
||||
Update: &pb.Document{
|
||||
Name: "projects/projectID/databases/(default)/documents/C/d",
|
||||
Fields: test.write,
|
||||
},
|
||||
}
|
||||
}
|
||||
if test.mask != nil {
|
||||
w.UpdateMask = &pb.DocumentMask{FieldPaths: test.mask}
|
||||
}
|
||||
writes = append(writes, w)
|
||||
}
|
||||
if test.transform != nil {
|
||||
var fts []*pb.DocumentTransform_FieldTransform
|
||||
for _, p := range test.transform {
|
||||
fts = append(fts, &pb.DocumentTransform_FieldTransform{
|
||||
FieldPath: p,
|
||||
TransformType: requestTimeTransform,
|
||||
})
|
||||
}
|
||||
writes = append(writes, &pb.Write{
|
||||
Operation: &pb.Write_Transform{
|
||||
&pb.DocumentTransform{
|
||||
Document: "projects/projectID/databases/(default)/documents/C/d",
|
||||
FieldTransforms: fts,
|
||||
|
||||
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),
|
||||
}),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
srv.addRPC(&pb.CommitRequest{
|
||||
Database: "projects/projectID/databases/(default)",
|
||||
Writes: writes,
|
||||
}, commitResponseForSet)
|
||||
}
|
||||
var opts []SetOption
|
||||
if test.opt != nil {
|
||||
opts = []SetOption{test.opt}
|
||||
}
|
||||
wr, err := c.Collection("C").Doc("d").Set(ctx, test.data, opts...)
|
||||
if test.isErr && err == nil {
|
||||
t.Errorf("%s: got nil, want error")
|
||||
continue
|
||||
}
|
||||
if !test.isErr && err != nil {
|
||||
t.Errorf("%s: %v", test.desc, err)
|
||||
continue
|
||||
}
|
||||
if err == nil && !testEqual(wr, writeResultForSet) {
|
||||
t.Errorf("%s: got %v, want %v", test.desc, wr, writeResultForSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocCreate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, srv := newMock(t)
|
||||
wantReq := commitRequestForSet()
|
||||
wantReq.Writes[0].CurrentDocument = &pb.Precondition{
|
||||
ConditionType: &pb.Precondition_Exists{false},
|
||||
}
|
||||
srv.addRPC(wantReq, commitResponseForSet)
|
||||
wr, err := c.Collection("C").Doc("d").Create(ctx, testData)
|
||||
},
|
||||
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)
|
||||
}
|
||||
@@ -323,8 +110,20 @@ func TestDocCreate(t *testing.T) {
|
||||
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
|
||||
@@ -353,7 +152,7 @@ func TestDocCreate(t *testing.T) {
|
||||
},
|
||||
commitResponseForSet,
|
||||
)
|
||||
_, err = c.Collection("C").Doc("d").Create(ctx, &create{})
|
||||
_, err := c.Collection("C").Doc("d").Create(ctx, &create{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -381,351 +180,59 @@ func TestDocDelete(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocDeleteLastUpdateTime(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, srv := newMock(t)
|
||||
wantReq := &pb.CommitRequest{
|
||||
Database: "projects/projectID/databases/(default)",
|
||||
Writes: []*pb.Write{
|
||||
{
|
||||
Operation: &pb.Write_Delete{"projects/projectID/databases/(default)/documents/C/d"},
|
||||
CurrentDocument: &pb.Precondition{
|
||||
ConditionType: &pb.Precondition_UpdateTime{aTimestamp2},
|
||||
},
|
||||
}},
|
||||
}
|
||||
srv.addRPC(wantReq, commitResponseForSet)
|
||||
wr, err := c.Collection("C").Doc("d").Delete(ctx, LastUpdateTime(aTime2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testEqual(wr, writeResultForSet) {
|
||||
t.Errorf("got %+v, want %+v", wr, writeResultForSet)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
testData = map[string]interface{}{"a": 1}
|
||||
testFields = map[string]*pb.Value{"a": intval(1)}
|
||||
)
|
||||
|
||||
func TestUpdateMap(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, srv := newMock(t)
|
||||
// Update is tested by the cross-language tests.
|
||||
|
||||
func TestFPVsFromData(t *testing.T) {
|
||||
type S struct{ X int }
|
||||
|
||||
for _, test := range []struct {
|
||||
data map[string]interface{}
|
||||
wantFields map[string]*pb.Value
|
||||
wantPaths []string
|
||||
in interface{}
|
||||
want []fpv
|
||||
}{
|
||||
{
|
||||
data: map[string]interface{}{"a.b": 1},
|
||||
wantFields: map[string]*pb.Value{
|
||||
"a": mapval(map[string]*pb.Value{"b": intval(1)}),
|
||||
},
|
||||
wantPaths: []string{"a.b"},
|
||||
in: nil,
|
||||
want: []fpv{{nil, nil}},
|
||||
},
|
||||
{
|
||||
data: map[string]interface{}{
|
||||
in: map[string]interface{}{"a": nil},
|
||||
want: []fpv{{[]string{"a"}, nil}},
|
||||
},
|
||||
{
|
||||
in: map[string]interface{}{"a": 1},
|
||||
want: []fpv{{[]string{"a"}, 1}},
|
||||
},
|
||||
{
|
||||
in: map[string]interface{}{
|
||||
"a": 1,
|
||||
"b": Delete,
|
||||
"b": map[string]interface{}{"c": 2},
|
||||
},
|
||||
wantFields: map[string]*pb.Value{"a": intval(1)},
|
||||
wantPaths: []string{"a", "b"},
|
||||
want: []fpv{{[]string{"a"}, 1}, {[]string{"b", "c"}, 2}},
|
||||
},
|
||||
{
|
||||
in: map[string]interface{}{"s": &S{X: 3}},
|
||||
want: []fpv{{[]string{"s"}, &S{X: 3}}},
|
||||
},
|
||||
} {
|
||||
srv.reset()
|
||||
wantReq := &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: test.wantFields,
|
||||
}},
|
||||
UpdateMask: &pb.DocumentMask{FieldPaths: test.wantPaths},
|
||||
CurrentDocument: &pb.Precondition{
|
||||
ConditionType: &pb.Precondition_Exists{true},
|
||||
},
|
||||
}},
|
||||
}
|
||||
// Sort update masks, because map iteration order is random.
|
||||
sort.Strings(wantReq.Writes[0].UpdateMask.FieldPaths)
|
||||
srv.addRPCAdjust(wantReq, commitResponseForSet, func(gotReq proto.Message) {
|
||||
sort.Strings(gotReq.(*pb.CommitRequest).Writes[0].UpdateMask.FieldPaths)
|
||||
})
|
||||
wr, err := c.Collection("C").Doc("d").UpdateMap(ctx, test.data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testEqual(wr, writeResultForSet) {
|
||||
t.Errorf("%v:\ngot %+v, want %+v", test.data, wr, writeResultForSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateMapLastUpdateTime(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, srv := newMock(t)
|
||||
|
||||
wantReq := &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{"a": intval(1)},
|
||||
}},
|
||||
UpdateMask: &pb.DocumentMask{FieldPaths: []string{"a"}},
|
||||
CurrentDocument: &pb.Precondition{
|
||||
ConditionType: &pb.Precondition_UpdateTime{aTimestamp2},
|
||||
},
|
||||
}},
|
||||
}
|
||||
srv.addRPC(wantReq, commitResponseForSet)
|
||||
wr, err := c.Collection("C").Doc("d").UpdateMap(ctx, map[string]interface{}{"a": 1}, LastUpdateTime(aTime2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testEqual(wr, writeResultForSet) {
|
||||
t.Errorf("got %v, want %v", wr, writeResultForSet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateMapErrors(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, _ := newMock(t)
|
||||
for _, in := range []map[string]interface{}{
|
||||
nil, // no paths
|
||||
map[string]interface{}{"a~b": 1}, // invalid character
|
||||
map[string]interface{}{"a..b": 1}, // empty path component
|
||||
map[string]interface{}{"a.b": 1, "a": 2}, // prefix
|
||||
} {
|
||||
_, err := c.Collection("C").Doc("d").UpdateMap(ctx, in)
|
||||
if err == nil {
|
||||
t.Errorf("%v: got nil, want error", in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateStruct(t *testing.T) {
|
||||
type update struct{ A int }
|
||||
c, srv := newMock(t)
|
||||
wantReq := &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{"A": intval(2)},
|
||||
},
|
||||
},
|
||||
UpdateMask: &pb.DocumentMask{FieldPaths: []string{"A", "b.c"}},
|
||||
CurrentDocument: &pb.Precondition{
|
||||
ConditionType: &pb.Precondition_Exists{true},
|
||||
},
|
||||
}},
|
||||
}
|
||||
srv.addRPC(wantReq, commitResponseForSet)
|
||||
wr, err := c.Collection("C").Doc("d").
|
||||
UpdateStruct(context.Background(), []string{"A", "b.c"}, &update{A: 2})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testEqual(wr, writeResultForSet) {
|
||||
t.Errorf("got %+v, want %+v", wr, writeResultForSet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateStructErrors(t *testing.T) {
|
||||
type update struct{ A int }
|
||||
|
||||
ctx := context.Background()
|
||||
c, _ := newMock(t)
|
||||
doc := c.Collection("C").Doc("d")
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
fields []string
|
||||
data interface{}
|
||||
}{
|
||||
{
|
||||
desc: "data is not a struct or *struct",
|
||||
data: map[string]interface{}{"a": 1},
|
||||
},
|
||||
{
|
||||
desc: "no paths",
|
||||
fields: nil,
|
||||
data: update{},
|
||||
},
|
||||
{
|
||||
desc: "empty",
|
||||
fields: []string{""},
|
||||
data: update{},
|
||||
},
|
||||
{
|
||||
desc: "empty component",
|
||||
fields: []string{"a.b..c"},
|
||||
data: update{},
|
||||
},
|
||||
{
|
||||
desc: "duplicate field",
|
||||
fields: []string{"a", "b", "c", "a"},
|
||||
data: update{},
|
||||
},
|
||||
{
|
||||
desc: "invalid character",
|
||||
fields: []string{"a", "b]"},
|
||||
data: update{},
|
||||
},
|
||||
{
|
||||
desc: "prefix",
|
||||
fields: []string{"a", "b", "c", "b.c"},
|
||||
data: update{},
|
||||
},
|
||||
} {
|
||||
_, err := doc.UpdateStruct(ctx, test.fields, test.data)
|
||||
if err == nil {
|
||||
t.Errorf("%s: got nil, want error", test.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePaths(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
c, srv := newMock(t)
|
||||
for _, test := range []struct {
|
||||
data []FieldPathUpdate
|
||||
wantFields map[string]*pb.Value
|
||||
wantPaths []string
|
||||
}{
|
||||
{
|
||||
data: []FieldPathUpdate{
|
||||
{Path: []string{"*", "~"}, Value: 1},
|
||||
{Path: []string{"*", "/"}, Value: 2},
|
||||
},
|
||||
wantFields: map[string]*pb.Value{
|
||||
"*": mapval(map[string]*pb.Value{
|
||||
"~": intval(1),
|
||||
"/": intval(2),
|
||||
}),
|
||||
},
|
||||
wantPaths: []string{"`*`.`~`", "`*`.`/`"},
|
||||
},
|
||||
{
|
||||
data: []FieldPathUpdate{
|
||||
{Path: []string{"*"}, Value: 1},
|
||||
{Path: []string{"]"}, Value: Delete},
|
||||
},
|
||||
wantFields: map[string]*pb.Value{"*": intval(1)},
|
||||
wantPaths: []string{"`*`", "`]`"},
|
||||
},
|
||||
} {
|
||||
srv.reset()
|
||||
wantReq := &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: test.wantFields,
|
||||
}},
|
||||
UpdateMask: &pb.DocumentMask{FieldPaths: test.wantPaths},
|
||||
CurrentDocument: &pb.Precondition{
|
||||
ConditionType: &pb.Precondition_Exists{true},
|
||||
},
|
||||
}},
|
||||
}
|
||||
// Sort update masks, because map iteration order is random.
|
||||
sort.Strings(wantReq.Writes[0].UpdateMask.FieldPaths)
|
||||
srv.addRPCAdjust(wantReq, commitResponseForSet, func(gotReq proto.Message) {
|
||||
sort.Strings(gotReq.(*pb.CommitRequest).Writes[0].UpdateMask.FieldPaths)
|
||||
})
|
||||
wr, err := c.Collection("C").Doc("d").UpdatePaths(ctx, test.data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !testEqual(wr, writeResultForSet) {
|
||||
t.Errorf("%v:\ngot %+v, want %+v", test.data, wr, writeResultForSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdatePathsErrors(t *testing.T) {
|
||||
fpu := func(s ...string) FieldPathUpdate { return FieldPathUpdate{Path: s} }
|
||||
|
||||
ctx := context.Background()
|
||||
c, _ := newMock(t)
|
||||
doc := c.Collection("C").Doc("d")
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
data []FieldPathUpdate
|
||||
}{
|
||||
{"no updates", nil},
|
||||
{"empty", []FieldPathUpdate{fpu("")}},
|
||||
{"empty component", []FieldPathUpdate{fpu("*", "")}},
|
||||
{"duplicate field", []FieldPathUpdate{fpu("~"), fpu("*"), fpu("~")}},
|
||||
{"prefix", []FieldPathUpdate{fpu("*", "a"), fpu("b"), fpu("*", "a", "b")}},
|
||||
} {
|
||||
_, err := doc.UpdatePaths(ctx, test.data)
|
||||
if err == nil {
|
||||
t.Errorf("%s: got nil, want error", test.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
var got []fpv
|
||||
fpvsFromData(reflect.ValueOf(test.in), nil, &got)
|
||||
sort.Sort(byFieldPath(got))
|
||||
if !testEqual(got, test.want) {
|
||||
t.Errorf("%+v: got %v, want %v", test.in, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type byFieldPath []fpv
|
||||
|
||||
func (b byFieldPath) Len() int { return len(b) }
|
||||
func (b byFieldPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
func (b byFieldPath) Less(i, j int) bool { return b[i].fieldPath.less(b[j].fieldPath) }
|
||||
|
||||
func commitRequestForSet() *pb.CommitRequest {
|
||||
return &pb.CommitRequest{
|
||||
Database: "projects/projectID/databases/(default)",
|
||||
@@ -741,3 +248,51 @@ func commitRequestForSet() *pb.CommitRequest {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user