diff --git a/src/OneBitSoftware.Utilities.OperationResult/OneBitSoftware.Utilities.OperationResult.csproj b/src/OneBitSoftware.Utilities.OperationResult/OneBitSoftware.Utilities.OperationResult.csproj index 6881c37..d3b9270 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/OneBitSoftware.Utilities.OperationResult.csproj +++ b/src/OneBitSoftware.Utilities.OperationResult/OneBitSoftware.Utilities.OperationResult.csproj @@ -32,7 +32,7 @@ False README.md OneBitSoftware; OperationResult; - 2.0.0 + 2.1.0 diff --git a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs index bc060b8..5682a1a 100644 --- a/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs +++ b/src/OneBitSoftware.Utilities.OperationResult/OperationResult.cs @@ -295,6 +295,17 @@ public OperationResult(TResult resultObject) : base() /// public TResult? ResultObject { get; set; } + /// + /// Appends error from to the current instance. + /// + /// The to append from. + /// The original with the appended messages from . + public new OperationResult AppendErrors(OperationResult? otherOperationResult) + { + base.AppendErrors(otherOperationResult); + return this; + } + /// /// This method will append an error with a specific `user-friendly` message to this operation result instance. /// diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs index b18b318..282ef61 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs @@ -177,4 +177,318 @@ public void AppendErrors_ShouldLogWhenCreatedWithNoLogger() // Assert Assert.Equal(1, testLogger.LogMessages.Count); } + + [Fact] + public void AppendErrors_NonGenericSource_To_GenericTarget_Retains_Generic_Type_And_MergesErrors() + { + // Arrange + var source = new OperationResult(); + source.AppendError("E1", 101, LogLevel.Warning, "D1"); + + var target = new OperationResult(); + target.AppendError("E0", 100, LogLevel.Information, "D0"); + + // Act + var returned = target.AppendErrors(source); + + // Assert + Assert.Same(target, returned); + Assert.True(target.Fail); + Assert.Equal(2, target.Errors.Count); + Assert.NotNull(target.Errors.Single(e => e is { Code: 100, Message: "E0" })); + Assert.NotNull(target.Errors.Single(e => e is { Code: 101, Message: "E1" })); + } + + [Fact] + public void AppendErrors_GenericSource_To_GenericTarget_Retains_Generic_Type_And_MergesErrors() + { + // Arrange + var source = new OperationResult(); + source.AppendError("E1", 101, LogLevel.Warning, "D1"); + + var target = new OperationResult(); + target.AppendError("E0", 100, LogLevel.Information, "D0"); + + // Act + var returned = target.AppendErrors(source); + + // Assert + Assert.Same(target, returned); + Assert.True(target.Fail); + Assert.Equal(2, target.Errors.Count); + Assert.NotNull(target.Errors.Single(e => e is { Code: 100, Message: "E0" })); + Assert.NotNull(target.Errors.Single(e => e is { Code: 101, Message: "E1" })); + } + + [Fact] + public void AppendErrors_WithNullSource_On_NonGenericTarget_Returns_Same_Instance_And_NoChange() + { + // Arrange + var target = new OperationResult(); + target.AppendError("E0", 100, LogLevel.Information, "D0"); + var beforeCount = target.Errors.Count; + + // Act + var returned = target.AppendErrors(null); + + // Assert + Assert.Same(target, returned); + Assert.Equal(beforeCount, target.Errors.Count); + } + + [Fact] + public void AppendErrors_WithNullSource_On_GenericTarget_Returns_Same_Instance_And_NoChange() + { + // Arrange + var target = new OperationResult(); + target.AppendError("E0", 100, LogLevel.Information, "D0"); + var beforeCount = target.Errors.Count; + + // Act + var returned = target.AppendErrors((OperationResult)null); + + // Assert + Assert.Same(target, returned); + Assert.Equal(beforeCount, target.Errors.Count); + } + + [Fact] + public void AppendErrors_PreservesResultObject_WhenMergingErrors() + { + // Arrange + var originalResult = new { Id = 42, Name = "Test" }; + var target = new OperationResult(originalResult); + + var source = new OperationResult(); + source.AppendError("Source error", 500); + + // Act + var returned = target.AppendErrors(source); + + // Assert + Assert.Same(target, returned); + Assert.Same(originalResult, target.ResultObject); + Assert.Single(target.Errors); + Assert.True(target.Fail); + } + + [Fact] + public void AppendErrors_MaintainsFluentChaining_WithGenericType() + { + // Arrange + var source1 = new OperationResult(); + source1.AppendError("E1", 1); + + var source2 = new OperationResult(); + source2.AppendError("E2", 2); + + var target = new OperationResult(); + + // Act - Chain multiple AppendErrors calls + var returned = target + .AppendErrors(source1) + .AppendErrors(source2) + .AppendError("E3", 3); + + // Assert + Assert.Same(target, returned); + Assert.IsType>(returned); + Assert.Equal(3, target.Errors.Count); + } + + [Fact] + public void AppendErrors_PreservesInitialException_FromSource() + { + // Arrange + var sourceException = new InvalidOperationException("Source exception"); + var source = new OperationResult(); + source.AppendException(sourceException, 999); + + var target = new OperationResult(); + + // Act + target.AppendErrors(source); + + // Assert + Assert.Null(target.InitialException); // Target doesn't inherit source's InitialException + Assert.Single(target.Errors); + Assert.Contains(sourceException.ToString(), target.Errors[0].Message); + } + + [Fact] + public void AppendErrors_HandlesSourceWithMultipleErrorTypes() + { + // Arrange + var source = new OperationResult>(); + source.AppendError("Standard error", 100); + source.AppendException(new ArgumentNullException("param"), 200); + source.AppendError("Another error", 300, LogLevel.Critical, "Critical details"); + + var target = new OperationResult(); + + // Act + target.AppendErrors(source); + + // Assert + Assert.Equal(3, target.Errors.Count); + Assert.NotNull(target.Errors.Single(e => e.Code == 100)); + Assert.NotNull(target.Errors.Single(e => e.Code == 200)); + Assert.NotNull(target.Errors.Single(e => e is { Code: 300, Details: "Critical details" })); + } + + [Fact] + public void AppendErrors_MergesSuccessMessages_AreNotTransferred() + { + // Arrange + var source = new OperationResult(); + source.AddSuccessMessage("Success from source"); + source.AppendError("But has error", 1); + + var target = new OperationResult(); + target.AddSuccessMessage("Success from target"); + + // Act + target.AppendErrors(source); + + // Assert - SuccessMessages are not merged in AppendErrors + Assert.Single(target.SuccessMessages); + Assert.Contains("Success from target", target.SuccessMessages); + } + + [Fact] + public void AppendErrors_EmptySource_DoesNotAffectTarget() + { + // Arrange + var source = new OperationResult(); + var target = new OperationResult(); + target.AppendError("Target error", 1); + + // Act + var returned = target.AppendErrors(source); + + // Assert + Assert.Same(target, returned); + Assert.Single(target.Errors); + Assert.True(target.Fail); + } + + [Fact] + public void AppendErrors_EmptyTarget_AcceptsSourceErrors() + { + // Arrange + var source = new OperationResult(); + source.AppendError("Source error", 100); + + var target = new OperationResult(); + + // Act + target.AppendErrors(source); + + // Assert + Assert.True(target.Fail); + Assert.Single(target.Errors); + Assert.Equal("Source error", target.Errors[0].Message); + } + + [Fact] + public void AppendErrors_WithDifferentGenericTypes_MaintainsTargetType() + { + // Arrange + var sourceInt = new OperationResult { ResultObject = 42 }; + sourceInt.AppendError("Int error", 1); + + var targetString = new OperationResult { ResultObject = "test" }; + + // Act + var returned = targetString.AppendErrors(sourceInt); + + // Assert + Assert.IsType>(returned); + Assert.Equal("test", targetString.ResultObject); + Assert.Single(targetString.Errors); + } + + [Fact] + public void AppendErrors_ChainedCalls_AccumulatesAllErrors() + { + // Arrange + var s1 = new OperationResult(); + s1.AppendError("E1", 1); + + var s2 = new OperationResult(); + s2.AppendError("E2", 2); + + var s3 = new OperationResult(); + s3.AppendError("E3", 3); + + var target = new OperationResult(); + + // Act + target.AppendErrors(s1).AppendErrors(s2).AppendErrors(s3); + + // Assert + Assert.Equal(3, target.Errors.Count); + Assert.Contains(target.Errors, e => e.Code == 1); + Assert.Contains(target.Errors, e => e.Code == 2); + Assert.Contains(target.Errors, e => e.Code == 3); + } + + [Fact] + public void AppendErrors_WithComplexGenericType_PreservesTypeIntegrity() + { + // Arrange + var complexObject = new Dictionary> + { + ["key1"] = [1, 2, 3] + }; + + var target = new OperationResult>>(complexObject); + var source = new OperationResult(); + source.AppendError("Complex type error", 999); + + // Act + var returned = target.AppendErrors(source); + + // Assert + Assert.Same(complexObject, target.ResultObject); + Assert.IsType>>>(returned); + Assert.Single(target.Errors); + } + + [Fact] + public void AppendErrors_ReturnsCorrectType_AfterMultipleOperations() + { + // Arrange + var target = new OperationResult { ResultObject = 100 }; + var source1 = new OperationResult(); + source1.AppendError("E1", 1); + + // Act + var step1 = target.AppendErrors(source1); + var step2 = step1.AppendError("E2", 2); + var step3 = step2.AppendException(new Exception("E3"), 3); + + // Assert + Assert.IsType>(step1); + Assert.IsType>(step2); + Assert.IsType>(step3); + Assert.Same(target, step3); + Assert.Equal(3, target.Errors.Count); + } + + [Fact] + public void AppendErrors_GenericTarget_ShouldLogWhenCreatedWithALogger() + { + // Arrange + var testLogger = new TestLogger(); + var operationResultNoLogger = new OperationResult(); + var operationResultWithLogger = new OperationResult(testLogger); + + // Act + operationResultNoLogger.AppendError("test"); + operationResultWithLogger.AppendErrors(operationResultNoLogger); + + // Assert + Assert.Single(testLogger.LogMessages); + } }