You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

240 line
5.7 KiB

  1. // Copyright 2017 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package firestore
  15. import (
  16. "reflect"
  17. "sort"
  18. "testing"
  19. "time"
  20. pb "google.golang.org/genproto/googleapis/firestore/v1beta1"
  21. tspb "github.com/golang/protobuf/ptypes/timestamp"
  22. )
  23. func TestToProtoDocument(t *testing.T) {
  24. type s struct{ I int }
  25. for _, test := range []struct {
  26. in interface{}
  27. want *pb.Document
  28. wantErr bool
  29. }{
  30. {nil, nil, true},
  31. {[]int{1}, nil, true},
  32. {map[string]int{"a": 1},
  33. &pb.Document{Fields: map[string]*pb.Value{"a": intval(1)}},
  34. false},
  35. {s{2}, &pb.Document{Fields: map[string]*pb.Value{"I": intval(2)}}, false},
  36. {&s{3}, &pb.Document{Fields: map[string]*pb.Value{"I": intval(3)}}, false},
  37. } {
  38. got, _, gotErr := toProtoDocument(test.in)
  39. if (gotErr != nil) != test.wantErr {
  40. t.Errorf("%v: got error %v, want %t", test.in, gotErr, test.wantErr)
  41. }
  42. if gotErr != nil {
  43. continue
  44. }
  45. if !testEqual(got, test.want) {
  46. t.Errorf("%v: got %v, want %v", test.in, got, test.want)
  47. }
  48. }
  49. }
  50. func TestNewDocumentSnapshot(t *testing.T) {
  51. c := &Client{
  52. projectID: "projID",
  53. databaseID: "(database)",
  54. }
  55. docRef := c.Doc("C/a")
  56. in := &pb.Document{
  57. CreateTime: &tspb.Timestamp{Seconds: 10},
  58. UpdateTime: &tspb.Timestamp{Seconds: 20},
  59. Fields: map[string]*pb.Value{"a": intval(1)},
  60. }
  61. want := &DocumentSnapshot{
  62. Ref: docRef,
  63. CreateTime: time.Unix(10, 0).UTC(),
  64. UpdateTime: time.Unix(20, 0).UTC(),
  65. ReadTime: aTime,
  66. proto: in,
  67. c: c,
  68. }
  69. got, err := newDocumentSnapshot(docRef, in, c, aTimestamp)
  70. if err != nil {
  71. t.Fatal(err)
  72. }
  73. if !testEqual(got, want) {
  74. t.Errorf("got %+v\nwant %+v", got, want)
  75. }
  76. }
  77. func TestData(t *testing.T) {
  78. doc := &DocumentSnapshot{
  79. proto: &pb.Document{
  80. Fields: map[string]*pb.Value{"a": intval(1), "b": strval("x")},
  81. },
  82. }
  83. got := doc.Data()
  84. want := map[string]interface{}{"a": int64(1), "b": "x"}
  85. if !testEqual(got, want) {
  86. t.Errorf("got %#v\nwant %#v", got, want)
  87. }
  88. var got2 map[string]interface{}
  89. if err := doc.DataTo(&got2); err != nil {
  90. t.Fatal(err)
  91. }
  92. if !testEqual(got2, want) {
  93. t.Errorf("got %#v\nwant %#v", got2, want)
  94. }
  95. type s struct {
  96. A int
  97. B string
  98. }
  99. var got3 s
  100. if err := doc.DataTo(&got3); err != nil {
  101. t.Fatal(err)
  102. }
  103. want2 := s{A: 1, B: "x"}
  104. if !testEqual(got3, want2) {
  105. t.Errorf("got %#v\nwant %#v", got3, want2)
  106. }
  107. }
  108. var testDoc = &DocumentSnapshot{
  109. proto: &pb.Document{
  110. Fields: map[string]*pb.Value{
  111. "a": intval(1),
  112. "b": mapval(map[string]*pb.Value{
  113. "`": intval(2),
  114. "~": mapval(map[string]*pb.Value{
  115. "x": intval(3),
  116. }),
  117. }),
  118. },
  119. },
  120. }
  121. func TestDataAt(t *testing.T) {
  122. for _, test := range []struct {
  123. fieldPath string
  124. want interface{}
  125. }{
  126. {"a", int64(1)},
  127. {"b.`", int64(2)},
  128. } {
  129. got, err := testDoc.DataAt(test.fieldPath)
  130. if err != nil {
  131. t.Errorf("%q: %v", test.fieldPath, err)
  132. continue
  133. }
  134. if !testEqual(got, test.want) {
  135. t.Errorf("%q: got %v, want %v", test.fieldPath, got, test.want)
  136. }
  137. }
  138. for _, bad := range []string{
  139. "c.~.x", // bad field path
  140. "a.b", // "a" isn't a map
  141. "z.b", // bad non-final key
  142. "b.z", // bad final key
  143. } {
  144. _, err := testDoc.DataAt(bad)
  145. if err == nil {
  146. t.Errorf("%q: got nil, want error", bad)
  147. }
  148. }
  149. }
  150. func TestDataAtPath(t *testing.T) {
  151. for _, test := range []struct {
  152. fieldPath FieldPath
  153. want interface{}
  154. }{
  155. {[]string{"a"}, int64(1)},
  156. {[]string{"b", "`"}, int64(2)},
  157. {[]string{"b", "~"}, map[string]interface{}{"x": int64(3)}},
  158. {[]string{"b", "~", "x"}, int64(3)},
  159. } {
  160. got, err := testDoc.DataAtPath(test.fieldPath)
  161. if err != nil {
  162. t.Errorf("%v: %v", test.fieldPath, err)
  163. continue
  164. }
  165. if !testEqual(got, test.want) {
  166. t.Errorf("%v: got %v, want %v", test.fieldPath, got, test.want)
  167. }
  168. }
  169. for _, bad := range []FieldPath{
  170. []string{"c", "", "x"}, // bad field path
  171. []string{"a", "b"}, // "a" isn't a map
  172. []string{"z", "~"}, // bad non-final key
  173. []string{"b", "z"}, // bad final key
  174. } {
  175. _, err := testDoc.DataAtPath(bad)
  176. if err == nil {
  177. t.Errorf("%v: got nil, want error", bad)
  178. }
  179. }
  180. }
  181. func TestExtractTransformPaths(t *testing.T) {
  182. type S struct {
  183. A time.Time `firestore:",serverTimestamp"`
  184. B time.Time `firestore:",serverTimestamp"`
  185. C *time.Time `firestore:",serverTimestamp"`
  186. D *time.Time `firestore:"d.d,serverTimestamp"`
  187. E *time.Time `firestore:",serverTimestamp"`
  188. F time.Time
  189. G int
  190. }
  191. m := map[string]interface{}{
  192. "x": 1,
  193. "y": &S{
  194. // A is a zero time: included
  195. B: aTime, // not a zero time: excluded
  196. // C is nil: included
  197. D: &time.Time{}, // pointer to a zero time: included
  198. E: &aTime, // pointer to a non-zero time: excluded
  199. // F is a zero time, but does not have the right tag: excluded
  200. G: 15, // not a time.Time
  201. },
  202. "z": map[string]interface{}{"w": ServerTimestamp},
  203. }
  204. got, err := extractTransformPaths(reflect.ValueOf(m), nil)
  205. if err != nil {
  206. t.Fatal(err)
  207. }
  208. sort.Sort(byPath(got))
  209. want := []FieldPath{{"y", "A"}, {"y", "C"}, {"y", "d.d"}, {"z", "w"}}
  210. if !testEqual(got, want) {
  211. t.Errorf("got %#v, want %#v", got, want)
  212. }
  213. }
  214. func TestExtractTransformPathsErrors(t *testing.T) {
  215. type S struct {
  216. A int `firestore:",serverTimestamp"`
  217. }
  218. _, err := extractTransformPaths(reflect.ValueOf(S{}), nil)
  219. if err == nil {
  220. t.Error("got nil, want error")
  221. }
  222. }