// Copyright 2017 Google LLC // // 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" "strings" "testing" "time" tspb "github.com/golang/protobuf/ptypes/timestamp" pb "google.golang.org/genproto/googleapis/firestore/v1" ) func TestToProtoDocument(t *testing.T) { type s struct{ I int } for _, test := range []struct { in interface{} want *pb.Document wantErr bool }{ {nil, nil, true}, {[]int{1}, nil, true}, {map[string]int{"a": 1}, &pb.Document{Fields: map[string]*pb.Value{"a": intval(1)}}, false}, {s{2}, &pb.Document{Fields: map[string]*pb.Value{"I": intval(2)}}, false}, {&s{3}, &pb.Document{Fields: map[string]*pb.Value{"I": intval(3)}}, false}, } { got, _, gotErr := toProtoDocument(test.in) if (gotErr != nil) != test.wantErr { t.Errorf("%v: got error %v, want %t", test.in, gotErr, test.wantErr) } if gotErr != nil { continue } if !testEqual(got, test.want) { t.Errorf("%v: got %v, want %v", test.in, got, test.want) } } } func TestNewDocumentSnapshot(t *testing.T) { c := &Client{ projectID: "projID", databaseID: "(database)", } docRef := c.Doc("C/a") in := &pb.Document{ CreateTime: &tspb.Timestamp{Seconds: 10}, UpdateTime: &tspb.Timestamp{Seconds: 20}, Fields: map[string]*pb.Value{"a": intval(1)}, } want := &DocumentSnapshot{ Ref: docRef, CreateTime: time.Unix(10, 0).UTC(), UpdateTime: time.Unix(20, 0).UTC(), ReadTime: aTime, proto: in, c: c, } got, err := newDocumentSnapshot(docRef, in, c, aTimestamp) if err != nil { t.Fatal(err) } if !testEqual(got, want) { t.Errorf("got %+v\nwant %+v", got, want) } } func TestData(t *testing.T) { doc := &DocumentSnapshot{ proto: &pb.Document{ Fields: map[string]*pb.Value{"a": intval(1), "b": strval("x")}, }, } got := doc.Data() want := map[string]interface{}{"a": int64(1), "b": "x"} if !testEqual(got, want) { t.Errorf("got %#v\nwant %#v", got, want) } var got2 map[string]interface{} if err := doc.DataTo(&got2); err != nil { t.Fatal(err) } if !testEqual(got2, want) { t.Errorf("got %#v\nwant %#v", got2, want) } type s struct { A int B string } var got3 s if err := doc.DataTo(&got3); err != nil { t.Fatal(err) } want2 := s{A: 1, B: "x"} if !testEqual(got3, want2) { t.Errorf("got %#v\nwant %#v", got3, want2) } } var testDoc = &DocumentSnapshot{ proto: &pb.Document{ Fields: map[string]*pb.Value{ "a": intval(1), "b": mapval(map[string]*pb.Value{ "`": intval(2), "~": mapval(map[string]*pb.Value{ "x": intval(3), }), }), }, }, } func TestDataAt(t *testing.T) { for _, test := range []struct { fieldPath string want interface{} }{ {"a", int64(1)}, {"b.`", int64(2)}, } { got, err := testDoc.DataAt(test.fieldPath) if err != nil { t.Errorf("%q: %v", test.fieldPath, err) continue } if !testEqual(got, test.want) { t.Errorf("%q: got %v, want %v", test.fieldPath, got, test.want) } } for _, bad := range []string{ "c.~.x", // bad field path "a.b", // "a" isn't a map "z.b", // bad non-final key "b.z", // bad final key } { _, err := testDoc.DataAt(bad) if err == nil { t.Errorf("%q: got nil, want error", bad) } } } func TestDataAtPath(t *testing.T) { for _, test := range []struct { fieldPath FieldPath want interface{} }{ {[]string{"a"}, int64(1)}, {[]string{"b", "`"}, int64(2)}, {[]string{"b", "~"}, map[string]interface{}{"x": int64(3)}}, {[]string{"b", "~", "x"}, int64(3)}, } { got, err := testDoc.DataAtPath(test.fieldPath) if err != nil { t.Errorf("%v: %v", test.fieldPath, err) continue } if !testEqual(got, test.want) { t.Errorf("%v: got %v, want %v", test.fieldPath, got, test.want) } } for _, bad := range []FieldPath{ []string{"c", "", "x"}, // bad field path []string{"a", "b"}, // "a" isn't a map []string{"z", "~"}, // bad non-final key []string{"b", "z"}, // bad final key } { _, err := testDoc.DataAtPath(bad) if err == nil { t.Errorf("%v: got nil, want error", bad) } } } func TestExtractTransforms(t *testing.T) { type S struct { A time.Time `firestore:",serverTimestamp"` B time.Time `firestore:",serverTimestamp"` C *time.Time `firestore:",serverTimestamp"` D *time.Time `firestore:"d.d,serverTimestamp"` E *time.Time `firestore:",serverTimestamp"` F time.Time G int } m := map[string]interface{}{ "ar": map[string]interface{}{"k2": ArrayRemove("e", "f", "g")}, "au": map[string]interface{}{"k1": ArrayUnion("a", "b", "c")}, "x": 1, "y": &S{ // A is a zero time: included B: aTime, // not a zero time: excluded //C is nil: included D: &time.Time{}, // pointer to a zero time: included E: &aTime, // pointer to a non-zero time: excluded //F is a zero time, but does not have the right tag: excluded G: 15, // not a time.Time }, "z": map[string]interface{}{"w": ServerTimestamp}, } got, err := extractTransforms(reflect.ValueOf(m), nil) if err != nil { t.Fatal(err) } want := []*pb.DocumentTransform_FieldTransform{ { FieldPath: "ar.k2", TransformType: &pb.DocumentTransform_FieldTransform_RemoveAllFromArray{ RemoveAllFromArray: &pb.ArrayValue{Values: []*pb.Value{ {ValueType: &pb.Value_StringValue{"e"}}, {ValueType: &pb.Value_StringValue{"f"}}, {ValueType: &pb.Value_StringValue{"g"}}, }}, }, }, { FieldPath: "au.k1", TransformType: &pb.DocumentTransform_FieldTransform_AppendMissingElements{ AppendMissingElements: &pb.ArrayValue{Values: []*pb.Value{ {ValueType: &pb.Value_StringValue{"a"}}, {ValueType: &pb.Value_StringValue{"b"}}, {ValueType: &pb.Value_StringValue{"c"}}, }}, }, }, { FieldPath: "y.A", TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{ SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME, }, }, { FieldPath: "y.C", TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{ SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME, }, }, { FieldPath: "y.`d.d`", TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{ SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME, }, }, { FieldPath: "z.w", TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{ SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME, }, }, } if len(got) != len(want) { t.Fatalf("Expected output array of size %d, got %d: %v", len(want), len(got), got) } sort.Sort(byDocumentTransformFieldPath(got)) for i, vw := range want { vg := got[i] if !testEqual(vw, vg) { t.Fatalf("index %d: got %#v, want %#v", i, vg, vw) } } } type byDocumentTransformFieldPath []*pb.DocumentTransform_FieldTransform func (b byDocumentTransformFieldPath) Len() int { return len(b) } func (b byDocumentTransformFieldPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func (b byDocumentTransformFieldPath) Less(i, j int) bool { return strings.Compare(b[i].FieldPath, b[j].FieldPath) < 1 } func TestExtractTransformPathsErrors(t *testing.T) { type S struct { A int `firestore:",serverTimestamp"` } _, err := extractTransforms(reflect.ValueOf(S{}), nil) if err == nil { t.Error("got nil, want error") } }