diff --git a/NEW/src/JdeScoping.DataSync/Etl/Results/DestinationResult.cs b/NEW/src/JdeScoping.DataSync/Etl/Results/DestinationResult.cs new file mode 100644 index 0000000..5916fd0 --- /dev/null +++ b/NEW/src/JdeScoping.DataSync/Etl/Results/DestinationResult.cs @@ -0,0 +1,12 @@ +namespace JdeScoping.DataSync.Etl.Results; + +/// +/// Represents the result of writing data to a destination. +/// +/// The number of rows successfully written to the destination. +/// The number of batches used to write the data. +/// The total time taken to write all batches. +public record DestinationResult( + long RowsProcessed, + int BatchCount, + TimeSpan Elapsed); diff --git a/NEW/src/JdeScoping.DataSync/Etl/Results/PipelineResult.cs b/NEW/src/JdeScoping.DataSync/Etl/Results/PipelineResult.cs new file mode 100644 index 0000000..9085fe5 --- /dev/null +++ b/NEW/src/JdeScoping.DataSync/Etl/Results/PipelineResult.cs @@ -0,0 +1,38 @@ +namespace JdeScoping.DataSync.Etl.Results; + +/// +/// Represents the complete result of an ETL pipeline execution. +/// +/// Indicates whether the pipeline completed successfully. +/// The total number of rows processed by the pipeline. +/// The total time taken to execute the pipeline. +/// The results of each step executed in the pipeline. +/// The exception that caused the pipeline to fail, if any. +public record PipelineResult( + bool Success, + long TotalRows, + TimeSpan Elapsed, + IReadOnlyList Steps, + Exception? Error = null) +{ + /// + /// Creates a successful pipeline result. + /// + /// The total number of rows processed. + /// The total execution time. + /// The results of each step. + /// A PipelineResult indicating success. + public static PipelineResult Succeeded(long totalRows, TimeSpan elapsed, IReadOnlyList steps) + => new(true, totalRows, elapsed, steps); + + /// + /// Creates a failed pipeline result. + /// + /// The number of rows processed before failure. + /// The total execution time including failure. + /// The results of steps executed before failure. + /// The exception that caused the failure. + /// A PipelineResult indicating failure with error details. + public static PipelineResult Failed(long totalRows, TimeSpan elapsed, IReadOnlyList steps, Exception error) + => new(false, totalRows, elapsed, steps, error); +} diff --git a/NEW/src/JdeScoping.DataSync/Etl/Results/StepResult.cs b/NEW/src/JdeScoping.DataSync/Etl/Results/StepResult.cs new file mode 100644 index 0000000..794b32e --- /dev/null +++ b/NEW/src/JdeScoping.DataSync/Etl/Results/StepResult.cs @@ -0,0 +1,14 @@ +namespace JdeScoping.DataSync.Etl.Results; + +/// +/// Represents the result of a single pipeline step execution. +/// +/// The name of the step that was executed. +/// The type of step (e.g., "Transform", "Load"). +/// The number of rows affected by this step. +/// The time taken to execute this step. +public record StepResult( + string StepName, + string StepType, + long RowsAffected, + TimeSpan Elapsed); diff --git a/NEW/tests/JdeScoping.DataSync.Tests/Etl/Results/PipelineResultTests.cs b/NEW/tests/JdeScoping.DataSync.Tests/Etl/Results/PipelineResultTests.cs new file mode 100644 index 0000000..55a474b --- /dev/null +++ b/NEW/tests/JdeScoping.DataSync.Tests/Etl/Results/PipelineResultTests.cs @@ -0,0 +1,149 @@ +using JdeScoping.DataSync.Etl.Results; +using Shouldly; + +namespace JdeScoping.DataSync.Tests.Etl.Results; + +public class PipelineResultTests +{ + [Fact] + public void Succeeded_Creates_Result_With_Success_True() + { + // Arrange + var steps = new List + { + new("ExtractStep", "Extract", 100, TimeSpan.FromSeconds(1)), + new("TransformStep", "Transform", 100, TimeSpan.FromSeconds(2)), + new("LoadStep", "Load", 100, TimeSpan.FromSeconds(3)) + }; + var elapsed = TimeSpan.FromSeconds(6); + + // Act + var result = PipelineResult.Succeeded(100, elapsed, steps); + + // Assert + result.Success.ShouldBeTrue(); + result.TotalRows.ShouldBe(100); + result.Elapsed.ShouldBe(elapsed); + result.Steps.ShouldBe(steps); + result.Error.ShouldBeNull(); + } + + [Fact] + public void Succeeded_With_Empty_Steps_Creates_Valid_Result() + { + // Arrange + var steps = Array.Empty(); + var elapsed = TimeSpan.Zero; + + // Act + var result = PipelineResult.Succeeded(0, elapsed, steps); + + // Assert + result.Success.ShouldBeTrue(); + result.TotalRows.ShouldBe(0); + result.Elapsed.ShouldBe(TimeSpan.Zero); + result.Steps.ShouldBeEmpty(); + result.Error.ShouldBeNull(); + } + + [Fact] + public void Failed_Creates_Result_With_Success_False() + { + // Arrange + var steps = new List + { + new("ExtractStep", "Extract", 50, TimeSpan.FromSeconds(1)) + }; + var elapsed = TimeSpan.FromSeconds(1); + var error = new InvalidOperationException("Pipeline failed during transform"); + + // Act + var result = PipelineResult.Failed(50, elapsed, steps, error); + + // Assert + result.Success.ShouldBeFalse(); + result.TotalRows.ShouldBe(50); + result.Elapsed.ShouldBe(elapsed); + result.Steps.ShouldBe(steps); + result.Error.ShouldBe(error); + } + + [Fact] + public void Failed_Preserves_Error_Details() + { + // Arrange + var innerException = new TimeoutException("Database timeout"); + var error = new InvalidOperationException("Pipeline failed", innerException); + var steps = Array.Empty(); + + // Act + var result = PipelineResult.Failed(0, TimeSpan.FromSeconds(30), steps, error); + + // Assert + result.Error.ShouldNotBeNull(); + result.Error.Message.ShouldBe("Pipeline failed"); + result.Error.InnerException.ShouldBe(innerException); + } + + [Fact] + public void StepResult_Stores_All_Properties_Correctly() + { + // Arrange & Act + var stepResult = new StepResult("MyStep", "Transform", 500, TimeSpan.FromMilliseconds(250)); + + // Assert + stepResult.StepName.ShouldBe("MyStep"); + stepResult.StepType.ShouldBe("Transform"); + stepResult.RowsAffected.ShouldBe(500); + stepResult.Elapsed.ShouldBe(TimeSpan.FromMilliseconds(250)); + } + + [Fact] + public void DestinationResult_Stores_All_Properties_Correctly() + { + // Arrange & Act + var destResult = new DestinationResult(1000, 10, TimeSpan.FromSeconds(5)); + + // Assert + destResult.RowsProcessed.ShouldBe(1000); + destResult.BatchCount.ShouldBe(10); + destResult.Elapsed.ShouldBe(TimeSpan.FromSeconds(5)); + } + + [Fact] + public void PipelineResult_Records_Are_Equal_When_Properties_Match() + { + // Arrange + var steps = new List { new("Step1", "Extract", 100, TimeSpan.FromSeconds(1)) }; + var elapsed = TimeSpan.FromSeconds(1); + + // Act + var result1 = PipelineResult.Succeeded(100, elapsed, steps); + var result2 = PipelineResult.Succeeded(100, elapsed, steps); + + // Assert + result1.ShouldBe(result2); + } + + [Fact] + public void StepResult_Records_Are_Equal_When_Properties_Match() + { + // Arrange & Act + var step1 = new StepResult("Step", "Load", 100, TimeSpan.FromSeconds(1)); + var step2 = new StepResult("Step", "Load", 100, TimeSpan.FromSeconds(1)); + + // Assert + step1.ShouldBe(step2); + } + + [Fact] + public void DestinationResult_Records_Are_Equal_When_Properties_Match() + { + // Arrange & Act + var dest1 = new DestinationResult(500, 5, TimeSpan.FromSeconds(2)); + var dest2 = new DestinationResult(500, 5, TimeSpan.FromSeconds(2)); + + // Assert + dest1.ShouldBe(dest2); + } +}