Chromium Code Reviews| 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") |
| + }) |
| + }) |
| + }) |
| +} |