From 81a3cc866cb57e0b853d5b7b3d300504ab49d00d Mon Sep 17 00:00:00 2001 From: Kevin Dinh <92726228+Dindexx@users.noreply.github.com> Date: Wed, 18 Sep 2024 10:55:04 +0200 Subject: [PATCH] add validation tests (#182) Signed-off-by: Kevin --- global.json | 12 +- .../Validation/ApplyTests.cs | 34 +++ .../Validation/ValidationTests.cs | 244 ++++++++++++++++++ .../WalletFramework.Core.Tests.csproj | 29 +++ src/WalletFramework.sln | 7 + 5 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 src/WalletFramework.Core.Tests/Validation/ApplyTests.cs create mode 100644 src/WalletFramework.Core.Tests/Validation/ValidationTests.cs create mode 100644 src/WalletFramework.Core.Tests/WalletFramework.Core.Tests.csproj diff --git a/global.json b/global.json index 7ecd3c27..3fea262b 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ -{ - "sdk": { - "version": "6.0.0", - "rollForward": "latestFeature" - } -} +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestFeature" + } +} diff --git a/src/WalletFramework.Core.Tests/Validation/ApplyTests.cs b/src/WalletFramework.Core.Tests/Validation/ApplyTests.cs new file mode 100644 index 00000000..bc710ddc --- /dev/null +++ b/src/WalletFramework.Core.Tests/Validation/ApplyTests.cs @@ -0,0 +1,34 @@ +using FluentAssertions; +using WalletFramework.Core.Functional; +using Xunit; + +namespace WalletFramework.Core.Tests.Validation; + +public class ApplyTests +{ + private record Sample(int X1, int X2, int X3, int X4, int X5, int X6, int X7) + { + public int Sum() => X1 + X2 + X3 + X4 + X5 + X6 + X7; + } + + private static Sample CreateSample(int x1, int x2, int x3, int x4, int x5, int x6, int x7) => + new(x1, x2, x3, x4, x5, x6, x7); + + [Fact] + public void ApplyWorks() + { + const int expected = 1 + 2 + 3 + 4 + 5 + 6 + 7; + var func = ValidationFun.Valid(CreateSample); + + var sut = func + .Apply(1) + .Apply(2) + .Apply(3) + .Apply(4) + .Apply(5) + .Apply(6) + .Apply(7); + + sut.UnwrapOrThrow().Sum().Should().Be(expected); + } +} diff --git a/src/WalletFramework.Core.Tests/Validation/ValidationTests.cs b/src/WalletFramework.Core.Tests/Validation/ValidationTests.cs new file mode 100644 index 00000000..88081dbb --- /dev/null +++ b/src/WalletFramework.Core.Tests/Validation/ValidationTests.cs @@ -0,0 +1,244 @@ +using FluentAssertions; +using LanguageExt; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; +using Xunit; + +namespace WalletFramework.Core.Tests.Validation; + +public class ValidationTests +{ + [Fact] + public void AggregationWorks() + { + Validator greaterThanZero = i => i > 0 + ? ValidationFun.Valid(i) + : new SampleError(); + + Validator isEven = i => i % 2 == 0 + ? ValidationFun.Valid(i) + : new SampleError(); + + var sut = new List> { greaterThanZero, isEven }.AggregateValidators(); + + var one = sut(1); + var two = sut(2); + + one.IsSuccess.Should().BeFalse(); + two.IsSuccess.Should().BeTrue(); + } + + [Fact] + public void FallbackWorks() + { + var one = new SampleError().ToInvalid>(); + + var sut = one.Fallback(2); + + sut.UnwrapOrThrow().Should().Be(2); + } + + [Fact] + public void FirstValidWorks() + { + const string one = "1"; + const string nan = "NaN"; + Validator valid = str => + { + try + { + return ValidationFun.Valid(int.Parse(str)); + } + catch (Exception ) + { + return new SampleError(); + } + }; + Validator invalid = _ => new SampleError(); + + var sut = new List> { invalid, valid }.FirstValid(); + + var oneValid = sut(one); + var nanInvalid = sut(nan); + + oneValid.UnwrapOrThrow().Should().Be(1); + nanInvalid.Match( + i => Assert.Fail("Validation must fail"), + errors => + { + errors.Should().AllBeOfType>(); + errors.Should().ContainSingle(); + } + ); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void MatchWorks(bool valid) + { + Validation validation = valid + ? 1 + : new SampleError(); + + validation.Match( + _ => valid.Should().BeTrue(), + errors => + { + valid.Should().BeFalse(); + errors.Should().AllBeOfType(); + errors.Should().ContainSingle(); + }); + } + + [Fact] + public void OnSuccessWorks() + { + var one = ValidationFun.Valid("1"); + + var sut = one.OnSuccess(int.Parse); + + sut.UnwrapOrThrow().Should().Be(1); + } + + [Fact] + public async Task OnSuccessAsyncWorks() + { + var one = ValidationFun.Valid("1").AsTask(); + + var sut = await one.OnSuccess(int.Parse); + + sut.UnwrapOrThrow().Should().Be(1); + } + + [Fact] + public void SelectManyWorks() + { + var one = ValidationFun.Valid("1"); + + var sut = one.SelectMany( + _ => ValidationFun.Valid(1), + (e1, e2) => int.Parse(e1) + e2 + ); + + sut.UnwrapOrThrow().Should().Be(2); + } + + [Fact] + public void SelectWorks() + { + var one = ValidationFun.Valid("1"); + + var sut = one.Select(int.Parse); + + sut.Match( + i => i.Should().Be(1), + _ => Assert.Fail("Validation must not fail")); + } + + [Fact] + public void TraverseAllWorks() + { + var validStrs = new List + { + "1", + "2", + "3" + }; + + var invalidStrs = new List + { + "1", + "2", + "Three" + }; + + var sutValid = validStrs.TraverseAll(s => + { + try + { + return ValidationFun.Valid(int.Parse(s)); + } + catch (Exception) + { + return new SampleError(); + } + }); + + var sutInvalid = invalidStrs.TraverseAll(str => + { + try + { + return ValidationFun.Valid(int.Parse(str)); + } + catch (Exception) + { + return new SampleError(); + } + }); + + sutValid.IsSuccess.Should().BeTrue(); + sutInvalid.IsSuccess.Should().BeFalse(); + } + + [Fact] + public void TraverseAnyWorks() + { + var strs = new List + { + "One", + "2", + "Three" + }; + + var sut = strs.TraverseAny(str => + { + try + { + return ValidationFun.Valid(int.Parse(str)); + } + catch (Exception) + { + return new SampleError(); + } + }); + + sut.Match( + ints => + { + var list = ints.ToList(); + + list.Should().ContainSingle(); + list.First().Should().Be(2); + }, + errors => + { + Assert.Fail("Validation must not fail"); + errors.Should().ContainSingle(); + errors.Should().AllBeOfType(); + } + ); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void UnwrapOrThrowWorks(bool valid) + { + Validation validation = valid + ? 1 + : new SampleError(); + + try + { + validation.UnwrapOrThrow(); + valid.Should().BeTrue(); + } + catch (Exception) + { + valid.Should().BeFalse(); + } + } + + private record SampleError() : Error("This is sample error for testing"); +} diff --git a/src/WalletFramework.Core.Tests/WalletFramework.Core.Tests.csproj b/src/WalletFramework.Core.Tests/WalletFramework.Core.Tests.csproj new file mode 100644 index 00000000..aa465159 --- /dev/null +++ b/src/WalletFramework.Core.Tests/WalletFramework.Core.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/WalletFramework.sln b/src/WalletFramework.sln index 42a3c01b..0c39a233 100644 --- a/src/WalletFramework.sln +++ b/src/WalletFramework.sln @@ -53,6 +53,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mdoc", "Mdoc", "{A1DD69B3-D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.Integration.Tests", "..\test\WalletFramework.Integration.Tests\WalletFramework.Integration.Tests\WalletFramework.Integration.Tests.csproj", "{70DB749B-255A-4B71-8B76-BAD6B091DA7C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.Core.Tests", "WalletFramework.Core.Tests\WalletFramework.Core.Tests.csproj", "{93B3ED00-4158-4F79-9532-D3E940630A8A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -135,6 +137,10 @@ Global {70DB749B-255A-4B71-8B76-BAD6B091DA7C}.Debug|Any CPU.Build.0 = Debug|Any CPU {70DB749B-255A-4B71-8B76-BAD6B091DA7C}.Release|Any CPU.ActiveCfg = Release|Any CPU {70DB749B-255A-4B71-8B76-BAD6B091DA7C}.Release|Any CPU.Build.0 = Release|Any CPU + {93B3ED00-4158-4F79-9532-D3E940630A8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {93B3ED00-4158-4F79-9532-D3E940630A8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {93B3ED00-4158-4F79-9532-D3E940630A8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {93B3ED00-4158-4F79-9532-D3E940630A8A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -160,6 +166,7 @@ Global {F6B3A24B-CDA2-4CC1-9F68-380203355099} = {873772C5-60B9-442B-B06E-C279919B963C} {0EDD27CB-967F-4451-81AE-309E7F534F1C} = {A1DD69B3-DC35-43CF-AE14-D751722F074A} {70DB749B-255A-4B71-8B76-BAD6B091DA7C} = {02ADBA96-A50C-44F0-A9D9-FA0629AA2DF4} + {93B3ED00-4158-4F79-9532-D3E940630A8A} = {02ADBA96-A50C-44F0-A9D9-FA0629AA2DF4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4FFA80F9-ADC6-40DB-BBD1-A522B8A68560}