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.
 
 
 

301 lines
7.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. "strings"
  19. "testing"
  20. "time"
  21. tspb "github.com/golang/protobuf/ptypes/timestamp"
  22. pb "google.golang.org/genproto/googleapis/firestore/v1"
  23. )
  24. func TestToProtoDocument(t *testing.T) {
  25. type s struct{ I int }
  26. for _, test := range []struct {
  27. in interface{}
  28. want *pb.Document
  29. wantErr bool
  30. }{
  31. {nil, nil, true},
  32. {[]int{1}, nil, true},
  33. {map[string]int{"a": 1},
  34. &pb.Document{Fields: map[string]*pb.Value{"a": intval(1)}},
  35. false},
  36. {s{2}, &pb.Document{Fields: map[string]*pb.Value{"I": intval(2)}}, false},
  37. {&s{3}, &pb.Document{Fields: map[string]*pb.Value{"I": intval(3)}}, false},
  38. } {
  39. got, _, gotErr := toProtoDocument(test.in)
  40. if (gotErr != nil) != test.wantErr {
  41. t.Errorf("%v: got error %v, want %t", test.in, gotErr, test.wantErr)
  42. }
  43. if gotErr != nil {
  44. continue
  45. }
  46. if !testEqual(got, test.want) {
  47. t.Errorf("%v: got %v, want %v", test.in, got, test.want)
  48. }
  49. }
  50. }
  51. func TestNewDocumentSnapshot(t *testing.T) {
  52. c := &Client{
  53. projectID: "projID",
  54. databaseID: "(database)",
  55. }
  56. docRef := c.Doc("C/a")
  57. in := &pb.Document{
  58. CreateTime: &tspb.Timestamp{Seconds: 10},
  59. UpdateTime: &tspb.Timestamp{Seconds: 20},
  60. Fields: map[string]*pb.Value{"a": intval(1)},
  61. }
  62. want := &DocumentSnapshot{
  63. Ref: docRef,
  64. CreateTime: time.Unix(10, 0).UTC(),
  65. UpdateTime: time.Unix(20, 0).UTC(),
  66. ReadTime: aTime,
  67. proto: in,
  68. c: c,
  69. }
  70. got, err := newDocumentSnapshot(docRef, in, c, aTimestamp)
  71. if err != nil {
  72. t.Fatal(err)
  73. }
  74. if !testEqual(got, want) {
  75. t.Errorf("got %+v\nwant %+v", got, want)
  76. }
  77. }
  78. func TestData(t *testing.T) {
  79. doc := &DocumentSnapshot{
  80. proto: &pb.Document{
  81. Fields: map[string]*pb.Value{"a": intval(1), "b": strval("x")},
  82. },
  83. }
  84. got := doc.Data()
  85. want := map[string]interface{}{"a": int64(1), "b": "x"}
  86. if !testEqual(got, want) {
  87. t.Errorf("got %#v\nwant %#v", got, want)
  88. }
  89. var got2 map[string]interface{}
  90. if err := doc.DataTo(&got2); err != nil {
  91. t.Fatal(err)
  92. }
  93. if !testEqual(got2, want) {
  94. t.Errorf("got %#v\nwant %#v", got2, want)
  95. }
  96. type s struct {
  97. A int
  98. B string
  99. }
  100. var got3 s
  101. if err := doc.DataTo(&got3); err != nil {
  102. t.Fatal(err)
  103. }
  104. want2 := s{A: 1, B: "x"}
  105. if !testEqual(got3, want2) {
  106. t.Errorf("got %#v\nwant %#v", got3, want2)
  107. }
  108. }
  109. var testDoc = &DocumentSnapshot{
  110. proto: &pb.Document{
  111. Fields: map[string]*pb.Value{
  112. "a": intval(1),
  113. "b": mapval(map[string]*pb.Value{
  114. "`": intval(2),
  115. "~": mapval(map[string]*pb.Value{
  116. "x": intval(3),
  117. }),
  118. }),
  119. },
  120. },
  121. }
  122. func TestDataAt(t *testing.T) {
  123. for _, test := range []struct {
  124. fieldPath string
  125. want interface{}
  126. }{
  127. {"a", int64(1)},
  128. {"b.`", int64(2)},
  129. } {
  130. got, err := testDoc.DataAt(test.fieldPath)
  131. if err != nil {
  132. t.Errorf("%q: %v", test.fieldPath, err)
  133. continue
  134. }
  135. if !testEqual(got, test.want) {
  136. t.Errorf("%q: got %v, want %v", test.fieldPath, got, test.want)
  137. }
  138. }
  139. for _, bad := range []string{
  140. "c.~.x", // bad field path
  141. "a.b", // "a" isn't a map
  142. "z.b", // bad non-final key
  143. "b.z", // bad final key
  144. } {
  145. _, err := testDoc.DataAt(bad)
  146. if err == nil {
  147. t.Errorf("%q: got nil, want error", bad)
  148. }
  149. }
  150. }
  151. func TestDataAtPath(t *testing.T) {
  152. for _, test := range []struct {
  153. fieldPath FieldPath
  154. want interface{}
  155. }{
  156. {[]string{"a"}, int64(1)},
  157. {[]string{"b", "`"}, int64(2)},
  158. {[]string{"b", "~"}, map[string]interface{}{"x": int64(3)}},
  159. {[]string{"b", "~", "x"}, int64(3)},
  160. } {
  161. got, err := testDoc.DataAtPath(test.fieldPath)
  162. if err != nil {
  163. t.Errorf("%v: %v", test.fieldPath, err)
  164. continue
  165. }
  166. if !testEqual(got, test.want) {
  167. t.Errorf("%v: got %v, want %v", test.fieldPath, got, test.want)
  168. }
  169. }
  170. for _, bad := range []FieldPath{
  171. []string{"c", "", "x"}, // bad field path
  172. []string{"a", "b"}, // "a" isn't a map
  173. []string{"z", "~"}, // bad non-final key
  174. []string{"b", "z"}, // bad final key
  175. } {
  176. _, err := testDoc.DataAtPath(bad)
  177. if err == nil {
  178. t.Errorf("%v: got nil, want error", bad)
  179. }
  180. }
  181. }
  182. func TestExtractTransforms(t *testing.T) {
  183. type S struct {
  184. A time.Time `firestore:",serverTimestamp"`
  185. B time.Time `firestore:",serverTimestamp"`
  186. C *time.Time `firestore:",serverTimestamp"`
  187. D *time.Time `firestore:"d.d,serverTimestamp"`
  188. E *time.Time `firestore:",serverTimestamp"`
  189. F time.Time
  190. G int
  191. }
  192. m := map[string]interface{}{
  193. "ar": map[string]interface{}{"k2": ArrayRemove("e", "f", "g")},
  194. "au": map[string]interface{}{"k1": ArrayUnion("a", "b", "c")},
  195. "x": 1,
  196. "y": &S{
  197. // A is a zero time: included
  198. B: aTime, // not a zero time: excluded
  199. //C is nil: included
  200. D: &time.Time{}, // pointer to a zero time: included
  201. E: &aTime, // pointer to a non-zero time: excluded
  202. //F is a zero time, but does not have the right tag: excluded
  203. G: 15, // not a time.Time
  204. },
  205. "z": map[string]interface{}{"w": ServerTimestamp},
  206. }
  207. got, err := extractTransforms(reflect.ValueOf(m), nil)
  208. if err != nil {
  209. t.Fatal(err)
  210. }
  211. want := []*pb.DocumentTransform_FieldTransform{
  212. {
  213. FieldPath: "ar.k2",
  214. TransformType: &pb.DocumentTransform_FieldTransform_RemoveAllFromArray{
  215. RemoveAllFromArray: &pb.ArrayValue{Values: []*pb.Value{
  216. {ValueType: &pb.Value_StringValue{"e"}},
  217. {ValueType: &pb.Value_StringValue{"f"}},
  218. {ValueType: &pb.Value_StringValue{"g"}},
  219. }},
  220. },
  221. },
  222. {
  223. FieldPath: "au.k1",
  224. TransformType: &pb.DocumentTransform_FieldTransform_AppendMissingElements{
  225. AppendMissingElements: &pb.ArrayValue{Values: []*pb.Value{
  226. {ValueType: &pb.Value_StringValue{"a"}},
  227. {ValueType: &pb.Value_StringValue{"b"}},
  228. {ValueType: &pb.Value_StringValue{"c"}},
  229. }},
  230. },
  231. },
  232. {
  233. FieldPath: "y.A",
  234. TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{
  235. SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME,
  236. },
  237. },
  238. {
  239. FieldPath: "y.C",
  240. TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{
  241. SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME,
  242. },
  243. },
  244. {
  245. FieldPath: "y.`d.d`",
  246. TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{
  247. SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME,
  248. },
  249. },
  250. {
  251. FieldPath: "z.w",
  252. TransformType: &pb.DocumentTransform_FieldTransform_SetToServerValue{
  253. SetToServerValue: pb.DocumentTransform_FieldTransform_REQUEST_TIME,
  254. },
  255. },
  256. }
  257. if len(got) != len(want) {
  258. t.Fatalf("Expected output array of size %d, got %d: %v", len(want), len(got), got)
  259. }
  260. sort.Sort(byDocumentTransformFieldPath(got))
  261. for i, vw := range want {
  262. vg := got[i]
  263. if !testEqual(vw, vg) {
  264. t.Fatalf("index %d: got %#v, want %#v", i, vg, vw)
  265. }
  266. }
  267. }
  268. type byDocumentTransformFieldPath []*pb.DocumentTransform_FieldTransform
  269. func (b byDocumentTransformFieldPath) Len() int { return len(b) }
  270. func (b byDocumentTransformFieldPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
  271. func (b byDocumentTransformFieldPath) Less(i, j int) bool {
  272. return strings.Compare(b[i].FieldPath, b[j].FieldPath) < 1
  273. }
  274. func TestExtractTransformPathsErrors(t *testing.T) {
  275. type S struct {
  276. A int `firestore:",serverTimestamp"`
  277. }
  278. _, err := extractTransforms(reflect.ValueOf(S{}), nil)
  279. if err == nil {
  280. t.Error("got nil, want error")
  281. }
  282. }