From 545967bce4ea4375b273a00b5c6742e8e80c71cb Mon Sep 17 00:00:00 2001 From: Svetlozar Stoykov Date: Mon, 17 Nov 2025 13:27:19 +0200 Subject: [PATCH 1/3] Add `AppendErrors` method to `OperationResult` and related tests --- .../OperationResult.cs | 11 +++ .../OperationResultAppendErrorsTests.cs | 74 +++++++++++++++++++ 2 files changed, 85 insertions(+) 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..8bfe134 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs @@ -177,4 +177,78 @@ 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); + } } From f9f064afabc2433300a64f540196f904f420ef6e Mon Sep 17 00:00:00 2001 From: Svetlozar Stoykov Date: Tue, 18 Nov 2025 10:45:00 +0200 Subject: [PATCH 2/3] Add extensive tests for `AppendErrors` method in `OperationResult` classes --- .../OperationResultAppendErrorsTests.cs | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs index 8bfe134..b00f9e7 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs @@ -251,4 +251,228 @@ public void AppendErrors_WithNullSource_On_GenericTarget_Returns_Same_Instance_A 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(); // No errors + 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); + } } From d08d4d9f2e5566d273f9f7fbfa1730097e5e35d7 Mon Sep 17 00:00:00 2001 From: Svetlozar Stoykov Date: Wed, 19 Nov 2025 10:44:07 +0200 Subject: [PATCH 3/3] Bump version to 2.1.0 and update OperationResult tests to use generic types --- ...tSoftware.Utilities.OperationResult.csproj | 2 +- .../OperationResultAppendErrorsTests.cs | 32 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) 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/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs index b00f9e7..282ef61 100644 --- a/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs +++ b/tests/OneBitSoftware.Utilities.OperationResultTests/OperationResultAppendErrorsTests.cs @@ -259,7 +259,7 @@ public void AppendErrors_PreservesResultObject_WhenMergingErrors() var originalResult = new { Id = 42, Name = "Test" }; var target = new OperationResult(originalResult); - var source = new OperationResult(); + var source = new OperationResult(); source.AppendError("Source error", 500); // Act @@ -276,7 +276,7 @@ public void AppendErrors_PreservesResultObject_WhenMergingErrors() public void AppendErrors_MaintainsFluentChaining_WithGenericType() { // Arrange - var source1 = new OperationResult(); + var source1 = new OperationResult(); source1.AppendError("E1", 1); var source2 = new OperationResult(); @@ -301,7 +301,7 @@ public void AppendErrors_PreservesInitialException_FromSource() { // Arrange var sourceException = new InvalidOperationException("Source exception"); - var source = new OperationResult(); + var source = new OperationResult(); source.AppendException(sourceException, 999); var target = new OperationResult(); @@ -340,7 +340,7 @@ public void AppendErrors_HandlesSourceWithMultipleErrorTypes() public void AppendErrors_MergesSuccessMessages_AreNotTransferred() { // Arrange - var source = new OperationResult(); + var source = new OperationResult(); source.AddSuccessMessage("Success from source"); source.AppendError("But has error", 1); @@ -359,7 +359,7 @@ public void AppendErrors_MergesSuccessMessages_AreNotTransferred() public void AppendErrors_EmptySource_DoesNotAffectTarget() { // Arrange - var source = new OperationResult(); // No errors + var source = new OperationResult(); var target = new OperationResult(); target.AppendError("Target error", 1); @@ -376,7 +376,7 @@ public void AppendErrors_EmptySource_DoesNotAffectTarget() public void AppendErrors_EmptyTarget_AcceptsSourceErrors() { // Arrange - var source = new OperationResult(); + var source = new OperationResult(); source.AppendError("Source error", 100); var target = new OperationResult(); @@ -443,7 +443,7 @@ public void AppendErrors_WithComplexGenericType_PreservesTypeIntegrity() }; var target = new OperationResult>>(complexObject); - var source = new OperationResult(); + var source = new OperationResult(); source.AppendError("Complex type error", 999); // Act @@ -460,7 +460,7 @@ public void AppendErrors_ReturnsCorrectType_AfterMultipleOperations() { // Arrange var target = new OperationResult { ResultObject = 100 }; - var source1 = new OperationResult(); + var source1 = new OperationResult(); source1.AppendError("E1", 1); // Act @@ -475,4 +475,20 @@ public void AppendErrors_ReturnsCorrectType_AfterMultipleOperations() 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); + } }