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.
 
 
 

312 lines
7.6 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. "context"
  17. "reflect"
  18. "sort"
  19. "testing"
  20. "time"
  21. pb "google.golang.org/genproto/googleapis/firestore/v1"
  22. "google.golang.org/genproto/googleapis/type/latlng"
  23. "google.golang.org/grpc"
  24. "google.golang.org/grpc/codes"
  25. )
  26. var (
  27. writeResultForSet = &WriteResult{UpdateTime: aTime}
  28. commitResponseForSet = &pb.CommitResponse{
  29. WriteResults: []*pb.WriteResult{{UpdateTime: aTimestamp}},
  30. }
  31. )
  32. func TestDocGet(t *testing.T) {
  33. ctx := context.Background()
  34. c, srv := newMock(t)
  35. path := "projects/projectID/databases/(default)/documents/C/a"
  36. pdoc := &pb.Document{
  37. Name: path,
  38. CreateTime: aTimestamp,
  39. UpdateTime: aTimestamp,
  40. Fields: map[string]*pb.Value{"f": intval(1)},
  41. }
  42. srv.addRPC(&pb.BatchGetDocumentsRequest{
  43. Database: c.path(),
  44. Documents: []string{path},
  45. }, []interface{}{
  46. &pb.BatchGetDocumentsResponse{
  47. Result: &pb.BatchGetDocumentsResponse_Found{pdoc},
  48. ReadTime: aTimestamp2,
  49. },
  50. })
  51. ref := c.Collection("C").Doc("a")
  52. gotDoc, err := ref.Get(ctx)
  53. if err != nil {
  54. t.Fatal(err)
  55. }
  56. wantDoc := &DocumentSnapshot{
  57. Ref: ref,
  58. CreateTime: aTime,
  59. UpdateTime: aTime,
  60. ReadTime: aTime2,
  61. proto: pdoc,
  62. c: c,
  63. }
  64. if !testEqual(gotDoc, wantDoc) {
  65. t.Fatalf("\ngot %+v\nwant %+v", gotDoc, wantDoc)
  66. }
  67. path2 := "projects/projectID/databases/(default)/documents/C/b"
  68. srv.addRPC(
  69. &pb.BatchGetDocumentsRequest{
  70. Database: c.path(),
  71. Documents: []string{path2},
  72. }, []interface{}{
  73. &pb.BatchGetDocumentsResponse{
  74. Result: &pb.BatchGetDocumentsResponse_Missing{path2},
  75. ReadTime: aTimestamp3,
  76. },
  77. })
  78. _, err = c.Collection("C").Doc("b").Get(ctx)
  79. if grpc.Code(err) != codes.NotFound {
  80. t.Errorf("got %v, want NotFound", err)
  81. }
  82. }
  83. func TestDocSet(t *testing.T) {
  84. // Most tests for Set are in the conformance tests.
  85. ctx := context.Background()
  86. c, srv := newMock(t)
  87. doc := c.Collection("C").Doc("d")
  88. // Merge with a struct and FieldPaths.
  89. srv.addRPC(&pb.CommitRequest{
  90. Database: "projects/projectID/databases/(default)",
  91. Writes: []*pb.Write{
  92. {
  93. Operation: &pb.Write_Update{
  94. Update: &pb.Document{
  95. Name: "projects/projectID/databases/(default)/documents/C/d",
  96. Fields: map[string]*pb.Value{
  97. "*": mapval(map[string]*pb.Value{
  98. "~": boolval(true),
  99. }),
  100. },
  101. },
  102. },
  103. UpdateMask: &pb.DocumentMask{FieldPaths: []string{"`*`.`~`"}},
  104. },
  105. },
  106. }, commitResponseForSet)
  107. data := struct {
  108. A map[string]bool `firestore:"*"`
  109. }{A: map[string]bool{"~": true}}
  110. wr, err := doc.Set(ctx, data, Merge([]string{"*", "~"}))
  111. if err != nil {
  112. t.Fatal(err)
  113. }
  114. if !testEqual(wr, writeResultForSet) {
  115. t.Errorf("got %v, want %v", wr, writeResultForSet)
  116. }
  117. // MergeAll cannot be used with structs.
  118. _, err = doc.Set(ctx, data, MergeAll)
  119. if err == nil {
  120. t.Errorf("got nil, want error")
  121. }
  122. }
  123. func TestDocCreate(t *testing.T) {
  124. // Verify creation with structs. In particular, make sure zero values
  125. // are handled well.
  126. // Other tests for Create are handled by the conformance tests.
  127. ctx := context.Background()
  128. c, srv := newMock(t)
  129. type create struct {
  130. Time time.Time
  131. Bytes []byte
  132. Geo *latlng.LatLng
  133. }
  134. srv.addRPC(
  135. &pb.CommitRequest{
  136. Database: "projects/projectID/databases/(default)",
  137. Writes: []*pb.Write{
  138. {
  139. Operation: &pb.Write_Update{
  140. Update: &pb.Document{
  141. Name: "projects/projectID/databases/(default)/documents/C/d",
  142. Fields: map[string]*pb.Value{
  143. "Time": tsval(time.Time{}),
  144. "Bytes": bytesval(nil),
  145. "Geo": nullValue,
  146. },
  147. },
  148. },
  149. CurrentDocument: &pb.Precondition{
  150. ConditionType: &pb.Precondition_Exists{false},
  151. },
  152. },
  153. },
  154. },
  155. commitResponseForSet,
  156. )
  157. _, err := c.Collection("C").Doc("d").Create(ctx, &create{})
  158. if err != nil {
  159. t.Fatal(err)
  160. }
  161. }
  162. func TestDocDelete(t *testing.T) {
  163. ctx := context.Background()
  164. c, srv := newMock(t)
  165. srv.addRPC(
  166. &pb.CommitRequest{
  167. Database: "projects/projectID/databases/(default)",
  168. Writes: []*pb.Write{
  169. {Operation: &pb.Write_Delete{"projects/projectID/databases/(default)/documents/C/d"}},
  170. },
  171. },
  172. &pb.CommitResponse{
  173. WriteResults: []*pb.WriteResult{{}},
  174. })
  175. wr, err := c.Collection("C").Doc("d").Delete(ctx)
  176. if err != nil {
  177. t.Fatal(err)
  178. }
  179. if !testEqual(wr, &WriteResult{}) {
  180. t.Errorf("got %+v, want %+v", wr, writeResultForSet)
  181. }
  182. }
  183. var (
  184. testData = map[string]interface{}{"a": 1}
  185. testFields = map[string]*pb.Value{"a": intval(1)}
  186. )
  187. // Update is tested by the conformance tests.
  188. func TestFPVsFromData(t *testing.T) {
  189. type S struct{ X int }
  190. for _, test := range []struct {
  191. in interface{}
  192. want []fpv
  193. }{
  194. {
  195. in: nil,
  196. want: []fpv{{nil, nil}},
  197. },
  198. {
  199. in: map[string]interface{}{"a": nil},
  200. want: []fpv{{[]string{"a"}, nil}},
  201. },
  202. {
  203. in: map[string]interface{}{"a": 1},
  204. want: []fpv{{[]string{"a"}, 1}},
  205. },
  206. {
  207. in: map[string]interface{}{
  208. "a": 1,
  209. "b": map[string]interface{}{"c": 2},
  210. },
  211. want: []fpv{{[]string{"a"}, 1}, {[]string{"b", "c"}, 2}},
  212. },
  213. {
  214. in: map[string]interface{}{"s": &S{X: 3}},
  215. want: []fpv{{[]string{"s"}, &S{X: 3}}},
  216. },
  217. } {
  218. var got []fpv
  219. fpvsFromData(reflect.ValueOf(test.in), nil, &got)
  220. sort.Sort(byFieldPath(got))
  221. if !testEqual(got, test.want) {
  222. t.Errorf("%+v: got %v, want %v", test.in, got, test.want)
  223. }
  224. }
  225. }
  226. type byFieldPath []fpv
  227. func (b byFieldPath) Len() int { return len(b) }
  228. func (b byFieldPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
  229. func (b byFieldPath) Less(i, j int) bool { return b[i].fieldPath.less(b[j].fieldPath) }
  230. func commitRequestForSet() *pb.CommitRequest {
  231. return &pb.CommitRequest{
  232. Database: "projects/projectID/databases/(default)",
  233. Writes: []*pb.Write{
  234. {
  235. Operation: &pb.Write_Update{
  236. Update: &pb.Document{
  237. Name: "projects/projectID/databases/(default)/documents/C/d",
  238. Fields: testFields,
  239. },
  240. },
  241. },
  242. },
  243. }
  244. }
  245. func TestUpdateProcess(t *testing.T) {
  246. for _, test := range []struct {
  247. in Update
  248. want fpv
  249. wantErr bool
  250. }{
  251. {
  252. in: Update{Path: "a", Value: 1},
  253. want: fpv{fieldPath: []string{"a"}, value: 1},
  254. },
  255. {
  256. in: Update{Path: "c.d", Value: Delete},
  257. want: fpv{fieldPath: []string{"c", "d"}, value: Delete},
  258. },
  259. {
  260. in: Update{FieldPath: []string{"*", "~"}, Value: ServerTimestamp},
  261. want: fpv{fieldPath: []string{"*", "~"}, value: ServerTimestamp},
  262. },
  263. {
  264. in: Update{Path: "*"},
  265. wantErr: true, // bad rune in path
  266. },
  267. {
  268. in: Update{Path: "a", FieldPath: []string{"b"}},
  269. wantErr: true, // both Path and FieldPath
  270. },
  271. {
  272. in: Update{Value: 1},
  273. wantErr: true, // neither Path nor FieldPath
  274. },
  275. {
  276. in: Update{FieldPath: []string{"", "a"}},
  277. wantErr: true, // empty FieldPath component
  278. },
  279. } {
  280. got, err := test.in.process()
  281. if test.wantErr {
  282. if err == nil {
  283. t.Errorf("%+v: got nil, want error", test.in)
  284. }
  285. } else if err != nil {
  286. t.Errorf("%+v: got error %v, want nil", test.in, err)
  287. } else if !testEqual(got, test.want) {
  288. t.Errorf("%+v: got %+v, want %+v", test.in, got, test.want)
  289. }
  290. }
  291. }