func TestDatastoreDelete(t *testing.T) { context, _ := aetest.NewContext(nil) defer context.Close() Convey("Given I have an entity in datastore", t, func() { userInDatastore := NewUser(User{ Name: "Borges", Email: "*****@*****.**", SSN: "123123123", }) appx.NewKeyResolver(context).Resolve(userInDatastore) datastore.Put(context, userInDatastore.Key(), userInDatastore) Convey("And I have a cached entity though not yet saved to datastore", func() { cachedUser := NewUser(User{ Name: "Diego", Email: "*****@*****.**", SSN: "321321321", }) cached := appx.CachedEntity{ Entity: cachedUser, Key: cachedUser.Key(), ParentKey: cachedUser.ParentKey(), } memcache.JSON.Set(context, &memcache.Item{ Key: cachedUser.CacheID(), Object: cached, }) Convey("And I have a non existent user", func() { nonExistentUser := NewUser(User{Name: "not existent"}) Convey("When I delete the all", func() { err := appx.NewDatastore(context).Delete(userInDatastore, cachedUser, nonExistentUser) Convey("Then the entities are deleted from cache and datastore", func() { So(err, ShouldBeNil) err := datastore.Get(context, userInDatastore.Key(), userInDatastore) So(err, ShouldEqual, datastore.ErrNoSuchEntity) _, err = memcache.Get(context, cachedUser.CacheID()) So(err, ShouldEqual, memcache.ErrCacheMiss) }) }) }) }) }) }
func TestLoadMultiEntityKind(t *testing.T) { context, _ := aetest.NewContext(nil) defer context.Close() Convey("Given I have users and devices in datastore", t, func() { user1 := NewUser(User{ Name: "borges", Email: "*****@*****.**", }) user2 := NewUser(User{ Name: "diego", Email: "*****@*****.**", }) device1 := &Device{ ID: 1, Owner: "borges", } device2 := &Device{ ID: 2, Owner: "diego", } db := appx.NewDatastore(context) err := db.Save(user1, device1, user2, device2) So(err, ShouldBeNil) Convey("When I load them all with appx", func() { user1FromDatastore := NewUser(User{Name: "borges"}) user2FromDatastore := NewUser(User{Name: "diego"}) device1FromDatastore := &Device{ID: 1} device2FromDatastore := &Device{ID: 2} err := db.Load( user1FromDatastore, device1FromDatastore, user2FromDatastore, device2FromDatastore) So(err, ShouldBeNil) So(user1FromDatastore, ShouldResemble, user1) So(user2FromDatastore, ShouldResemble, user2) So(device1FromDatastore, ShouldResemble, device1) So(device2FromDatastore, ShouldResemble, device2) }) }) }
func Register(router *httprouter.Router) { r := render.New() router.POST("/deployments", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { deployment := DeploymentFrom(req) macaroon := CreateDeploymentMacaroon(deployment) RequestApproval(appengine.NewContext(req), deployment, macaroon) db := appx.NewDatastore(appengine.NewContext(req)) if err := db.Save(deployment); err != nil { log.Panic(err) } b, _ := macaroon.MarshalBinary() token := base64.URLEncoding.EncodeToString(b) r.JSON(w, 200, JSON{ "token": token, }) }) router.POST("/validate", func(w http.ResponseWriter, req *http.Request, params httprouter.Params) { form := &MacaroonForm{} json.NewDecoder(req.Body).Decode(form) bytes, err := base64.URLEncoding.DecodeString(form.Token) if err != nil { r.JSON(w, 400, JSON{ "message": "Error deserializing macaroon.", "error": err.Error(), }) return } err = VerifyMacaroon(bytes) if err != nil { r.JSON(w, 400, JSON{ "message": "Macaroon invalid.", "error": err.Error(), }) return } r.JSON(w, 200, JSON{ "message": "Macaroon valid.", }) }) }
func TestItemsIterator(t *testing.T) { c, _ := aetest.NewContext(nil) defer c.Close() tags := []*Tag{ &Tag{Name: "golang", Owner: "Borges"}, &Tag{Name: "ruby", Owner: "Borges"}, &Tag{Name: "scala", Owner: "Borges"}, &Tag{Name: "swift", Owner: "Diego"}, } createAll(c, tags...) // TODO fix non deterministic issue Convey("ItemsIterator", t, func() { Convey("Given I have an items iterator with 3 pages each with 1 item", func() { q := datastore.NewQuery(new(Tag).KeySpec().Kind).Filter("Owner=", "Borges").Limit(1) iter := appx.NewDatastore(c).Query(q).ItemsIterator() tagsFromIterator := []*Tag{&Tag{}, &Tag{}, &Tag{}} Convey("Then I can load the first item", func() { So(iter.HasNext(), ShouldBeTrue) So(iter.Cursor(), ShouldBeEmpty) So(iter.LoadNext(tagsFromIterator[0]), ShouldBeNil) So(iter.HasNext(), ShouldBeTrue) So(iter.Cursor(), ShouldNotBeEmpty) So(tagsFromIterator[0], ShouldResemble, tags[0]) Convey("Then I can load the second item", func() { So(iter.LoadNext(tagsFromIterator[1]), ShouldBeNil) So(iter.HasNext(), ShouldBeTrue) So(iter.Cursor(), ShouldNotBeEmpty) So(tagsFromIterator[1], ShouldResemble, tags[2]) Convey("Then I can load the third item", func() { So(iter.LoadNext(tagsFromIterator[2]), ShouldBeNil) So(iter.HasNext(), ShouldBeTrue) So(iter.Cursor(), ShouldNotBeEmpty) So(tagsFromIterator[2], ShouldResemble, tags[1]) Convey("Then I cannot load more items", func() { So(iter.LoadNext(&Tag{}), ShouldEqual, datastore.Done) So(iter.HasNext(), ShouldBeFalse) So(iter.Cursor(), ShouldBeEmpty) }) }) }) Convey("I can create a new iterator using the cursor from the previous one", func() { iterWithCursor := appx.NewDatastore(c).Query(q).StartFrom(iter.Cursor()).ItemsIterator() Convey("I can load the second item", func() { So(iterWithCursor.LoadNext(tagsFromIterator[1]), ShouldBeNil) So(iterWithCursor.HasNext(), ShouldBeTrue) So(iter.Cursor(), ShouldNotBeEmpty) So(tagsFromIterator[1], ShouldResemble, tags[1]) }) }) }) Convey("Then I can load items until iterator has no more items", func() { items := []*Tag{} for iter.HasNext() { item := &Tag{} if err := iter.LoadNext(item); err == nil { items = append(items, item) } } So(len(items), ShouldEqual, 3) So(items[0], ShouldResemble, tags[0]) So(items[1], ShouldResemble, tags[1]) So(items[2], ShouldResemble, tags[2]) }) }) Convey("Given I have an items iterator with zero items", func() { q := datastore.NewQuery(new(Tag).KeySpec().Kind).Filter("Owner=", "non existent").Limit(1) iter := appx.NewDatastore(c).Query(q).ItemsIterator() Convey("When I load the next item", func() { firstItem := Tag{} So(iter.Cursor(), ShouldBeEmpty) So(iter.LoadNext(&firstItem), ShouldEqual, datastore.Done) So(iter.Cursor(), ShouldBeEmpty) Convey("Then the item is not populated", func() { So(firstItem, ShouldResemble, Tag{}) Convey("Then it has no more results", func() { So(iter.HasNext(), ShouldBeFalse) }) }) }) }) }) }
func TestDatastoreSave(t *testing.T) { context, _ := aetest.NewContext(nil) defer context.Close() Convey("Given I have an entity in datastore", t, func() { user1 := NewUser(User{ Name: "Borges", Email: "*****@*****.**", SSN: "123123123", }) appx.NewKeyResolver(context).Resolve(user1) datastore.Put(context, user1.Key(), user1) Convey("And I have an entity not yet saved to datastore", func() { user2 := NewUser(User{ Name: "Diego", Email: "*****@*****.**", SSN: "321321321", }) Convey("When I update the entities in datastore", func() { err := appx.NewDatastore(context).Save(user1, user2) Convey("Then the entities are updated in the datastore", func() { So(err, ShouldBeNil) user1FromDatastore := &User{} user2FromDatastore := &User{} user1FromDatastore.SetKey(user1.Key()) user2FromDatastore.SetKey(user2.Key()) datastore.Get(context, user1.Key(), user1FromDatastore) datastore.Get(context, user2.Key(), user2FromDatastore) So(user1FromDatastore.SSN, ShouldEqual, user1.SSN) So(user1FromDatastore.Name, ShouldEqual, user1.Name) So(user1FromDatastore.Email, ShouldEqual, user1.Email) So(user1FromDatastore.Key(), ShouldResemble, user1.Key()) So(user2FromDatastore.SSN, ShouldEqual, user2.SSN) So(user2FromDatastore.Name, ShouldEqual, user2.Name) So(user2FromDatastore.Email, ShouldEqual, user2.Email) So(user2FromDatastore.Key(), ShouldResemble, user2.Key()) }) }) }) }) Convey("Given I have an entity with incomplete key in datastore", t, func() { user := &User{ Name: "Borges", keySpec: &appx.KeySpec{ Kind: "Users", Incomplete: true, }, } err := appx.NewDatastore(context).Save(user) So(err, ShouldBeNil) userKey := user.Key() Convey("When I update the entity", func() { user.Email = "*****@*****.**" err := appx.NewDatastore(context).Save(user) So(err, ShouldBeNil) Convey("Then the user key is not overriden", func() { So(userKey, ShouldResemble, user.Key()) }) }) }) }
func TestDatastoreLoad(t *testing.T) { context, _ := aetest.NewContext(nil) defer context.Close() Convey("Given I have a cached entity", t, func() { user := NewUser(User{ Name: "Borges", Email: "*****@*****.**", SSN: "123123123", }) appx.NewKeyResolver(context).Resolve(user) cached := appx.CachedEntity{ Entity: user, Key: user.Key(), ParentKey: user.ParentKey(), } memcache.JSON.Set(context, &memcache.Item{ Key: user.CacheID(), Object: cached, }) Convey("When I load it with appx datastore", func() { userFromCache := NewUser(User{ SSN: user.SSN, }) err := appx.NewDatastore(context).Load(userFromCache) Convey("Then the entity data is properly loaded", func() { So(err, ShouldBeNil) So(userFromCache.SSN, ShouldEqual, user.SSN) So(userFromCache.Name, ShouldEqual, user.Name) So(userFromCache.Email, ShouldEqual, user.Email) So(userFromCache.Key(), ShouldResemble, user.Key()) }) }) }) Convey("Given I have a queriable entity", t, func() { user := NewUser(User{ Name: "Borges", Email: "*****@*****.**", SSN: "321321", }) appx.NewKeyResolver(context).Resolve(user) datastore.Put(context, user.Key(), user) // Give datastore some time to index the data before querying time.Sleep(200 * time.Millisecond) Convey("When I load it with appx datastore", func() { userFromDatastore := NewUser(User{ Email: user.Email, SSN: "321321", }) err := appx.NewDatastore(context).Load(userFromDatastore) Convey("Then the entity data is properly loaded", func() { So(err, ShouldBeNil) So(userFromDatastore.SSN, ShouldEqual, user.SSN) So(userFromDatastore.Name, ShouldEqual, user.Name) So(userFromDatastore.Email, ShouldEqual, user.Email) So(userFromDatastore.Key(), ShouldResemble, user.Key()) Convey("And the entity is cached in case it is cacheable", func() { userFromCache := NewUser(User{Name: "Borges"}) cached := appx.CachedEntity{ Entity: userFromCache, } item, err := memcache.Get(context, user.CacheID()) json.Unmarshal(item.Value, &cached) appx.NewKeyResolver(context).Resolve(userFromCache) So(err, ShouldBeNil) So(userFromCache, ShouldResemble, user) }) }) }) }) Convey("Given I have a lookupable entity", t, func() { user := NewUser(User{ Name: "Borges", Email: "*****@*****.**", SSN: "987987", }) appx.NewKeyResolver(context).Resolve(user) datastore.Put(context, user.Key(), user) Convey("When I load it with appx datastore", func() { userFromDatastore := NewUser(User{ Name: "Borges", }) err := appx.NewDatastore(context).Load(userFromDatastore) Convey("Then the entity data is properly loaded", func() { So(err, ShouldBeNil) So(userFromDatastore.SSN, ShouldEqual, user.SSN) So(userFromDatastore.Name, ShouldEqual, user.Name) So(userFromDatastore.Email, ShouldEqual, user.Email) So(userFromDatastore.Key(), ShouldResemble, user.Key()) Convey("And the entity is not cached if its cache id is empty", func() { _, err := memcache.Get(context, user.CacheID()) So(err, ShouldEqual, memcache.ErrCacheMiss) }) }) }) }) }
func TestQuery(t *testing.T) { c, _ := aetest.NewContext(nil) defer c.Close() golang := &Tag{Name: "golang", Owner: "Borges"} swift := &Tag{Name: "swift", Owner: "Borges"} ruby := &Tag{Name: "ruby", Owner: "Diego"} createAll(c, golang, swift, ruby) Convey("Given I have a QueryRunner", t, func() { byOwner := datastore.NewQuery(new(Tag).KeySpec().Kind).Filter("Owner=", "Borges") runner := appx.NewDatastore(c).Query(byOwner) Convey("When I run Results", func() { result := []*Tag{} err := runner.Results(&result) Convey("Then it succeeds", func() { So(err, ShouldBeNil) Convey("Then it loads the matched entities into the given slice", func() { So(result, ShouldResemble, []*Tag{golang, swift}) Convey("Then it sets keys back to the entities", func() { So(result[0].Key(), ShouldNotBeNil) So(result[1].Key(), ShouldNotBeNil) }) }) }) }) Convey("When I run Result", func() { tag := &Tag{} err := runner.Result(tag) Convey("Then it succeeds", func() { So(err, ShouldBeNil) Convey("Then it loads data into the given entity", func() { So(tag, ShouldResemble, golang) Convey("Then it sets the key back to the entity", func() { So(tag.Key(), ShouldNotBeNil) }) }) }) }) Convey("When I run Count", func() { count, err := runner.Count() Convey("Then it succeeds", func() { So(err, ShouldBeNil) Convey("Then count is 2", func() { So(count, ShouldEqual, 2) }) }) }) Convey("When I stream the data", func() { s := runner.StreamOf(Tag{}).Stream Convey("Then the entities are streammed", func() { So(s.ReadAll(), ShouldResemble, []stream.T{golang, swift}) }) }) }) }
func AppxProvider(c martini.Context, context appengine.Context) { c.Map(appx.NewDatastore(context)) }
func TestLoadOver1000Entities(t *testing.T) { context, _ := aetest.NewContext(nil) defer context.Close() Convey("Given I have tons of entities in datastore", t, func() { datastoreUsers := []appx.Entity{} for i := 0; i < 1200; i++ { datastoreUsers = append(datastoreUsers, NewUser(User{ Name: fmt.Sprintf("borges %v", i), Email: fmt.Sprintf("*****@*****.**", i), SSN: fmt.Sprintf("SSN %v", i), })) } db := appx.NewDatastore(context) err := db.Save(datastoreUsers...) So(err, ShouldBeNil) Convey("And some entities only in the cache", func() { for i := 0; i < 100; i++ { err := datastore.Delete(context, datastoreUsers[i].Key()) So(err, ShouldBeNil) } // Give datastore some time to index data time.Sleep(5 * time.Second) Convey("When I load all of them with appx", func() { loadedUsers := []appx.Entity{} // from cache for i := 0; i < 100; i++ { loadedUsers = append(loadedUsers, NewUser(User{ SSN: fmt.Sprintf("SSN %v", i), })) } // by querying in datastore for i := 100; i < 200; i++ { loadedUsers = append(loadedUsers, NewUser(User{ Email: fmt.Sprintf("*****@*****.**", i), })) } // by datastore key lookup for i := 200; i < 1200; i++ { loadedUsers = append(loadedUsers, NewUser(User{ Name: fmt.Sprintf("borges %v", i), })) } err := db.Load(loadedUsers...) Convey("Then all entities are loaded accordingly", func() { So(err, ShouldBeNil) for i := 0; i < 1200; i++ { So(loadedUsers[i].(*User).Name, ShouldEqual, datastoreUsers[i].(*User).Name) So(loadedUsers[i].(*User).Email, ShouldEqual, datastoreUsers[i].(*User).Email) So(loadedUsers[i].(*User).SSN, ShouldEqual, datastoreUsers[i].(*User).SSN) So(loadedUsers[i].Key(), ShouldResemble, datastoreUsers[i].Key()) } }) }) }) }) }
func FromAccount(context appengine.Context) *AccountAppxRepository { return &AccountAppxRepository{ db: appx.NewDatastore(context), } }
func TestPagesIterator(t *testing.T) { c, _ := aetest.NewContext(nil) defer c.Close() tags := []*Tag{ &Tag{Name: "golang", Owner: "Borges"}, &Tag{Name: "ruby", Owner: "Borges"}, &Tag{Name: "scala", Owner: "Borges"}, &Tag{Name: "swift", Owner: "Diego"}, } createAll(c, tags...) Convey("PagesIterator", t, func() { Convey("Given I have a pages iterator with 2 pages each with 2 items", func() { q := datastore.NewQuery(new(Tag).KeySpec().Kind).Limit(2) iter := appx.NewDatastore(c).Query(q).PagesIterator() Convey("Then I can load the first page", func() { firstPage := []*Tag{} So(iter.Cursor(), ShouldBeEmpty) So(iter.LoadNext(&firstPage), ShouldBeNil) So(iter.HasNext(), ShouldBeTrue) So(firstPage, ShouldResemble, tags[0:2]) Convey("Then I can load the second page", func() { secondPage := []*Tag{} So(iter.Cursor(), ShouldNotBeEmpty) So(iter.LoadNext(&secondPage), ShouldBeNil) So(iter.HasNext(), ShouldBeTrue) So(secondPage, ShouldResemble, tags[2:]) Convey("Then I cannot load more pages", func() { page := []*Tag{} So(iter.LoadNext(&page), ShouldEqual, datastore.Done) So(iter.HasNext(), ShouldBeFalse) So(iter.Cursor(), ShouldBeEmpty) So(page, ShouldBeEmpty) }) }) }) }) Convey("Given I have a pages iterator with 4 pages each with 1 item", func() { q := datastore.NewQuery(new(Tag).KeySpec().Kind).Limit(1) iter := appx.NewDatastore(c).Query(q).PagesIterator() Convey("Then I can load pages until iterator has no more pages", func() { pages := [][]*Tag{} for iter.HasNext() { page := []*Tag{} if err := iter.LoadNext(&page); err == nil { pages = append(pages, page) } } So(len(pages), ShouldEqual, 4) So(pages[0], ShouldResemble, []*Tag{tags[0]}) So(pages[1], ShouldResemble, []*Tag{tags[1]}) So(pages[2], ShouldResemble, []*Tag{tags[2]}) So(pages[3], ShouldResemble, []*Tag{tags[3]}) }) }) Convey("Given I have a pages iterator with cursor starting from the second and last page", func() { q := datastore.NewQuery(new(Tag).KeySpec().Kind).Limit(2) firstPage := &[]*Tag{} prevIter := appx.NewDatastore(c).Query(q).PagesIterator() prevIter.LoadNext(firstPage) iterStartingFromSecondPage := appx.NewDatastore(c).Query(q).StartFrom(prevIter.Cursor()).PagesIterator() Convey("Then I can load the page", func() { secondPage := []*Tag{} So(iterStartingFromSecondPage.LoadNext(&secondPage), ShouldBeNil) So(iterStartingFromSecondPage.HasNext(), ShouldBeTrue) So(iterStartingFromSecondPage.Cursor(), ShouldNotBeEmpty) So(secondPage, ShouldResemble, tags[2:]) Convey("Then I cannot load more pages", func() { page := []*Tag{} So(iterStartingFromSecondPage.LoadNext(&page), ShouldEqual, datastore.Done) So(iterStartingFromSecondPage.HasNext(), ShouldBeFalse) So(iterStartingFromSecondPage.Cursor(), ShouldBeEmpty) So(page, ShouldBeEmpty) }) }) }) Convey("Given I have a pages iterator with zero items", func() { q := datastore.NewQuery(new(Tag).KeySpec().Kind).Filter("Owner=", "non existent").Limit(1) iter := appx.NewDatastore(c).Query(q).PagesIterator() Convey("When I load the next page", func() { firstPage := []*Tag{} So(iter.LoadNext(&firstPage), ShouldEqual, datastore.Done) Convey("Then the page is empty", func() { So(firstPage, ShouldBeEmpty) Convey("Then the cursor is empty", func() { So(iter.Cursor(), ShouldBeEmpty) Convey("Then it has no more results", func() { So(iter.HasNext(), ShouldBeFalse) }) }) }) }) }) }) }