public async Task A_pipeline_can_be_used_to_continue_on_exceptions()
        {
            var aggregator = Aggregator
                .Create<BalanceProjection, IDomainEvent>((projection, events) =>
                {
                    Task.Run(() =>
                    {
                        throw new Exception("DRAT!");
                    });
                })
                .Pipeline(async (projection, events, next) =>
                {
                    try
                    {
                        return await next(projection, events);
                    }
                    catch (Exception)
                    {
                        return projection;
                    }
                });

            var balanceProjection = new BalanceProjection();
            var returnedProjection = await aggregator.Aggregate(balanceProjection, null);

            balanceProjection.Should().BeSameAs(returnedProjection);
        }
        public async Task An_aggregator_can_be_short_circuited_using_Pipeline_and_returning_rather_than_calling_next()
        {
            var wasCalled = false;

            var aggregator = Aggregator.Create<BalanceProjection, IDomainEvent>((projection, events) => wasCalled = true)
                                       .Pipeline(async (projection, events, next) => projection);

            var balanceProjection = new BalanceProjection();
            var returnedProjection = await aggregator.Aggregate(balanceProjection, null);

            wasCalled.Should().BeFalse();
            balanceProjection.Should().BeSameAs(returnedProjection);
        }
        public async Task When_a_stream_has_no_events_after_the_projection_cursor_then_no_data_is_fetched()
        {
            var initialProjection = new BalanceProjection
            {
                Balance = 321m,
                CursorPosition = 5
            };

            var finalProjection = await stream.Aggregate(AccountBalanceProjector(),
                                                         initialProjection);

            finalProjection.ShouldBeEquivalentTo(initialProjection,
                                                 "the projection cursor is past the end of the event stream so no events should be applied");
        }
        public async Task Projections_can_be_updated_from_a_previously_stored_state()
        {
            var projection = new BalanceProjection
            {
                Balance = 100m,
                CursorPosition = 2
            };

            var balanceProjection = await stream.Aggregate(AccountBalanceProjector(),
                                                             projection);

            balanceProjection.Balance
                             .Should()
                             .Be(111m,
                                 "the first two items in the sequence should not have been applied, and the prior projection state should have been used");
        }
        public async Task Getting_and_storing_projections_and_cursors_can_operate_transactionally_via_a_closure()
        {
            BalanceProjection finalProjection = null;

            var catchup = StreamCatchup.Create(stream);
            FetchAndSaveProjection<BalanceProjection> fetchAndSaveProjection = (async (id, callAggregatorPipeline) =>
            {
                using (var transaction = new TransactionScope())
                {
                    // e.g. get the projection / cursor from the store
                    var proj = new BalanceProjection
                    {
                        CursorPosition = 5
                    };

                    proj = await callAggregatorPipeline(proj);

                    finalProjection = proj;

                    // save the projection / cursor back to the store 
                    transaction.Complete();
                }
            });

            catchup.Subscribe(new BalanceProjector(), fetchAndSaveProjection);

            store.WriteEvents(streamId, amount: 100m, howMany: 5);

            await catchup.RunSingleBatch();

            finalProjection.Balance.Should().Be(100m);
        }
        public async Task The_same_projection_is_not_queried_more_than_once_during_a_batch()
        {
            var projection = new BalanceProjection
            {
                CursorPosition = 1
            };

            var getCount = 0;
            var projectionStore = ProjectionStore.Create<string, BalanceProjection>(
                get: async key =>
                {
                    if (key.Contains(streamId))
                    {
                        Console.WriteLine("Get");
                        Interlocked.Increment(ref getCount);
                    }
                    return projection;
                },
                put: async (key, p) =>
                {
                    if (streamId == key)
                    {
                        Console.WriteLine("Put");
                    }
                    projection = p;
                });

            var catchup = StreamCatchup.Create(stream);
            catchup.Subscribe(new BalanceProjector(), projectionStore);

            store.WriteEvents(streamId, howMany: 3);

            await catchup.RunSingleBatch();

            getCount.Should().Be(1);
        }