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 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 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())
				})
			})
		})
	})
}