using System.Data; using JdeScoping.DataSync.Etl.Contracts; using JdeScoping.DataSync.Etl.Pipeline; using JdeScoping.DataSync.Etl.Results; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using NSubstitute.ExceptionExtensions; namespace JdeScoping.DataSync.Tests.Etl.Pipeline; public class EtlPipelineTests { [Fact] public async Task ExecuteAsync_SuccessfulPipeline_ReturnsSuccessResult() { var source = CreateMockSource(); var destination = CreateMockDestination(100); var pipeline = new EtlPipelineBuilder() .WithName("TestPipeline") .WithSource(source) .WithDestination(destination) .WithLogger(NullLogger.Instance) .Build(); var result = await pipeline.ExecuteAsync(); Assert.True(result.Success); Assert.Equal(100, result.TotalRows); Assert.Null(result.Error); } [Fact] public async Task ExecuteAsync_WithPreScript_RunsScriptBeforeDestination() { var callOrder = new List(); var source = CreateMockSource(); var destination = CreateMockDestination(100); destination.When(d => d.WriteAsync(Arg.Any(), Arg.Any())) .Do(_ => callOrder.Add("destination")); var preScript = Substitute.For(); preScript.ScriptName.Returns("PreScript"); preScript.When(s => s.ExecuteAsync(Arg.Any())) .Do(_ => callOrder.Add("prescript")); var pipeline = new EtlPipelineBuilder() .WithName("TestPipeline") .WithSource(source) .WithDestination(destination) .WithPreScript(preScript) .WithLogger(NullLogger.Instance) .Build(); await pipeline.ExecuteAsync(); Assert.Equal(new[] { "prescript", "destination" }, callOrder); } [Fact] public async Task ExecuteAsync_DestinationFails_ReturnsFailedResult() { var source = CreateMockSource(); var destination = Substitute.For(); destination.DestinationName.Returns("FailingDest"); destination.WriteAsync(Arg.Any(), Arg.Any()) .ThrowsAsync(new InvalidOperationException("Destination failed")); var pipeline = new EtlPipelineBuilder() .WithName("TestPipeline") .WithSource(source) .WithDestination(destination) .WithLogger(NullLogger.Instance) .Build(); var result = await pipeline.ExecuteAsync(); Assert.False(result.Success); Assert.NotNull(result.Error); Assert.IsType(result.Error); } [Fact] public async Task ExecuteAsync_TracksStepResults() { var source = CreateMockSource(); var destination = CreateMockDestination(100); var pipeline = new EtlPipelineBuilder() .WithName("TestPipeline") .WithSource(source) .WithDestination(destination) .WithLogger(NullLogger.Instance) .Build(); var result = await pipeline.ExecuteAsync(); Assert.Equal(2, result.Steps.Count); Assert.Equal("Source", result.Steps[0].StepType); Assert.Equal("Destination", result.Steps[1].StepType); } [Fact] public void Build_WithoutSource_ThrowsInvalidOperationException() { var destination = CreateMockDestination(100); var builder = new EtlPipelineBuilder() .WithName("TestPipeline") .WithDestination(destination); Assert.Throws(() => builder.Build()); } [Fact] public void Build_WithoutDestination_ThrowsInvalidOperationException() { var source = CreateMockSource(); var builder = new EtlPipelineBuilder() .WithName("TestPipeline") .WithSource(source); Assert.Throws(() => builder.Build()); } private static IImportSource CreateMockSource() { var reader = Substitute.For(); reader.Read().Returns(false); reader.FieldCount.Returns(0); var source = Substitute.For(); source.SourceName.Returns("MockSource"); source.ReadDataAsync(Arg.Any()).Returns(Task.FromResult(reader)); return source; } private static IImportDestination CreateMockDestination(long rows) { var destination = Substitute.For(); destination.DestinationName.Returns("MockDestination"); destination.WriteAsync(Arg.Any(), Arg.Any()) .Returns(Task.FromResult(new DestinationResult(rows, 1, TimeSpan.FromSeconds(1)))); return destination; } }