diff --git a/.github/workflows/continues_integration.yml b/.github/workflows/continues_integration.yml index ace20ba..ef274f8 100644 --- a/.github/workflows/continues_integration.yml +++ b/.github/workflows/continues_integration.yml @@ -26,4 +26,4 @@ jobs: - name: Build run: dotnet build Ghanavats.ResultPattern.sln --configuration Release - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test tests/Ghanavats.ResultPattern.Tests/Ghanavats.ResultPattern.Tests.csproj diff --git a/Directory.Packages.props b/Directory.Packages.props index 94f7a3d..cc32700 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,17 @@ - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/Ghanavats.ResultPattern.sln b/Ghanavats.ResultPattern.sln index 6bb330a..2170bc8 100644 --- a/Ghanavats.ResultPattern.sln +++ b/Ghanavats.ResultPattern.sln @@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ghanavats.ResultPattern.Tests", "tests\Ghanavats.ResultPattern.Tests\Ghanavats.ResultPattern.Tests.csproj", "{857C6761-2A3B-4919-8FA7-433E2B2157B4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,8 +25,13 @@ Global {654CA5E2-972C-4561-BA0E-B532E1327A15}.Debug|Any CPU.Build.0 = Debug|Any CPU {654CA5E2-972C-4561-BA0E-B532E1327A15}.Release|Any CPU.ActiveCfg = Release|Any CPU {654CA5E2-972C-4561-BA0E-B532E1327A15}.Release|Any CPU.Build.0 = Release|Any CPU + {857C6761-2A3B-4919-8FA7-433E2B2157B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {857C6761-2A3B-4919-8FA7-433E2B2157B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {857C6761-2A3B-4919-8FA7-433E2B2157B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {857C6761-2A3B-4919-8FA7-433E2B2157B4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {654CA5E2-972C-4561-BA0E-B532E1327A15} = {211150B0-9A81-462E-8D30-C00B45504AD6} + {857C6761-2A3B-4919-8FA7-433E2B2157B4} = {78FEF537-F8DF-4722-BEB6-091C57BA134A} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 8803b65..be5e6f8 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,15 @@ A Robust and Flexible Result Handling Framework for Modern Applications ## Overview -Ghanavats.ResultPattern is a comprehensive framework designed to bring consistency +Ghanavats.ResultPattern is a small/simple, unambitious framework designed to bring consistency and flexibility to result handling in your applications. Whether you need to return simple success/error outcomes or handle complex validation scenarios, this solution provides the tools you need to implement robust result patterns effortlessly. +The idea behind this was to learn the Result Pattern by implementing it in a good way. +The plan was not and is not to get all developers, worldwide, to adopt it. +Instead, to teach myself, and maybe if I am lucky, to show you what it may look like. + ## The solution includes 1. **Ghanavats.ResultPattern NuGet Package** diff --git a/src/Ghanavats.ResultPattern/Extensions/FluentValidationFailureExtension.cs b/src/Ghanavats.ResultPattern/Extensions/FluentValidationFailureExtension.cs index 486b25a..a2122f7 100644 --- a/src/Ghanavats.ResultPattern/Extensions/FluentValidationFailureExtension.cs +++ b/src/Ghanavats.ResultPattern/Extensions/FluentValidationFailureExtension.cs @@ -10,6 +10,11 @@ public static class FluentValidationFailureExtension { public static IEnumerable PopulateValidationErrors(this ValidationResult? input) { - return input is null ? [] : input.Errors.Select(x => new ValidationError(x.ErrorMessage, x.ErrorCode, (ValidationErrorType)x.Severity)); + return input is null + ? [] + : input.Errors + .Select(validationFailure => new ValidationError(validationFailure.ErrorMessage, + validationFailure.ErrorCode, + (ValidationErrorType)validationFailure.Severity)); } } diff --git a/src/Ghanavats.ResultPattern/Extensions/ResultExtensions.cs b/src/Ghanavats.ResultPattern/Extensions/ResultExtensions.cs new file mode 100644 index 0000000..5b26eeb --- /dev/null +++ b/src/Ghanavats.ResultPattern/Extensions/ResultExtensions.cs @@ -0,0 +1,53 @@ +using Ghanavats.ResultPattern.Enums; + +namespace Ghanavats.ResultPattern.Extensions; + +public static class ResultExtensions +{ + internal static IReadOnlyCollection GetMessages(this Result[] results, ResultStatus status, + bool includeValidationErrors = false) + { + switch (status) + { + case ResultStatus.Error: + return GetErrorMessages(); + case ResultStatus.Invalid: + return includeValidationErrors + ? GetValidationErrors() + : GetValidationMessages(); + case ResultStatus.Ok: + case ResultStatus.None: + case ResultStatus.NotFound: + throw new NotSupportedException("Not supported result status."); + default: + throw new InvalidOperationException("Operation not supported."); + } + + IReadOnlyCollection GetErrorMessages() + { + var resultErrors = results + .Where(result => result.Status == ResultStatus.Error + && result.ErrorMessages.Any()) + .SelectMany(error => error.ErrorMessages).ToList(); + + return resultErrors; + } + + IReadOnlyCollection GetValidationMessages() + { + return results + .Where(invalids => invalids.ValidationErrors.Any() + && invalids.Status == ResultStatus.Invalid) + .SelectMany(invalidMessages => invalidMessages.ValidationErrors + .Select(x => x.ErrorMessage)).ToList(); + } + + IReadOnlyCollection GetValidationErrors() + { + return results + .Where(invalids => invalids.ValidationErrors.Any() + && invalids.Status == ResultStatus.Invalid) + .SelectMany(invalidMessages => invalidMessages.ValidationErrors).ToList(); + } + } +} diff --git a/src/Ghanavats.ResultPattern/Ghanavats.ResultPattern.csproj b/src/Ghanavats.ResultPattern/Ghanavats.ResultPattern.csproj index 587de89..ef04473 100644 --- a/src/Ghanavats.ResultPattern/Ghanavats.ResultPattern.csproj +++ b/src/Ghanavats.ResultPattern/Ghanavats.ResultPattern.csproj @@ -12,7 +12,7 @@ result-pattern git https://github.com/ghanavat/ResultPattern - 1.0.2 + 1.1.0 README.md LICENSE diff --git a/src/Ghanavats.ResultPattern/Models/AggregateResultsModel.cs b/src/Ghanavats.ResultPattern/Models/AggregateResultsModel.cs new file mode 100644 index 0000000..3d9875a --- /dev/null +++ b/src/Ghanavats.ResultPattern/Models/AggregateResultsModel.cs @@ -0,0 +1,9 @@ +using Ghanavats.ResultPattern.Enums; + +namespace Ghanavats.ResultPattern.Models; + +public class AggregateResultsModel +{ + public ResultStatus Status { get; init; } + public IReadOnlyCollection Messages { get; init; } = []; +} diff --git a/src/Ghanavats.ResultPattern/Result.Void.cs b/src/Ghanavats.ResultPattern/Result.Void.cs index c31e0e6..3d6fa94 100644 --- a/src/Ghanavats.ResultPattern/Result.Void.cs +++ b/src/Ghanavats.ResultPattern/Result.Void.cs @@ -1,4 +1,6 @@ using Ghanavats.ResultPattern.Enums; +using Ghanavats.ResultPattern.Extensions; +using Ghanavats.ResultPattern.Models; namespace Ghanavats.ResultPattern; @@ -10,15 +12,19 @@ public class Result : Result { /// /// Private constructor that is used in this class. - /// No need to set Status here as its default id OK. + /// No need to set Status here as its default is OK. /// - private Result() { } + private Result() + { + } /// /// A constructor that accepts /// /// - private Result(ResultStatus status) : base(status) { } + private Result(ResultStatus status) : base(status) + { + } /// /// Represents a successful operation without a return type @@ -47,4 +53,30 @@ private Result(ResultStatus status) : base(status) { } { ErrorMessages = [errorMessage] }; + + /// + /// Represents the not found result for non-generic scenarios + /// + /// Result with NotFound status + public new static Result NotFound() => new(ResultStatus.NotFound); + + /// + /// To gather all non-success results of type Result (non-generic) into a single object. + /// + /// Indicates whether a full ValidationError collection should be aggregated. + /// Default is false. + /// Array of all Results (non-generic) + /// A Read-Only Collection of Aggregate Results Model + public static IReadOnlyCollection Aggregate(bool includeValidationErrors = false, + params Result[] results) + { + return results + .GroupBy(result => result.Status) + .Skip(results.Count(x => x.Status is ResultStatus.Ok or ResultStatus.NotFound)) + .Select(whatIWant => new AggregateResultsModel + { + Status = whatIWant.Key, + Messages = results.GetMessages(whatIWant.Key, includeValidationErrors) + }).ToList().AsReadOnly(); + } } diff --git a/src/Ghanavats.ResultPattern/Result.cs b/src/Ghanavats.ResultPattern/Result.cs index bdf4b6a..ac3fd86 100644 --- a/src/Ghanavats.ResultPattern/Result.cs +++ b/src/Ghanavats.ResultPattern/Result.cs @@ -1,6 +1,9 @@ -using System.Text.Json.Serialization; +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; using Ghanavats.ResultPattern.Enums; +[assembly: InternalsVisibleTo(assemblyName: "Ghanavats.ResultPattern.Tests")] + namespace Ghanavats.ResultPattern; /// @@ -10,6 +13,15 @@ namespace Ghanavats.ResultPattern; /// public class Result { + /// + /// A constructor that accepts . + /// + /// Constructor parameter of type + public Result(T data) + { + Data = data; + } + /// /// A constructor that accepts . /// It is used internally in this class and Result.Void @@ -19,24 +31,15 @@ internal Result(ResultStatus status) { Status = status; } - + /// /// Default protected constructor. /// /// - /// It is used in Result.Void to return an instance of Success status without needing to pass any extra types. + /// It is used in Result.Void to return an instance of + /// Success status without needing to pass any extra types. /// protected internal Result() { } - - /// - /// A constructor that accepts . - /// It is used internally by this class. - /// - /// Constructor parameter of type - private Result(T data) - { - Data = data; - } /// /// A constructor that accepts . @@ -44,8 +47,9 @@ private Result(T data) /// /// Constructor parameter of type /// Constructor parameter of type - private Result(T data, string successMessage) : this(data) + internal Result(T data, string successMessage) { + Data = data; SuccessMessage = successMessage; } @@ -62,7 +66,7 @@ private Result(T data, string successMessage) : this(data) public T? Data { get; set; } /// - /// Use this property to accurately determine the exact status of the Result + /// Use this property to accurately determine the exact Result status /// [JsonInclude] public ResultStatus Status { get; protected set; } = ResultStatus.Ok; @@ -137,7 +141,7 @@ public static Result Invalid(IEnumerable validationErrors) } /// - /// An operator to automatically convert the return type in a method to the type being returned + /// An operator to automatically convert the generic T type to Result of type T /// /// The return data public static implicit operator Result(T data) => new(data); diff --git a/tests/Ghanavats.ResultPattern.Tests/DummyData/DummyFeatureService.cs b/tests/Ghanavats.ResultPattern.Tests/DummyData/DummyFeatureService.cs new file mode 100644 index 0000000..d3cebbc --- /dev/null +++ b/tests/Ghanavats.ResultPattern.Tests/DummyData/DummyFeatureService.cs @@ -0,0 +1,34 @@ +namespace Ghanavats.ResultPattern.Tests.DummyData; + +public class DummyFeatureService +{ + public Result DoSomething() + { + var response = new DummyResponseModel + { + DummyFieldOne = "Some data" + }; + + return response; + } + + public DummyResponseModel DoSomethingImplicitConversionOfResultGenericToInnerType() + { + var response = new DummyResponseModel + { + DummyFieldOne = "Test Field Data" + }; + + return Result.Success(response); + } + + public Result DoSomethingImplicitConversionOfNonGenericResultToGenericResultWithDefault() + { + _ = new DummyResponseModel + { + DummyFieldOne = "DataNotGoingToBeUsed" + }; + + return Result.Success(); + } +} diff --git a/tests/Ghanavats.ResultPattern.Tests/DummyData/DummyResponseModel.cs b/tests/Ghanavats.ResultPattern.Tests/DummyData/DummyResponseModel.cs new file mode 100644 index 0000000..353f306 --- /dev/null +++ b/tests/Ghanavats.ResultPattern.Tests/DummyData/DummyResponseModel.cs @@ -0,0 +1,6 @@ +namespace Ghanavats.ResultPattern.Tests.DummyData; + +public class DummyResponseModel +{ + public string DummyFieldOne { get; set; } = string.Empty; +} diff --git a/tests/Ghanavats.ResultPattern.Tests/Ghanavats.ResultPattern.Tests.csproj b/tests/Ghanavats.ResultPattern.Tests/Ghanavats.ResultPattern.Tests.csproj new file mode 100644 index 0000000..72b3319 --- /dev/null +++ b/tests/Ghanavats.ResultPattern.Tests/Ghanavats.ResultPattern.Tests.csproj @@ -0,0 +1,24 @@ + + + + false + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Ghanavats.ResultPattern.Tests/ResultTests.cs b/tests/Ghanavats.ResultPattern.Tests/ResultTests.cs new file mode 100644 index 0000000..e0ca21a --- /dev/null +++ b/tests/Ghanavats.ResultPattern.Tests/ResultTests.cs @@ -0,0 +1,467 @@ +using FluentValidation; +using FluentValidation.Results; +using Ghanavats.ResultPattern.Enums; +using Ghanavats.ResultPattern.Extensions; +using Ghanavats.ResultPattern.Tests.DummyData; +using Shouldly; + +namespace Ghanavats.ResultPattern.Tests; + +public class ResultTests +{ + [Fact] + public void ResultConstructorWithData_ShouldBeInitialised_WithCorrectValueAndStatus() + { + //Arrange/Act + var sut = new Result(1234); + + //Assert + sut.ShouldNotBeNull(); + sut.Status.ShouldBe(ResultStatus.Ok); + } + + [Fact] + public void ResultConstructorWithStatus_ShouldBeInitialised_WithCorrectValueAndStatus() + { + //Arrange/Act + var sut = new Result(ResultStatus.Ok); + + //Assert + sut.ShouldNotBeNull(); + sut.IsSuccess.ShouldBeTrue(); + sut.Status.ShouldBe(ResultStatus.Ok); + } + + [Fact] + public void ResultSuccessWithDataAndSuccessMessage_ShouldCorrectlySetStatusWithData() + { + //arrange/act + var actual = Result.Success("SomeValue", "Successfully executed."); + + //assert + actual.ShouldNotBeNull(); + actual.ErrorMessages.ShouldBeEmpty(); + actual.Status.ShouldBe(ResultStatus.Ok); + actual.Data.ShouldNotBeNull(); + actual.Data.GetType().Name.ShouldBe("String"); + actual.IsSuccess.ShouldBeTrue(); + actual.SuccessMessage.ShouldBe("Successfully executed."); + } + + [Fact] + public void ResultSuccessWithData_ShouldCorrectlySetStatusWithData() + { + //arrange/act + var actual = Result.Success(1234); + + //assert + actual.ShouldNotBeNull(); + actual.ErrorMessages.ShouldBeEmpty(); + actual.Status.ShouldBe(ResultStatus.Ok); + actual.Data.ShouldBe(1234); + actual.IsSuccess.ShouldBeTrue(); + } + + [Fact] + public void ResultNonGenericSuccessWithDataAndSuccessMessage_ShouldCorrectlySetStatusWithData() + { + //arrange/act + var actual = Result.Success(); + + //assert + actual.ShouldNotBeNull(); + actual.ErrorMessages.ShouldBeEmpty(); + actual.Status.ShouldBe(ResultStatus.Ok); + actual.Data.ShouldBeNull(); + actual.IsSuccess.ShouldBeTrue(); + } + + [Fact] + public void ResultError_ShouldCorrectlySetStatusWithNullData() + { + //arrange/act + var actual = Result.Error("Something wrong happened."); + + //assert + actual.ShouldNotBeNull(); + actual.ErrorMessages.ShouldNotBeEmpty(); + actual.Status.ShouldBe(ResultStatus.Error); + actual.Data.ShouldBeNull(); + actual.IsSuccess.ShouldBeFalse(); + } + + [Fact] + public void ResultNonGenericInvalid_ShouldCorrectlySetStatusAndValidationErrors() + { + //arrange/act + var expectedValidationResult = new ValidationResult + { + Errors = [ + new ValidationFailure + { + ErrorMessage = "Test validation failure message.", + ErrorCode = "ErrorCodeTest", + PropertyName = "TestProperty", + Severity = Severity.Error + } + ] + }; + + var actual = Result.Invalid(expectedValidationResult.PopulateValidationErrors()); + + //assert + actual.ShouldNotBeNull(); + actual.ValidationErrors.ShouldNotBeEmpty(); + actual.ErrorMessages.ShouldBeEmpty(); + actual.Status.ShouldBe(ResultStatus.Invalid); + actual.Data.ShouldBeNull(); + actual.IsSuccess.ShouldBeFalse(); + } + + [Fact] + public void ResultInvalid_ShouldCorrectlySetStatusAndValidationErrors() + { + //arrange/act + var expectedValidationResult = new ValidationResult + { + Errors = [ + new ValidationFailure + { + ErrorMessage = "Test validation failure message.", + ErrorCode = "ErrorCodeTest", + PropertyName = "DummyFieldOne", + Severity = Severity.Error + } + ] + }; + + var actual = Result.Invalid(expectedValidationResult.PopulateValidationErrors()); + + //assert + actual.ShouldNotBeNull(); + actual.ValidationErrors.ShouldNotBeEmpty(); + actual.ErrorMessages.ShouldBeEmpty(); + actual.Status.ShouldBe(ResultStatus.Invalid); + actual.Data.ShouldBeNull(); + actual.IsSuccess.ShouldBeFalse(); + + foreach (var item in actual.ValidationErrors) + { + item.ErrorMessage.ShouldNotBeNullOrEmpty(); + item.ErrorCode.ShouldNotBeNullOrEmpty(); + } + } + + [Fact] + public void ResultNotFound_ShouldCorrectlySetStatus() + { + //arrange/act + var actual = Result.NotFound(); + + //assert + actual.ShouldNotBeNull(); + actual.Status.ShouldBe(ResultStatus.NotFound); + actual.Data.ShouldBeNull(); + actual.IsSuccess.ShouldBeFalse(); + } + + [Fact] + public void Result_ShouldAggregateAllResults_WhenItIsCalledWithMultipleResults_AndIncludeValidationErrorsIsFalse() + { + //arrange + var result1 = Result.Invalid([new ValidationError("Validation error Jan", "3434", ValidationErrorType.Error)]); + var result2 = Result.Invalid([new ValidationError("Validation error Feb", "5554", ValidationErrorType.Error)]); + var result3 = Result.Invalid([new ValidationError("Validation error March", "6678", ValidationErrorType.Error)]); + + var result4 = Result.Error("Failure Error 234"); + var result5 = Result.Error("Failure Error 2676"); + var result6 = Result.Error("Failure Error 3009"); + + //act + var actual = Result.Aggregate(false, result1, result2, result3, result4, result5, result6); + + //assert + actual.ShouldNotBeNull(); + actual.ShouldNotBeEmpty(); + actual.Count.ShouldBe(2); + + actual.ToList().Where(x => x.Status == ResultStatus.Error).ShouldNotBeEmpty(); + actual.ToList().Where(x => x.Status == ResultStatus.Invalid).ShouldNotBeEmpty(); + + foreach (var item in actual.ToList()) + { + item.Messages.ShouldNotBeEmpty(); + item.Messages.ShouldBeAssignableTo>(); + } + } + + [Fact] + public void Result_ShouldAggregateAllResults_WhenItIsCalledWithMultipleResults_AndIncludeValidationErrorsIsTrue() + { + //arrange + var result1 = Result.Invalid([new ValidationError("Validation error April", "666", ValidationErrorType.Error)]); + var result2 = Result.Invalid([new ValidationError("Validation error May", "777", ValidationErrorType.Error)]); + var result3 = Result.Invalid([new ValidationError("Validation error June", "888", ValidationErrorType.Error)]); + + var result4 = Result.Error("Failure Error 999"); + var result5 = Result.Error("Failure Error 101010"); + var result6 = Result.Error("Failure Error 111111"); + + //act + var actual = Result.Aggregate(true, result1, result2, result3, result4, result5, result6); + + //assert + actual.ShouldNotBeNull(); + actual.ShouldNotBeEmpty(); + actual.Count.ShouldBe(2); + + actual.ToList().Where(x => x.Status == ResultStatus.Error).ShouldNotBeEmpty(); + actual.ToList().Where(x => x.Status == ResultStatus.Invalid).ShouldNotBeEmpty(); + + var validationErrors = actual.Where(x => x.Status == ResultStatus.Invalid) + .SelectMany(y => y.Messages.Select(x => x)).ToList().AsReadOnly(); + + validationErrors.ShouldNotBeEmpty(); + + foreach (var item in validationErrors) + { + item.ShouldBeAssignableTo(); + + var validationError = item as ValidationError; + validationError.ShouldNotBeNull(); + validationError.ValidationErrorType.ShouldBe(ValidationErrorType.Error); + validationError.ErrorMessage.ShouldNotBeEmpty(); + validationError.ErrorMessage.ShouldContain("Validation error"); + } + + var errors = actual.Where(x => x.Status == ResultStatus.Error) + .SelectMany(y => y.Messages.Select(x => x)).ToList().AsReadOnly(); + + errors.ShouldNotBeEmpty(); + + foreach (var item in errors) + { + item.ShouldBeAssignableTo(); + + var errorMessage = item as string; + errorMessage.ShouldNotBeNull(); + } + } + + [Fact] + public void Result_ShouldIgnoreAggregatingNoneOkNotFoundStatuses() + { + //arrange + var result1 = Result.Success(); + var result2 = Result.NotFound(); + + //act + var action = Result.Aggregate(true, result1, result2); + + //assert + action.ShouldNotBeNull(); + action.ShouldBeEmpty(); + } + + [Fact] + public void Result_ShouldIgnoreAggregatingNoneOkNotFound_WhenMixedWithErrorStatusResults() + { + //arrange + var result1 = Result.Success(); + var result2 = Result.NotFound(); + var result3 = Result.Error("Something went wrong"); + + //act + //Action action = () => Result.Aggregate(true, result1, result2, result3); + var actual = Result.Aggregate(true, result1, result2, result3); + + //assert + actual.ShouldNotBeNull(); + actual.ShouldNotBeEmpty(); + actual.Count.ShouldBe(1); + actual.ToList()[0].Status.ShouldBe(ResultStatus.Error); + } + + [Fact] + public void Result_ImplicitOperator_ShouldCorrectlyConvertTheDataTypeToResultOfTypeImplicitly() + { + //arrange + var sut = new DummyFeatureService(); + + //act + var actual = sut.DoSomething(); + + //assert + actual.ShouldNotBeNull(); + actual.ShouldBeOfType>(); + actual.IsSuccess.ShouldBeTrue(); + actual.Data.ShouldNotBeNull(); + } + + [Fact] + public void Result_ImplicitOperator_ShouldCorrectlyConvertTheResultOfGenericTypeToTheInnerType() + { + //arrange + var sut = new DummyFeatureService(); + + //act + var actual = sut.DoSomethingImplicitConversionOfResultGenericToInnerType(); + + //assert + actual.ShouldNotBeNull(); + actual.ShouldBeOfType(); + } + + [Fact] + public void Result_ImplicitOperator_ShouldCorrectlyConvertNonGenericResultToGenericResultWithDefaultState() + { + //arrange + var sut = new DummyFeatureService(); + + //act + var actual = sut.DoSomethingImplicitConversionOfNonGenericResultToGenericResultWithDefault(); + + //assert + actual.ShouldNotBeNull(); + actual.ShouldBeOfType>(); + actual.IsSuccess.ShouldBeTrue(); + actual.Data.ShouldBeNull(); + actual.ErrorMessages.ShouldBeEmpty(); + actual.ValidationErrors.ShouldBeEmpty(); + actual.SuccessMessage.ShouldBeEmpty(); + actual.Status.ShouldBe(ResultStatus.Ok); + } + + // [Fact] + // public void ResultOfTypeString_ShouldAggregateAllResults_WhenItIsCalledWithMultipleResults_AndIncludeValidationErrorsIsFalse() + // { + // //arrange + // var result1 = Result.Invalid([new ValidationError("Validation error April", "666", ValidationErrorType.Error)]); + // var result2 = Result.Invalid([new ValidationError("Validation error May", "777", ValidationErrorType.Error)]); + // + // var result4 = Result.Error("Failure Error 999"); + // var result5 = Result.Error("Failure Error 101010"); + // + // //act + // var actual = Result.Aggregate(false, result1, result2, result4, result5); + // + // //assert + // var actualList = actual.ToList(); + // actualList.ShouldNotBeEmpty(); + // actualList.Where(x => x.Status == ResultStatus.Error).ShouldNotBeEmpty(); + // actualList.Where(x => x.Status == ResultStatus.Invalid).ShouldNotBeEmpty(); + // + // foreach (var item in actualList) + // { + // item.TypeName.ShouldNotBeNull(); + // item.TypeName.GetType().Name.ShouldBe("String"); + // } + // + // var validationErrors = actual.Where(x => x.Status == ResultStatus.Invalid) + // .SelectMany(y => y.Messages.Select(x => x)).ToList().AsReadOnly(); + // + // validationErrors.ShouldNotBeEmpty(); + // + // foreach (var item in validationErrors) + // { + // item.ShouldBeAssignableTo(); + // + // var validationError = item as string; + // validationError.ShouldNotBeNull(); + // validationError.ShouldContain("Validation error"); + // } + // + // var errors = actual.Where(x => x.Status == ResultStatus.Error) + // .SelectMany(y => y.Messages.Select(x => x)).ToList().AsReadOnly(); + // + // errors.ShouldNotBeEmpty(); + // + // foreach (var item in errors) + // { + // item.ShouldBeAssignableTo(); + // + // var errorMessage = item as string; + // errorMessage.ShouldNotBeNull(); + // } + // } + // + // [Fact] + // public void ResultOfComplexType_ShouldAggregateAllResults_WhenItIsCalledWithMultipleResults_AndIncludeValidationErrorsIsFalse() + // { + // //arrange + // var result1 = Result.Invalid([new ValidationError("Validation error June", "668787", ValidationErrorType.Error)]); + // var result2 = Result.Invalid([new ValidationError("Validation error July", "4847", ValidationErrorType.Error)]); + // + // var result4 = Result.Error("Failure Error 89393"); + // var result5 = Result.Error("Failure Error 111112"); + // + // //act + // var actual = Result.Aggregate(false, result1, result2, result4, result5); + // + // //assert + // var actualList = actual.ToList(); + // actualList.ShouldNotBeEmpty(); + // actualList.Where(x => x.Status == ResultStatus.Error).ShouldNotBeEmpty(); + // actualList.Where(x => x.Status == ResultStatus.Invalid).ShouldNotBeEmpty(); + // + // foreach (var item in actualList) + // { + // item.TypeName.ShouldNotBeNull(); + // item.TypeName.ShouldBe(nameof(DummyResponseModel)); + // } + // + // var validationErrors = actual.Where(x => x.Status == ResultStatus.Invalid) + // .SelectMany(y => y.Messages.Select(x => x)).ToList().AsReadOnly(); + // + // validationErrors.ShouldNotBeEmpty(); + // + // foreach (var item in validationErrors) + // { + // item.ShouldBeAssignableTo(); + // + // var validationError = item as string; + // validationError.ShouldNotBeNull(); + // validationError.ShouldContain("Validation error"); + // } + // + // var errors = actual.Where(x => x.Status == ResultStatus.Error) + // .SelectMany(y => y.Messages.Select(x => x)).ToList().AsReadOnly(); + // + // errors.ShouldNotBeEmpty(); + // + // foreach (var item in errors) + // { + // item.ShouldBeAssignableTo(); + // + // var errorMessage = item as string; + // errorMessage.ShouldNotBeNull(); + // } + // } + + // [Fact] + // public void Result_ShouldAggregateAllSuccessResults() + // { + // //arrange + // var result1 = Result.Success(); + // var result2 = Result.Success(new DummyResponseModel + // { + // DummyFieldOne = "SomeData" + // }, + // "Success message two."); + // + // //act + // var actual = Result.Aggregate(false, result1, result2); + // + // //assert + // var actualList = actual.ToList(); + // actualList.ShouldNotBeEmpty(); + // actualList.Where(x => x.Status == ResultStatus.Ok).ShouldNotBeEmpty(); + // + // foreach (var item in actualList) + // { + // item.TypeName.ShouldNotBeNull(); + // item.TypeName.ShouldBe(nameof(DummyResponseModel)); + // item.Messages.ShouldNotBeEmpty(); + // } + // } +}