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

Unified Diff: service/datastore/datastore_test.go

Issue 1525453002: Handle unexpected entity fields gracefully. (Closed) Base URL: https://github.com/luci/gae.git@master
Patch Set: Created 5 years 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | service/datastore/pls.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: service/datastore/datastore_test.go
diff --git a/service/datastore/datastore_test.go b/service/datastore/datastore_test.go
index c24efb33e37609becdc1900da71888e4b47bfffd..276d799e63e5962fddbfabc25520ddda36ed5385 100644
--- a/service/datastore/datastore_test.go
+++ b/service/datastore/datastore_test.go
@@ -926,3 +926,296 @@ func TestRun(t *testing.T) {
})
})
}
+
+type fixedDataDatastore struct {
+ RawInterface
+
+ data map[string]PropertyMap
+}
+
+func (d *fixedDataDatastore) GetMulti(keys []*Key, _ MultiMetaGetter, cb GetMultiCB) error {
+ for _, k := range keys {
+ data, ok := d.data[k.String()]
+ if ok {
+ cb(data, nil)
+ } else {
+ cb(nil, ErrNoSuchEntity)
+ }
+ }
+ return nil
+}
+
+func (d *fixedDataDatastore) PutMulti(keys []*Key, vals []PropertyMap, cb PutMultiCB) error {
+ if d.data == nil {
+ d.data = make(map[string]PropertyMap, len(keys))
+ }
+ for i, k := range keys {
+ if k.Incomplete() {
+ panic("key is incomplete, don't do that.")
+ }
+ d.data[k.String()], _ = vals[i].Save(false)
+ cb(k, nil)
+ }
+ return nil
+}
+
+func TestSchemaChange(t *testing.T) {
+ t.Parallel()
+
+ Convey("Test changing schemas", t, func() {
+ fds := fixedDataDatastore{}
+ ds := &datastoreImpl{&fds, "", ""}
+
+ Convey("Can add fields", func() {
+ initial := PropertyMap{
+ "$key": {mpNI(ds.MakeKey("Val", 10))},
+ "Val": {mp(100)},
+ }
+ So(ds.Put(initial), ShouldBeNil)
+
+ type Val struct {
+ ID int64 `gae:"$id"`
+
+ Val int64
+ TwoVal int64 // whoa, TWO vals! amazing
+ }
+ tv := &Val{ID: 10, TwoVal: 2}
+ So(ds.Get(tv), ShouldBeNil)
+ So(tv, ShouldResemble, &Val{ID: 10, Val: 100, TwoVal: 2})
+ })
+
+ Convey("Removing fields", func() {
+ initial := PropertyMap{
+ "$key": {mpNI(ds.MakeKey("Val", 10))},
+ "Val": {mp(100)},
+ "TwoVal": {mp(200)},
+ }
+ So(ds.Put(initial), ShouldBeNil)
+
+ Convey("is normally an error", func() {
+ type Val struct {
+ ID int64 `gae:"$id"`
+
+ Val int64
+ }
+ tv := &Val{ID: 10}
+ So(ds.Get(tv), ShouldErrLike,
+ `gae: cannot load field "TwoVal" into a "datastore.Val`)
+ So(tv, ShouldResemble, &Val{ID: 10, Val: 100})
+ })
+
+ Convey("Unless you have an ,extra field!", func() {
+ type Val struct {
+ ID int64 `gae:"$id"`
+
+ Val int64
+ Extra PropertyMap `gae:",extra"`
+ }
+ tv := &Val{ID: 10}
+ So(ds.Get(tv), ShouldBeNil)
+ So(tv, ShouldResembleV, &Val{
+ ID: 10,
+ Val: 100,
+ Extra: PropertyMap{
+ "TwoVal": {mp(200)},
+ },
+ })
+ })
+ })
+
+ Convey("Can round-trip extra fields", func() {
+ type Expando struct {
+ ID int64 `gae:"$id"`
+
+ Something int
+ Extra PropertyMap `gae:",extra"`
+ }
+ ex := &Expando{10, 17, PropertyMap{
+ "Hello": {mp("Hello")},
+ "World": {mp(true)},
+ }}
+ So(ds.Put(ex), ShouldBeNil)
+
+ ex = &Expando{ID: 10}
+ So(ds.Get(ex), ShouldBeNil)
+ So(ex, ShouldResembleV, &Expando{
+ ID: 10,
+ Something: 17,
+ Extra: PropertyMap{
+ "Hello": {mp("Hello")},
+ "World": {mp(true)},
+ },
+ })
+ })
+
+ Convey("Can read-but-not-write", func() {
+ initial := PropertyMap{
+ "$key": {mpNI(ds.MakeKey("Convert", 10))},
+ "Val": {mp(100)},
+ "TwoVal": {mp(200)},
+ }
+ So(ds.Put(initial), ShouldBeNil)
+ type Convert struct {
+ ID int64 `gae:"$id"`
+
+ Val int64
+ NewVal int64
+ Extra PropertyMap `gae:"-,extra"`
+ }
+ c := &Convert{ID: 10}
+ So(ds.Get(c), ShouldBeNil)
+ So(c, ShouldResembleV, &Convert{
+ ID: 10, Val: 100, NewVal: 0, Extra: PropertyMap{"TwoVal": {mp(200)}},
+ })
+ c.NewVal = c.Extra["TwoVal"][0].Value().(int64)
+ So(ds.Put(c), ShouldBeNil)
+
+ c = &Convert{ID: 10}
+ So(ds.Get(c), ShouldBeNil)
+ So(c, ShouldResembleV, &Convert{
+ ID: 10, Val: 100, NewVal: 200, Extra: nil,
+ })
+ })
+
+ Convey("Can black hole", func() {
+ initial := PropertyMap{
+ "$key": {mpNI(ds.MakeKey("BlackHole", 10))},
+ "Val": {mp(100)},
+ "TwoVal": {mp(200)},
+ }
+ So(ds.Put(initial), ShouldBeNil)
+ type BlackHole struct {
+ ID int64 `gae:"$id"`
+
+ NewStuff string
+ blackHole PropertyMap `gae:"-,extra"`
+ }
+ b := &BlackHole{ID: 10, NewStuff: "amazeballs"}
dnj 2015/12/12 20:18:34 (╯°□°)╯︵ ┻━┻
iannucci 2015/12/12 21:14:42 good point. done.
dnj (Google) 2015/12/13 02:09:23 +1
+ So(ds.Get(b), ShouldBeNil)
+ So(b, ShouldResemble, &BlackHole{ID: 10, NewStuff: "amazeballs"})
+ })
+
+ Convey("Can change field types", func() {
+ initial := PropertyMap{
+ "$key": {mpNI(ds.MakeKey("IntChange", 10))},
+ "Val": {mp(100)},
+ }
+ So(ds.Put(initial), ShouldBeNil)
+
+ type IntChange struct {
+ ID int64 `gae:"$id"`
+ Val string
+ Extra PropertyMap `gae:"-,extra"`
+ }
+ i := &IntChange{ID: 10}
+ So(ds.Get(i), ShouldBeNil)
+ So(i, ShouldResembleV, &IntChange{ID: 10, Extra: PropertyMap{"Val": {mp(100)}}})
+ i.Val = fmt.Sprint(i.Extra["Val"][0].Value())
+ So(ds.Put(i), ShouldBeNil)
+
+ i = &IntChange{ID: 10}
+ So(ds.Get(i), ShouldBeNil)
+ So(i, ShouldResembleV, &IntChange{ID: 10, Val: "100"})
+ })
+
+ Convey("Native fields have priority over Extra fields", func() {
+ type Dup struct {
+ ID int64 `gae:"$id"`
+ Val int64
+ Extra PropertyMap `gae:",extra"`
+ }
+ d := &Dup{ID: 10, Val: 100, Extra: PropertyMap{
+ "Val": {mp(200)},
+ "Other": {mp("other")},
+ }}
+ So(ds.Put(d), ShouldBeNil)
+
+ d = &Dup{ID: 10}
+ So(ds.Get(d), ShouldBeNil)
+ So(d, ShouldResembleV, &Dup{
+ ID: 10, Val: 100, Extra: PropertyMap{"Other": {mp("other")}},
+ })
+ })
+
+ Convey("Can change repeated field to non-repeating field", func() {
+ initial := PropertyMap{
+ "$key": {mpNI(ds.MakeKey("NonRepeating", 10))},
+ "Val": {mp(100), mp(200), mp(400)},
+ }
+ So(ds.Put(initial), ShouldBeNil)
+
+ type NonRepeating struct {
+ ID int64 `gae:"$id"`
+ Val int64
+ Extra PropertyMap `gae:",extra"`
+ }
+ n := &NonRepeating{ID: 10}
+ So(ds.Get(n), ShouldBeNil)
+ So(n, ShouldResembleV, &NonRepeating{
+ ID: 10, Val: 0, Extra: PropertyMap{
+ "Val": {mp(100), mp(200), mp(400)},
+ },
+ })
+ })
+
+ Convey("Deals correctly with recursive types", func() {
+ initial := PropertyMap{
+ "$key": {mpNI(ds.MakeKey("Outer", 10))},
+ "I.A": {mp(1), mp(2), mp(4)},
+ "I.B": {mp(10), mp(20), mp(40)},
+ "I.C": {mp(100), mp(200), mp(400)},
+ }
+ So(ds.Put(initial), ShouldBeNil)
+ type Inner struct {
+ A int64
+ B int64
+ }
+ type Outer struct {
+ ID int64 `gae:"$id"`
+
+ I []Inner
+ Extra PropertyMap `gae:",extra"`
+ }
+ o := &Outer{ID: 10}
+ So(ds.Get(o), ShouldBeNil)
+ So(o, ShouldResembleV, &Outer{
+ ID: 10,
+ I: []Inner{
+ {1, 10},
+ {2, 20},
+ {4, 40},
+ },
+ Extra: PropertyMap{
+ "I.C": {mp(100), mp(200), mp(400)},
+ },
+ })
+ })
+
+ Convey("Problems", func() {
+ Convey("multiple extra fields", func() {
+ type Bad struct {
+ A PropertyMap `gae:",extra"`
+ B PropertyMap `gae:",extra"`
+ }
+ So(func() { GetPLS(&Bad{}) }, ShouldPanicLike,
+ "multiple fields tagged as 'extra'")
+ })
+
+ Convey("extra field with name", func() {
+ type Bad struct {
+ A PropertyMap `gae:"wut,extra"`
+ }
+ So(func() { GetPLS(&Bad{}) }, ShouldPanicLike,
+ "struct 'extra' field has invalid name wut")
+ })
+
+ Convey("extra field with bad type", func() {
+ type Bad struct {
+ A int64 `gae:",extra"`
+ }
+ So(func() { GetPLS(&Bad{}) }, ShouldPanicLike,
+ "struct 'extra' field has invalid type int64")
+ })
+ })
+ })
+}
« no previous file with comments | « no previous file | service/datastore/pls.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698