Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(192)

Side by Side Diff: service/rawdatastore/datastore_test.go

Issue 1259593005: Add 'user friendly' datastore API. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: 100% coverage of new code Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // adapted from github.com/golang/appengine/datastore
6
7 package rawdatastore
8
9 import (
10 "bytes"
11 "encoding/json"
12 "fmt"
13 "math"
14 "reflect"
15 "strconv"
16 "strings"
17 "testing"
18 "time"
19
20 "github.com/luci/gae/service/blobstore"
21 . "github.com/smartystreets/goconvey/convey"
22 )
23
24 var (
25 mp = MkProperty
26 mpNI = MkPropertyNI
27 )
28
29 const testAppID = "testApp"
30
31 type (
32 myBlob []byte
33 myByte byte
34 myString string
35 )
36
37 func makeMyByteSlice(n int) []myByte {
38 b := make([]myByte, n)
39 for i := range b {
40 b[i] = myByte(i)
41 }
42 return b
43 }
44
45 func makeInt8Slice(n int) []int8 {
46 b := make([]int8, n)
47 for i := range b {
48 b[i] = int8(i)
49 }
50 return b
51 }
52
53 func makeUint8Slice(n int) []uint8 {
54 b := make([]uint8, n)
55 for i := range b {
56 b[i] = uint8(i)
57 }
58 return b
59 }
60
61 var (
62 testKey0 = mkKey("aid", "", "kind", "name0")
63 testKey1a = mkKey("aid", "", "kind", "name1")
64 testKey1b = mkKey("aid", "", "kind", "name1")
65 testKey2a = mkKey("aid", "", "kind", "name0", "kind", "name2")
66 testKey2b = mkKey("aid", "", "kind", "name0", "kind", "name2")
67 testGeoPt0 = GeoPoint{Lat: 1.2, Lng: 3.4}
68 testGeoPt1 = GeoPoint{Lat: 5, Lng: 10}
69 testBadGeoPt = GeoPoint{Lat: 1000, Lng: 34}
70 )
71
72 type B0 struct {
73 B []byte
74 }
75
76 type B1 struct {
77 B []int8
78 }
79
80 type B2 struct {
81 B myBlob
82 }
83
84 type B3 struct {
85 B []myByte
86 }
87
88 type B4 struct {
89 B [][]byte
90 }
91
92 type B5 struct {
93 B ByteString
94 }
95
96 type C0 struct {
97 I int
98 C chan int
99 }
100
101 type C1 struct {
102 I int
103 C *chan int
104 }
105
106 type C2 struct {
107 I int
108 C []chan int
109 }
110
111 type C3 struct {
112 C string
113 }
114
115 type E struct{}
116
117 type G0 struct {
118 G GeoPoint
119 }
120
121 type G1 struct {
122 G []GeoPoint
123 }
124
125 type K0 struct {
126 K Key
127 }
128
129 type K1 struct {
130 K []Key
131 }
132
133 type N0 struct {
134 X0
135 ID int64 `gae:"$id"`
136 _kind string `gae:"$kind,whatnow"`
137 Nonymous X0
138 Ignore string `gae:"-"`
139 Other string
140 }
141
142 type N1 struct {
143 X0
144 Nonymous []X0
145 Ignore string `gae:"-"`
146 Other string
147 }
148
149 type N2 struct {
150 N1 `gae:"red"`
151 Green N1 `gae:"green"`
152 Blue N1
153 White N1 `gae:"-"`
154 }
155
156 type O0 struct {
157 I int64
158 }
159
160 type O1 struct {
161 I int32
162 }
163
164 type U0 struct {
165 U uint
166 }
167
168 type U1 struct {
169 U string
170 }
171
172 type T struct {
173 T time.Time
174 }
175
176 type X0 struct {
177 S string
178 I int
179 i int
180 }
181
182 type X1 struct {
183 S myString
184 I int32
185 J int64
186 }
187
188 type X2 struct {
189 Z string
190 i int
191 }
192
193 type X3 struct {
194 S bool
195 I int
196 }
197
198 type Y0 struct {
199 B bool
200 F []float64
201 G []float64
202 }
203
204 type Y1 struct {
205 B bool
206 F float64
207 }
208
209 type Y2 struct {
210 B bool
211 F []int64
212 }
213
214 type Y3 struct {
215 B bool
216 F int64
217 }
218
219 type Tagged struct {
220 A int `gae:"a,noindex"`
221 B []int `gae:"b1"`
222 C int `gae:",noindex"`
223 D int `gae:""`
224 E int
225 I int `gae:"-"`
226 J int `gae:",noindex" json:"j"`
227
228 Y0 `gae:"-"`
229 Z chan int `gae:"-,"`
230 }
231
232 type InvalidTagged1 struct {
233 I int `gae:"\t"`
234 }
235
236 type InvalidTagged2 struct {
237 I int
238 J int `gae:"I"`
239 }
240
241 type InvalidTagged3 struct {
242 I int `gae:"a\t"`
243 }
244
245 type InvalidTagged4 struct {
246 I int `gae:"a."`
247 }
248
249 type InvalidTaggedSub struct {
250 I int
251 }
252
253 type InvalidTagged5 struct {
254 I int `gae:"V.I"`
255 V []InvalidTaggedSub
256 }
257
258 type Inner1 struct {
259 W int32
260 X string
261 }
262
263 type Inner2 struct {
264 Y float64
265 }
266
267 type Inner3 struct {
268 Z bool
269 }
270
271 type Outer struct {
272 A int16
273 I []Inner1
274 J Inner2
275 Inner3
276 }
277
278 type OuterEquivalent struct {
279 A int16
280 IDotW []int32 `gae:"I.W"`
281 IDotX []string `gae:"I.X"`
282 JDotY float64 `gae:"J.Y"`
283 Z bool
284 }
285
286 type Dotted struct {
287 A DottedA `gae:"A0.A1.A2"`
288 }
289
290 type DottedA struct {
291 B DottedB `gae:"B3"`
292 }
293
294 type DottedB struct {
295 C int `gae:"C4.C5"`
296 }
297
298 type SliceOfSlices struct {
299 I int
300 S []struct {
301 J int
302 F []float64
303 }
304 }
305
306 type Recursive struct {
307 I int
308 R []Recursive
309 }
310
311 type MutuallyRecursive0 struct {
312 I int
313 R []MutuallyRecursive1
314 }
315
316 type MutuallyRecursive1 struct {
317 I int
318 R []MutuallyRecursive0
319 }
320
321 type ExoticTypes struct {
322 BS blobstore.Key
323 DSBS ByteString
324 }
325
326 type Underspecified struct {
327 Iface PropertyConverter
328 }
329
330 type MismatchTypes struct {
331 S string
332 B bool
333 F float32
334 K Key
335 T time.Time
336 G GeoPoint
337 IS []int
338 }
339
340 type BadSpecial struct {
341 ID int64 `gae:"$id"`
342 id string `gae:"$id"`
343 }
344
345 type Doubler struct {
346 S string
347 I int64
348 B bool
349 }
350
351 func (d *Doubler) Load(props PropertyMap) error {
352 return GetPLS(d).Load(props)
353 }
354
355 func (d *Doubler) Save(withMeta bool) (PropertyMap, error) {
356 pls := GetPLS(d)
357 propMap, err := pls.Save(withMeta)
358 if err != nil {
359 return nil, err
360 }
361
362 // Edit that map and send it on.
363 for _, props := range propMap {
364 for i := range props {
365 switch v := props[i].Value().(type) {
366 case string:
367 // + means string concatenation.
368 props[i].SetValue(v+v, props[i].IndexSetting())
369 case int64:
370 // + means integer addition.
371 props[i].SetValue(v+v, props[i].IndexSetting())
372 }
373 }
374 }
375 return propMap, nil
376 }
377
378 func (d *Doubler) GetMeta(string) (interface{}, error) { return nil, ErrMetaFiel dUnset }
379 func (d *Doubler) SetMeta(string, interface{}) error { return ErrMetaFieldUnse t }
380 func (d *Doubler) Problem() error { return nil }
381
382 var _ PropertyLoadSaver = (*Doubler)(nil)
383
384 type Deriver struct {
385 S, Derived, Ignored string
386 }
387
388 func (d *Deriver) Load(props PropertyMap) error {
389 for name, p := range props {
390 if name != "S" {
391 continue
392 }
393 d.S = p[0].Value().(string)
394 d.Derived = "derived+" + d.S
395 }
396 return nil
397 }
398
399 func (d *Deriver) Save(withMeta bool) (PropertyMap, error) {
400 return map[string][]Property{
401 "S": {mp(d.S)},
402 }, nil
403 }
404
405 func (d *Deriver) GetMeta(string) (interface{}, error) { return nil, ErrMetaFiel dUnset }
406 func (d *Deriver) SetMeta(string, interface{}) error { return ErrMetaFieldUnse t }
407 func (d *Deriver) Problem() error { return nil }
408
409 var _ PropertyLoadSaver = (*Deriver)(nil)
410
411 type BK struct {
412 Key blobstore.Key
413 }
414
415 type Convertable []int64
416
417 var _ PropertyConverter = (*Convertable)(nil)
418
419 func (c *Convertable) ToProperty() (ret Property, err error) {
420 buf := make([]string, len(*c))
421 for i, v := range *c {
422 buf[i] = strconv.FormatInt(v, 10)
423 }
424 err = ret.SetValue(strings.Join(buf, ","), NoIndex)
425 return
426 }
427
428 func (c *Convertable) FromProperty(pv Property) error {
429 if sval, ok := pv.Value().(string); ok {
430 for _, t := range strings.Split(sval, ",") {
431 ival, err := strconv.ParseInt(t, 10, 64)
432 if err != nil {
433 return err
434 }
435 *c = append(*c, ival)
436 }
437 return nil
438 }
439 return fmt.Errorf("nope")
440 }
441
442 type Impossible struct {
443 Nested []ImpossibleInner
444 }
445
446 type ImpossibleInner struct {
447 Ints Convertable `gae:"wot"`
448 }
449
450 type Convertable2 struct {
451 Data string
452 Exploded []string
453 }
454
455 func (c *Convertable2) ToProperty() (ret Property, err error) {
456 err = ret.SetValue(c.Data, ShouldIndex)
457 return
458 }
459
460 func (c *Convertable2) FromProperty(pv Property) error {
461 if sval, ok := pv.Value().(string); ok {
462 c.Data = sval
463 c.Exploded = []string{"turn", "down", "for", "what"}
464 return nil
465 }
466 return fmt.Errorf("nope")
467 }
468
469 type Impossible2 struct {
470 Nested []ImpossibleInner2
471 }
472
473 type ImpossibleInner2 struct {
474 Thingy Convertable2 `gae:"nerb"`
475 }
476
477 type JSONKVProp map[string]interface{}
478
479 var _ PropertyConverter = (*JSONKVProp)(nil)
480
481 func (j *JSONKVProp) ToProperty() (ret Property, err error) {
482 data, err := json.Marshal(map[string]interface{}(*j))
483 if err != nil {
484 return
485 }
486 err = ret.SetValue(data, NoIndex)
487 return
488 }
489
490 func (j *JSONKVProp) FromProperty(pv Property) error {
491 if bval, ok := pv.Value().([]byte); ok {
492 dec := json.NewDecoder(bytes.NewBuffer(bval))
493 dec.UseNumber()
494 return dec.Decode((*map[string]interface{})(j))
495 }
496 return fmt.Errorf("nope")
497 }
498
499 type Impossible3 struct {
500 KMap JSONKVProp `gae:"kewelmap"`
501 }
502
503 type Complex complex128
504
505 var _ PropertyConverter = (*Complex)(nil)
506
507 func (c *Complex) ToProperty() (ret Property, err error) {
508 // cheat hardkore and usurp GeoPoint so datastore will index these sucke rs
509 // (note that this won't REALLY work, since GeoPoints are limited to a v ery
510 // limited range of values, but it's nice to pretend ;)). You'd probably
511 // really end up with a packed binary representation.
512 err = ret.SetValue(GeoPoint{Lat: real(*c), Lng: imag(*c)}, ShouldIndex)
513 return
514 }
515
516 func (c *Complex) FromProperty(p Property) error {
517 if gval, ok := p.Value().(GeoPoint); ok {
518 *c = Complex(complex(gval.Lat, gval.Lng))
519 return nil
520 }
521 return fmt.Errorf("nope")
522 }
523
524 type Impossible4 struct {
525 Values []Complex
526 }
527
528 type DerivedKey struct {
529 K *GenericKey
530 }
531
532 type IfaceKey struct {
533 K Key
534 }
535
536 type testCase struct {
537 desc string
538 src interface{}
539 want interface{}
540 plsErr string
541 saveErr string
542 actualNoIndex bool
543 plsLoadErr string
544 loadErr string
545 }
546
547 var testCases = []testCase{
548 {
549 desc: "chan save fails",
550 src: &C0{I: -1},
551 plsErr: `field "C" has invalid type: chan int`,
552 },
553 {
554 desc: "*chan save fails",
555 src: &C1{I: -1},
556 plsErr: `field "C" has invalid type: *chan int`,
557 },
558 {
559 desc: "[]chan save fails",
560 src: &C2{I: -1, C: make([]chan int, 8)},
561 plsErr: `field "C" has invalid type: []chan int`,
562 },
563 {
564 desc: "chan load fails",
565 src: &C3{C: "not a chan"},
566 want: &C0{},
567 plsLoadErr: `field "C" has invalid type: chan int`,
568 },
569 {
570 desc: "*chan load fails",
571 src: &C3{C: "not a *chan"},
572 want: &C1{},
573 plsLoadErr: `field "C" has invalid type: *chan int`,
574 },
575 {
576 desc: "[]chan load fails",
577 src: &C3{C: "not a []chan"},
578 want: &C2{},
579 plsLoadErr: `field "C" has invalid type: []chan int`,
580 },
581 {
582 desc: "empty struct",
583 src: &E{},
584 want: &E{},
585 },
586 {
587 desc: "geopoint",
588 src: &G0{G: testGeoPt0},
589 want: &G0{G: testGeoPt0},
590 },
591 {
592 desc: "geopoint invalid",
593 src: &G0{G: testBadGeoPt},
594 saveErr: "invalid GeoPoint value",
595 },
596 {
597 desc: "geopoint as props",
598 src: &G0{G: testGeoPt0},
599 want: PropertyMap{
600 "G": {mp(testGeoPt0)},
601 },
602 },
603 {
604 desc: "geopoint slice",
605 src: &G1{G: []GeoPoint{testGeoPt0, testGeoPt1}},
606 want: &G1{G: []GeoPoint{testGeoPt0, testGeoPt1}},
607 },
608 {
609 desc: "key",
610 src: &K0{K: testKey1a},
611 want: &K0{K: testKey1b},
612 },
613 {
614 desc: "key with parent",
615 src: &K0{K: testKey2a},
616 want: &K0{K: testKey2b},
617 },
618 {
619 desc: "nil key",
620 src: &K0{},
621 want: &K0{},
622 },
623 {
624 desc: "all nil keys in slice",
625 src: &K1{[]Key{nil, nil}},
626 want: &K1{[]Key{nil, nil}},
627 },
628 {
629 desc: "some nil keys in slice",
630 src: &K1{[]Key{testKey1a, nil, testKey2a}},
631 want: &K1{[]Key{testKey1b, nil, testKey2b}},
632 },
633 {
634 desc: "overflow",
635 src: &O0{I: 1 << 48},
636 want: &O1{},
637 loadErr: "overflow",
638 },
639 {
640 desc: "time",
641 src: &T{T: time.Unix(1e9, 0).UTC()},
642 want: &T{T: time.Unix(1e9, 0).UTC()},
643 },
644 {
645 desc: "time as props",
646 src: &T{T: time.Unix(1e9, 0).UTC()},
647 want: PropertyMap{
648 "T": {mp(time.Unix(1e9, 0).UTC())},
649 },
650 },
651 {
652 desc: "uint save",
653 src: &U0{U: 1},
654 plsErr: `field "U" has invalid type: uint`,
655 },
656 {
657 desc: "uint load",
658 src: &U1{U: "not a uint"},
659 want: &U0{},
660 plsLoadErr: `field "U" has invalid type: uint`,
661 },
662 {
663 desc: "zero",
664 src: &X0{},
665 want: &X0{},
666 },
667 {
668 desc: "basic",
669 src: &X0{S: "one", I: 2, i: 3},
670 want: &X0{S: "one", I: 2},
671 },
672 {
673 desc: "save string/int load myString/int32",
674 src: &X0{S: "one", I: 2, i: 3},
675 want: &X1{S: "one", I: 2},
676 },
677 {
678 desc: "missing fields",
679 src: &X0{S: "one", I: 2, i: 3},
680 want: &X2{},
681 loadErr: "no such struct field",
682 },
683 {
684 desc: "save string load bool",
685 src: &X0{S: "one", I: 2, i: 3},
686 want: &X3{I: 2},
687 loadErr: "type mismatch",
688 },
689 {
690 desc: "basic slice",
691 src: &Y0{B: true, F: []float64{7, 8, 9}},
692 want: &Y0{B: true, F: []float64{7, 8, 9}},
693 },
694 {
695 desc: "save []float64 load float64",
696 src: &Y0{B: true, F: []float64{7, 8, 9}},
697 want: &Y1{B: true},
698 loadErr: "requires a slice",
699 },
700 {
701 desc: "save single []int64 load int64",
702 src: &Y2{B: true, F: []int64{7}},
703 want: &Y3{B: true, F: 7},
704 },
705 {
706 desc: "save int64 load single []int64",
707 src: &Y3{B: true, F: 7},
708 want: &Y2{B: true, F: []int64{7}},
709 },
710 {
711 desc: "use convertable slice",
712 src: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con vertable{2, 4, 6}}}},
713 want: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con vertable{2, 4, 6}}}},
714 },
715 {
716 desc: "use convertable slice (to map)",
717 src: &Impossible{[]ImpossibleInner{{Convertable{1, 5, 9}}, {Con vertable{2, 4, 6}}}},
718 want: PropertyMap{
719 "Nested.wot": {mpNI("1,5,9"), mpNI("2,4,6")},
720 },
721 },
722 {
723 desc: "convertable slice (bad load)",
724 src: PropertyMap{"Nested.wot": {mpNI([]byte("ohai"))}},
725 want: &Impossible{[]ImpossibleInner{{}}},
726 loadErr: "nope",
727 },
728 {
729 desc: "use convertable struct",
730 src: &Impossible2{
731 []ImpossibleInner2{
732 {Convertable2{"nerb", nil}},
733 },
734 },
735 want: &Impossible2{
736 []ImpossibleInner2{
737 {Convertable2{"nerb", []string{"turn", "down", " for", "what"}}},
738 },
739 },
740 },
741 {
742 desc: "convertable json KVMap",
743 src: &Impossible3{
744 JSONKVProp{
745 "epic": "success",
746 "no_way!": []interface{}{true, "story"},
747 "what": []interface{}{"is", "really", 100},
748 },
749 },
750 want: &Impossible3{
751 JSONKVProp{
752 "epic": "success",
753 "no_way!": []interface{}{true, "story"},
754 "what": []interface{}{"is", "really", json.Nu mber("100")},
755 },
756 },
757 },
758 {
759 desc: "convertable json KVMap (to map)",
760 src: &Impossible3{
761 JSONKVProp{
762 "epic": "success",
763 "no_way!": []interface{}{true, "story"},
764 "what": []interface{}{"is", "really", 100},
765 },
766 },
767 want: PropertyMap{
768 "kewelmap": {
769 mpNI([]byte(
770 `{"epic":"success","no_way!":[true,"stor y"],"what":["is","really",100]}`))},
771 },
772 },
773 {
774 desc: "convertable complex slice",
775 src: &Impossible4{
776 []Complex{complex(1, 2), complex(3, 4)},
777 },
778 want: &Impossible4{
779 []Complex{complex(1, 2), complex(3, 4)},
780 },
781 },
782 {
783 desc: "convertable complex slice (to map)",
784 src: &Impossible4{
785 []Complex{complex(1, 2), complex(3, 4)},
786 },
787 want: PropertyMap{
788 "Values": {
789 mp(GeoPoint{Lat: 1, Lng: 2}), mp(GeoPoint{Lat: 3 , Lng: 4})},
790 },
791 },
792 {
793 desc: "convertable complex slice (bad load)",
794 src: PropertyMap{"Values": {mp("hello")}},
795 want: &Impossible4{[]Complex(nil)},
796 loadErr: "nope",
797 },
798 {
799 desc: "allow concrete Key implementors (save)",
800 src: &DerivedKey{testKey2a.(*GenericKey)},
801 want: &IfaceKey{testKey2b},
802 },
803 {
804 desc: "allow concrete Key implementors (load)",
805 src: &IfaceKey{testKey2b},
806 want: &DerivedKey{testKey2a.(*GenericKey)},
807 },
808 {
809 desc: "save []float64 load []int64",
810 src: &Y0{B: true, F: []float64{7, 8, 9}},
811 want: &Y2{B: true},
812 loadErr: "type mismatch",
813 },
814 {
815 desc: "single slice is too long",
816 src: &Y0{F: make([]float64, maxIndexedProperties+1)},
817 want: &Y0{},
818 saveErr: "gae: too many indexed properties",
819 },
820 {
821 desc: "two slices are too long",
822 src: &Y0{F: make([]float64, maxIndexedProperties), G: make([ ]float64, maxIndexedProperties)},
823 want: &Y0{},
824 saveErr: "gae: too many indexed properties",
825 },
826 {
827 desc: "one slice and one scalar are too long",
828 src: &Y0{F: make([]float64, maxIndexedProperties), B: true},
829 want: &Y0{},
830 saveErr: "gae: too many indexed properties",
831 },
832 {
833 desc: "long blob",
834 src: &B0{B: makeUint8Slice(maxIndexedProperties + 1)},
835 want: &B0{B: makeUint8Slice(maxIndexedProperties + 1)},
836 },
837 {
838 desc: "long []int8 is too long",
839 src: &B1{B: makeInt8Slice(maxIndexedProperties + 1)},
840 want: &B1{},
841 saveErr: "gae: too many indexed properties",
842 },
843 {
844 desc: "short []int8",
845 src: &B1{B: makeInt8Slice(3)},
846 want: &B1{B: makeInt8Slice(3)},
847 },
848 {
849 desc: "long myBlob",
850 src: &B2{B: makeUint8Slice(maxIndexedProperties + 1)},
851 want: &B2{B: makeUint8Slice(maxIndexedProperties + 1)},
852 },
853 {
854 desc: "short myBlob",
855 src: &B2{B: makeUint8Slice(3)},
856 want: &B2{B: makeUint8Slice(3)},
857 },
858 {
859 desc: "long []myByte",
860 src: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)},
861 want: &B3{B: makeMyByteSlice(maxIndexedProperties + 1)},
862 },
863 {
864 desc: "short []myByte",
865 src: &B3{B: makeMyByteSlice(3)},
866 want: &B3{B: makeMyByteSlice(3)},
867 },
868 {
869 desc: "slice of blobs",
870 src: &B4{B: [][]byte{
871 makeUint8Slice(3),
872 makeUint8Slice(4),
873 makeUint8Slice(5),
874 }},
875 want: &B4{B: [][]byte{
876 makeUint8Slice(3),
877 makeUint8Slice(4),
878 makeUint8Slice(5),
879 }},
880 },
881 {
882 desc: "short ByteString",
883 src: &B5{B: ByteString(makeUint8Slice(3))},
884 want: &B5{B: ByteString(makeUint8Slice(3))},
885 },
886 {
887 desc: "short ByteString as props",
888 src: &B5{B: ByteString(makeUint8Slice(3))},
889 want: PropertyMap{
890 "B": {mp(ByteString(makeUint8Slice(3)))},
891 },
892 },
893 {
894 desc: "[]byte must be noindex",
895 src: PropertyMap{
896 "B": {mp(makeUint8Slice(3))},
897 },
898 actualNoIndex: true,
899 },
900 {
901 desc: "save tagged load props",
902 src: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6 , J: 7},
903 want: PropertyMap{
904 // A and B are renamed to a and b; A and C are noindex, I is ignored.
905 // Indexed properties are loaded before raw properties. Thus, the
906 // result is: b, b, b, D, E, a, c.
907 "b1": {
908 mp(21),
909 mp(22),
910 mp(23),
911 },
912 "D": {mp(4)},
913 "E": {mp(5)},
914 "a": {mpNI(1)},
915 "C": {mpNI(3)},
916 "J": {mpNI(7)},
917 },
918 },
919 {
920 desc: "save tagged load tagged",
921 src: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, I: 6 , J: 7},
922 want: &Tagged{A: 1, B: []int{21, 22, 23}, C: 3, D: 4, E: 5, J: 7 },
923 },
924 {
925 desc: "save props load tagged",
926 src: PropertyMap{
927 "A": {mpNI(11)},
928 "a": {mpNI(12)},
929 },
930 want: &Tagged{A: 12},
931 loadErr: `cannot load field "A"`,
932 },
933 {
934 desc: "invalid tagged1",
935 src: &InvalidTagged1{I: 1},
936 plsErr: `struct tag has invalid property name: "\t"`,
937 },
938 {
939 desc: "invalid tagged2",
940 src: &InvalidTagged2{I: 1, J: 2},
941 want: &InvalidTagged2{},
942 plsErr: `struct tag has repeated property name: "I"`,
943 },
944 {
945 desc: "invalid tagged3",
946 src: &InvalidTagged3{I: 1},
947 plsErr: `struct tag has invalid property name: "a\t"`,
948 },
949 {
950 desc: "invalid tagged4",
951 src: &InvalidTagged4{I: 1},
952 plsErr: `struct tag has invalid property name: "a."`,
953 },
954 {
955 desc: "invalid tagged5",
956 src: &InvalidTagged5{I: 19, V: []InvalidTaggedSub{{1}}},
957 plsErr: `struct tag has repeated property name: "V.I"`,
958 },
959 {
960 desc: "doubler",
961 src: &Doubler{S: "s", I: 1, B: true},
962 want: &Doubler{S: "ss", I: 2, B: true},
963 },
964 {
965 desc: "save struct load props",
966 src: &X0{S: "s", I: 1},
967 want: PropertyMap{
968 "S": {mp("s")},
969 "I": {mp(1)},
970 },
971 },
972 {
973 desc: "save props load struct",
974 src: PropertyMap{
975 "S": {mp("s")},
976 "I": {mp(1)},
977 },
978 want: &X0{S: "s", I: 1},
979 },
980 {
981 desc: "nil-value props",
982 src: PropertyMap{
983 "I": {mp(nil)},
984 "B": {mp(nil)},
985 "S": {mp(nil)},
986 "F": {mp(nil)},
987 "K": {mp(nil)},
988 "T": {mp(nil)},
989 "J": {
990 mp(nil),
991 mp(7),
992 mp(nil),
993 },
994 },
995 want: &struct {
996 I int64
997 B bool
998 S string
999 F float64
1000 K Key
1001 T time.Time
1002 J []int64
1003 }{
1004 J: []int64{0, 7, 0},
1005 },
1006 },
1007 {
1008 desc: "save outer load props",
1009 src: &Outer{
1010 A: 1,
1011 I: []Inner1{
1012 {10, "ten"},
1013 {20, "twenty"},
1014 {30, "thirty"},
1015 },
1016 J: Inner2{
1017 Y: 3.14,
1018 },
1019 Inner3: Inner3{
1020 Z: true,
1021 },
1022 },
1023 want: PropertyMap{
1024 "A": {mp(1)},
1025 "I.W": {
1026 mp(10),
1027 mp(20),
1028 mp(30),
1029 },
1030 "I.X": {
1031 mp("ten"),
1032 mp("twenty"),
1033 mp("thirty"),
1034 },
1035 "J.Y": {mp(3.14)},
1036 "Z": {mp(true)},
1037 },
1038 },
1039 {
1040 desc: "save props load outer-equivalent",
1041 src: PropertyMap{
1042 "A": {mp(1)},
1043 "I.W": {
1044 mp(10),
1045 mp(20),
1046 mp(30),
1047 },
1048 "I.X": {
1049 mp("ten"),
1050 mp("twenty"),
1051 mp("thirty"),
1052 },
1053 "J.Y": {mp(3.14)},
1054 "Z": {mp(true)},
1055 },
1056 want: &OuterEquivalent{
1057 A: 1,
1058 IDotW: []int32{10, 20, 30},
1059 IDotX: []string{"ten", "twenty", "thirty"},
1060 JDotY: 3.14,
1061 Z: true,
1062 },
1063 },
1064 {
1065 desc: "save outer-equivalent load outer",
1066 src: &OuterEquivalent{
1067 A: 1,
1068 IDotW: []int32{10, 20, 30},
1069 IDotX: []string{"ten", "twenty", "thirty"},
1070 JDotY: 3.14,
1071 Z: true,
1072 },
1073 want: &Outer{
1074 A: 1,
1075 I: []Inner1{
1076 {10, "ten"},
1077 {20, "twenty"},
1078 {30, "thirty"},
1079 },
1080 J: Inner2{
1081 Y: 3.14,
1082 },
1083 Inner3: Inner3{
1084 Z: true,
1085 },
1086 },
1087 },
1088 {
1089 desc: "dotted names save",
1090 src: &Dotted{A: DottedA{B: DottedB{C: 88}}},
1091 want: PropertyMap{
1092 "A0.A1.A2.B3.C4.C5": {mp(88)},
1093 },
1094 },
1095 {
1096 desc: "dotted names load",
1097 src: PropertyMap{
1098 "A0.A1.A2.B3.C4.C5": {mp(99)},
1099 },
1100 want: &Dotted{A: DottedA{B: DottedB{C: 99}}},
1101 },
1102 {
1103 desc: "save struct load deriver",
1104 src: &X0{S: "s", I: 1},
1105 want: &Deriver{S: "s", Derived: "derived+s"},
1106 },
1107 {
1108 desc: "save deriver load struct",
1109 src: &Deriver{S: "s", Derived: "derived+s", Ignored: "ignored"} ,
1110 want: &X0{S: "s"},
1111 },
1112 // Regression: CL 25062824 broke handling of appengine.BlobKey fields.
1113 {
1114 desc: "appengine.BlobKey",
1115 src: &BK{Key: "blah"},
1116 want: &BK{Key: "blah"},
1117 },
1118 {
1119 desc: "zero time.Time",
1120 src: &T{T: time.Time{}},
1121 want: &T{T: time.Time{}},
1122 },
1123 {
1124 desc: "time.Time near Unix zero time",
1125 src: &T{T: time.Unix(0, 4e3).UTC()},
1126 want: &T{T: time.Unix(0, 4e3).UTC()},
1127 },
1128 {
1129 desc: "time.Time, far in the future",
1130 src: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)},
1131 want: &T{T: time.Date(99999, 1, 1, 0, 0, 0, 0, time.UTC)},
1132 },
1133 {
1134 desc: "time.Time, very far in the past",
1135 src: &T{T: time.Date(-300000, 1, 1, 0, 0, 0, 0, time.UTC)},
1136 want: &T{},
1137 saveErr: "time value out of range",
1138 },
1139 {
1140 desc: "time.Time, very far in the future",
1141 src: &T{T: time.Date(294248, 1, 1, 0, 0, 0, 0, time.UTC)},
1142 want: &T{},
1143 saveErr: "time value out of range",
1144 },
1145 {
1146 desc: "structs",
1147 src: &N0{
1148 X0: X0{S: "one", I: 2, i: 3},
1149 Nonymous: X0{S: "four", I: 5, i: 6},
1150 Ignore: "ignore",
1151 Other: "other",
1152 },
1153 want: &N0{
1154 X0: X0{S: "one", I: 2},
1155 Nonymous: X0{S: "four", I: 5},
1156 Other: "other",
1157 },
1158 },
1159 {
1160 desc: "exotic types",
1161 src: &ExoticTypes{
1162 BS: "sup",
1163 DSBS: ByteString("nerds"),
1164 },
1165 want: &ExoticTypes{
1166 BS: "sup",
1167 DSBS: ByteString("nerds"),
1168 },
1169 },
1170 {
1171 desc: "underspecified types",
1172 src: &Underspecified{},
1173 plsErr: "non-concrete interface",
1174 },
1175 {
1176 desc: "mismatch (string)",
1177 src: PropertyMap{
1178 "K": {mp(199)},
1179 "S": {mp([]byte("cats"))},
1180 "F": {mp(ByteString("nurbs"))},
1181 },
1182 want: &MismatchTypes{},
1183 loadErr: "type mismatch",
1184 },
1185 {
1186 desc: "mismatch (float)",
1187 src: PropertyMap{"F": {mp(blobstore.Key("wot"))}},
1188 want: &MismatchTypes{},
1189 loadErr: "type mismatch",
1190 },
1191 {
1192 desc: "mismatch (float/overflow)",
1193 src: PropertyMap{"F": {mp(math.MaxFloat64)}},
1194 want: &MismatchTypes{},
1195 loadErr: "overflows",
1196 },
1197 {
1198 desc: "mismatch (key)",
1199 src: PropertyMap{"K": {mp(false)}},
1200 want: &MismatchTypes{},
1201 loadErr: "type mismatch",
1202 },
1203 {
1204 desc: "mismatch (bool)",
1205 src: PropertyMap{"B": {mp(testKey0)}},
1206 want: &MismatchTypes{},
1207 loadErr: "type mismatch",
1208 },
1209 {
1210 desc: "mismatch (time)",
1211 src: PropertyMap{"T": {mp(GeoPoint{})}},
1212 want: &MismatchTypes{},
1213 loadErr: "type mismatch",
1214 },
1215 {
1216 desc: "mismatch (geopoint)",
1217 src: PropertyMap{"G": {mp(time.Now().UTC())}},
1218 want: &MismatchTypes{},
1219 loadErr: "type mismatch",
1220 },
1221 {
1222 desc: "slice of structs",
1223 src: &N1{
1224 X0: X0{S: "one", I: 2, i: 3},
1225 Nonymous: []X0{
1226 {S: "four", I: 5, i: 6},
1227 {S: "seven", I: 8, i: 9},
1228 {S: "ten", I: 11, i: 12},
1229 {S: "thirteen", I: 14, i: 15},
1230 },
1231 Ignore: "ignore",
1232 Other: "other",
1233 },
1234 want: &N1{
1235 X0: X0{S: "one", I: 2},
1236 Nonymous: []X0{
1237 {S: "four", I: 5},
1238 {S: "seven", I: 8},
1239 {S: "ten", I: 11},
1240 {S: "thirteen", I: 14},
1241 },
1242 Other: "other",
1243 },
1244 },
1245 {
1246 desc: "structs with slices of structs",
1247 src: &N2{
1248 N1: N1{
1249 X0: X0{S: "rouge"},
1250 Nonymous: []X0{
1251 {S: "rosso0"},
1252 {S: "rosso1"},
1253 },
1254 },
1255 Green: N1{
1256 X0: X0{S: "vert"},
1257 Nonymous: []X0{
1258 {S: "verde0"},
1259 {S: "verde1"},
1260 {S: "verde2"},
1261 },
1262 },
1263 Blue: N1{
1264 X0: X0{S: "bleu"},
1265 Nonymous: []X0{
1266 {S: "blu0"},
1267 {S: "blu1"},
1268 {S: "blu2"},
1269 {S: "blu3"},
1270 },
1271 },
1272 },
1273 want: &N2{
1274 N1: N1{
1275 X0: X0{S: "rouge"},
1276 Nonymous: []X0{
1277 {S: "rosso0"},
1278 {S: "rosso1"},
1279 },
1280 },
1281 Green: N1{
1282 X0: X0{S: "vert"},
1283 Nonymous: []X0{
1284 {S: "verde0"},
1285 {S: "verde1"},
1286 {S: "verde2"},
1287 },
1288 },
1289 Blue: N1{
1290 X0: X0{S: "bleu"},
1291 Nonymous: []X0{
1292 {S: "blu0"},
1293 {S: "blu1"},
1294 {S: "blu2"},
1295 {S: "blu3"},
1296 },
1297 },
1298 },
1299 },
1300 {
1301 desc: "save structs load props",
1302 src: &N2{
1303 N1: N1{
1304 X0: X0{S: "rouge"},
1305 Nonymous: []X0{
1306 {S: "rosso0"},
1307 {S: "rosso1"},
1308 },
1309 },
1310 Green: N1{
1311 X0: X0{S: "vert"},
1312 Nonymous: []X0{
1313 {S: "verde0"},
1314 {S: "verde1"},
1315 {S: "verde2"},
1316 },
1317 },
1318 Blue: N1{
1319 X0: X0{S: "bleu"},
1320 Nonymous: []X0{
1321 {S: "blu0"},
1322 {S: "blu1"},
1323 {S: "blu2"},
1324 {S: "blu3"},
1325 },
1326 },
1327 },
1328 want: PropertyMap{
1329 "red.S": {mp("rouge")},
1330 "red.I": {mp(0)},
1331 "red.Nonymous.S": {mp("rosso0"), mp("rosso1")},
1332 "red.Nonymous.I": {mp(0), mp(0)},
1333 "red.Other": {mp("")},
1334 "green.S": {mp("vert")},
1335 "green.I": {mp(0)},
1336 "green.Nonymous.S": {mp("verde0"), mp("verde1"), mp("ver de2")},
1337 "green.Nonymous.I": {mp(0), mp(0), mp(0)},
1338 "green.Other": {mp("")},
1339 "Blue.S": {mp("bleu")},
1340 "Blue.I": {mp(0)},
1341 "Blue.Nonymous.S": {mp("blu0"), mp("blu1"), mp("blu2"), mp("blu3")},
1342 "Blue.Nonymous.I": {mp(0), mp(0), mp(0), mp(0)},
1343 "Blue.Other": {mp("")},
1344 },
1345 },
1346 {
1347 desc: "save props load structs with ragged fields",
1348 src: PropertyMap{
1349 "red.S": {mp("rot")},
1350 "green.Nonymous.I": {mp(10), mp(11), mp(12), mp(13)},
1351 "Blue.Nonymous.S": {mp("blau0"), mp("blau1"), mp("blau2 ")},
1352 "Blue.Nonymous.I": {mp(20), mp(21)},
1353 },
1354 want: &N2{
1355 N1: N1{
1356 X0: X0{S: "rot"},
1357 },
1358 Green: N1{
1359 Nonymous: []X0{
1360 {I: 10},
1361 {I: 11},
1362 {I: 12},
1363 {I: 13},
1364 },
1365 },
1366 Blue: N1{
1367 Nonymous: []X0{
1368 {S: "blau0", I: 20},
1369 {S: "blau1", I: 21},
1370 {S: "blau2"},
1371 },
1372 },
1373 },
1374 },
1375 {
1376 desc: "save structs with noindex tags",
1377 src: &struct {
1378 A struct {
1379 X string `gae:",noindex"`
1380 Y string
1381 } `gae:",noindex"`
1382 B struct {
1383 X string `gae:",noindex"`
1384 Y string
1385 }
1386 }{},
1387 want: PropertyMap{
1388 "B.Y": {mp("")},
1389 "A.X": {mpNI("")},
1390 "A.Y": {mpNI("")},
1391 "B.X": {mpNI("")},
1392 },
1393 },
1394 {
1395 desc: "embedded struct with name override",
1396 src: &struct {
1397 Inner1 `gae:"foo"`
1398 }{},
1399 want: PropertyMap{
1400 "foo.W": {mp(0)},
1401 "foo.X": {mp("")},
1402 },
1403 },
1404 {
1405 desc: "slice of slices",
1406 src: &SliceOfSlices{},
1407 plsErr: `flattening nested structs leads to a slice of slices: f ield "S"`,
1408 },
1409 {
1410 desc: "recursive struct",
1411 src: &Recursive{},
1412 plsErr: `field "R" is recursively defined`,
1413 },
1414 {
1415 desc: "mutually recursive struct",
1416 src: &MutuallyRecursive0{},
1417 plsErr: `field "R" has problem: field "R" is recursively defined `,
1418 },
1419 {
1420 desc: "non-exported struct fields",
1421 src: &struct {
1422 i, J int64
1423 }{i: 1, J: 2},
1424 want: PropertyMap{
1425 "J": {mp(2)},
1426 },
1427 },
1428 {
1429 desc: "json.RawMessage",
1430 src: &struct {
1431 J json.RawMessage
1432 }{
1433 J: json.RawMessage("rawr"),
1434 },
1435 want: PropertyMap{
1436 "J": {mp([]byte("rawr"))},
1437 },
1438 },
1439 {
1440 desc: "json.RawMessage to myBlob",
1441 src: &struct {
1442 B json.RawMessage
1443 }{
1444 B: json.RawMessage("rawr"),
1445 },
1446 want: &B2{B: myBlob("rawr")},
1447 },
1448 }
1449
1450 // checkErr returns the empty string if either both want and err are zero,
1451 // or if want is a non-empty substring of err's string representation.
1452 func checkErr(want string, err error) string {
1453 if err != nil {
1454 got := err.Error()
1455 if want == "" || strings.Index(got, want) == -1 {
1456 return got
1457 }
1458 } else if want != "" {
1459 return fmt.Sprintf("want error %q", want)
1460 }
1461 return ""
1462 }
1463
1464 func ShouldErrLike(actual interface{}, expected ...interface{}) string {
1465 e2s := func(o interface{}) (string, bool) {
1466 switch x := o.(type) {
1467 case nil:
1468 return "", true
1469 case string:
1470 return x, true
1471 case error:
1472 if x != nil {
1473 return x.Error(), true
1474 }
1475 return "", true
1476 }
1477 return fmt.Sprintf("unknown argument type %T, expected string, e rror or nil", actual), false
1478 }
1479
1480 as, ok := e2s(actual)
1481 if !ok {
1482 return as
1483 }
1484
1485 if len(expected) != 1 {
1486 return fmt.Sprintf("Assertion requires 1 expected value, got %d" , len(expected))
1487 }
1488
1489 err, ok := e2s(expected[0])
1490 if !ok {
1491 return err
1492 }
1493 return ShouldContainSubstring(as, err)
1494 }
1495
1496 func TestRoundTrip(t *testing.T) {
1497 t.Parallel()
1498
1499 checkErr := func(actual interface{}, expected string) bool {
1500 So(actual, ShouldErrLike, expected)
1501 return expected != ""
1502 }
1503
1504 Convey("Test round-trip", t, func() {
1505 for _, tc := range testCases {
1506 tc := tc
1507 Convey(tc.desc, func() {
1508 pls, ok := tc.src.(PropertyLoadSaver)
1509 if !ok {
1510 pls = GetPLS(tc.src)
1511 }
1512 if checkErr(pls.Problem(), tc.plsErr) {
1513 return
1514 }
1515 So(pls, ShouldNotBeNil)
1516
1517 savedProps, err := pls.Save(false)
1518 if checkErr(err, tc.saveErr) {
1519 return
1520 }
1521 So(savedProps, ShouldNotBeNil)
1522
1523 if tc.actualNoIndex {
1524 for _, props := range savedProps {
1525 So(props[0].IndexSetting(), Shou ldEqual, NoIndex)
1526 return
1527 }
1528 So(true, ShouldBeFalse) // shouldn't get here
1529 }
1530
1531 var got interface{}
1532 if _, ok := tc.want.(PropertyMap); ok {
1533 pls = PropertyMap{}
1534 got = pls
1535 } else {
1536 got = reflect.New(reflect.TypeOf(tc.want ).Elem()).Interface()
1537 if pls, ok = got.(PropertyLoadSaver); !o k {
1538 pls = GetPLS(got)
1539 }
1540 }
1541
1542 if checkErr(pls.Problem(), tc.plsLoadErr) {
1543 return
1544 }
1545 So(pls, ShouldNotBeNil)
1546
1547 err = pls.Load(savedProps)
1548 if checkErr(err, tc.loadErr) {
1549 return
1550 }
1551 if tc.want == nil {
1552 return
1553 }
1554
1555 if gotT, ok := got.(*T); ok {
1556 // Round tripping a time.Time can result in a different time.Location: Local instead of UTC.
1557 // We therefore test equality explicitly , instead of relying on reflect.DeepEqual.
1558 So(gotT.T.Equal(tc.want.(*T).T), ShouldB eTrue)
1559 } else {
1560 So(got, ShouldResemble, tc.want)
1561 }
1562 })
1563 }
1564 })
1565 }
1566
1567 func TestSpecial(t *testing.T) {
1568 t.Parallel()
1569
1570 Convey("Test special fields", t, func() {
1571 Convey("Can retrieve from struct", func() {
1572 o := &N0{ID: 100}
1573 pls := GetPLS(o)
1574 val, err := pls.GetMeta("id")
1575 So(err, ShouldBeNil)
1576 So(val, ShouldEqual, 100)
1577
1578 val, err = pls.GetMeta("kind")
1579 So(err, ShouldBeNil)
1580 So(val, ShouldEqual, "whatnow")
1581 })
1582
1583 Convey("Getting something not there is an error", func() {
1584 o := &N0{ID: 100}
1585 pls := GetPLS(o)
1586 _, err := pls.GetMeta("wat")
1587 So(err, ShouldEqual, ErrMetaFieldUnset)
1588 })
1589
1590 Convey("getting/setting from a bad struct is an error", func() {
1591 o := &Recursive{}
1592 pls := GetPLS(o)
1593 _, err := pls.GetMeta("wat")
1594 So(err, ShouldNotBeNil)
1595
1596 err = pls.SetMeta("wat", 100)
1597 So(err, ShouldNotBeNil)
1598 })
1599
1600 Convey("can assign values to exported special fields", func() {
1601 o := &N0{ID: 100}
1602 pls := GetPLS(o)
1603 err := pls.SetMeta("id", int64(200))
1604 So(err, ShouldBeNil)
1605 So(o.ID, ShouldEqual, 200)
1606
1607 })
1608
1609 Convey("assigning to unsassiagnable fields is a simple error", f unc() {
1610 o := &N0{ID: 100}
1611 pls := GetPLS(o)
1612 err := pls.SetMeta("kind", "hi")
1613 So(err.Error(), ShouldContainSubstring, "unexported fiel d")
1614
1615 err = pls.SetMeta("noob", "hi")
1616 So(err, ShouldEqual, ErrMetaFieldUnset)
1617 })
1618 })
1619
1620 Convey("StructPLS Miscellaneous", t, func() {
1621 Convey("multiple overlapping fields is an error", func() {
1622 o := &BadSpecial{}
1623 pls := GetPLS(o)
1624 err := pls.Load(nil)
1625 So(err, ShouldErrLike, "multiple times")
1626 e := pls.Problem()
1627 _, err = pls.Save(true)
1628 So(err, ShouldEqual, e)
1629 err = pls.Load(nil)
1630 So(err, ShouldEqual, e)
1631 })
1632
1633 Convey("empty property names are invalid", func() {
1634 So(validPropertyName(""), ShouldBeFalse)
1635 })
1636
1637 Convey("attempting to get a PLS for a non *struct is an error", func() {
1638 pls := GetPLS((*[]string)(nil))
1639 So(pls.Problem(), ShouldEqual, ErrInvalidEntityType)
1640 })
1641
1642 Convey("convertible meta default types", func() {
1643 type OKDefaults struct {
1644 When string `gae:"$when,tomorrow"`
1645 Amount int64 `gae:"$amt,100"`
1646 DoIt Toggle `gae:"$doit,on"`
1647 }
1648 okd := &OKDefaults{}
1649 pls := GetPLS(okd)
1650 So(pls.Problem(), ShouldBeNil)
1651
1652 v, err := pls.GetMeta("when")
1653 So(err, ShouldBeNil)
1654 So(v, ShouldEqual, "tomorrow")
1655
1656 v, err = pls.GetMeta("amt")
1657 So(err, ShouldBeNil)
1658 So(v, ShouldEqual, int64(100))
1659
1660 So(okd.DoIt, ShouldEqual, Auto)
1661 v, err = pls.GetMeta("doit")
1662 So(err, ShouldBeNil)
1663 So(v, ShouldBeTrue)
1664
1665 err = pls.SetMeta("doit", false)
1666 So(err, ShouldBeNil)
1667 v, err = pls.GetMeta("doit")
1668 So(err, ShouldBeNil)
1669 So(v, ShouldBeFalse)
1670 So(okd.DoIt, ShouldEqual, Off)
1671
1672 err = pls.SetMeta("doit", true)
1673 So(err, ShouldBeNil)
1674 v, err = pls.GetMeta("doit")
1675 So(err, ShouldBeNil)
1676 So(v, ShouldBeTrue)
1677 So(okd.DoIt, ShouldEqual, On)
1678
1679 Convey("Toggle fields REQUIRE a default", func() {
1680 type BadToggle struct {
1681 Bad Toggle `gae:"$wut"`
1682 }
1683 pls := GetPLS(&BadToggle{})
1684 So(pls.Problem().Error(), ShouldContainSubstring , "bad/missing default")
1685 })
1686 })
1687
1688 Convey("meta fields can be saved", func() {
1689 type OKDefaults struct {
1690 When string `gae:"$when,tomorrow"`
1691 Amount int64 `gae:"$amt,100"`
1692 }
1693 pls := GetPLS(&OKDefaults{})
1694 pm, err := pls.Save(true)
1695 So(err, ShouldBeNil)
1696 So(pm, ShouldResemble, PropertyMap{
1697 "$when": {mpNI("tomorrow")},
1698 "$amt": {mpNI(100)},
1699 })
1700
1701 v, err := pm.GetMeta("when")
1702 So(err, ShouldBeNil)
1703 So(v, ShouldEqual, "tomorrow")
1704
1705 v, err = pm.GetMeta("amt")
1706 So(err, ShouldBeNil)
1707 So(v, ShouldEqual, int64(100))
1708 })
1709
1710 Convey("default are optional", func() {
1711 type OverrideDefault struct {
1712 Val int64 `gae:"$val"`
1713 }
1714 o := &OverrideDefault{}
1715 pls := GetPLS(o)
1716
1717 v, err := pls.GetMeta("val")
1718 So(err, ShouldBeNil)
1719 So(v, ShouldEqual, int64(0))
1720 })
1721
1722 Convey("overridable defaults", func() {
1723 type OverrideDefault struct {
1724 Val int64 `gae:"$val,100"`
1725 }
1726 o := &OverrideDefault{}
1727 pls := GetPLS(o)
1728
1729 v, err := pls.GetMeta("val")
1730 So(err, ShouldBeNil)
1731 So(v, ShouldEqual, int64(100))
1732
1733 o.Val = 10
1734 v, err = pls.GetMeta("val")
1735 So(err, ShouldBeNil)
1736 So(v, ShouldEqual, int64(10))
1737 })
1738
1739 Convey("Bad default meta type", func() {
1740 type BadDefault struct {
1741 Val time.Time `gae:"$meta,tomorrow"`
1742 }
1743 pls := GetPLS(&BadDefault{})
1744 So(pls.Problem().Error(), ShouldContainSubstring, "bad t ype")
1745 })
1746 })
1747 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698