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.
 
 
 

290 lines
6.6 KiB

  1. // Copyright 2016 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 datastore
  15. import (
  16. "testing"
  17. "time"
  18. "cloud.google.com/go/internal/testutil"
  19. pb "google.golang.org/genproto/googleapis/datastore/v1"
  20. )
  21. func TestInterfaceToProtoNil(t *testing.T) {
  22. // A nil *Key, or a nil value of any other pointer type, should convert to a NullValue.
  23. for _, in := range []interface{}{
  24. (*Key)(nil),
  25. (*int)(nil),
  26. (*string)(nil),
  27. (*bool)(nil),
  28. (*float64)(nil),
  29. (*GeoPoint)(nil),
  30. (*time.Time)(nil),
  31. } {
  32. got, err := interfaceToProto(in, false)
  33. if err != nil {
  34. t.Fatalf("%T: %v", in, err)
  35. }
  36. _, ok := got.ValueType.(*pb.Value_NullValue)
  37. if !ok {
  38. t.Errorf("%T: got: %T\nwant: %T", in, got.ValueType, &pb.Value_NullValue{})
  39. }
  40. }
  41. }
  42. func TestSaveEntityNested(t *testing.T) {
  43. type WithKey struct {
  44. X string
  45. I int
  46. K *Key `datastore:"__key__"`
  47. }
  48. type NestedWithKey struct {
  49. Y string
  50. N WithKey
  51. }
  52. type WithoutKey struct {
  53. X string
  54. I int
  55. }
  56. type NestedWithoutKey struct {
  57. Y string
  58. N WithoutKey
  59. }
  60. type a struct {
  61. S string
  62. }
  63. type UnexpAnonym struct {
  64. a
  65. }
  66. testCases := []struct {
  67. desc string
  68. src interface{}
  69. key *Key
  70. want *pb.Entity
  71. }{
  72. {
  73. desc: "nested entity with key",
  74. src: &NestedWithKey{
  75. Y: "yyy",
  76. N: WithKey{
  77. X: "two",
  78. I: 2,
  79. K: testKey1a,
  80. },
  81. },
  82. key: testKey0,
  83. want: &pb.Entity{
  84. Key: keyToProto(testKey0),
  85. Properties: map[string]*pb.Value{
  86. "Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}},
  87. "N": {ValueType: &pb.Value_EntityValue{
  88. EntityValue: &pb.Entity{
  89. Key: keyToProto(testKey1a),
  90. Properties: map[string]*pb.Value{
  91. "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
  92. "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
  93. },
  94. },
  95. }},
  96. },
  97. },
  98. },
  99. {
  100. desc: "nested entity with incomplete key",
  101. src: &NestedWithKey{
  102. Y: "yyy",
  103. N: WithKey{
  104. X: "two",
  105. I: 2,
  106. K: incompleteKey,
  107. },
  108. },
  109. key: testKey0,
  110. want: &pb.Entity{
  111. Key: keyToProto(testKey0),
  112. Properties: map[string]*pb.Value{
  113. "Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}},
  114. "N": {ValueType: &pb.Value_EntityValue{
  115. EntityValue: &pb.Entity{
  116. Key: keyToProto(incompleteKey),
  117. Properties: map[string]*pb.Value{
  118. "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
  119. "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
  120. },
  121. },
  122. }},
  123. },
  124. },
  125. },
  126. {
  127. desc: "nested entity without key",
  128. src: &NestedWithoutKey{
  129. Y: "yyy",
  130. N: WithoutKey{
  131. X: "two",
  132. I: 2,
  133. },
  134. },
  135. key: testKey0,
  136. want: &pb.Entity{
  137. Key: keyToProto(testKey0),
  138. Properties: map[string]*pb.Value{
  139. "Y": {ValueType: &pb.Value_StringValue{StringValue: "yyy"}},
  140. "N": {ValueType: &pb.Value_EntityValue{
  141. EntityValue: &pb.Entity{
  142. Properties: map[string]*pb.Value{
  143. "X": {ValueType: &pb.Value_StringValue{StringValue: "two"}},
  144. "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 2}},
  145. },
  146. },
  147. }},
  148. },
  149. },
  150. },
  151. {
  152. desc: "key at top level",
  153. src: &WithKey{
  154. X: "three",
  155. I: 3,
  156. K: testKey0,
  157. },
  158. key: testKey0,
  159. want: &pb.Entity{
  160. Key: keyToProto(testKey0),
  161. Properties: map[string]*pb.Value{
  162. "X": {ValueType: &pb.Value_StringValue{StringValue: "three"}},
  163. "I": {ValueType: &pb.Value_IntegerValue{IntegerValue: 3}},
  164. },
  165. },
  166. },
  167. {
  168. desc: "nested unexported anonymous struct field",
  169. src: &UnexpAnonym{
  170. a{S: "hello"},
  171. },
  172. key: testKey0,
  173. want: &pb.Entity{
  174. Key: keyToProto(testKey0),
  175. Properties: map[string]*pb.Value{
  176. "S": {ValueType: &pb.Value_StringValue{StringValue: "hello"}},
  177. },
  178. },
  179. },
  180. }
  181. for _, tc := range testCases {
  182. got, err := saveEntity(tc.key, tc.src)
  183. if err != nil {
  184. t.Errorf("saveEntity: %s: %v", tc.desc, err)
  185. continue
  186. }
  187. if !testutil.Equal(tc.want, got) {
  188. t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, got, tc.want)
  189. }
  190. }
  191. }
  192. func TestSavePointers(t *testing.T) {
  193. for _, test := range []struct {
  194. desc string
  195. in interface{}
  196. want []Property
  197. }{
  198. {
  199. desc: "nil pointers save as nil-valued properties",
  200. in: &Pointers{},
  201. want: []Property{
  202. {Name: "Pi", Value: nil},
  203. {Name: "Ps", Value: nil},
  204. {Name: "Pb", Value: nil},
  205. {Name: "Pf", Value: nil},
  206. {Name: "Pg", Value: nil},
  207. {Name: "Pt", Value: nil},
  208. },
  209. },
  210. {
  211. desc: "nil omitempty pointers not saved",
  212. in: &PointersOmitEmpty{},
  213. want: []Property(nil),
  214. },
  215. {
  216. desc: "non-nil omitempty zero-valued pointers are saved",
  217. in: func() *PointersOmitEmpty { pi := 0; return &PointersOmitEmpty{Pi: &pi} }(),
  218. want: []Property{{Name: "Pi", Value: int64(0)}},
  219. },
  220. {
  221. desc: "non-nil zero-valued pointers save as zero values",
  222. in: populatedPointers(),
  223. want: []Property{
  224. {Name: "Pi", Value: int64(0)},
  225. {Name: "Ps", Value: ""},
  226. {Name: "Pb", Value: false},
  227. {Name: "Pf", Value: 0.0},
  228. {Name: "Pg", Value: GeoPoint{}},
  229. {Name: "Pt", Value: time.Time{}},
  230. },
  231. },
  232. {
  233. desc: "non-nil non-zero-valued pointers save as the appropriate values",
  234. in: func() *Pointers {
  235. p := populatedPointers()
  236. *p.Pi = 1
  237. *p.Ps = "x"
  238. *p.Pb = true
  239. *p.Pf = 3.14
  240. *p.Pg = GeoPoint{Lat: 1, Lng: 2}
  241. *p.Pt = time.Unix(100, 0)
  242. return p
  243. }(),
  244. want: []Property{
  245. {Name: "Pi", Value: int64(1)},
  246. {Name: "Ps", Value: "x"},
  247. {Name: "Pb", Value: true},
  248. {Name: "Pf", Value: 3.14},
  249. {Name: "Pg", Value: GeoPoint{Lat: 1, Lng: 2}},
  250. {Name: "Pt", Value: time.Unix(100, 0)},
  251. },
  252. },
  253. } {
  254. got, err := SaveStruct(test.in)
  255. if err != nil {
  256. t.Fatalf("%s: %v", test.desc, err)
  257. }
  258. if !testutil.Equal(got, test.want) {
  259. t.Errorf("%s\ngot %#v\nwant %#v\n", test.desc, got, test.want)
  260. }
  261. }
  262. }
  263. func TestSaveEmptySlice(t *testing.T) {
  264. // Zero-length slice fields are not saved.
  265. for _, slice := range [][]string{nil, {}} {
  266. got, err := SaveStruct(&struct{ S []string }{S: slice})
  267. if err != nil {
  268. t.Fatal(err)
  269. }
  270. if len(got) != 0 {
  271. t.Errorf("%#v: got %d properties, wanted zero", slice, len(got))
  272. }
  273. }
  274. }