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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « no previous file | service/datastore/pls.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 // adapted from github.com/golang/appengine/datastore 5 // adapted from github.com/golang/appengine/datastore
6 6
7 package datastore 7 package datastore
8 8
9 import ( 9 import (
10 "fmt" 10 "fmt"
(...skipping 908 matching lines...) Expand 10 before | Expand all | Expand 10 after
919 So(ds.Run(q, func(k *Key, _ CursorCB) bool { 919 So(ds.Run(q, func(k *Key, _ CursorCB) bool {
920 So(k.IntID(), ShouldEqual, i+1) 920 So(k.IntID(), ShouldEqual, i+1)
921 i++ 921 i++
922 return true 922 return true
923 }), ShouldBeNil) 923 }), ShouldBeNil)
924 }) 924 })
925 925
926 }) 926 })
927 }) 927 })
928 } 928 }
929
930 type fixedDataDatastore struct {
931 RawInterface
932
933 data map[string]PropertyMap
934 }
935
936 func (d *fixedDataDatastore) GetMulti(keys []*Key, _ MultiMetaGetter, cb GetMult iCB) error {
937 for _, k := range keys {
938 data, ok := d.data[k.String()]
939 if ok {
940 cb(data, nil)
941 } else {
942 cb(nil, ErrNoSuchEntity)
943 }
944 }
945 return nil
946 }
947
948 func (d *fixedDataDatastore) PutMulti(keys []*Key, vals []PropertyMap, cb PutMul tiCB) error {
949 if d.data == nil {
950 d.data = make(map[string]PropertyMap, len(keys))
951 }
952 for i, k := range keys {
953 if k.Incomplete() {
954 panic("key is incomplete, don't do that.")
955 }
956 d.data[k.String()], _ = vals[i].Save(false)
957 cb(k, nil)
958 }
959 return nil
960 }
961
962 func TestSchemaChange(t *testing.T) {
963 t.Parallel()
964
965 Convey("Test changing schemas", t, func() {
966 fds := fixedDataDatastore{}
967 ds := &datastoreImpl{&fds, "", ""}
968
969 Convey("Can add fields", func() {
970 initial := PropertyMap{
971 "$key": {mpNI(ds.MakeKey("Val", 10))},
972 "Val": {mp(100)},
973 }
974 So(ds.Put(initial), ShouldBeNil)
975
976 type Val struct {
977 ID int64 `gae:"$id"`
978
979 Val int64
980 TwoVal int64 // whoa, TWO vals! amazing
981 }
982 tv := &Val{ID: 10, TwoVal: 2}
983 So(ds.Get(tv), ShouldBeNil)
984 So(tv, ShouldResemble, &Val{ID: 10, Val: 100, TwoVal: 2} )
985 })
986
987 Convey("Removing fields", func() {
988 initial := PropertyMap{
989 "$key": {mpNI(ds.MakeKey("Val", 10))},
990 "Val": {mp(100)},
991 "TwoVal": {mp(200)},
992 }
993 So(ds.Put(initial), ShouldBeNil)
994
995 Convey("is normally an error", func() {
996 type Val struct {
997 ID int64 `gae:"$id"`
998
999 Val int64
1000 }
1001 tv := &Val{ID: 10}
1002 So(ds.Get(tv), ShouldErrLike,
1003 `gae: cannot load field "TwoVal" into a "datastore.Val`)
1004 So(tv, ShouldResemble, &Val{ID: 10, Val: 100})
1005 })
1006
1007 Convey("Unless you have an ,extra field!", func() {
1008 type Val struct {
1009 ID int64 `gae:"$id"`
1010
1011 Val int64
1012 Extra PropertyMap `gae:",extra"`
1013 }
1014 tv := &Val{ID: 10}
1015 So(ds.Get(tv), ShouldBeNil)
1016 So(tv, ShouldResembleV, &Val{
1017 ID: 10,
1018 Val: 100,
1019 Extra: PropertyMap{
1020 "TwoVal": {mp(200)},
1021 },
1022 })
1023 })
1024 })
1025
1026 Convey("Can round-trip extra fields", func() {
1027 type Expando struct {
1028 ID int64 `gae:"$id"`
1029
1030 Something int
1031 Extra PropertyMap `gae:",extra"`
1032 }
1033 ex := &Expando{10, 17, PropertyMap{
1034 "Hello": {mp("Hello")},
1035 "World": {mp(true)},
1036 }}
1037 So(ds.Put(ex), ShouldBeNil)
1038
1039 ex = &Expando{ID: 10}
1040 So(ds.Get(ex), ShouldBeNil)
1041 So(ex, ShouldResembleV, &Expando{
1042 ID: 10,
1043 Something: 17,
1044 Extra: PropertyMap{
1045 "Hello": {mp("Hello")},
1046 "World": {mp(true)},
1047 },
1048 })
1049 })
1050
1051 Convey("Can read-but-not-write", func() {
1052 initial := PropertyMap{
1053 "$key": {mpNI(ds.MakeKey("Convert", 10))},
1054 "Val": {mp(100)},
1055 "TwoVal": {mp(200)},
1056 }
1057 So(ds.Put(initial), ShouldBeNil)
1058 type Convert struct {
1059 ID int64 `gae:"$id"`
1060
1061 Val int64
1062 NewVal int64
1063 Extra PropertyMap `gae:"-,extra"`
1064 }
1065 c := &Convert{ID: 10}
1066 So(ds.Get(c), ShouldBeNil)
1067 So(c, ShouldResembleV, &Convert{
1068 ID: 10, Val: 100, NewVal: 0, Extra: PropertyMap{ "TwoVal": {mp(200)}},
1069 })
1070 c.NewVal = c.Extra["TwoVal"][0].Value().(int64)
1071 So(ds.Put(c), ShouldBeNil)
1072
1073 c = &Convert{ID: 10}
1074 So(ds.Get(c), ShouldBeNil)
1075 So(c, ShouldResembleV, &Convert{
1076 ID: 10, Val: 100, NewVal: 200, Extra: nil,
1077 })
1078 })
1079
1080 Convey("Can black hole", func() {
1081 initial := PropertyMap{
1082 "$key": {mpNI(ds.MakeKey("BlackHole", 10))},
1083 "Val": {mp(100)},
1084 "TwoVal": {mp(200)},
1085 }
1086 So(ds.Put(initial), ShouldBeNil)
1087 type BlackHole struct {
1088 ID int64 `gae:"$id"`
1089
1090 NewStuff string
1091 blackHole PropertyMap `gae:"-,extra"`
1092 }
1093 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
1094 So(ds.Get(b), ShouldBeNil)
1095 So(b, ShouldResemble, &BlackHole{ID: 10, NewStuff: "amaz eballs"})
1096 })
1097
1098 Convey("Can change field types", func() {
1099 initial := PropertyMap{
1100 "$key": {mpNI(ds.MakeKey("IntChange", 10))},
1101 "Val": {mp(100)},
1102 }
1103 So(ds.Put(initial), ShouldBeNil)
1104
1105 type IntChange struct {
1106 ID int64 `gae:"$id"`
1107 Val string
1108 Extra PropertyMap `gae:"-,extra"`
1109 }
1110 i := &IntChange{ID: 10}
1111 So(ds.Get(i), ShouldBeNil)
1112 So(i, ShouldResembleV, &IntChange{ID: 10, Extra: Propert yMap{"Val": {mp(100)}}})
1113 i.Val = fmt.Sprint(i.Extra["Val"][0].Value())
1114 So(ds.Put(i), ShouldBeNil)
1115
1116 i = &IntChange{ID: 10}
1117 So(ds.Get(i), ShouldBeNil)
1118 So(i, ShouldResembleV, &IntChange{ID: 10, Val: "100"})
1119 })
1120
1121 Convey("Native fields have priority over Extra fields", func() {
1122 type Dup struct {
1123 ID int64 `gae:"$id"`
1124 Val int64
1125 Extra PropertyMap `gae:",extra"`
1126 }
1127 d := &Dup{ID: 10, Val: 100, Extra: PropertyMap{
1128 "Val": {mp(200)},
1129 "Other": {mp("other")},
1130 }}
1131 So(ds.Put(d), ShouldBeNil)
1132
1133 d = &Dup{ID: 10}
1134 So(ds.Get(d), ShouldBeNil)
1135 So(d, ShouldResembleV, &Dup{
1136 ID: 10, Val: 100, Extra: PropertyMap{"Other": {m p("other")}},
1137 })
1138 })
1139
1140 Convey("Can change repeated field to non-repeating field", func( ) {
1141 initial := PropertyMap{
1142 "$key": {mpNI(ds.MakeKey("NonRepeating", 10))},
1143 "Val": {mp(100), mp(200), mp(400)},
1144 }
1145 So(ds.Put(initial), ShouldBeNil)
1146
1147 type NonRepeating struct {
1148 ID int64 `gae:"$id"`
1149 Val int64
1150 Extra PropertyMap `gae:",extra"`
1151 }
1152 n := &NonRepeating{ID: 10}
1153 So(ds.Get(n), ShouldBeNil)
1154 So(n, ShouldResembleV, &NonRepeating{
1155 ID: 10, Val: 0, Extra: PropertyMap{
1156 "Val": {mp(100), mp(200), mp(400)},
1157 },
1158 })
1159 })
1160
1161 Convey("Deals correctly with recursive types", func() {
1162 initial := PropertyMap{
1163 "$key": {mpNI(ds.MakeKey("Outer", 10))},
1164 "I.A": {mp(1), mp(2), mp(4)},
1165 "I.B": {mp(10), mp(20), mp(40)},
1166 "I.C": {mp(100), mp(200), mp(400)},
1167 }
1168 So(ds.Put(initial), ShouldBeNil)
1169 type Inner struct {
1170 A int64
1171 B int64
1172 }
1173 type Outer struct {
1174 ID int64 `gae:"$id"`
1175
1176 I []Inner
1177 Extra PropertyMap `gae:",extra"`
1178 }
1179 o := &Outer{ID: 10}
1180 So(ds.Get(o), ShouldBeNil)
1181 So(o, ShouldResembleV, &Outer{
1182 ID: 10,
1183 I: []Inner{
1184 {1, 10},
1185 {2, 20},
1186 {4, 40},
1187 },
1188 Extra: PropertyMap{
1189 "I.C": {mp(100), mp(200), mp(400)},
1190 },
1191 })
1192 })
1193
1194 Convey("Problems", func() {
1195 Convey("multiple extra fields", func() {
1196 type Bad struct {
1197 A PropertyMap `gae:",extra"`
1198 B PropertyMap `gae:",extra"`
1199 }
1200 So(func() { GetPLS(&Bad{}) }, ShouldPanicLike,
1201 "multiple fields tagged as 'extra'")
1202 })
1203
1204 Convey("extra field with name", func() {
1205 type Bad struct {
1206 A PropertyMap `gae:"wut,extra"`
1207 }
1208 So(func() { GetPLS(&Bad{}) }, ShouldPanicLike,
1209 "struct 'extra' field has invalid name w ut")
1210 })
1211
1212 Convey("extra field with bad type", func() {
1213 type Bad struct {
1214 A int64 `gae:",extra"`
1215 }
1216 So(func() { GetPLS(&Bad{}) }, ShouldPanicLike,
1217 "struct 'extra' field has invalid type i nt64")
1218 })
1219 })
1220 })
1221 }
OLDNEW
« 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