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.
 
 
 

308 lines
8.2 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. "fmt"
  17. "reflect"
  18. "testing"
  19. "time"
  20. ts "github.com/golang/protobuf/ptypes/timestamp"
  21. pb "google.golang.org/genproto/googleapis/firestore/v1"
  22. "google.golang.org/genproto/googleapis/type/latlng"
  23. )
  24. type testStruct1 struct {
  25. B bool
  26. I int
  27. U uint32
  28. F float64
  29. S string
  30. Y []byte
  31. T time.Time
  32. Ts *ts.Timestamp
  33. G *latlng.LatLng
  34. L []int
  35. M map[string]int
  36. P *int
  37. }
  38. var (
  39. p = new(int)
  40. testVal1 = testStruct1{
  41. B: true,
  42. I: 1,
  43. U: 2,
  44. F: 3.0,
  45. S: "four",
  46. Y: []byte{5},
  47. T: tm,
  48. Ts: ptm,
  49. G: ll,
  50. L: []int{6},
  51. M: map[string]int{"a": 7},
  52. P: p,
  53. }
  54. mapVal1 = mapval(map[string]*pb.Value{
  55. "B": boolval(true),
  56. "I": intval(1),
  57. "U": intval(2),
  58. "F": floatval(3),
  59. "S": {ValueType: &pb.Value_StringValue{"four"}},
  60. "Y": bytesval([]byte{5}),
  61. "T": tsval(tm),
  62. "Ts": {ValueType: &pb.Value_TimestampValue{ptm}},
  63. "G": geoval(ll),
  64. "L": arrayval(intval(6)),
  65. "M": mapval(map[string]*pb.Value{"a": intval(7)}),
  66. "P": intval(8),
  67. })
  68. )
  69. func TestToProtoValue(t *testing.T) {
  70. *p = 8
  71. for _, test := range []struct {
  72. in interface{}
  73. want *pb.Value
  74. }{
  75. {nil, nullValue},
  76. {[]int(nil), nullValue},
  77. {map[string]int(nil), nullValue},
  78. {(*testStruct1)(nil), nullValue},
  79. {(*ts.Timestamp)(nil), nullValue},
  80. {(*latlng.LatLng)(nil), nullValue},
  81. {(*DocumentRef)(nil), nullValue},
  82. {true, boolval(true)},
  83. {3, intval(3)},
  84. {uint32(3), intval(3)},
  85. {1.5, floatval(1.5)},
  86. {"str", strval("str")},
  87. {[]byte{1, 2}, bytesval([]byte{1, 2})},
  88. {tm, tsval(tm)},
  89. {ptm, &pb.Value{ValueType: &pb.Value_TimestampValue{ptm}}},
  90. {ll, geoval(ll)},
  91. {[]int{1, 2}, arrayval(intval(1), intval(2))},
  92. {&[]int{1, 2}, arrayval(intval(1), intval(2))},
  93. {[]int{}, arrayval()},
  94. {map[string]int{"a": 1, "b": 2},
  95. mapval(map[string]*pb.Value{"a": intval(1), "b": intval(2)})},
  96. {map[string]int{}, mapval(map[string]*pb.Value{})},
  97. {p, intval(8)},
  98. {&p, intval(8)},
  99. {map[string]interface{}{"a": 1, "p": p, "s": "str"},
  100. mapval(map[string]*pb.Value{"a": intval(1), "p": intval(8), "s": strval("str")})},
  101. {map[string]fmt.Stringer{"a": tm},
  102. mapval(map[string]*pb.Value{"a": tsval(tm)})},
  103. {testVal1, mapVal1},
  104. {
  105. &DocumentRef{
  106. ID: "d",
  107. Path: "projects/P/databases/D/documents/c/d",
  108. Parent: &CollectionRef{
  109. ID: "c",
  110. parentPath: "projects/P/databases/D",
  111. Path: "projects/P/databases/D/documents/c",
  112. Query: Query{collectionID: "c", parentPath: "projects/P/databases/D"},
  113. },
  114. },
  115. refval("projects/P/databases/D/documents/c/d"),
  116. },
  117. // ServerTimestamps are removed, possibly leaving nil.
  118. {map[string]interface{}{"a": ServerTimestamp}, nil},
  119. {
  120. map[string]interface{}{
  121. "a": map[string]interface{}{
  122. "b": map[string]interface{}{
  123. "c": ServerTimestamp,
  124. },
  125. },
  126. },
  127. nil,
  128. },
  129. {
  130. map[string]interface{}{
  131. "a": map[string]interface{}{
  132. "b": map[string]interface{}{
  133. "c": ServerTimestamp,
  134. "d": ServerTimestamp,
  135. },
  136. },
  137. },
  138. nil,
  139. },
  140. {
  141. map[string]interface{}{
  142. "a": map[string]interface{}{
  143. "b": map[string]interface{}{
  144. "c": ServerTimestamp,
  145. "d": ServerTimestamp,
  146. "e": 1,
  147. },
  148. },
  149. },
  150. mapval(map[string]*pb.Value{
  151. "a": mapval(map[string]*pb.Value{
  152. "b": mapval(map[string]*pb.Value{"e": intval(1)}),
  153. }),
  154. }),
  155. },
  156. // Transforms are allowed in maps, but won't show up in the returned proto. Instead, we rely
  157. // on seeing sawTransforms=true and a call to extractTransforms.
  158. {map[string]interface{}{"a": ServerTimestamp, "b": 5}, mapval(map[string]*pb.Value{"b": intval(5)})},
  159. {map[string]interface{}{"a": ArrayUnion(1, 2, 3), "b": 5}, mapval(map[string]*pb.Value{"b": intval(5)})},
  160. {map[string]interface{}{"a": ArrayRemove(1, 2, 3), "b": 5}, mapval(map[string]*pb.Value{"b": intval(5)})},
  161. } {
  162. got, _, err := toProtoValue(reflect.ValueOf(test.in))
  163. if err != nil {
  164. t.Errorf("%v (%T): %v", test.in, test.in, err)
  165. continue
  166. }
  167. if !testEqual(got, test.want) {
  168. t.Errorf("%+v (%T):\ngot\n%+v\nwant\n%+v", test.in, test.in, got, test.want)
  169. }
  170. }
  171. }
  172. type stringy struct{}
  173. func (stringy) String() string { return "stringy" }
  174. func TestToProtoValueErrors(t *testing.T) {
  175. for _, in := range []interface{}{
  176. uint64(0), // a bad fit for int64
  177. map[int]bool{}, // map key type is not string
  178. make(chan int), // can't handle type
  179. map[string]fmt.Stringer{"a": stringy{}}, // only empty interfaces
  180. ServerTimestamp, // ServerTimestamp can only be a field value
  181. struct{ A interface{} }{A: ServerTimestamp},
  182. map[string]interface{}{"a": []interface{}{ServerTimestamp}},
  183. map[string]interface{}{"a": []interface{}{
  184. map[string]interface{}{"b": ServerTimestamp},
  185. }},
  186. Delete, // Delete should never appear
  187. []interface{}{Delete},
  188. map[string]interface{}{"a": Delete},
  189. map[string]interface{}{"a": []interface{}{Delete}},
  190. // Transforms are not allowed to occur in an array.
  191. []interface{}{ServerTimestamp},
  192. []interface{}{ArrayUnion(1, 2, 3)},
  193. []interface{}{ArrayRemove(1, 2, 3)},
  194. // Transforms are not allowed to occur in a struct.
  195. struct{ A interface{} }{A: ServerTimestamp},
  196. struct{ A interface{} }{A: ArrayUnion()},
  197. struct{ A interface{} }{A: ArrayRemove()},
  198. } {
  199. _, _, err := toProtoValue(reflect.ValueOf(in))
  200. if err == nil {
  201. t.Errorf("%v: got nil, want error", in)
  202. }
  203. }
  204. }
  205. func TestToProtoValue_SawTransform(t *testing.T) {
  206. for i, in := range []interface{}{
  207. map[string]interface{}{"a": ServerTimestamp},
  208. map[string]interface{}{"a": ArrayUnion()},
  209. map[string]interface{}{"a": ArrayRemove()},
  210. } {
  211. _, sawTransform, err := toProtoValue(reflect.ValueOf(in))
  212. if err != nil {
  213. t.Fatalf("%d %v: got err %v\nexpected nil", i, in, err)
  214. }
  215. if !sawTransform {
  216. t.Errorf("%d %v: got sawTransform=false, expected sawTransform=true", i, in)
  217. }
  218. }
  219. }
  220. type testStruct2 struct {
  221. Ignore int `firestore:"-"`
  222. Rename int `firestore:"a"`
  223. OmitEmpty int `firestore:",omitempty"`
  224. OmitEmptyTime time.Time `firestore:",omitempty"`
  225. }
  226. func TestToProtoValueTags(t *testing.T) {
  227. in := &testStruct2{
  228. Ignore: 1,
  229. Rename: 2,
  230. OmitEmpty: 3,
  231. OmitEmptyTime: aTime,
  232. }
  233. got, _, err := toProtoValue(reflect.ValueOf(in))
  234. if err != nil {
  235. t.Fatal(err)
  236. }
  237. want := mapval(map[string]*pb.Value{
  238. "a": intval(2),
  239. "OmitEmpty": intval(3),
  240. "OmitEmptyTime": tsval(aTime),
  241. })
  242. if !testEqual(got, want) {
  243. t.Errorf("got %+v, want %+v", got, want)
  244. }
  245. got, _, err = toProtoValue(reflect.ValueOf(testStruct2{}))
  246. if err != nil {
  247. t.Fatal(err)
  248. }
  249. want = mapval(map[string]*pb.Value{"a": intval(0)})
  250. if !testEqual(got, want) {
  251. t.Errorf("got\n%+v\nwant\n%+v", got, want)
  252. }
  253. }
  254. func TestToProtoValueEmbedded(t *testing.T) {
  255. // Embedded time.Time, LatLng, or Timestamp should behave like non-embedded.
  256. type embed struct {
  257. time.Time
  258. *latlng.LatLng
  259. *ts.Timestamp
  260. }
  261. got, _, err := toProtoValue(reflect.ValueOf(embed{tm, ll, ptm}))
  262. if err != nil {
  263. t.Fatal(err)
  264. }
  265. want := mapval(map[string]*pb.Value{
  266. "Time": tsval(tm),
  267. "LatLng": geoval(ll),
  268. "Timestamp": {ValueType: &pb.Value_TimestampValue{ptm}},
  269. })
  270. if !testEqual(got, want) {
  271. t.Errorf("got %+v, want %+v", got, want)
  272. }
  273. }
  274. func TestIsEmpty(t *testing.T) {
  275. for _, e := range []interface{}{int(0), float32(0), false, "", []int{}, []int(nil), (*int)(nil)} {
  276. if !isEmptyValue(reflect.ValueOf(e)) {
  277. t.Errorf("%v (%T): want true, got false", e, e)
  278. }
  279. }
  280. i := 3
  281. for _, n := range []interface{}{int(1), float32(1), true, "x", []int{1}, &i} {
  282. if isEmptyValue(reflect.ValueOf(n)) {
  283. t.Errorf("%v (%T): want false, got true", n, n)
  284. }
  285. }
  286. }