func TestLoadBatchFromDatastore(t *testing.T) { gaeCtx, _ := aetest.NewContext(nil) defer gaeCtx.Close() Convey("Given I have a load batch from datastore transformer", t, func() { riversCtx := rivers.NewContext() loadBatchProcessor := appx.NewStep(riversCtx).LoadBatchFromDatastore(gaeCtx) Convey("And I have a few entities in datastore", func() { user1 := NewUser(User{ Name: "Borges", Email: "*****@*****.**", SSN: "123123123", }) user2 := NewUser(User{ Name: "Borges", Email: "*****@*****.**", SSN: "123123123", }) err := appx.NewKeyResolver(gaeCtx).Resolve(user1) So(err, ShouldBeNil) err = appx.NewKeyResolver(gaeCtx).Resolve(user2) So(err, ShouldBeNil) _, err = datastore.Put(gaeCtx, user1.Key(), user1) So(err, ShouldBeNil) _, err = datastore.Put(gaeCtx, user2.Key(), user2) So(err, ShouldBeNil) Convey("When I transform the incoming batch", func() { userFromDatastore1 := NewUser(User{Name: user1.Name}) userFromDatastore2 := NewUser(User{Name: user2.Name}) appx.NewKeyResolver(gaeCtx).Resolve(userFromDatastore1) appx.NewKeyResolver(gaeCtx).Resolve(userFromDatastore2) batch := &appx.DatastoreBatch{ Size: 2, Keys: []*datastore.Key{ userFromDatastore1.Key(), userFromDatastore2.Key(), }, Items: []appx.Entity{ userFromDatastore1, userFromDatastore2, }, } loadBatchProcessor(batch) Convey("And entities are loaded from datastore", func() { So(userFromDatastore1, ShouldResemble, user1) So(userFromDatastore2, ShouldResemble, user2) }) }) }) }) }
func createAll(c appengine.Context, tags ...*Tag) { keys := make([]*datastore.Key, len(tags)) for i, tag := range tags { appx.NewKeyResolver(c).Resolve(tag) keys[i] = tag.Key() } datastore.PutMulti(c, keys, tags) time.Sleep(2 * time.Second) }
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 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 TestLoadBatchFromCache(t *testing.T) { gaeCtx, _ := aetest.NewContext(nil) defer gaeCtx.Close() Convey("Given I have a load batch from cache transformer", t, func() { riversCtx := rivers.NewContext() loadBatchProcessor := appx.NewStep(riversCtx).LoadBatchFromCache(gaeCtx) Convey("And I have a few entities in the cache", func() { user1 := NewUser(User{ Name: "Borges", Email: "*****@*****.**", SSN: "123123123", }) user2 := NewUser(User{ Name: "Diego", Email: "*****@*****.**", SSN: "321321", }) appx.NewKeyResolver(gaeCtx).Resolve(user1) appx.NewKeyResolver(gaeCtx).Resolve(user2) memcache.JSON.Set(gaeCtx, &memcache.Item{ Key: user1.CacheID(), Object: appx.CachedEntity{ Entity: user1, Key: user1.Key(), }, }) memcache.JSON.Set(gaeCtx, &memcache.Item{ Key: user2.CacheID(), Object: appx.CachedEntity{ Entity: user2, Key: user2.Key(), }, }) Convey("When I transform the incoming batch", func() { notCachedUser := NewUser(User{ Name: "not cached", SSN: "notcached", }) userFromCache1 := NewUser(User{Name: user1.Name}) userFromCache2 := NewUser(User{Name: user2.Name}) batchItems := make(map[string]*appx.CachedEntity) batchItems[user1.CacheID()] = &appx.CachedEntity{ Entity: userFromCache1, } batchItems[user2.CacheID()] = &appx.CachedEntity{ Entity: userFromCache2, } batchItems[notCachedUser.CacheID()] = &appx.CachedEntity{ Entity: notCachedUser, } batch := &appx.MemcacheLoadBatch{ Keys: []string{user1.CacheID(), user2.CacheID()}, Items: batchItems, } in, out := stream.New(1) loadBatchProcessor(batch, stream.NewEmitter(rivers.NewContext(), out)) close(out) Convey("Then cache misses are sent downstream", func() { So(in.ReadAll(), ShouldResemble, []stream.T{notCachedUser}) Convey("And entities are loaded from cache", func() { So(userFromCache1, ShouldResemble, user1) So(userFromCache2, ShouldResemble, user2) }) }) }) }) }) }
func TestKeyResolver(t *testing.T) { context, _ := aetest.NewContext(nil) defer context.Close() Convey("Given I have a key manager", t, func() { manager := appx.NewKeyResolver(context) Convey("When I resolve a key of an entity with the key already set", func() { entity := &User{ keySpec: &appx.KeySpec{ Kind: "Entity", IntID: 123, }, } key := datastore.NewKey(context, entity.keySpec.Kind, "", entity.keySpec.IntID, nil) entity.SetKey(key) err := manager.Resolve(entity) Convey("Then it succeeds", func() { So(err, ShouldBeNil) Convey("And the entity key is not overriden", func() { So(entity.Key(), ShouldEqual, key) }) }) }) Convey("When I resolve a key of an entity with no parent", func() { entity := &User{ keySpec: &appx.KeySpec{ Kind: "Entity", IntID: 123, }, } err := manager.Resolve(entity) Convey("Then it succeeds", func() { So(err, ShouldBeNil) Convey("And the resolved key is set back to the entity", func() { So(entity.Key().StringID(), ShouldEqual, "") So(entity.Key().IntID(), ShouldEqual, entity.keySpec.IntID) So(entity.Key().Kind(), ShouldEqual, entity.keySpec.Kind) So(entity.Key().Parent(), ShouldBeNil) }) }) }) Convey("When I resolve a key of an entity with parent", func() { entity := &User{ keySpec: &appx.KeySpec{ Kind: "Entity", IntID: 123, HasParent: true, }, } parentKey := datastore.NewKey(context, "Parent", "key", 0, nil) entity.SetParentKey(parentKey) err := manager.Resolve(entity) Convey("Then it succeeds", func() { So(err, ShouldBeNil) Convey("And the resolved key is set back to the entity", func() { So(entity.Key().Kind(), ShouldEqual, entity.keySpec.Kind) So(entity.Key().IntID(), ShouldEqual, entity.keySpec.IntID) So(entity.Key().StringID(), ShouldEqual, "") So(entity.Key().Parent().Kind(), ShouldEqual, parentKey.Kind()) So(entity.Key().Parent().IntID(), ShouldEqual, 0) So(entity.Key().Parent().StringID(), ShouldEqual, parentKey.StringID()) }) }) }) Convey("When I resolve a key of an entity whose key spec is incomplete", func() { entity := &User{ keySpec: &appx.KeySpec{ Kind: "People", Incomplete: true, }, } err := manager.Resolve(entity) Convey("Then key is resolved as incomplete", func() { So(err, ShouldBeNil) So(entity.Key().Incomplete(), ShouldBeTrue) }) }) Convey("When I resolve a key of an entity whose key spec is missing kind information", func() { err := manager.Resolve(&User{ keySpec: &appx.KeySpec{}, }) Convey("Then it fails key resolution", func() { So(err, ShouldEqual, appx.ErrMissingEntityKind) }) }) Convey("When I resolve a key of an entity whose key spec is of an incomplete key", func() { err := manager.Resolve(&User{ keySpec: &appx.KeySpec{Kind: "Entity"}, }) Convey("Then it fails key resolution", func() { So(err, ShouldEqual, appx.ErrIncompleteKey) }) }) Convey("When I resolve a key of an entity whose key spec is incomplete an StringID is set", func() { err := manager.Resolve(&User{ keySpec: &appx.KeySpec{ Kind: "Entity", Incomplete: true, StringID: "string id", }, }) Convey("Then it fails key resolution", func() { So(err, ShouldEqual, appx.ErrIncompleteKey) }) }) Convey("When I resolve a key of an entity whose key spec is incomplete an IntID is set", func() { err := manager.Resolve(&User{ keySpec: &appx.KeySpec{ Kind: "Entity", Incomplete: true, IntID: 123, }, }) Convey("Then it fails key resolution", func() { So(err, ShouldEqual, appx.ErrIncompleteKey) }) }) Convey("When I resolve a key of an entity whose key spec requires a parent key and it's missing", func() { err := manager.Resolve(&User{ keySpec: &appx.KeySpec{ Kind: "Entity", IntID: 123, HasParent: true, }, }) Convey("Then it fails key resolution", func() { So(err, ShouldEqual, appx.ErrMissingParentKey) }) }) Convey("When I resolve a key of an entity whose key spec requires a parent key and parent key is incomplete", func() { entity := &User{ keySpec: &appx.KeySpec{ Kind: "Entity", IntID: 123, HasParent: true, }, } entity.SetParentKey(datastore.NewIncompleteKey(context, "parent", nil)) err := manager.Resolve(entity) Convey("Then it fails key resolution", func() { So(err, ShouldEqual, appx.ErrIncompleteParentKey) }) }) }) }
func TestQueryEntityFromDatastore(t *testing.T) { gaeCtx, _ := aetest.NewContext(nil) defer gaeCtx.Close() user := &User{ Name: "Borges", Email: "*****@*****.**", SSN: "123123123", keySpec: &appx.KeySpec{ Kind: "Users", StringID: "borges", }, } parentKey := datastore.NewKey(gaeCtx, "Parent", "parent id", 0, nil) user.SetParentKey(parentKey) Convey("Given I have a query entity from datastore transformer", t, func() { riversCtx := rivers.NewContext() queryProcessor := appx.NewStep(riversCtx).QueryEntityFromDatastore(gaeCtx) Convey("When I transform the inbound stream with non existent entity", func() { nonExistentUser := &User{ Email: "*****@*****.**", keySpec: &appx.KeySpec{ Kind: "Users", }, } runQuery := func() { queryProcessor(nonExistentUser) } Convey("Then query processor panics", func() { So(runQuery, ShouldPanic) }) }) Convey("And I have an entity in datastore", func() { err := appx.NewKeyResolver(gaeCtx).Resolve(user) So(err, ShouldBeNil) _, err = datastore.Put(gaeCtx, user.Key(), user) So(err, ShouldBeNil) // Give datastore some time so that the created entity is available to be queried time.Sleep(200 * time.Millisecond) Convey("When I transform the inbound stream", func() { userFromDatastore := &User{ Email: "*****@*****.**", keySpec: &appx.KeySpec{ Kind: "Users", }, } queryProcessor(userFromDatastore) Convey("And queryable entities are loaded from datastore", func() { So(userFromDatastore.Name, ShouldEqual, user.Name) So(userFromDatastore.Email, ShouldEqual, user.Email) So(userFromDatastore.Key(), ShouldResemble, user.Key()) So(userFromDatastore.ParentKey(), ShouldResemble, user.ParentKey()) }) }) }) }) }