public async Task RunsPivotStep()
        {
            using (var writer = new TestBulkWriter <PipelineTestsMyTestClass>())
            {
                var idCounter = 0;
                var items     = Enumerable.Range(1, 10).ToList();
                var pipeline  = EtlPipeline.StartWith(items)
                                .Pivot(i =>
                {
                    var result = new List <PipelineTestsMyTestClass>();
                    for (var j = 0; j < i; j++)
                    {
                        ++idCounter;
                        result.Add(new PipelineTestsMyTestClass {
                            Id = idCounter, Name = $"Bob {idCounter}"
                        });
                    }
                    return(result);
                })
                                .WriteTo(writer);

                await pipeline.ExecuteAsync();

                var expectedSum = items.Sum();
                Assert.Equal(expectedSum, writer.ItemsWritten.Count);
                Assert.Equal(1, writer.ItemsWritten[0].Id);
                Assert.Equal("Bob 1", writer.ItemsWritten[0].Name);
                Assert.Equal(expectedSum, writer.ItemsWritten[expectedSum - 1].Id);
                Assert.Equal($"Bob {expectedSum}", writer.ItemsWritten[expectedSum - 1].Name);
            }
        }
        public async Task RunsTransformerStep()
        {
            using (var writer = new TestBulkWriter <PipelineTestsMyTestClass>())
            {
                var items = Enumerable.Range(1, 1000).Select(i => new PipelineTestsMyTestClass {
                    Id = i, Name = "Bob"
                });
                var pipeline = EtlPipeline
                               .StartWith(items)
                               .TransformInPlace(i =>
                {
                    i.Id  -= 1;
                    i.Name = $"Alice {i.Id}";
                })
                               .WriteTo(writer);

                await pipeline.ExecuteAsync();

                Assert.Equal(1000, writer.ItemsWritten.Count);
                Assert.Equal(0, writer.ItemsWritten[0].Id);
                Assert.Equal("Alice 0", writer.ItemsWritten[0].Name);
                Assert.Equal(999, writer.ItemsWritten[999].Id);
                Assert.Equal("Alice 999", writer.ItemsWritten[999].Name);
            }
        }
        public async Task LogsExceptionInPipelineStep()
        {
            using (var writer = new TestBulkWriter <PipelineTestsMyTestClass>())
            {
                var items = Enumerable
                            .Range(1, 1000)
                            .Select(i => new PipelineTestsMyTestClass {
                    Id = i, Name = "Bob"
                })
                            .ToAsyncEnumerable();

                var loggerFactory = new FakeLoggerFactory();
                var pipeline      = EtlPipeline
                                    .StartWith(items)
                                    .Project <PipelineTestsMyTestClass>(i => throw new Exception("This is my exception"))
                                    .LogWith(loggerFactory)
                                    .WriteTo(writer);

                var pipelineTask = pipeline.ExecuteAsync();
                await Assert.ThrowsAsync <Exception>(() => pipelineTask);

                var errorMessages = loggerFactory.LoggedMessages.Where(m => m.LogLevel == LogLevel.Error).ToList();
                Assert.Single(errorMessages);

                Assert.Contains("Error", errorMessages[0].Message);
                Assert.Equal("This is my exception", errorMessages[0].Exception.Message);
            }
        }
        public async Task IgnoresNullLogger()
        {
            using (var writer = new TestBulkWriter <PipelineTestsMyTestClass>())
            {
                var items = Enumerable
                            .Range(1, 1000)
                            .Select(i => new PipelineTestsMyTestClass {
                    Id = i, Name = "Bob"
                })
                            .ToAsyncEnumerable();

                var pipeline = EtlPipeline
                               .StartWith(items)
                               .TransformInPlace(i =>
                {
                    i.Id  -= 1;
                    i.Name = $"Alice {i.Id}";
                })
                               .LogWith(null)
                               .WriteTo(writer);

                await pipeline.ExecuteAsync();

                Assert.Equal(1000, writer.ItemsWritten.Count);
            }
        }
        public async Task RunsProjectorStep()
        {
            using (var writer = new TestBulkWriter <PipelineTestsMyTestClass>())
            {
                var items = Enumerable
                            .Range(1, 1000)
                            .Select(i => new PipelineTestsOtherTestClass {
                    Id = i, FirstName = "Bob", LastName = $"{i}"
                })
                            .ToAsyncEnumerable();

                var pipeline = EtlPipeline
                               .StartWith(items)
                               .Project(i => new PipelineTestsMyTestClass {
                    Id = i.Id, Name = $"{i.FirstName} {i.LastName}"
                })
                               .WriteTo(writer);

                await pipeline.ExecuteAsync();

                Assert.Equal(1000, writer.ItemsWritten.Count);
                Assert.Equal(1, writer.ItemsWritten[0].Id);
                Assert.Equal("Bob 1", writer.ItemsWritten[0].Name);
                Assert.Equal(1000, writer.ItemsWritten[999].Id);
                Assert.Equal("Bob 1000", writer.ItemsWritten[999].Name);
            }
        }
        public async Task LogsStartAndFinishOfPipelineSteps()
        {
            using (var writer = new TestBulkWriter <PipelineTestsOtherTestClass>())
            {
                var items = Enumerable.Range(1, 1000).Select(i => new PipelineTestsMyTestClass {
                    Id = i, Name = "Bob"
                });
                var loggerFactory = new FakeLoggerFactory();
                var pipeline      = EtlPipeline
                                    .StartWith(items)
                                    .LogWith(loggerFactory)
                                    .Aggregate(f =>
                {
                    Thread.Sleep(1);
                    return(f.Max(c => c.Id));
                })
                                    .Pivot(i =>
                {
                    var result = new List <PipelineTestsMyTestClass>();
                    for (var j = 1; j <= i; j++)
                    {
                        result.Add(new PipelineTestsMyTestClass {
                            Id = j, Name = $"Bob {j}"
                        });
                    }
                    return(result);
                })
                                    .Project(i =>
                {
                    var nameParts = i.Name.Split(' ');
                    return(new PipelineTestsOtherTestClass {
                        Id = i.Id, FirstName = nameParts[0], LastName = nameParts[1]
                    });
                })
                                    .TransformInPlace(i =>
                {
                    i.Id       -= 1;
                    i.FirstName = "Alice";
                    i.LastName  = $"{i.Id}";
                })
                                    .WriteTo(writer);

                await pipeline.ExecuteAsync();

                var totalStepsExpected = 6;
                Assert.True(2 * totalStepsExpected == loggerFactory.LoggedMessages.Count, string.Join("\r\n", loggerFactory.LoggedMessages.Select(m => m.Message))); //2 log messages for each step in the pipeline

                for (var i = 1; i <= totalStepsExpected; i++)
                {
                    var messagesForStep = loggerFactory.LoggedMessages.Where(m => m.Message.Contains($"step {i} of {totalStepsExpected}")).ToList();
                    Assert.True(messagesForStep.Count == 2, $"Found {messagesForStep.Count} messages for step {i}");

                    Assert.Equal(1, messagesForStep.Count(m => m.Message.Contains("Starting")));
                    Assert.Equal(1, messagesForStep.Count(m => m.Message.Contains("Completing")));
                }
            }
        }
        public async Task RunsAllStepsInMultiStagePipeline()
        {
            using (var writer = new TestBulkWriter <PipelineTestsOtherTestClass>())
            {
                var items = Enumerable
                            .Range(1, 1000)
                            .Select(i => new PipelineTestsMyTestClass {
                    Id = i, Name = "Bob"
                })
                            .ToAsyncEnumerable();

                var pipeline = EtlPipeline
                               .StartWith(items)
                               .Aggregate(f => f.Max(c => c.Id))
                               .Pivot(i =>
                {
                    var result = new List <PipelineTestsMyTestClass>();
                    for (var j = 1; j <= i; j++)
                    {
                        result.Add(new PipelineTestsMyTestClass {
                            Id = j, Name = $"Bob {j}"
                        });
                    }
                    return(result);
                })
                               .Project(i =>
                {
                    var nameParts = i.Name.Split(' ');
                    return(new PipelineTestsOtherTestClass {
                        Id = i.Id, FirstName = nameParts[0], LastName = nameParts[1]
                    });
                })
                               .TransformInPlace(i =>
                {
                    i.Id       -= 1;
                    i.FirstName = "Alice";
                    i.LastName  = $"{i.Id}";
                })
                               .WriteTo(writer);

                await pipeline.ExecuteAsync();

                Assert.Equal(1000, writer.ItemsWritten.Count);
                Assert.Equal(0, writer.ItemsWritten[0].Id);
                Assert.Equal("Alice", writer.ItemsWritten[0].FirstName);
                Assert.Equal("0", writer.ItemsWritten[0].LastName);
                Assert.Equal(999, writer.ItemsWritten[999].Id);
                Assert.Equal("Alice", writer.ItemsWritten[999].FirstName);
                Assert.Equal("999", writer.ItemsWritten[999].LastName);
            }
        }
        public async Task RunsAggregatorStep()
        {
            using (var writer = new TestBulkWriter <int>())
            {
                var items = Enumerable.Range(1, 1000).Select(i => new PipelineTestsMyTestClass {
                    Id = i, Name = "Bob"
                });
                var pipeline = EtlPipeline.StartWith(items)
                               .Aggregate(f => f.Sum(c => c.Id))
                               .WriteTo(writer);

                await pipeline.ExecuteAsync();

                Assert.Single(writer.ItemsWritten);
                Assert.Equal(Enumerable.Range(1, 1000).Sum(), writer.ItemsWritten[0]);
            }
        }