diff --git a/CHANGELOG.md b/CHANGELOG.md index 740112a..dce46f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## 0.15.0 + +- Introduced validation failure for `null` root instances instead of throwing FluentValidation exceptions. +- Introduce abstract `ValidationProfile` class. +- Added an optional `Action` `onSuccessValidation` parameter to `ExpressValidatorBuilder.AddFunc` for handling successful validation of `Func` result. +- Refactor `ExpressValidatorBuilder.Build` to remove dependency on `ExpressValidator`. +- Deprecate the `ExpressValidator` class. +- Add internal `PropertyValidationProcessor` class. +- Switch to using `PropertyValidationProcessor` in `ExpressPropertyValidator` and `ExpressPropertyValidator`. +- Refactor `ExpressPropertyValidator` to set `IsAsync` in the constructor. +- Add internal utility `TypeHelper` class with `IsNull` method and use it in `TypeValidatorBase`. +- Introduce internal utility `TypeTraits` class. +- Update to FluentValidation 12.1.1. +- Add internal static `ValidationFallbackProvider` class representing validation failure for a `null` instance. +- Add ExpressValidator.Tests.Net8.csproj for testing ExpressValidator when TargetFramework is net8.0. +- Update tests to System.ValueTuple 4.6.2. +- Improve test coverage for null handling. +- Update NuGet.md and README.md. + + ## 0.12.2 - Move the instance field `TypeValidatorBase._shouldBeComparedToNull` to a static readonly field (renamed to `_canBeNull`) to cache the reflection result per `TypeValidatorBase` type and eliminate redundant per-instance evaluations. diff --git a/ExpressValidator.sln b/ExpressValidator.sln index 8f16c88..fb22c39 100644 --- a/ExpressValidator.sln +++ b/ExpressValidator.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressValidator.Tests", "t EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "bench\Benchmark\Benchmark.csproj", "{163D81E7-128E-431F-B827-F2AA2BD1D077}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressValidator.Tests.Net8", "tests\ExpressValidator.Tests.Net8\ExpressValidator.Tests.Net8.csproj", "{C74789B4-A028-45FC-9771-F5848491AB89}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {163D81E7-128E-431F-B827-F2AA2BD1D077}.Debug|Any CPU.Build.0 = Debug|Any CPU {163D81E7-128E-431F-B827-F2AA2BD1D077}.Release|Any CPU.ActiveCfg = Release|Any CPU {163D81E7-128E-431F-B827-F2AA2BD1D077}.Release|Any CPU.Build.0 = Release|Any CPU + {C74789B4-A028-45FC-9771-F5848491AB89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C74789B4-A028-45FC-9771-F5848491AB89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C74789B4-A028-45FC-9771-F5848491AB89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C74789B4-A028-45FC-9771-F5848491AB89}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index 9e2f329..9bd4617 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ ExpressValidator is a library that provides the ability to validate objects usin - Supports adding a property or field for validation. - Verifies that a property expression is a property and a field expression is a field, and throws `ArgumentException` if it is not. - Supports adding a `Func` that provides a value for validation. -- Provides quick and easy validation using `QuickValidator`, with built-in tolerance for `null` values. +- Built-in `null` tolerance - `null` root instances fail validation instead of throwing exceptions. +- Quick and easy validation with `QuickValidator`, with robust support for `null` values. - Supports asynchronous validation. - Targets .NET Standard 2.0+ diff --git a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md index f051f0b..98c9a85 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md +++ b/src/ExpressValidator.Extensions.DependencyInjection/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.4.0 + +- Introduced class-based validation configuration with dedicated configurator classes inheriting from `ValidatorConfigurator` and registered through `AddExpressValidation`. +- Update to ExpressValidator 0.12.2 and FluentValidation 12.1.0. +- Update Microsoft nuget packages. +- Edit NuGet README. +- Edit README.md. +- Add Shared.csproj to the ExpressValidator.Extensions.DependencyInjection.Sample.sln solution. +- Split sample project into multiple projects illustrating README-described features. + + ## 0.3.12 - Support .NET 8.0 and FluentValidation 12.0.0. diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj index c561f19..2467860 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj +++ b/src/ExpressValidator.Extensions.DependencyInjection/ExpressValidator.Extensions.DependencyInjection.csproj @@ -3,7 +3,7 @@ netstandard2.0;net8.0 true - 0.3.12 + 0.4.0 true Andrey Kolesnichenko MIT @@ -15,7 +15,7 @@ FluentValidation Validation DependencyInjection The ExpressValidator.Extensions.DependencyInjection package extends ExpressValidator to provide integration with Microsoft Dependency Injection. Copyright 2024 Andrey Kolesnichenko - 0.3.12.0 + 0.4.0.0 diff --git a/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs b/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs index e4bb06d..22a56e2 100644 --- a/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs +++ b/src/ExpressValidator.Extensions.DependencyInjection/ProxyValidator.cs @@ -1,15 +1,12 @@ using FluentValidation.Results; using Microsoft.Extensions.DependencyInjection; using System; -using System.Collections.Generic; -using System.Data; -using System.Text; using System.Threading; using System.Threading.Tasks; namespace ExpressValidator.Extensions.DependencyInjection { - internal class ProxyValidator : IExpressValidator + internal class ProxyValidator : IExpressValidator { private readonly IExpressValidator _innerValidator; public ProxyValidator(IServiceProvider serviceProvider) diff --git a/src/ExpressValidator/ExpressValidator.TOptions.cs b/src/ExpressValidator/ExpressValidator.TOptions.cs index 93fc22d..e94984f 100644 --- a/src/ExpressValidator/ExpressValidator.TOptions.cs +++ b/src/ExpressValidator/ExpressValidator.TOptions.cs @@ -12,20 +12,24 @@ namespace ExpressValidator /// /// /// +#pragma warning disable S1133 // Deprecated code should be removed + [Obsolete("The ExpressValidator class is obsolete and will be removed in a future version.")] +#pragma warning restore S1133 // Deprecated code should be removed public class ExpressValidator : IExpressValidator { - private readonly IEnumerable> _validators; private readonly OnFirstPropertyValidatorFailed _validationMode; + private readonly IEnumerable> _validators; internal ExpressValidator(TOptions options, IEnumerable> validators, OnFirstPropertyValidatorFailed validationMode) { - _validators = validators; _validationMode = validationMode; - foreach (var validator in _validators) + foreach (var validator in validators) { validator.ApplyOptions(options); } + + _validators = validators; } public ValidationResult Validate(TObj obj) diff --git a/src/ExpressValidator/ExpressValidator.cs b/src/ExpressValidator/ExpressValidator.cs index 29e7897..80cc1a2 100644 --- a/src/ExpressValidator/ExpressValidator.cs +++ b/src/ExpressValidator/ExpressValidator.cs @@ -27,11 +27,19 @@ public ValidationResult Validate(TObj obj) { throw new InvalidOperationException($"Object validator has a property or field with asynchronous validation rules. Please use {nameof(ValidateAsync)} method."); } + if (TypeHelper.IsNull(obj)) + { + return ValidationFallbackProvider.GetNullFailure(); + } return _validationMode == OnFirstPropertyValidatorFailed.Break ? ValidateWithBreak(obj) : ValidateWithContinue(obj); } public Task ValidateAsync(TObj obj, CancellationToken token = default) { + if (TypeHelper.IsNull(obj)) + { + return Task.FromResult(ValidationFallbackProvider.GetNullFailure()); + } return _validationMode == OnFirstPropertyValidatorFailed.Break ? ValidateWithBreakAsync(obj, token) : ValidateWithContinueAsync(obj, token); } diff --git a/src/ExpressValidator/ExpressValidator.csproj b/src/ExpressValidator/ExpressValidator.csproj index 1aa3f89..3f8ecc6 100644 --- a/src/ExpressValidator/ExpressValidator.csproj +++ b/src/ExpressValidator/ExpressValidator.csproj @@ -3,7 +3,7 @@ netstandard2.0;net8.0 true - 0.12.2 + 0.15.0 true Andrey Kolesnichenko ExpressValidator is a library that provides the ability to validate objects using the FluentValidation library, but without object inheritance from `AbstractValidator`. @@ -15,7 +15,7 @@ ExpressValidator.png NuGet.md - 0.12.2.0 + 0.15.0.0 0.0.0.0 @@ -24,7 +24,7 @@ - + @@ -51,6 +51,12 @@ + + + <_Parameter1>ExpressValidator.Tests.Net8 + + + True diff --git a/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs b/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs index 8a9faa0..afb8cb4 100644 --- a/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs +++ b/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.TOptions.cs @@ -17,11 +17,16 @@ internal class ExpressPropertyValidator : IExpressPropertyVal private Action> _action; - public ExpressPropertyValidator(Func propertyFunc, string propName, bool isAsync) + private readonly Action _onSuccessValidation; + + private PropertyValidationProcessor _validationProcessor; + + public ExpressPropertyValidator(Func propertyFunc, string propName, bool isAsync, Action onSuccessValidation = null) { _propertyFunc = propertyFunc; _propName = propName; IsAsync = isAsync; + _onSuccessValidation = onSuccessValidation; } public void SetValidation(Action> action) @@ -31,16 +36,12 @@ public void SetValidation(Action> action) public Task<(bool IsValid, List Failures)> ValidateAsync(TObj obj, CancellationToken token = default) { - return _typeValidator.ValidateExAsync(_propertyFunc(obj), token); + return _validationProcessor.ValidateAsync(obj, token); } public (bool IsValid, List Failures) Validate(TObj obj) { - if (IsAsync) - { - throw new InvalidOperationException(); - } - return _typeValidator.ValidateEx(_propertyFunc(obj)); + return _validationProcessor.Validate(obj); } public void ApplyOptions(TOptions options) @@ -60,6 +61,7 @@ private void SetTypeValidator() _typeValidator = new TypeValidator(); } _typeValidator.SetValidation(_action, _propName); + _validationProcessor = new PropertyValidationProcessor(_propertyFunc, _typeValidator, _onSuccessValidation); } public bool IsAsync { get; } diff --git a/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.cs b/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.cs index a0ad0f5..ae01760 100644 --- a/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.cs +++ b/src/ExpressValidator/PropertyValidators/ExpressPropertyValidator.cs @@ -12,62 +12,34 @@ internal class ExpressPropertyValidator : IExpressPropertyValidator _typeValidator; private readonly Func _propertyFunc; - private readonly Action _onSuccessValidation; + private PropertyValidationProcessor _validationProcessor; public ExpressPropertyValidator(Func propertyFunc, string propName, TypeValidatorBase typeValidator, Action onSuccessValidation = null) { _propertyFunc = propertyFunc; _propName = propName; _typeValidator = typeValidator; + IsAsync = _typeValidator.IsAsync == true; _onSuccessValidation = onSuccessValidation; } public void SetValidation(Action> action) { _typeValidator.SetValidation(action, _propName); + _validationProcessor = new PropertyValidationProcessor(_propertyFunc, _typeValidator, _onSuccessValidation); } - public async Task<(bool IsValid, List Failures)> ValidateAsync(TObj obj, CancellationToken token = default) + public Task<(bool IsValid, List Failures)> ValidateAsync(TObj obj, CancellationToken token = default) { - if (_onSuccessValidation != null) - { - var value = _propertyFunc(obj); - var res = await _typeValidator.ValidateExAsync(value, token); - if (res.IsValid) - { - _onSuccessValidation(value); - } - return res; - } - else - { - return await _typeValidator.ValidateExAsync(_propertyFunc(obj), token); - } + return _validationProcessor.ValidateAsync(obj, token); } public (bool IsValid, List Failures) Validate(TObj obj) { - if (IsAsync) - { - throw new InvalidOperationException(); - } - if (_onSuccessValidation != null) - { - var value = _propertyFunc(obj); - var res = _typeValidator.ValidateEx(value); - if (res.IsValid) - { - _onSuccessValidation(value); - } - return res; - } - else - { - return _typeValidator.ValidateEx(_propertyFunc(obj)); - } + return _validationProcessor.Validate(obj); } - public bool IsAsync => _typeValidator.IsAsync == true; + public bool IsAsync { get; } } } diff --git a/src/ExpressValidator/PropertyValidators/PropertyValidationProcessor.cs b/src/ExpressValidator/PropertyValidators/PropertyValidationProcessor.cs new file mode 100644 index 0000000..50124ba --- /dev/null +++ b/src/ExpressValidator/PropertyValidators/PropertyValidationProcessor.cs @@ -0,0 +1,64 @@ +using FluentValidation.Results; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; + +namespace ExpressValidator +{ + internal class PropertyValidationProcessor + { + private readonly TypeValidatorBase _typeValidator; + private readonly Func _propertyFunc; + private readonly bool _isAsync; + private readonly Action _onSuccessValidation; + + public PropertyValidationProcessor(Func propertyFunc, TypeValidatorBase typeValidator, Action onSuccessValidation) + { + _propertyFunc = propertyFunc; + _typeValidator = typeValidator; + _isAsync = _typeValidator.IsAsync == true; + _onSuccessValidation = onSuccessValidation; + } + + public async Task<(bool IsValid, List Failures)> ValidateAsync(TObj obj, CancellationToken token = default) + { + if (_onSuccessValidation != null) + { + var value = _propertyFunc(obj); + var res = await _typeValidator.ValidateExAsync(value, token); + if (res.IsValid) + { + _onSuccessValidation(value); + } + return res; + } + else + { + return await _typeValidator.ValidateExAsync(_propertyFunc(obj), token); + } + } + + public (bool IsValid, List Failures) Validate(TObj obj) + { + if (_isAsync) + { + throw new InvalidOperationException(); + } + if (_onSuccessValidation != null) + { + var value = _propertyFunc(obj); + var res = _typeValidator.ValidateEx(value); + if (res.IsValid) + { + _onSuccessValidation(value); + } + return res; + } + else + { + return _typeValidator.ValidateEx(_propertyFunc(obj)); + } + } + } +} diff --git a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs index 12d33a8..8a3f04d 100644 --- a/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs +++ b/src/ExpressValidator/TypeValidators/TypeValidatorBase.cs @@ -15,8 +15,6 @@ internal abstract class TypeValidatorBase : AbstractValidator private IValidationRule _rule; private string _propName; - private static readonly bool _canBeNull = !typeof(T).IsValueType || (Nullable.GetUnderlyingType(typeof(T)) != null); - protected override void OnRuleAdded(IValidationRule rule) { _rule = rule; @@ -30,7 +28,7 @@ protected override void OnRuleAdded(IValidationRule rule) /// protected override bool PreValidate(ValidationContext context, ValidationResult result) { - if (IsValueNull(context.InstanceToValidate)) + if(TypeHelper.IsNull(context.InstanceToValidate)) { result.Errors.Add(new ValidationFailure(_propName, NullFallbackMessageProvider.GetMessage(_propName, context))); return false; @@ -86,12 +84,7 @@ public void SetValidation(Action> action, string propN internal abstract bool? IsAsync { get; } - protected bool ShouldValidate(T value) => !IsValueNull(value) || HasNonEmptyValidators; - - private static bool IsValueNull(T value) - { - return _canBeNull && EqualityComparer.Default.Equals(value, default); - } + protected bool ShouldValidate(T value) => !TypeHelper.IsNull(value) || HasNonEmptyValidators; private bool HasNonEmptyValidators { get; set; } diff --git a/src/ExpressValidator/TypeValidators/ValidationFallbackProvider.cs b/src/ExpressValidator/TypeValidators/ValidationFallbackProvider.cs new file mode 100644 index 0000000..9562429 --- /dev/null +++ b/src/ExpressValidator/TypeValidators/ValidationFallbackProvider.cs @@ -0,0 +1,26 @@ +using FluentValidation; +using FluentValidation.Results; + +namespace ExpressValidator +{ + internal static class ValidationFallbackProvider + { + /// + /// Generates a ValidationResult representing a failure due to a null instance. + /// + public static ValidationResult GetNullFailure() + { + var typeName = TypeTraits.TypeName; + + var message = NullFallbackMessageProvider.GetMessage( + typeName, + new ValidationContext(null) + ); + + var failures = new[] { new ValidationFailure(typeName, message) }; + return new ValidationResult( + failures + ); + } + } +} diff --git a/src/ExpressValidator/Utilities/TypeHelper.cs b/src/ExpressValidator/Utilities/TypeHelper.cs new file mode 100644 index 0000000..2ec5608 --- /dev/null +++ b/src/ExpressValidator/Utilities/TypeHelper.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace ExpressValidator +{ + internal static class TypeHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNull(T value) + { + return TypeTraits.CanBeNull && EqualityComparer.Default.Equals(value, default); + } + } +} diff --git a/src/ExpressValidator/Utilities/TypeTraits.cs b/src/ExpressValidator/Utilities/TypeTraits.cs new file mode 100644 index 0000000..1aca5e4 --- /dev/null +++ b/src/ExpressValidator/Utilities/TypeTraits.cs @@ -0,0 +1,10 @@ +using System; + +namespace ExpressValidator +{ + internal static class TypeTraits + { + public static readonly bool CanBeNull = !typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) != null; + public static readonly string TypeName = typeof(T).Name; + } +} \ No newline at end of file diff --git a/src/ExpressValidator/ValidationProfile.cs b/src/ExpressValidator/ValidationProfile.cs new file mode 100644 index 0000000..d57fbf8 --- /dev/null +++ b/src/ExpressValidator/ValidationProfile.cs @@ -0,0 +1,9 @@ +namespace ExpressValidator +{ +#pragma warning disable S1694 // An abstract class should have both abstract and concrete methods + public abstract class ValidationProfile +#pragma warning restore S1694 // An abstract class should have both abstract and concrete methods + { + public abstract void Configure(ExpressValidatorBuilder expressValidatorBuilder); + } +} diff --git a/src/ExpressValidator/ValidatorBuilders/BuilderWithPropValidator.TOptions.cs b/src/ExpressValidator/ValidatorBuilders/BuilderWithPropValidator.TOptions.cs index 5246b01..a01c66d 100644 --- a/src/ExpressValidator/ValidatorBuilders/BuilderWithPropValidator.TOptions.cs +++ b/src/ExpressValidator/ValidatorBuilders/BuilderWithPropValidator.TOptions.cs @@ -10,27 +10,30 @@ namespace ExpressValidator public class BuilderWithPropValidator : IBuilderWithPropValidator { private readonly string _propName; - private readonly Func _propertyFunc; + private readonly Func _propertyFunc; + + private readonly Action _onSuccessValidation; internal BuilderWithPropValidator(ExpressValidatorBuilder expressValidatorBuilder, MemberInfo memberInfo) : this(expressValidatorBuilder, memberInfo.GetTypedValue, memberInfo?.Name ?? string.Empty) { } - internal BuilderWithPropValidator(ExpressValidatorBuilder expressValidatorBuilder, Func propertyFunc, string propName) + internal BuilderWithPropValidator(ExpressValidatorBuilder expressValidatorBuilder, Func propertyFunc, string propName, Action onSuccessValidation = null) { ExpressValidatorBuilder = expressValidatorBuilder; _propertyFunc = propertyFunc; _propName = propName; + _onSuccessValidation = onSuccessValidation; } public ExpressValidatorBuilder WithAsyncValidation(Action> action) { - return WithValidationByRules(action, new ExpressPropertyValidator(_propertyFunc, _propName, true)); + return WithValidationByRules(action, new ExpressPropertyValidator(_propertyFunc, _propName, true, _onSuccessValidation)); } public ExpressValidatorBuilder WithValidation(Action> action) { - return WithValidationByRules(action, new ExpressPropertyValidator(_propertyFunc, _propName, false)); + return WithValidationByRules(action, new ExpressPropertyValidator(_propertyFunc, _propName, false, _onSuccessValidation)); } private ExpressValidatorBuilder WithValidationByRules(Action> action, IExpressPropertyValidator expressPropertyValidator) diff --git a/src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.TOptions.cs b/src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.TOptions.cs index c8ee95e..0e296d2 100644 --- a/src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.TOptions.cs +++ b/src/ExpressValidator/ValidatorBuilders/ExpressValidatorBuilder.TOptions.cs @@ -41,18 +41,19 @@ public IBuilderWithPropValidator AddField(Expression(this, memInfo); - } - + } + /// /// Add Func for object to get value to validate. /// /// A type of value. /// Func for object. /// A name of the property if the validation failed. + /// Specifies a method to execute when validation succeeds. /// - public IBuilderWithPropValidator AddFunc(Func func, string propName) + public IBuilderWithPropValidator AddFunc(Func func, string propName, Action onSuccessValidation = null) { - return new BuilderWithPropValidator(this, func, propName); + return new BuilderWithPropValidator(this, func, propName, onSuccessValidation); } /// @@ -60,8 +61,13 @@ public IBuilderWithPropValidator AddFunc(Func fun /// /// public IExpressValidator Build(TOptions options) - { - return new ExpressValidator(options, _objectValidators, _validationMode); + { + foreach (var validator in _objectValidators) + { + validator.ApplyOptions(options); + } + + return new ExpressValidator(_objectValidators, _validationMode); } internal void AddValidator(IObjectValidator objectValidator) diff --git a/src/ExpressValidator/docs/NuGet.md b/src/ExpressValidator/docs/NuGet.md index dfa8de4..2dffa56 100644 --- a/src/ExpressValidator/docs/NuGet.md +++ b/src/ExpressValidator/docs/NuGet.md @@ -8,7 +8,8 @@ ExpressValidator is a library that provides the ability to validate objects usin - Supports adding a property or field for validation. - Verifies that a property expression is a property and a field expression is a field, and throws `ArgumentException` if it is not. - Supports adding a `Func` that provides a value for validation. -- Provides quick and easy validation using `QuickValidator`, with built-in tolerance for `null` values. +- Built-in `null` tolerance - `null` root instances fail validation instead of throwing exceptions. +- Quick and easy validation with `QuickValidator`, with robust support for `null` values. - Supports asynchronous validation. - Targets .NET Standard 2.0+ diff --git a/tests/ExpressValidator.Tests.Net8/ExpressAsyncValidatorTests.cs b/tests/ExpressValidator.Tests.Net8/ExpressAsyncValidatorTests.cs new file mode 100644 index 0000000..97cfef9 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressAsyncValidatorTests.cs @@ -0,0 +1,172 @@ +using FluentValidation; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal class ExpressAsyncValidatorTests + { + [Test] + public async Task Should_Work_For_When_IsValid_Eq_True_And_AllValidatorsSync() + { + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddField(o => o._sField) + .WithValidation(o => o.MinimumLength(1)) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation(o => o.InclusiveBetween(0, 100)) + .Build() + .ValidateAsync(new ObjWithTwoPublicProps() { I = 2, S = "b", _sField = "a", PercentValue1 = 99, PercentValue2 = 1}); + ClassicAssert.AreEqual(true, result.IsValid); + } + + [Test] + public async Task Should_Work_For_When_IsValid_Eq_False_And_AllValidatorsSync() + { + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddField(o => o._sField) + .WithValidation(o => o.MinimumLength(1)) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation(o => o.InclusiveBetween(0, 100)) + .Build() + .ValidateAsync(new ObjWithTwoPublicProps() { I = -2, S = "ab", _sField = "", PercentValue1 = 100, PercentValue2 = 2 }); + ClassicAssert.AreEqual(false, result.IsValid); + ClassicAssert.AreEqual(4, result.Errors.Count); + } + + [Test] + public void Should_ValidateAsyncThrow_If_Cancellation_Occurs() + { + using var ctSource = new CancellationTokenSource(); + ctSource.Cancel(); + var builder = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .Build(); + + Assert.ThrowsAsync(async () => await builder.ValidateAsync(new ObjWithTwoPublicProps() { I = 2, S = "b" }, ctSource.Token)); + } + + [Test] + public async Task Should_IsValid_Equals_True_WhenNoValidators() + { + var result = await new ExpressValidatorBuilder() + .Build() + .ValidateAsync(new ObjWithTwoPublicProps()); + Assert.That(result.IsValid, Is.True); + } + + [Test] + [TestCase(SetPropertyNameType.WithName, MemberTypes.Property)] + [TestCase(SetPropertyNameType.NotSetExplicitly, MemberTypes.Property)] + [TestCase(SetPropertyNameType.Override, MemberTypes.Property)] + [TestCase(SetPropertyNameType.WithName, MemberTypes.Field)] + [TestCase(SetPropertyNameType.NotSetExplicitly, MemberTypes.Field)] + [TestCase(SetPropertyNameType.Override, MemberTypes.Field)] + public async Task Should_Preserve_Property_Name(SetPropertyNameType setPropertyNameType, MemberTypes memberTypes) + { + var builder = new ExpressValidatorBuilder(); + IBuilderWithPropValidator builderWithProperty; + + if (memberTypes == MemberTypes.Property) + { + builderWithProperty = builder.AddProperty(o => o.I); + } + else + { + builderWithProperty = builder.AddField(o => o._iField); + } + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + builder = builderWithProperty.WithAsyncValidation(o => o.GreaterThan(0).MustAsync(async(_, __) => { await Task.Delay(1); return true; })); + break; + case SetPropertyNameType.Override: + builder = builderWithProperty.WithAsyncValidation(o => o.GreaterThan(0).MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + .OverridePropertyName("TestPropName")); + break; + case SetPropertyNameType.WithName: + builder = builderWithProperty.WithAsyncValidation(o => o.GreaterThan(0).MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + .WithName("TestName")); + break; + } + var result = await builder.Build().ValidateAsync(new ObjWithTwoPublicProps()); + + Assert.That(result.IsValid, Is.False); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + case SetPropertyNameType.WithName: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, memberTypes == MemberTypes.Property ? Is.EqualTo("I") : Is.EqualTo("_iField")); + break; + case SetPropertyNameType.Override: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo("TestPropName")); + break; + } + + if (setPropertyNameType == SetPropertyNameType.WithName) + { + Assert.That(result.Errors.FirstOrDefault()?.ErrorMessage, Does.Contain("TestName")); + } + } + + [Test] + [TestCase(SetPropertyNameType.WithName)] + [TestCase(SetPropertyNameType.NotSetExplicitly)] + [TestCase(SetPropertyNameType.Override)] + public async Task Should_AddFunc_Preserve_Property_Name(SetPropertyNameType setPropertyNameType) + { + var builder = new ExpressValidatorBuilder(); + var builderWithProperty = builder.AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum"); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + builder = builderWithProperty.WithAsyncValidation(o => o.InclusiveBetween(0, 100).MustAsync(async (_, __) => { await Task.Delay(1); return true; })); + break; + case SetPropertyNameType.Override: + builder = builderWithProperty.WithAsyncValidation(o => o.InclusiveBetween(0, 100).MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + .OverridePropertyName("TestPropName")); + break; + case SetPropertyNameType.WithName: + builder = builderWithProperty.WithAsyncValidation(o => o.InclusiveBetween(0, 100).MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + .WithName("TestName")); + break; + } + + var result = await builder.Build().ValidateAsync(new ObjWithTwoPublicProps() { PercentValue1 = 1, PercentValue2 = 100 }); + + Assert.That(result.IsValid, Is.False); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + case SetPropertyNameType.WithName: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo("percentSum")); + break; + case SetPropertyNameType.Override: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo("TestPropName")); + break; + } + + if (setPropertyNameType == SetPropertyNameType.WithName) + { + Assert.That(result.Errors.FirstOrDefault()?.ErrorMessage, Does.Contain("TestName")); + } + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ExpressAsyncValidatorWithOptionsTests.cs b/tests/ExpressValidator.Tests.Net8/ExpressAsyncValidatorWithOptionsTests.cs new file mode 100644 index 0000000..2d8f043 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressAsyncValidatorWithOptionsTests.cs @@ -0,0 +1,153 @@ +using FluentValidation; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal class ExpressAsyncValidatorWithOptionsTests + { + private readonly ObjWithTwoPublicPropsOptions _objWithTwoPublicPropsOptions = new ObjWithTwoPublicPropsOptions() + { + IGreaterThanValue = 0, + SMaximumLengthValue = 1, + SFieldMaximumLengthValue = 1 + }; + + [Test] + public async Task Should_Work_When_IsValid() + { + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithAsyncValidation((to, p) => p.GreaterThan(to.IGreaterThanValue).MustAsync(async (_, __) => { await Task.Delay(1); return true;})) + .AddProperty(o => o.S) + .WithAsyncValidation((to, p) => p.MaximumLength(to.SMaximumLengthValue).MustAsync(async (_, __) => { await Task.Delay(1); return true;})) + .AddField(o => o._sField) + .WithAsyncValidation((to, f) => f.MaximumLength(to.SFieldMaximumLengthValue).MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(_objWithTwoPublicPropsOptions) + .ValidateAsync(new ObjWithTwoPublicProps() { I = 1, S = "b", _sField = "1" }); + ClassicAssert.AreEqual(true, result.IsValid); + } + + [Test] + public async Task Should_Validation_Result_Change_When_Options_Change() + { + var builder = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithAsyncValidation((to, p) => p.GreaterThan(to.IGreaterThanValue).MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .AddProperty(o => o.S) + .WithAsyncValidation((to, p) => p.MaximumLength(to.SMaximumLengthValue).MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .AddField(o => o._sField) + .WithAsyncValidation((to, f) => f.MaximumLength(to.SFieldMaximumLengthValue).MustAsync(async (_, __) => { await Task.Delay(1); return true; })); + + var result1 = await builder.Build(_objWithTwoPublicPropsOptions) + .ValidateAsync(new ObjWithTwoPublicProps() { I = 1, S = "b", _sField = "1" }); + ClassicAssert.AreEqual(true, result1.IsValid); + + var options2 = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 2, SMaximumLengthValue = 2, SFieldMaximumLengthValue = 1 }; + var result2 = await builder.Build(options2) + .ValidateAsync(new ObjWithTwoPublicProps() { I = 1, S = "abc", _sField = "12" }); + ClassicAssert.AreEqual(false, result2.IsValid); + ClassicAssert.AreEqual(3, result2.Errors.Count); + + var options3 = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 3, SMaximumLengthValue = 3, SFieldMaximumLengthValue = 2 }; + var result3 = await builder.Build(options3) + .ValidateAsync(new ObjWithTwoPublicProps() { I = 2, S = "abcd", _sField = "123" }); + ClassicAssert.AreEqual(false, result3.IsValid); + ClassicAssert.AreEqual(3, result3.Errors.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_Work_When_TheSamePropertyValidators_In_A_Row(bool isValid) + { + var options = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 1, IGreaterThanValue2 = 2 }; + int i = isValid ? 3 : -1; + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation((topt, p) => p.GreaterThan(topt.IGreaterThanValue)) + .AddProperty(o => o.I) + .WithValidation((topt, p) => p.GreaterThan(topt.IGreaterThanValue2)) + .Build(options); + + var result = await validator.ValidateAsync(new ObjWithTwoPublicProps() { I = i }); + if (isValid) + { + Assert.That(result.IsValid, Is.True); + } + else + { + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.IsValid, Is.False); + } + } + + [Test] + [TestCase(SetPropertyNameType.WithName, MemberTypes.Property)] + [TestCase(SetPropertyNameType.NotSetExplicitly, MemberTypes.Property)] + [TestCase(SetPropertyNameType.Override, MemberTypes.Property)] + [TestCase(SetPropertyNameType.WithName, MemberTypes.Field)] + [TestCase(SetPropertyNameType.NotSetExplicitly, MemberTypes.Field)] + [TestCase(SetPropertyNameType.Override, MemberTypes.Field)] + public async Task Should_Preserve_Property_Name(SetPropertyNameType setPropertyNameType, MemberTypes memberTypes) + { + var builder = new ExpressValidatorBuilder(); + IBuilderWithPropValidator builderWithProperty; + + if (memberTypes == MemberTypes.Property) + { + builderWithProperty = builder.AddProperty(o => o.I); + } + else + { + builderWithProperty = builder.AddField(o => o._iField); + } + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + builder = builderWithProperty.WithAsyncValidation((topt, p) => p.GreaterThan(topt.IGreaterThanValue).MustAsync(async (_, __) => { await Task.Delay(1); return true; })); + break; + case SetPropertyNameType.Override: + builder = builderWithProperty.WithAsyncValidation((topt, p) => p.GreaterThan(topt.IGreaterThanValue).MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + .OverridePropertyName("TestPropName")); + break; + case SetPropertyNameType.WithName: + builder = builderWithProperty.WithAsyncValidation((topt, p) => p.GreaterThan(topt.IGreaterThanValue).MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + .WithName("TestName")); + break; + } + var result = await builder.Build(_objWithTwoPublicPropsOptions).ValidateAsync(new ObjWithTwoPublicProps()); + + Assert.That(result.IsValid, Is.False); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + case SetPropertyNameType.WithName: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, memberTypes == MemberTypes.Property ? Is.EqualTo("I") : Is.EqualTo("_iField")); + break; + case SetPropertyNameType.Override: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo("TestPropName")); + break; + } + + if (setPropertyNameType == SetPropertyNameType.WithName) + { + Assert.That(result.Errors.FirstOrDefault()?.ErrorMessage, Does.Contain("TestName")); + } + } + + [Test] + public async Task Should_IsValid_Equals_True_WhenNoValidators() + { + var result = await new ExpressValidatorBuilder() + .Build(_objWithTwoPublicPropsOptions) + .ValidateAsync(new ObjWithTwoPublicProps()); + Assert.That(result.IsValid, Is.True); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ExpressValidator.Tests.Net8.csproj b/tests/ExpressValidator.Tests.Net8/ExpressValidator.Tests.Net8.csproj new file mode 100644 index 0000000..0388385 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressValidator.Tests.Net8.csproj @@ -0,0 +1,34 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/tests/ExpressValidator.Tests.Net8/ExpressValidatorExtensionsTests.cs b/tests/ExpressValidator.Tests.Net8/ExpressValidatorExtensionsTests.cs new file mode 100644 index 0000000..ea54b2a --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressValidatorExtensionsTests.cs @@ -0,0 +1,82 @@ +using NUnit.Framework; +using FluentValidation; +using ExpressValidator.Extensions; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal class ExpressValidatorExtensionsTests + { + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_BuildAndValidate_Work(bool isValid) + { + int i = isValid ? 1 : -1; + var objToValidate = new ObjWithTwoPublicProps() { I = i }; + + var result = new ExpressValidatorBuilder() + .AddProperty((o) => o.I) + .WithValidation((opt) => opt.GreaterThan(0)) + .BuildAndValidate(objToValidate); + + Assert.That(result.IsValid, Is.EqualTo(isValid)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_BuildAndValidate_With_TOptions_Param_Work(bool isValid) + { + var objWithTwoPublicPropsOptions = new ObjWithTwoPublicPropsOptions() + { + IGreaterThanValue = isValid ? -1 : 1, + }; + + var objToValidate = new ObjWithTwoPublicProps() { I = 0 }; + + var result = new ExpressValidatorBuilder() + .AddProperty((o) => o.I) + .WithValidation((to, opt) => opt.GreaterThan(to.IGreaterThanValue)) + .BuildAndValidate(objToValidate, objWithTwoPublicPropsOptions); + + Assert.That(result.IsValid, Is.EqualTo(isValid)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_BuildAndValidateAsync_Work(bool isValid) + { + int i = isValid ? 1 : -1; + var objToValidate = new ObjWithTwoPublicProps() { I = i }; + + var result = await new ExpressValidatorBuilder() + .AddProperty((o) => o.I) + .WithAsyncValidation((opt) => opt.GreaterThan(0).MustAsync(async(_, __) => { await Task.Delay(1); return true; })) + .BuildAndValidateAsync(objToValidate); + + Assert.That(result.IsValid, Is.EqualTo(isValid)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_BuildAndValidateAsync_With_TOptions_Param_Work(bool isValid) + { + var objWithTwoPublicPropsOptions = new ObjWithTwoPublicPropsOptions() + { + IGreaterThanValue = isValid ? -1 : 1, + }; + + var objToValidate = new ObjWithTwoPublicProps() { I = 0 }; + + var result = await new ExpressValidatorBuilder() + .AddProperty((o) => o.I) + .WithAsyncValidation((to, opt) => opt.GreaterThan(to.IGreaterThanValue).MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .BuildAndValidateAsync(objToValidate, objWithTwoPublicPropsOptions); + + Assert.That(result.IsValid, Is.EqualTo(isValid)); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ExpressValidatorNullObjectTests.cs b/tests/ExpressValidator.Tests.Net8/ExpressValidatorNullObjectTests.cs new file mode 100644 index 0000000..5d2555e --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressValidatorNullObjectTests.cs @@ -0,0 +1,140 @@ +using FluentValidation; +using FluentValidation.Results; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal class ExpressValidatorNullObjectTests + { + private static readonly string NullErrorMessageForClass = NullFallbackMessageProvider.GetMessage(typeof(Contact).Name, + new ValidationContext(null!)); + + private static readonly string NullErrorMessageForStruct = NullFallbackMessageProvider.GetMessage(typeof(ContactStruct?).Name, + new ValidationContext(null!)); + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotThrow_When_Class_To_Validate_Is_Null(bool isAsync) + { + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o.Name) + .WithValidation(o => o.NotEmpty() + .MaximumLength(100)) + .AddProperty(o => o.Email) + .WithValidation(o => o.NotEmpty() + .EmailAddress()) + .Build(); + + ValidationResult result; + if (isAsync) + { + result = await validator.ValidateAsync(null!); + } + else + { +#pragma warning disable S6966 // Awaitable method should be used + result = validator.Validate(null!); +#pragma warning restore S6966 // Awaitable method should be used + } + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(NullErrorMessageForClass)); + + if (isAsync) + { + Assert.ThrowsAsync(async () => await new ContactValidator().ValidateAsync((Contact)null!)); + } + else + { + Assert.Throws(() => new ContactValidator().Validate((Contact)null!)); + } + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotThrow_When_Nullable_Struct_Is_Null(bool isAsync) + { + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o!.Value.Name) + .WithValidation(o => o.NotEmpty() + .MaximumLength(100)) + .AddProperty(o => o!.Value.Email) + .WithValidation(o => o.NotEmpty() + .EmailAddress()) + .Build(); + + ValidationResult result; + if (isAsync) + { + result = await validator.ValidateAsync(null); + } + else + { +#pragma warning disable S6966 // Awaitable method should be used + result = validator.Validate(null); +#pragma warning restore S6966 // Awaitable method should be used + } + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(NullErrorMessageForStruct)); + + if (isAsync) + { + Assert.ThrowsAsync(async () => await new ContactNullableStructValidator().ValidateAsync((ContactStruct?)null)); + } + else + { + Assert.Throws(() => new ContactNullableStructValidator().Validate((ContactStruct?)null)); + } + } + + [Test] + public async Task Should_NotThrow_When_Class_To_Validate_Is_Null_And_WithAsyncValidation_Is_Used() + { + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o.Name) + .WithValidation(o => o.NotEmpty() + .MaximumLength(100)) + .AddProperty(o => o.Email) + .WithValidation(o => o.NotEmpty() + .EmailAddress()) + .AddProperty(o => o.K) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + + var result = await validator.ValidateAsync(null!); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(NullErrorMessageForClass)); + + Assert.ThrowsAsync(async () => await new ContactValidator().ValidateAsync((Contact)null!)); + } + + [Test] + public async Task Should_NotThrow_When_Struct_To_Validate_Is_Null_And_WithAsyncValidation_Is_Used() + { + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o!.Value.Email) + .WithValidation(o => o.NotEmpty() + .EmailAddress()) + .AddProperty(o => o!.Value.Name) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + + var result = await validator.ValidateAsync(null); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(NullErrorMessageForStruct)); + + Assert.ThrowsAsync(async () => await new ContactNullableStructValidator().ValidateAsync((ContactStruct?)null)); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ExpressValidatorTests.ForAsync.Tests.cs b/tests/ExpressValidator.Tests.Net8/ExpressValidatorTests.ForAsync.Tests.cs new file mode 100644 index 0000000..079f841 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressValidatorTests.ForAsync.Tests.cs @@ -0,0 +1,135 @@ +using FluentValidation; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class ExpressValidatorTests + { + [Test] + public void Should_Validate_Throw_For_AsyncRule_ForProperty() + { + var builder = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + var exc = Assert.Throws(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, S = "b" })); + Assert.That(exc.Message, Does.StartWith("Object validator has a property or field with asynchronous validation rules.")); + } + + [Test] + public void Should_Using_WithValidation_With_AsyncRule_Throw_AsyncValidatorInvokedSynchronouslyException_When_Validate() + { + var builder = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + Assert.Throws(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, S = "b" })); + } + + [Test] + public async Task Should_AsyncInvoke_SuccessValidationHandler_When_IsValid() + { + int percentSum = 0; + var result = await new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum", (p) => percentSum = p) + .WithValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; }).InclusiveBetween(0, 100)) + .Build() + .ValidateAsync(new ObjWithTwoPublicProps() { PercentValue1 = 20, PercentValue2 = 80 }); + Assert.That(percentSum, Is.EqualTo(100)); + Assert.That(result.IsValid, Is.True); + } + + [Test] + public async Task Should_Not_AsyncInvoke_SuccessValidationHandler_When_IsNotValid() + { + int percentSum = 0; + var result = await new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum", (p) => percentSum = p) + .WithValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; }).InclusiveBetween(0, 100)) + .Build() + .ValidateAsync(new ObjWithTwoPublicProps() { PercentValue1 = 20, PercentValue2 = 82 }); + Assert.That(percentSum, Is.EqualTo(0)); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Validate_Throw_For_AsyncRule_ForField() + { + var builder = new ExpressValidatorBuilder() + .AddField(o => o._sField) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + var exc = Assert.Throws(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, _sField = "b" })); + Assert.That(exc.Message, Does.StartWith("Object validator has a property or field with asynchronous validation rules.")); + } + + [Test] + public void Should_Validate_Throw_For_AsyncRule_ForFunc() + { + var builder = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + var exc = Assert.Throws(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, _sField = "b" })); + Assert.That(exc.Message, Does.StartWith("Object validator has a property or field with asynchronous validation rules.")); + } + + [Test] + public async Task Should_ValidateAsync_Work_For_AsyncRules() + { + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .AddField(o => o._sField) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build() + .ValidateAsync(new ObjWithTwoPublicProps() { I = 1, _sField = "" }); + + ClassicAssert.AreEqual(true, result.IsValid); + } + + [Test] + public async Task Should_ValidateAsync_When_Using_Combined_Validation_Strategy() + { + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return false; })) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation(o => o.InclusiveBetween(0, 100)) + .Build() + .ValidateAsync(new ObjWithTwoPublicProps() { I = 1, PercentValue1 = 200 }); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_ValidateAsync_When_Used_In_External_API(bool valid) + { + const int customerId = 1; + var apiClient = new SomeExternalWebApiClient(valid ? 2 : customerId); + var customer = new Customer() { CustomerId = customerId }; + + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.CustomerId) + .WithAsyncValidation(o => o.MustAsync(async (id, cancellation) => + + !await apiClient.IdExistsAsync(id, cancellation))) + + .Build() + .ValidateAsync(customer); + + Assert.That(result.IsValid, Is.EqualTo(valid)); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ExpressValidatorTests.cs b/tests/ExpressValidator.Tests.Net8/ExpressValidatorTests.cs new file mode 100644 index 0000000..5a6ffb3 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressValidatorTests.cs @@ -0,0 +1,394 @@ +using ExpressValidator.Extensions; +using FluentValidation; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class ExpressValidatorTests + { + [Test] + public void Should_Work_When_IsValid() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddField(o=>o._sField) + .WithValidation(o => o.MinimumLength(1)) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation(o => o.InclusiveBetween(0, 100)) + .Build() + .Validate(new ObjWithTwoPublicProps() { I = 1, S = "b", _sField = "1", PercentValue1 = 20, PercentValue2 = 80 }); + ClassicAssert.AreEqual(true, result.IsValid); + } + + [Test] + public void Should_Invoke_SuccessValidationHandler_When_IsValid() + { + int percentSum = 0; + var result = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum", (p)=> percentSum = p) + .WithValidation(o => o.InclusiveBetween(0, 100)) + .Build() + .Validate(new ObjWithTwoPublicProps() { PercentValue1 = 20, PercentValue2 = 80 }); + Assert.That(percentSum, Is.EqualTo(100)); + Assert.That(result.IsValid, Is.True); + } + + [Test] + public void Should_Not_Invoke_SuccessValidationHandler_When_IsNotValid() + { + int percentSum = 0; + var result = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum", (p) => percentSum = p) + .WithValidation(o => o.InclusiveBetween(0, 100)) + .Build() + .Validate(new ObjWithTwoPublicProps() { PercentValue1 = 20, PercentValue2 = 82 }); + Assert.That(percentSum, Is.EqualTo(0)); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_NotThrow_When_MembersAreNull() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddField(o => o._sField) + .WithValidation(o => o.MinimumLength(1)) + .Build() + .Validate(new ObjWithTwoPublicProps()); + + var em1 = NullFallbackMessageProvider.GetMessage("S", new ValidationContext(null!)); + var em2 = NullFallbackMessageProvider.GetMessage("_sField", new ValidationContext(null!)); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(em1)); + Assert.That(result.Errors[1].ErrorMessage, Is.EqualTo(em2)); + } + + [Test] + public void Should_Work_When_IsValid_ForSubObjWithSimpleConditionForComplexProperty() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddProperty(o => o.Contact) + .WithValidation(o => o.Null()) + .Build() + .Validate(new SubObjWithComplexProperty() { I = 1, S = "b"}); + ClassicAssert.AreEqual(true, result.IsValid); + } + + [Test] + public void Should_Work_When_NotValid_ForSubObjWithSimpleConditionForComplexProperty_WithFluentValidator_AndTwoPropsNotValid() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddProperty(o => o.Contact) + .WithValidation(o => o.SetValidator(new SimpleContactValidator()!)) + .Build() + .Validate(new SubObjWithComplexProperty() { I = 2, S = "b", Contact = new Contact()}); + ClassicAssert.AreEqual(false, result.IsValid); + ClassicAssert.AreEqual(2, result.Errors.Count); + } + + [Test] + public void Should_Work_When_NotValid_ForSubObjWithSimpleConditionForComplexProperty_WithFluentCollectionValidator_PropertyIsNotSet() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddProperty(o => o.Contacts) + .WithValidation(o => o.ForEach(o1 => o1.SetValidator(new SimpleContactValidator()!))) + .Build() + .Validate(new SubObjWithComplexCollectionProperty() { I = 1, S = "b"}); + ClassicAssert.AreEqual(false, result.IsValid); + ClassicAssert.AreEqual(1, result.Errors.Count); + } + + [Test] + public void Should_Work_When_NotValid_ForSubObjWithSimpleConditionForComplexProperty_WithFluentCollectionValidator_AndTwoObjectsWithTwoPropsNotValid() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddProperty(o => o.Contacts) + .WithValidation(o => o.NotEmpty().ForEach(o1 => o1.SetValidator(new SimpleContactValidator()))) + .Build() + .Validate(new SubObjWithComplexCollectionProperty() { I = 1, S = "b", + Contacts = [new Contact(), new Contact()] }); + ClassicAssert.AreEqual(false, result.IsValid); + ClassicAssert.AreEqual(4, result.Errors.Count); + } + + [Test] + public void Should_Work_When_Valid_ForSubObjWithSimpleConditionForComplexProperty_WithFluentCollectionValidator() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddProperty(o => o.Contacts) + .WithValidation(o => o.ForEach(o1 => o1.SetValidator(new SimpleContactValidator()))) + .Build() + .Validate(new SubObjWithComplexCollectionProperty() + { + I = 1, + S = "b", + Contacts = [new Contact() { Email = "", Name = "" }, new Contact() { Email = "", Name = "" }] + }); + ClassicAssert.AreEqual(true, result.IsValid); + } + + [Test] + [TestCase(OnFirstPropertyValidatorFailed.Break)] + [TestCase(OnFirstPropertyValidatorFailed.Continue)] + public void Should_Work_When_NotValid(OnFirstPropertyValidatorFailed validationMode) + { + var result = new ExpressValidatorBuilder(validationMode) + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.MaximumLength(1)) + .AddField(o => o._sField) + .WithValidation(o => o.MinimumLength(1)) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation(o => o.InclusiveBetween(0, 100)) + .Build() + .Validate(new ObjWithTwoPublicProps() { I = -1, S = "ab", _sField = "", PercentValue1 = 2, PercentValue2 = 101}); + ClassicAssert.AreEqual(false, result.IsValid); + if (validationMode == OnFirstPropertyValidatorFailed.Break) + { + ClassicAssert.AreEqual(1, result.Errors.Count); + } + else + { + ClassicAssert.AreEqual(4, result.Errors.Count); + } + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Work_When_Nullable_And_Value_Prop_Values_Validated_WithNullValidator(bool propValueIsNull) + { + ObjWithTwoPublicProps objToTest; + + if (propValueIsNull) + { + objToTest = new ObjWithTwoPublicProps() { I = -1 }; + } + else + { + objToTest = new ObjWithTwoPublicProps() { I = -1, S = "ab" }; + } + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(0)) + .AddProperty(o => o.S) + .WithValidation(o => o.Null()) + .Build() + .Validate(objToTest); + + if (propValueIsNull) + { + ClassicAssert.AreEqual(1, result.Errors.Count); + } + else + { + ClassicAssert.AreEqual(2, result.Errors.Count); + } + } + + [Test] + public void Should_Throw_When_Non_Property() + { + Assert.Throws + (() => new ExpressValidatorBuilder() + .AddProperty(o => o) + .WithValidation(o => o.NotNull()) + .Build()); + } + + [Test] + public void Should_Throw_When_Non_Field() + { + Assert.Throws + (() => new ExpressValidatorBuilder() + .AddField(o => o) + .WithValidation(o => o.NotNull()) + .Build()); + } + + [Test] + public void Should_IsValid_Equals_True_WhenNoValidators() + { + var result = new ExpressValidatorBuilder() + .Build() + .Validate(new ObjWithTwoPublicProps()); + Assert.That(result.IsValid, Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Work_When_TheSamePropertyValidators_In_A_Row(bool isValid) + { + int i = isValid ? 3 : -1; + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(1)) + .AddProperty(o => o.I) + .WithValidation(o => o.GreaterThan(2)) + .Build(); + + var result = validator.Validate(new ObjWithTwoPublicProps() { I = i }); + if (isValid) + { + Assert.That(result.IsValid, Is.True); + } + else + { + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.IsValid, Is.False); + } + } + + [Test] + [TestCase(SetPropertyNameType.WithName, MemberTypes.Property)] + [TestCase(SetPropertyNameType.NotSetExplicitly, MemberTypes.Property)] + [TestCase(SetPropertyNameType.Override, MemberTypes.Property)] + [TestCase(SetPropertyNameType.WithName, MemberTypes.Field)] + [TestCase(SetPropertyNameType.NotSetExplicitly, MemberTypes.Field)] + [TestCase(SetPropertyNameType.Override, MemberTypes.Field)] + public void Should_Preserve_Property_Name(SetPropertyNameType setPropertyNameType, MemberTypes memberTypes) + { + var builder = new ExpressValidatorBuilder(); + IBuilderWithPropValidator builderWithProperty; + + if (memberTypes == MemberTypes.Property) + { + builderWithProperty = builder.AddProperty(o => o.I); + } + else + { + builderWithProperty = builder.AddField(o => o._iField); + } + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + builder = builderWithProperty.WithValidation(o => o.GreaterThan(0)); + break; + case SetPropertyNameType.Override: + builder = builderWithProperty.WithValidation(o => o.GreaterThan(0).OverridePropertyName("TestPropName")); + break; + case SetPropertyNameType.WithName: + builder = builderWithProperty.WithValidation(o => o.GreaterThan(0).WithName("TestName")); + break; + } + var result = builder.Build().Validate(new ObjWithTwoPublicProps() { I = -1 }); + + Assert.That(result.IsValid, Is.False); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + case SetPropertyNameType.WithName: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, memberTypes == MemberTypes.Property ? Is.EqualTo("I") : Is.EqualTo("_iField")); + break; + case SetPropertyNameType.Override: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo("TestPropName")); + break; + } + + if (setPropertyNameType == SetPropertyNameType.WithName) + { + Assert.That(result.Errors.FirstOrDefault()?.ErrorMessage, Does.Contain("TestName")); + } + } + + [Test] + [TestCase(SetPropertyNameType.WithName)] + [TestCase(SetPropertyNameType.NotSetExplicitly)] + [TestCase(SetPropertyNameType.Override)] + public void Should_AddFunc_Preserve_Property_Name(SetPropertyNameType setPropertyNameType) + { + var builder = new ExpressValidatorBuilder(); + var builderWithProperty = builder.AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum"); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + builder = builderWithProperty.WithValidation(o => o.InclusiveBetween(0, 100)); + break; + case SetPropertyNameType.Override: + builder = builderWithProperty.WithValidation(o => o.InclusiveBetween(0, 100) + .OverridePropertyName("TestPropName")); + break; + case SetPropertyNameType.WithName: + builder = builderWithProperty.WithValidation(o => o.InclusiveBetween(0, 100) + .WithName("TestName")); + break; + } + + var result = builder.Build().Validate(new ObjWithTwoPublicProps() { PercentValue1 = 1, PercentValue2 = 100 }); + + Assert.That(result.IsValid, Is.False); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + case SetPropertyNameType.WithName: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo("percentSum")); + break; + case SetPropertyNameType.Override: + Assert.That(result.Errors.FirstOrDefault()?.PropertyName, Is.EqualTo("TestPropName")); + break; + } + + if (setPropertyNameType == SetPropertyNameType.WithName) + { + Assert.That(result.Errors.FirstOrDefault()?.ErrorMessage, Does.Contain("TestName")); + } + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Validate_Primitive(bool valid) + { + int value; + + if(valid) + value = 3; + else + value = 1; + + var result = new ExpressValidatorBuilder() + .AddFunc(i => i, "value") + .WithValidation(o => o.GreaterThan(2)) + .BuildAndValidate(value); + + Assert.That(result.IsValid, Is.EqualTo(valid)); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ExpressValidatorWithOptionsTests.ForAsync.Tests.cs b/tests/ExpressValidator.Tests.Net8/ExpressValidatorWithOptionsTests.ForAsync.Tests.cs new file mode 100644 index 0000000..e4f0cbb --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressValidatorWithOptionsTests.ForAsync.Tests.cs @@ -0,0 +1,59 @@ +using FluentValidation; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class ExpressValidatorWithOptonsTests + { + [Test] + public void Should_Validate_Throw_For_AsyncRule_ForProperty() + { + var builder = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithAsyncValidation((_, p) => p.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(_objWithTwoPublicPropsOptions); + var exc = Assert.Throws(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, S = "b" })); + Assert.That(exc.Message, Does.StartWith("Object validator has a property or field with asynchronous validation rules.")); + } + + [Test] + public void Should_Validate_Throw_For_AsyncRule_ForField() + { + var builder = new ExpressValidatorBuilder() + .AddField(o => o._sField) + .WithAsyncValidation((_, f) => f.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(_objWithTwoPublicPropsOptions); + var exc = Assert.Throws(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, _sField = "b" })); + Assert.That(exc.Message, Does.StartWith("Object validator has a property or field with asynchronous validation rules.")); + } + + [Test] + public void Should_Validate_Throw_For_AsyncRule_ForFunc() + { + var builder = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithAsyncValidation((_, f) => f.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(_objWithTwoPublicPropsOptions); + var exc = Assert.Throws(() => builder.Validate(new ObjWithTwoPublicProps() { I = 1, _sField = "b" })); + Assert.That(exc.Message, Does.StartWith("Object validator has a property or field with asynchronous validation rules.")); + } + + [Test] + public async Task Should_ValidateAsync_Work_For_AsyncRules() + { + var result = await new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithAsyncValidation((_, p) => p.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .AddField(o => o._sField) + .WithAsyncValidation((_, p) => p.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithAsyncValidation((_, p) => p.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(_objWithTwoPublicPropsOptions) + .ValidateAsync(new ObjWithTwoPublicProps() { I = 1, _sField = "" }); + + Assert.That(result.IsValid, Is.EqualTo(true)); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ExpressValidatorWithOptionsTests.cs b/tests/ExpressValidator.Tests.Net8/ExpressValidatorWithOptionsTests.cs new file mode 100644 index 0000000..6858bb7 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ExpressValidatorWithOptionsTests.cs @@ -0,0 +1,277 @@ +using FluentValidation; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Linq; +using System.Reflection; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class ExpressValidatorWithOptonsTests + { + private readonly ObjWithTwoPublicPropsOptions _objWithTwoPublicPropsOptions = new() + { + IGreaterThanValue = 0, + SMaximumLengthValue = 1, + SFieldMaximumLengthValue = 1, + PercentSumMinValue = 0, + PercentSumMaxValue = 100, + }; + + [Test] + public void Should_Work_When_IsValid() + { + var result = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation((to, p) => p.GreaterThan(to.IGreaterThanValue)) + .AddProperty(o => o.S) + .WithValidation((to, p)=> p.MaximumLength(to.SMaximumLengthValue)) + .AddField(o => o._sField) + .WithValidation((to, f) => f.MaximumLength(to.SFieldMaximumLengthValue)) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation((to, f) => f.InclusiveBetween(to.PercentSumMinValue, to.PercentSumMaxValue)) + .Build(_objWithTwoPublicPropsOptions) + .Validate(new ObjWithTwoPublicProps() { I = 1, S = "b", _sField = "1", PercentValue2 = 80}); + ClassicAssert.AreEqual(true, result.IsValid); + } + + [Test] + public void Should_Validation_Result_Change_When_Options_Change() + { + var builder = new ExpressValidatorBuilder() + .AddProperty(o => o.I) + .WithValidation((to, p) => p.GreaterThan(to.IGreaterThanValue)) + .AddProperty(o => o.S) + .WithValidation((to, p) => p.MaximumLength(to.SMaximumLengthValue)) + .AddField(o => o._sField) + .WithValidation((to, f) => f.MaximumLength(to.SFieldMaximumLengthValue)) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation((to, f) => f.InclusiveBetween(to.PercentSumMinValue, to.PercentSumMaxValue)); + + var result1 = builder.Build(_objWithTwoPublicPropsOptions) + .Validate(new ObjWithTwoPublicProps() { I = 1, S = "b", _sField = "1", PercentValue2 = 80}); + ClassicAssert.AreEqual(true, result1.IsValid); + + var options2 = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 2, SMaximumLengthValue = 2, SFieldMaximumLengthValue = 1, PercentSumMaxValue = 80 }; + var result2 = builder.Build(options2) + .Validate(new ObjWithTwoPublicProps() { I = 1, S = "abc", _sField = "12", PercentValue2 = 90}); + ClassicAssert.AreEqual(false, result2.IsValid); + ClassicAssert.AreEqual(4, result2.Errors.Count); + + var options3 = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 3, SMaximumLengthValue = 3, SFieldMaximumLengthValue = 1, PercentSumMaxValue = 70 }; + var result3 = builder.Build(options3) + .Validate(new ObjWithTwoPublicProps() { I = 2, S = "abcd", _sField = "123", PercentValue2 = 80 }); + ClassicAssert.AreEqual(false, result3.IsValid); + ClassicAssert.AreEqual(4, result3.Errors.Count); + } + + [Test] + [TestCase(OnFirstPropertyValidatorFailed.Break)] + [TestCase(OnFirstPropertyValidatorFailed.Continue)] + public void Should_Work_When_NotValid(OnFirstPropertyValidatorFailed validationMode) + { + var result = new ExpressValidatorBuilder(validationMode) + .AddProperty(o => o.I) + .WithValidation((to, p) => p.GreaterThan(to.IGreaterThanValue)) + .AddProperty(o => o.S) + .WithValidation((to, p) => p.MaximumLength(to.SMaximumLengthValue)) + .AddField(o => o._sField) + .WithValidation((to, f) => f.MaximumLength(to.SFieldMaximumLengthValue)) + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum") + .WithValidation((to, f) => f.InclusiveBetween(to.PercentSumMinValue, to.PercentSumMaxValue)) + .Build(_objWithTwoPublicPropsOptions) + .Validate(new ObjWithTwoPublicProps() { I = -1, S = "ab", _sField = "ab", PercentValue1 = 2, PercentValue2 = 101 }); + ClassicAssert.AreEqual(false, result.IsValid); + if (validationMode == OnFirstPropertyValidatorFailed.Break) + { + ClassicAssert.AreEqual(1, result.Errors.Count); + } + else + { + ClassicAssert.AreEqual(4, result.Errors.Count); + } + } + + [Test] + [TestCase(SetPropertyNameType.WithName, MemberTypes.Property)] + [TestCase(SetPropertyNameType.NotSetExplicitly, MemberTypes.Property)] + [TestCase(SetPropertyNameType.Override, MemberTypes.Property)] + [TestCase(SetPropertyNameType.WithName, MemberTypes.Field)] + [TestCase(SetPropertyNameType.NotSetExplicitly, MemberTypes.Field)] + [TestCase(SetPropertyNameType.Override, MemberTypes.Field)] + public void Should_Preserve_Property_Name(SetPropertyNameType setPropertyNameType, MemberTypes memberTypes) + { + var builder = new ExpressValidatorBuilder(); + IBuilderWithPropValidator builderWithProperty = null; + + if (memberTypes == MemberTypes.Property) + { + builderWithProperty = builder.AddProperty(o => o.I); + } + else + { + builderWithProperty = builder.AddField(o => o._iField); + } + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + builder = builderWithProperty.WithValidation((to, p) => p.GreaterThan(to.IGreaterThanValue)); + break; + case SetPropertyNameType.Override: + builder = builderWithProperty.WithValidation((to, p) => p.GreaterThan(to.IGreaterThanValue).OverridePropertyName("TestPropName")); + break; + case SetPropertyNameType.WithName: + builder = builderWithProperty.WithValidation((to, p) => p.GreaterThan(to.IGreaterThanValue).WithName("TestName")); + break; + } + var result = builder.Build(_objWithTwoPublicPropsOptions).Validate(new ObjWithTwoPublicProps() { I = -1 }); + + Assert.That(result.IsValid, Is.False); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + case SetPropertyNameType.WithName: + Assert.That(result.Errors.FirstOrDefault().PropertyName, memberTypes == MemberTypes.Property ? Is.EqualTo("I") : Is.EqualTo("_iField")); + break; + case SetPropertyNameType.Override: + Assert.That(result.Errors.FirstOrDefault().PropertyName, Is.EqualTo("TestPropName")); + break; + } + + if (setPropertyNameType == SetPropertyNameType.WithName) + { + Assert.That(result.Errors.FirstOrDefault().ErrorMessage, Does.Contain("TestName")); + } + } + + [Test] + public void Should_IsValid_Equals_True_WhenNoValidators() + { + var result = new ExpressValidatorBuilder() + .Build(_objWithTwoPublicPropsOptions) + .Validate(new ObjWithTwoPublicProps()); + Assert.That(result.IsValid, Is.True); + } + + [Test] + public void Should_Throw_When_Non_Property() + { + Assert.Throws + (() => new ExpressValidatorBuilder() + .AddProperty(o => o) + .WithValidation((_, p) => p.NotNull()) + .Build(_objWithTwoPublicPropsOptions)); + } + + [Test] + public void Should_Throw_When_Non_Field() + { + Assert.Throws + (() => new ExpressValidatorBuilder() + .AddField(o => o) + .WithValidation((_, p) => p.NotNull()) + .Build(_objWithTwoPublicPropsOptions)); + } + + [Test] + [TestCase(SetPropertyNameType.WithName)] + [TestCase(SetPropertyNameType.NotSetExplicitly)] + [TestCase(SetPropertyNameType.Override)] + public void Should_AddFunc_Preserve_Property_Name(SetPropertyNameType setPropertyNameType) + { + var builder = new ExpressValidatorBuilder(); + var builderWithProperty = builder.AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum"); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + builder = builderWithProperty.WithValidation((to, p) => p.InclusiveBetween(to.PercentSumMinValue, to.PercentSumMaxValue)); + break; + case SetPropertyNameType.Override: + builder = builderWithProperty.WithValidation((to, p) => p.InclusiveBetween(to.PercentSumMinValue, to.PercentSumMaxValue) + .OverridePropertyName("TestPropName")); + break; + case SetPropertyNameType.WithName: + builder = builderWithProperty.WithValidation((to, p) => p.InclusiveBetween(to.PercentSumMinValue, to.PercentSumMaxValue) + .WithName("TestName")); + break; + } + + var result = builder.Build(_objWithTwoPublicPropsOptions).Validate(new ObjWithTwoPublicProps() { PercentValue1 = 1, PercentValue2 = 100 }); + + Assert.That(result.IsValid, Is.False); + + switch (setPropertyNameType) + { + case SetPropertyNameType.NotSetExplicitly: + case SetPropertyNameType.WithName: + Assert.That(result.Errors.FirstOrDefault().PropertyName, Is.EqualTo("percentSum")); + break; + case SetPropertyNameType.Override: + Assert.That(result.Errors.FirstOrDefault().PropertyName, Is.EqualTo("TestPropName")); + break; + } + + if (setPropertyNameType == SetPropertyNameType.WithName) + { + Assert.That(result.Errors.FirstOrDefault().ErrorMessage, Does.Contain("TestName")); + } + } + + [Test] + public void Should_Invoke_SuccessValidationHandler_When_IsValid() + { + int percentSum = 0; + var options = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 0, IGreaterThanValue2 = 100 }; + var result = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum", (p) => percentSum = p) + .WithValidation((topt, o) => o.InclusiveBetween(topt.IGreaterThanValue, topt.IGreaterThanValue2)) + .Build(options) + .Validate(new ObjWithTwoPublicProps() { PercentValue1 = 20, PercentValue2 = 80 }); + Assert.That(percentSum, Is.EqualTo(100)); + Assert.That(result.IsValid, Is.True); + } + + [Test] + public void Should_Not_Invoke_SuccessValidationHandler_When_IsNotValid() + { + int percentSum = 0; + var options = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 0, IGreaterThanValue2 = 100}; + var result = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum", (p) => percentSum = p) + .WithValidation((topt, o) => o.InclusiveBetween(topt.IGreaterThanValue, topt.IGreaterThanValue2)) + .Build(options) + .Validate(new ObjWithTwoPublicProps() { PercentValue1 = 21, PercentValue2 = 83 }); + Assert.That(percentSum, Is.EqualTo(0)); + Assert.That(result.IsValid, Is.False); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Workaround_For_Condition_Using_Validating_Object_Work(bool isValid) + { + var customer = new Customer() { CustomerDiscount = 0, IsPreferredCustomer = !isValid }; + + var result = new ExpressValidatorBuilder() + .AddProperty(c => c.CustomerDiscount) + .WithValidation((c, p) => p.GreaterThan(0) + .When((_) => c.IsPreferredCustomer)) + .Build(customer) + .Validate(customer); + + if (isValid) + { + Assert.That(result.IsValid, Is.True); + } + else + { + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors.FirstOrDefault().PropertyName, Is.EqualTo(nameof(Customer.CustomerDiscount))); + } + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/NotNullValidationMessageProviderTests.cs b/tests/ExpressValidator.Tests.Net8/NotNullValidationMessageProviderTests.cs new file mode 100644 index 0000000..4dac7ad --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/NotNullValidationMessageProviderTests.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using NUnit.Framework; +using System; + +namespace ExpressValidator.Tests.Net8 +{ + public class NotNullValidationMessageProviderTests + { +#pragma warning disable S1133 // Deprecated code should be removed + [Obsolete("This test is obsolete")] +#pragma warning restore S1133 // Deprecated code should be removed + [Test] + public void Should_GetMessage_Returns_CorrectMessage_For_Null_Instance() + { + const string propName = "TestPropName"; + var notNullMsgProvider = new NotNullValidationMessageProvider(propName); + var res = notNullMsgProvider.GetMessage(new ValidationContext(null)); + Assert.That(res.Contains(propName), Is.True); + } + + [Test] + public void Should_NullFallbackMessageProvider_Returns_CorrectMessage_For_Null_Instance() + { + const string propName = "TestPropName"; + var res = NullFallbackMessageProvider.GetMessage(propName, new ValidationContext(null)); + Assert.That(res.Contains(propName), Is.True); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ObjectsToTests.cs b/tests/ExpressValidator.Tests.Net8/ObjectsToTests.cs new file mode 100644 index 0000000..21207f4 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ObjectsToTests.cs @@ -0,0 +1,126 @@ +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + public class SubObjWithComplexProperty : ObjWithTwoPublicProps + { + public Contact? Contact { get; set; } + } + + public class SubObjWithComplexCollectionProperty : ObjWithTwoPublicProps + { + public IEnumerable? Contacts { get; set; } + } + + public class ObjWithTwoPublicProps + { + public int I { get; set; } + public string? S { get; set; } + public string? _sField; + public int _iField; + public int PercentValue1 { get; set; } + public int PercentValue2 { get; set; } + } + + public class ObjWithTwoPublicPropsOptions + { + public int IGreaterThanValue { get; set; } + public int IGreaterThanValue2 { get; set; } + public int SMaximumLengthValue { get; set; } + public int SFieldMaximumLengthValue { get; set; } + public int PercentSumMinValue { get; set; } + public int PercentSumMaxValue { get; set; } + } + + public class SimpleContactValidator : AbstractValidator + { + public SimpleContactValidator() + { + RuleFor(x => x.Name).NotNull(); + RuleFor(x => x.Email).NotNull(); + } + } + + public class ContactValidator : AbstractValidator + { + public ContactValidator() + { + RuleFor(x => x.Name) + .NotEmpty() + .MaximumLength(100); + + RuleFor(x => x.Email) + .NotEmpty() + .EmailAddress(); + } + } + + public class Contact + { + public string? Name { get; set; } + public string? Email { get; set; } + + public string? K { get; set; } + } + + public class ContactNullableStructValidator : AbstractValidator + { + public ContactNullableStructValidator() + { + RuleFor(x => x!.Value.Name) + .NotEmpty() + .MaximumLength(100); + + RuleFor(x => x!.Value.Email) + .NotEmpty() + .EmailAddress(); + } + } + + public struct ContactStruct + { + public string Name { get; set; } + public string Email { get; set; } + } + + internal class ObjWithNullable + { + public string Value { get; set; } = "Test"; + } + + internal enum SetPropertyNameType + { + NotSetExplicitly, + Override, + WithName + } + + public class Customer + { + public int CustomerId { get; set; } + public string? Name { get; set; } + public decimal CustomerDiscount { get; set; } + public bool IsPreferredCustomer { get; set; } + } + + public class SomeExternalWebApiClient + { + private readonly int _existedId; + + public SomeExternalWebApiClient(int existedId) + { + _existedId = existedId; + } + + public async Task IdExistsAsync(int id, CancellationToken token) + { + await Task.Delay(TimeSpan.FromTicks(1), token); + return _existedId == id; + } + + } +} diff --git a/tests/ExpressValidator.Tests.Net8/PropertyValidationProcessorTests.cs b/tests/ExpressValidator.Tests.Net8/PropertyValidationProcessorTests.cs new file mode 100644 index 0000000..d178703 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/PropertyValidationProcessorTests.cs @@ -0,0 +1,85 @@ +using FluentValidation; +using NUnit.Framework; +using System; +using System.Reflection; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal class PropertyValidationProcessorTests + { + [Test] + [TestCase("t", true, false)] + [TestCase("t", true, true)] + [TestCase("tt", false, null)] + public void Should_Validate_When_IsAsync_False(string whatToTest, bool result, bool? useHandler) + { + string handlerResult = null; + void successHandler(string s) { handlerResult = s; } + + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + var validator = new TypeValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + + var processor = new PropertyValidationProcessor(o => o.Value, validator, useHandler == true ? successHandler : null); + + var (IsValid, Failures) = processor.Validate(new ObjWithNullable() { Value = whatToTest }); + Assert.That (IsValid, Is.EqualTo(result)); + if (!result) + { + Assert.That(Failures.Count, Is.EqualTo(1)); + } + else + { + Assert.That(handlerResult, Is.EqualTo(useHandler == true ? whatToTest : null)); + } + } + + [Test] + [TestCase("t")] + [TestCase("tt")] + public void Should_Throw_On_Validate_When_IsAsync_True(string whatToTest) + { + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + var validator = new TypeAsyncValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + + var processor = new PropertyValidationProcessor(o => o.Value, validator, null); + + Assert.Throws(() => processor.Validate(new ObjWithNullable() { Value = whatToTest })); + } + + [Test] + [TestCase("t", true, false, true)] + [TestCase("t", true, true, true)] + [TestCase("tt", false, null, true)] + [TestCase("t", true, false, false)] + [TestCase("t", true, true, false)] + [TestCase("tt", false, null, false)] + public async Task Should_ValidateAsync_Do_Not_Depend_On_TypeValidator_Sync_Type(string whatToTest, bool result, bool? useHandler, bool isAsync) + { + string handlerResult = null; + void successHandler(string s) { handlerResult = s; } + + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + TypeValidatorBase validator = isAsync ? new TypeAsyncValidator() : new TypeValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + + var processor = new PropertyValidationProcessor(o => o.Value, validator, useHandler == true ? successHandler : null); + + var (IsValid, Failures) = await processor.ValidateAsync(new ObjWithNullable() { Value = whatToTest }); + Assert.That(IsValid, Is.EqualTo(result)); + if (!result) + { + Assert.That(Failures.Count, Is.EqualTo(1)); + } + else + { + Assert.That(handlerResult, Is.EqualTo(useHandler == true ? whatToTest : null)); + } + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/QuickValidatorTests.cs b/tests/ExpressValidator.Tests.Net8/QuickValidatorTests.cs new file mode 100644 index 0000000..0091207 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/QuickValidatorTests.cs @@ -0,0 +1,584 @@ +using ExpressValidator.QuickValidation; +using FluentValidation; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal class QuickValidatorTests + { + [Test] + public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForPrimitiveType_UsingOverload_WithPropertyName() + { + const int valueToTest = 5; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10) + .GreaterThan(15), + nameof(valueToTest)); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(valueToTest))); + } + + [Test] + public async Task Should_Fail_WithExpectedPropertyName_When_AsyncValidationFails_ForPrimitiveType_UsingOverload_WithPropertyName() + { + const int valueToTest = 5; + var result = await QuickValidator.ValidateAsync(valueToTest, + (opt) => opt.GreaterThan(10) + .GreaterThan(15) + .MustAsync(async (_, __) => { await Task.Delay(1); return true; }), + nameof(valueToTest)); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(valueToTest))); + } + + [Test] + public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForPrimitiveType_UsingOverload_WithPropertyName() + { + const int valueToTest = 5; + const string propName = "MyPropName"; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt + .OverridePropertyName(propName) + .GreaterThan(10) + .GreaterThan(15), + nameof(valueToTest)); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(propName)); + } + + [Test] + public async Task Should_Fail_WithOverriddenPropertyName_When_AsyncValidationFails_ForPrimitiveType_UsingOverload_WithPropertyName() + { + const int valueToTest = 5; + const string propName = "MyPropName"; + var result = await QuickValidator.ValidateAsync(valueToTest, + (opt) => opt + .OverridePropertyName(propName) + .GreaterThan(10) + .GreaterThan(15) + .MustAsync(async (_, __) => { await Task.Delay(1); return true; }), + nameof(valueToTest)); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(propName)); + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + const int valueToTest = 5; + const string propName = "MyPropName"; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt + .OverridePropertyName(propName) + .GreaterThan(10) + .GreaterThan(15), + mode); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(propName)); + Assert.That(result.Errors[1].PropertyName, Is.EqualTo(propName)); + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public async Task Should_Fail_WithOverriddenPropertyName_When_AsyncValidationFails_ForPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + const int valueToTest = 5; + const string propName = "MyPropName"; + var result = await QuickValidator.ValidateAsync(valueToTest, + (opt) => opt + .OverridePropertyName(propName) + .GreaterThan(10) + .GreaterThan(15) + .MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + , + mode); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(propName)); + Assert.That(result.Errors[1].PropertyName, Is.EqualTo(propName)); + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + const int valueToTest = 5; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10) + .GreaterThan(15), + mode); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input")); + } + else + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(typeof(int).Name)); + } + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public async Task Should_Fail_WithExpectedPropertyName_When_AsyncValidationFails_ForPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + const int valueToTest = 5; + var result = await QuickValidator.ValidateAsync(valueToTest, + (opt) => opt.GreaterThan(10) + .GreaterThan(15) + .MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + , + mode); + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input")); + } + else + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(typeof(int).Name)); + } + } + + [Test] + public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyName() + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetRule(); + + var result = QuickValidator.Validate(objToQuick, + rule, + nameof(objToQuick)); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick) + "." + nameof(ObjWithTwoPublicProps.I))); + } + + [Test] + public async Task Should_Fail_WithExpectedPropertyName_When_AsyncValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyName() + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetAsyncRule(); + + var result = await QuickValidator.ValidateAsync(objToQuick, + rule, + nameof(objToQuick)); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick) + "." + nameof(ObjWithTwoPublicProps.I))); + } + + [Test] + public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyName() + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetRuleWithOverriddenPropertyName(); + + var result = QuickValidator.Validate(objToQuick, + rule, + nameof(objToQuick)); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick) + ".MyPropNameI")); + } + + [Test] + public async Task Should_Fail_WithOverriddenPropertyName_When_AsyncValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyName() + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetAsyncRuleWithOverriddenPropertyName(); + + var result = await QuickValidator.ValidateAsync(objToQuick, + rule, + nameof(objToQuick)); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(objToQuick) + ".MyPropNameI")); + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public void Should_Fail_WithOverriddenPropertyName_When_ValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetRuleWithOverriddenPropertyName(); + + var result = QuickValidator.Validate(objToQuick, + rule, + mode); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input.MyPropNameI")); + } + else + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(ObjWithTwoPublicProps) + ".MyPropNameI")); + } + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public async Task Should_Fail_WithOverriddenPropertyName_When_AsyncValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetAsyncRuleWithOverriddenPropertyName(); + + var result = await QuickValidator.ValidateAsync(objToQuick, + rule, + mode); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input.MyPropNameI")); + } + else + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(ObjWithTwoPublicProps) + ".MyPropNameI")); + } + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public void Should_Fail_WithExpectedPropertyName_When_ValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetRule(); + + var result = QuickValidator.Validate(objToQuick, + rule, + mode); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input." + nameof(ObjWithTwoPublicProps.I))); + } + else + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(ObjWithTwoPublicProps) + "." + nameof(ObjWithTwoPublicProps.I))); + } + } + + [Test] + public void Should_Fail_When_NonPrimitive_Value_Is_Null_With_NotNull_Rule() + { + var rule = GetRule(); + + var result = QuickValidator.Validate(null, rule); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Fail_When_NonPrimitive_Value_Is_Null_With_Mixed_Rules() + { + var rule = GetMixedWithNullRules(); + + var result = QuickValidator.Validate(null, rule); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Valid_When_NonPrimitive_Value_Is_Null_With_Null_Rules() + { + var rule = GetNullRules(); + + var result = QuickValidator.Validate(null, rule); + Assert.That(result.IsValid, Is.True); + } + + [Test] + public void Should_Fail_When_Nullable_Struct_Is_Null_With_NotNull_Rule() + { + var result = QuickValidator.Validate(null, + (opt) => opt.GreaterThan(10) + .GreaterThan(15)); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Fail_When_Nullable_Struct_Is_Null_With_Mixed_Rule() + { + var result = QuickValidator.Validate(null, + (opt) => + opt + .Null() + .GreaterThan(10) + .GreaterThan(15)); + Assert.That(result.IsValid, Is.False); + } + + [Test] + public void Should_Valid_When_Nullable_Struct_Is_Null_With_Null_Rules() + { + var result = QuickValidator.Validate(null, + (opt) => + opt + .Null() + .Empty()); + Assert.That(result.IsValid, Is.True); + } + + [Test] + [TestCase(PropertyNameMode.Default)] + [TestCase(PropertyNameMode.TypeName)] + public async Task Should_Fail_WithExpectedPropertyName_When_AsyncValidationFails_ForNonPrimitiveType_UsingOverload_WithPropertyNameMode(PropertyNameMode mode) + { + var objToQuick = new ObjWithTwoPublicProps() { I = -1, PercentValue1 = 101 }; + var rule = GetAsyncRule(); + + var result = await QuickValidator.ValidateAsync(objToQuick, + rule, + mode); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(2)); + if (mode == PropertyNameMode.Default) + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo("Input." + nameof(ObjWithTwoPublicProps.I))); + } + else + { + Assert.That(result.Errors[0].PropertyName, Is.EqualTo(nameof(ObjWithTwoPublicProps) + "." + nameof(ObjWithTwoPublicProps.I))); + } + } + + [Test] + public void Should_Pass_Validation_When_Valid() + { + const int valueToTest = 25; + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10) + .InclusiveBetween(15, 25)); + Assert.That(result.IsValid, Is.True); + } + + [Test] + public async Task Should_Pass_AsyncValidation_When_Valid() + { + const int valueToTest = 25; + var result = await QuickValidator.ValidateAsync(valueToTest, + (opt) => opt.GreaterThan(10) + .InclusiveBetween(15, 25)); + Assert.That(result.IsValid, Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Call_OnSuccess_When_Validation_Succeeds(bool isValid) + { + int valueFromHandler = 0; + int valueToTest; + if (isValid) + { + valueToTest = 25; + } + else + { + valueToTest = 5; + } + + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10), + "vv", + (v) => valueFromHandler = v); + if (isValid) + { + Assert.That(result.IsValid, Is.True); + Assert.That(valueFromHandler, Is.EqualTo(25)); + } + else + { + Assert.That(result.IsValid, Is.False); + Assert.That(valueFromHandler, Is.EqualTo(0)); + } + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_Call_OnSuccess_When_ValidationAsync_Succeeds(bool isValid) + { + int valueFromHandler = 0; + int valueToTest; + if (isValid) + { + valueToTest = 25; + } + else + { + valueToTest = 5; + } + + var result = await QuickValidator.ValidateAsync(valueToTest, + (opt) => opt.GreaterThan(10), + "vv", + (v) => valueFromHandler = v); + if (isValid) + { + Assert.That(result.IsValid, Is.True); + Assert.That(valueFromHandler, Is.EqualTo(25)); + } + else + { + Assert.That(result.IsValid, Is.False); + Assert.That(valueFromHandler, Is.EqualTo(0)); + } + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_Call_OnSuccess_When_Validation_Succeeds_UsingOverload_WithPropertyNameMode(bool isValid) + { + int valueFromHandler = 0; + int valueToTest; + if (isValid) + { + valueToTest = 25; + } + else + { + valueToTest = 5; + } + + var result = QuickValidator.Validate(valueToTest, + (opt) => opt.GreaterThan(10), + PropertyNameMode.TypeName, + (v) => valueFromHandler = v); + if (isValid) + { + Assert.That(result.IsValid, Is.True); + Assert.That(valueFromHandler, Is.EqualTo(25)); + } + else + { + Assert.That(result.IsValid, Is.False); + Assert.That(valueFromHandler, Is.EqualTo(0)); + } + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_Call_OnSuccess_When_AsyncValidation_Succeeds_UsingOverload_WithPropertyNameMode(bool isValid) + { + int valueFromHandler = 0; + int valueToTest; + if (isValid) + { + valueToTest = 25; + } + else + { + valueToTest = 5; + } + + var result = await QuickValidator.ValidateAsync(valueToTest, + (opt) => opt.GreaterThan(10), + PropertyNameMode.TypeName, + (v) => valueFromHandler = v); + if (isValid) + { + Assert.That(result.IsValid, Is.True); + Assert.That(valueFromHandler, Is.EqualTo(25)); + } + else + { + Assert.That(result.IsValid, Is.False); + Assert.That(valueFromHandler, Is.EqualTo(0)); + } + } + + private static Action> GetRule() + { + return (opt) => + opt + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0)) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100)); + } + + private static Action> GetMixedWithNullRules() + { + return (opt) => + opt.Null() + .Empty() + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0)) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100)); + } + + private static Action> GetNullRules() + { + return (opt) => + opt.Null() + .Empty(); + } + + private static Action> GetAsyncRule() + { + return (opt) => + opt + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0)) + .MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100) + .MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + ); + } + + private static Action> GetRuleWithOverriddenPropertyName() + { + return (opt) => + opt + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0).OverridePropertyName("MyPropNameI")) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100)); + } + + private static Action> GetAsyncRuleWithOverriddenPropertyName() + { + return (opt) => + opt + .ChildRules((v) => v.RuleFor(o => o.I) + .GreaterThan(0).OverridePropertyName("MyPropNameI")) + .MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + .ChildRules((v) => v.RuleFor(o => o.PercentValue1) + .InclusiveBetween(0, 100) + .MustAsync(async (_, __) => { await Task.Delay(1); return true; }) + ); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.ForNullOrEmptyValidator.cs b/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.ForNullOrEmptyValidator.cs new file mode 100644 index 0000000..23e845f --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.ForNullOrEmptyValidator.cs @@ -0,0 +1,98 @@ +using FluentValidation; +using NUnit.Framework.Legacy; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class TypeAsyncValidatorTests + { + [Test] + [TestCase(true, null, true)] + [TestCase(false, null, true)] + [TestCase(true, "t", false)] + [TestCase(false, "t", false)] + public async Task Should_OnlyNullValidation_Be_Corect_ForNull_And_NotNull(bool single, string? valueToTest, bool isValid) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Null(), "someprop"); + else + validator.SetValidation(o => o.Null().Null(), "someprop"); + + var (IsValid, _) = await validator.ValidateExAsync(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + + [Test] + [TestCase(true, null, true)] + [TestCase(false, null, true)] + [TestCase(true, 1, false)] + [TestCase(false, 1, false)] + public async Task Should_OnlyNullValidation_Be_Corect_ForNull_And_NotNull_NullableValue(bool single, int? valueToTest, bool isValid) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Null(), "someprop"); + else + validator.SetValidation(o => o.Null().Null(), "someprop"); + + var (IsValid, _) = await validator.ValidateExAsync(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + + [Test] + [TestCase(true, null, true)] + [TestCase(false, null, true)] + [TestCase(true, "t", false)] + [TestCase(false, "t", false)] + public async Task Should_OnlyEmptyValidation_Be_Corect_ForNull_And_NotNull(bool single, string? valueToTest, bool isValid) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Empty(), string.Empty); + else + validator.SetValidation(o => o.Empty().Empty(), string.Empty); + + var (IsValid, _) = await validator.ValidateExAsync(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + + [Test] + [TestCase(true, null, true)] + [TestCase(false, null, true)] + [TestCase(true, 1, false)] + [TestCase(false, 1, false)] + public async Task Should_OnlyEmptyValidation_Be_Corect_ForNull_And_NotNull_For_NullableValue(bool single, int? valueToTest, bool isValid) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Empty(), string.Empty); + else + validator.SetValidation(o => o.Empty().Empty(), string.Empty); + + var (IsValid, _) = await validator.ValidateExAsync(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + + [Test] + [TestCase(null, true)] + [TestCase("t", false)] + public async Task Should_NullAndEmptyValidation_Be_Corect_ForNull_And_NotNull(string? valueToTest, bool isValid) + { + var validator = new TypeAsyncValidator(); + validator.SetValidation(o => o.Empty().Null().Null().Empty(), "someprop"); + var (IsValid, _) = await validator.ValidateExAsync(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + + [Test] + [TestCase(null, true)] + [TestCase(1, false)] + public async Task Should_NullAndEmptyValidation_Be_Corect_ForNull_And_NotNull_For_NullableValue(int? valueToTest, bool isValid) + { + var validator = new TypeAsyncValidator(); + validator.SetValidation(o => o.Empty().Null().Null().Empty(), "someprop"); + var (IsValid, _) = await validator.ValidateExAsync(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.ForNullOrEmptyWithOtherValidator.cs b/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.ForNullOrEmptyWithOtherValidator.cs new file mode 100644 index 0000000..6272f0d --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.ForNullOrEmptyWithOtherValidator.cs @@ -0,0 +1,122 @@ +using FluentValidation; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class TypeAsyncValidatorTests + { + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotOnlyNullValidation_Be_NotValid_ForNullValue(bool single) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Null().MinimumLength(1), "someprop"); + else + validator.SetValidation(o => o.Null().Null().MinimumLength(1), "someprop"); + + var (IsValid, Failures) = await validator.ValidateExAsync(null); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotOnlyNullValidation_Be_NotValid_ForNullValue_For_NullableValue(bool single) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Null().GreaterThan(1), "someprop"); + else + validator.SetValidation(o => o.Null().Null().GreaterThan(1), "someprop"); + + var (IsValid, Failures) = await validator.ValidateExAsync(null); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotOnlyEmptyValidation_Be_NotValid_ForNullValue(bool single) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Empty().MinimumLength(1), "someprop"); + else + validator.SetValidation(o => o.Empty().Empty().MinimumLength(1), "someprop"); + + var (IsValid, Failures) = await validator.ValidateExAsync(null); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotOnlyEmptyValidation_Be_NotValid_ForNullValue_For_NullableValue(bool single) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Empty().GreaterThan(1), "someprop"); + else + validator.SetValidation(o => o.Empty().Empty().GreaterThan(1), "someprop"); + + var (IsValid, Failures) = await validator.ValidateExAsync(null); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotOnlyEmptyValidation_Be_NotValid_ForNullValue_For_RefProperty_IfEmptyValidators_AreLast(bool single) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.MinimumLength(1).Empty(), "someprop"); + else + validator.SetValidation(o => o.MinimumLength(1).Empty().Empty(), "someprop"); + + var (IsValid, Failures) = await validator.ValidateExAsync(null); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotOnlyEmptyValidation_Be_NotValid_ForDefaultValue_For_ValueProperty(bool single) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Empty().GreaterThan(1), "someprop"); + else + validator.SetValidation(o => o.Empty().Empty().GreaterThan(1), "someprop"); + + var (IsValid, Failures) = await validator.ValidateExAsync(0); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotOnlyEmptyValidation_Be_NotValid_ForDefaultValue_For_NullableValueProperty(bool single) + { + var validator = new TypeAsyncValidator(); + if (single) + validator.SetValidation(o => o.Empty().GreaterThan(1), "someprop"); + else + validator.SetValidation(o => o.Empty().Empty().GreaterThan(1), "someprop"); + + var (IsValid, Failures) = await validator.ValidateExAsync(0); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(single ? 2 : 3, Failures.Count); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.cs b/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.cs new file mode 100644 index 0000000..1b70284 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/TypeAsyncValidatorTests.cs @@ -0,0 +1,38 @@ +using NUnit.Framework; +using System.Threading.Tasks; +using FluentValidation; +using System.Reflection; +using NUnit.Framework.Legacy; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class TypeAsyncValidatorTests + { + [Test] + [TestCase("t", true)] + [TestCase("tt", false)] + public async Task Should_ValidateAsync_ForUsualRules_Work(string whatToTest, bool result) + { + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + var validator = new TypeAsyncValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + var res = await validator.ValidateAsync(whatToTest); + ClassicAssert.AreEqual(result, res.IsValid); + if (!result) + { + ClassicAssert.AreEqual(1, res.Errors.Count); + } + } + + [Test] + public void Should_Validate_Throw() + { + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + var validator = new TypeAsyncValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + Assert.Throws(() => validator.Validate("t")) ; + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.ForNullOrEmptyValidator.cs b/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.ForNullOrEmptyValidator.cs new file mode 100644 index 0000000..94322d0 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.ForNullOrEmptyValidator.cs @@ -0,0 +1,68 @@ +using FluentValidation; +using NUnit.Framework.Legacy; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class TypeValidatorTests + { + [Test] + [TestCase(true, null, true)] + [TestCase(false, null, true)] + [TestCase(true, "t", false)] + [TestCase(false, "t", false)] + public void Should_OnlyNullValidation_Be_Corect_ForNull_And_NotNull(bool single, string? valueToTest, bool isValid) + { + var validator = new TypeValidator(); + if (single) + validator.SetValidation(o => o.Null(), "someprop"); + else + validator.SetValidation(o => o.Null().Null(), "someprop"); + + var (IsValid, _) = validator.ValidateEx(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + + [Test] + [TestCase(true, null, true)] + [TestCase(false, null, true)] + [TestCase(true, "t", false)] + [TestCase(false, "t", false)] + public void Should_OnlyEmptyValidation_Be_Corect_ForNull_And_NotNull(bool single, string? valueToTest, bool isValid) + { + var validator = new TypeValidator(); + if (single) + validator.SetValidation(o => o.Empty(), "someprop"); + else + validator.SetValidation(o => o.Empty().Empty(), "someprop"); + + var (IsValid, _) = validator.ValidateEx(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + + [Test] + [TestCase(null, true)] + [TestCase("t", false)] + public void Should_NullAndEmptyValidation_Be_Corect_ForNull_And_NotNull(string? valueToTest, bool isValid) + { + var validator = new TypeValidator(); + validator.SetValidation(o => o.Empty().Null().Null().Empty(), "someprop"); + var (IsValid, _) = validator.ValidateEx(valueToTest); + ClassicAssert.AreEqual(isValid, IsValid); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_NotOnlyNullValidation_Be_NotValid_ForNullValue(bool single) + { + var validator = new TypeValidator(); + if (single) + validator.SetValidation(o => o.Null().MinimumLength(1), "someprop"); + else + validator.SetValidation(o => o.Null().Null().MinimumLength(1), "someprop"); + + var (IsValid, _) = validator.ValidateEx(null!); + ClassicAssert.AreEqual(false, IsValid); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.ForNullOrEmptyWithOtherValidator.cs b/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.ForNullOrEmptyWithOtherValidator.cs new file mode 100644 index 0000000..095a370 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.ForNullOrEmptyWithOtherValidator.cs @@ -0,0 +1,78 @@ +using FluentValidation; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class TypeValidatorTests + { + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_NotOnlyEmptyValidation_Be_NotValid_ForNullValue_For_RefProperty(bool single) + { + var validator = new TypeValidator(); + if (single) + validator.SetValidation(o => o.Empty().MinimumLength(1), "someprop"); + else + validator.SetValidation(o => o.Empty().Empty().MinimumLength(1), "someprop"); + + var (IsValid, Failures) = validator.ValidateEx(null); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_NotOnlyEmptyValidation_Be_NotValid_ForNullValue_For_RefProperty_IfEmptyValidators_AreLast(bool single) + { + var validator = new TypeValidator(); + if (single) + validator.SetValidation(o => o.MinimumLength(1).Empty(), "someprop"); + else + validator.SetValidation(o => o.MinimumLength(1).Empty().Empty(), "someprop"); + + var (IsValid, Failures) = validator.ValidateEx(null); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_NotOnlyNullValidation_Be_NotValid_ForNullValue_For_RefProperty(bool single) + { + var validator = new TypeValidator(); + if (single) + validator.SetValidation(o => o.Null().MinimumLength(1), "someprop"); + else + validator.SetValidation(o => o.Null().Null().MinimumLength(1), "someprop"); + + var (IsValid, Failures) = validator.ValidateEx(null); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Should_NotOnlyEmptyValidation_Be_NotValid_ForDefaultValue_For_ValueProperty(bool single) + { + var validator = new TypeValidator(); + if (single) + validator.SetValidation(o => o.Empty().GreaterThan(1), "someprop"); + else + validator.SetValidation(o => o.Empty().Empty().GreaterThan(1), "someprop"); + + var (IsValid, Failures) = validator.ValidateEx(0); + ClassicAssert.AreEqual(false, IsValid); + ClassicAssert.AreEqual(1, Failures.Count); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.cs b/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.cs new file mode 100644 index 0000000..633fd87 --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/TypeValidatorTests.cs @@ -0,0 +1,45 @@ +using FluentValidation; +using NUnit.Framework; +using NUnit.Framework.Legacy; +using System.Reflection; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests.Net8 +{ + internal partial class TypeValidatorTests + { + [Test] + [TestCase("t", true)] + [TestCase("tt", false)] + public void Should_Validate_ForUsualRules_Work(string whatToTest, bool result) + { + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + var validator = new TypeValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + var res = validator.Validate(whatToTest); + ClassicAssert.AreEqual(result, res.IsValid); + if (!result) + { + ClassicAssert.AreEqual(1, res.Errors.Count); + } + } + + [Test] + [TestCase("t", true)] + [TestCase("tt", false)] + public async Task Should_ValidateAsync_ForUsualRules_Work(string whatToTest, bool result) + { + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + var validator = new TypeValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + var res = await validator.ValidateAsync(whatToTest); + ClassicAssert.AreEqual(result, res.IsValid); + if (!result) + { + ClassicAssert.AreEqual(1, res.Errors.Count); + } + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/UtilitiesTests.cs b/tests/ExpressValidator.Tests.Net8/UtilitiesTests.cs new file mode 100644 index 0000000..6e55c1a --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/UtilitiesTests.cs @@ -0,0 +1,109 @@ +using NUnit.Framework; +using System.Reflection; + +namespace ExpressValidator.Tests.Net8 +{ + internal class UtilitiesTests + { + [Test] + public void Should_PropertyInfoParser_TryParse_Work_Correctly() + { + var resParseS = MemberInfoParser.TryParse(s => s.S!, MemberTypes.Property, out MemberInfo propertyInfoS); + Assert.That(resParseS, Is.True); + Assert.That(propertyInfoS.Name, Is.EqualTo("S")); + + var resParseI = MemberInfoParser.TryParse(s => s.I, MemberTypes.Property, out MemberInfo propertyInfoI); + Assert.That(resParseI, Is.True); + Assert.That(propertyInfoI.Name, Is.EqualTo("I")); + + var resParse = MemberInfoParser.TryParse(s => s, MemberTypes.Property, out MemberInfo propertyInfo); + Assert.That(resParse, Is.False); + } + + [Test] + public void Should_PropertyInfoParser_TryParse_For_MemberInfo_Work_Correctly() + { + var resParseS = MemberInfoParser.TryParse(s => s.S!, MemberTypes.Property, out MemberInfo memberInfoS); + Assert.That(resParseS, Is.True); + Assert.That(memberInfoS.Name, Is.EqualTo("S")); + Assert.That(memberInfoS.MemberType, Is.EqualTo(MemberTypes.Property)); + + var resParseI = MemberInfoParser.TryParse(s => s.I, MemberTypes.Property, out MemberInfo memberInfoI); + Assert.That(resParseI, Is.True); + Assert.That(memberInfoI.Name, Is.EqualTo("I")); + Assert.That(memberInfoS.MemberType, Is.EqualTo(MemberTypes.Property)); + + var resParseField = MemberInfoParser.TryParse(s => s._sField!, MemberTypes.Field, out MemberInfo memberInfoField); + Assert.That(resParseField, Is.True); + Assert.That(memberInfoField.Name, Is.EqualTo("_sField")); + Assert.That(memberInfoField.MemberType, Is.EqualTo(MemberTypes.Field)); + } + + [Test] + public void Should_GetTypedValue_Work() + { + var objToTest = new ObjWithTwoPublicProps() { I = 1, S = "TestProp", _sField = "TestField" }; + + _ = MemberInfoParser.TryParse(s => s.S!, MemberTypes.Property, out MemberInfo memberInfoS); + Assert.That(memberInfoS.GetTypedValue(objToTest), Is.EqualTo("TestProp")); + + _ = MemberInfoParser.TryParse(s => s.I, MemberTypes.Property, out MemberInfo memberInfoI); + Assert.That(memberInfoI.GetTypedValue(objToTest), Is.EqualTo(1)); + + _ = MemberInfoParser.TryParse(s => s._sField!, MemberTypes.Field, out MemberInfo memberInfoF); + Assert.That(memberInfoF.GetTypedValue(objToTest), Is.EqualTo("TestField")); + } + + [Test] + public void Should_ReturnFalse_ForNonNullableValueType_WhenCheckingIsValueNull() + { + Assert.That(TypeTraits.CanBeNull, Is.False); + } + + [Test] + public void Should_ReturnTrue_ForNullableValueType_WhenCheckingIsValueNull_AndValueIsNull() + { + Assert.That(TypeTraits.CanBeNull, Is.True); + } + + [Test] + public void Should_ReturnTrue_ForReferenceType_WhenCheckingIsValueNull_AndValueIsNull() + { + Assert.That(TypeTraits.CanBeNull, Is.True); + } + + [Test] + public void Should_TypeHelper_ReturnFalse_ForNonNullableValueType_WhenCheckingIsValueNull() + { + Assert.That(TypeHelper.IsNull(0), Is.False); + } + + [Test] + public void Should_TypeHelper_ReturnTrue_ForNullableValueType_WhenCheckingIsValueNull_AndValueIsNull() + { + int? value = null; + Assert.That(TypeHelper.IsNull(value), Is.True); + } + + [Test] + public void Should_TypeHelper_ReturnFalse_ForNullableValueType_WhenCheckingIsValueNull_AndValueIsNotNull() + { + int? value = 5; + Assert.That(TypeHelper.IsNull(value), Is.False); + } + + [Test] + public void Should_TypeHelper_ReturnTrue_ForReferenceType_WhenCheckingIsValueNull_AndValueIsNull() + { + string? value = null; + Assert.That(TypeHelper.IsNull(value!), Is.True); + } + + [Test] + public void Should_TypeHelper_ReturnFalse_ForReferenceType_WhenCheckingIsValueNull_AndValueIsNotNull() + { + string value = "hello"; + Assert.That(TypeHelper.IsNull(value), Is.False); + } + } +} diff --git a/tests/ExpressValidator.Tests.Net8/ValidationProfileTests.cs b/tests/ExpressValidator.Tests.Net8/ValidationProfileTests.cs new file mode 100644 index 0000000..b6f393e --- /dev/null +++ b/tests/ExpressValidator.Tests.Net8/ValidationProfileTests.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; + +namespace ExpressValidator.Tests.Net8 +{ + [TestFixture] + internal class ValidationProfileTests + { + [Test] + public void Should_AllowDerivedClassesToConfigureValidator_WhenCreatingValidator() + { + // Arrange + var customProfile = new CustomValidatorProfile(); + + // Act + customProfile.Configure(new ExpressValidatorBuilder()); + + // Assert + Assert.That(customProfile.CustomConfigurationWasApplied, Is.True); + } + } + + internal class CustomValidatorProfile : ValidationProfile + { + public bool CustomConfigurationWasApplied { get; private set; } + + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + { + CustomConfigurationWasApplied = true; + } + } +} diff --git a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj index 2d107d5..d5bfbbd 100644 --- a/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj +++ b/tests/ExpressValidator.Tests/ExpressValidator.Tests.csproj @@ -69,6 +69,7 @@ ..\..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll + @@ -79,8 +80,10 @@ + + @@ -93,6 +96,7 @@ + @@ -112,8 +116,8 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + - + \ No newline at end of file diff --git a/tests/ExpressValidator.Tests/ExpressValidatorNullObjectTests.cs b/tests/ExpressValidator.Tests/ExpressValidatorNullObjectTests.cs new file mode 100644 index 0000000..acafa65 --- /dev/null +++ b/tests/ExpressValidator.Tests/ExpressValidatorNullObjectTests.cs @@ -0,0 +1,140 @@ +using FluentValidation; +using FluentValidation.Results; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests +{ + internal class ExpressValidatorNullObjectTests + { + private static readonly string NullErrorMessageForClass = NullFallbackMessageProvider.GetMessage(typeof(Contact).Name, + new ValidationContext(null)); + + private static readonly string NullErrorMessageForStruct = NullFallbackMessageProvider.GetMessage(typeof(ContactStruct?).Name, + new ValidationContext(null)); + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotThrow_When_Class_To_Validate_Is_Null(bool isAsync) + { + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o.Name) + .WithValidation(o => o.NotEmpty() + .MaximumLength(100)) + .AddProperty(o => o.Email) + .WithValidation(o => o.NotEmpty() + .EmailAddress()) + .Build(); + + ValidationResult result = null; + if (isAsync) + { + result = await validator.ValidateAsync(null); + } + else + { +#pragma warning disable S6966 // Awaitable method should be used + result = validator.Validate(null); +#pragma warning restore S6966 // Awaitable method should be used + } + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(NullErrorMessageForClass)); + + if (isAsync) + { + Assert.ThrowsAsync(async () => await new ContactValidator().ValidateAsync((Contact)null)); + } + else + { + Assert.Throws(() => new ContactValidator().Validate((Contact)null)); + } + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public async Task Should_NotThrow_When_Nullable_Struct_Is_Null(bool isAsync) + { + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o.Value.Name) + .WithValidation(o => o.NotEmpty() + .MaximumLength(100)) + .AddProperty(o => o.Value.Email) + .WithValidation(o => o.NotEmpty() + .EmailAddress()) + .Build(); + + ValidationResult result = null; + if (isAsync) + { + result = await validator.ValidateAsync(null); + } + else + { +#pragma warning disable S6966 // Awaitable method should be used + result = validator.Validate(null); +#pragma warning restore S6966 // Awaitable method should be used + } + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(NullErrorMessageForStruct)); + + if (isAsync) + { + Assert.ThrowsAsync(async () => await new ContactNullableStructValidator().ValidateAsync((ContactStruct?)null)); + } + else + { + Assert.Throws(() => new ContactNullableStructValidator().Validate((ContactStruct?)null)); + } + } + + [Test] + public async Task Should_NotThrow_When_Class_To_Validate_Is_Null_And_WithAsyncValidation_Is_Used() + { + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o.Name) + .WithValidation(o => o.NotEmpty() + .MaximumLength(100)) + .AddProperty(o => o.Email) + .WithValidation(o => o.NotEmpty() + .EmailAddress()) + .AddProperty(o => o.K) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + + var result = await validator.ValidateAsync(null); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(NullErrorMessageForClass)); + + Assert.ThrowsAsync(async () => await new ContactValidator().ValidateAsync((Contact)null)); + } + + [Test] + public async Task Should_NotThrow_When_Struct_To_Validate_Is_Null_And_WithAsyncValidation_Is_Used() + { + var validator = new ExpressValidatorBuilder() + .AddProperty(o => o.Value.Email) + .WithValidation(o => o.NotEmpty() + .EmailAddress()) + .AddProperty(o => o.Value.Name) + .WithAsyncValidation(o => o.MustAsync(async (_, __) => { await Task.Delay(1); return true; })) + .Build(); + + var result = await validator.ValidateAsync(null); + + Assert.That(result.IsValid, Is.False); + Assert.That(result.Errors.Count, Is.EqualTo(1)); + Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(NullErrorMessageForStruct)); + + Assert.ThrowsAsync(async () => await new ContactNullableStructValidator().ValidateAsync((ContactStruct?)null)); + } + } +} diff --git a/tests/ExpressValidator.Tests/ExpressValidatorTests.ForAsync.Tests.cs b/tests/ExpressValidator.Tests/ExpressValidatorTests.ForAsync.Tests.cs index 69040dc..0dad07d 100644 --- a/tests/ExpressValidator.Tests/ExpressValidatorTests.ForAsync.Tests.cs +++ b/tests/ExpressValidator.Tests/ExpressValidatorTests.ForAsync.Tests.cs @@ -122,11 +122,11 @@ public async Task Should_ValidateAsync_When_Used_In_External_API(bool valid) var result = await new ExpressValidatorBuilder() .AddProperty(o => o.CustomerId) - .WithAsyncValidation(o => o.MustAsync(async (id, cancellation) => + .WithAsyncValidation(o => o.MustAsync(async (id, cancellation) => !await apiClient.IdExistsAsync(id, cancellation))) - .Build() + .Build() .ValidateAsync(customer); Assert.That(result.IsValid, Is.EqualTo(valid)); diff --git a/tests/ExpressValidator.Tests/ExpressValidatorTests.cs b/tests/ExpressValidator.Tests/ExpressValidatorTests.cs index e0b4a0f..3518066 100644 --- a/tests/ExpressValidator.Tests/ExpressValidatorTests.cs +++ b/tests/ExpressValidator.Tests/ExpressValidatorTests.cs @@ -98,7 +98,7 @@ public void Should_Work_When_NotValid_ForSubObjWithSimpleConditionForComplexProp .AddProperty(o => o.S) .WithValidation(o => o.MaximumLength(1)) .AddProperty(o => o.Contact) - .WithValidation(o => o.SetValidator(new ContactValidator())) + .WithValidation(o => o.SetValidator(new SimpleContactValidator())) .Build() .Validate(new SubObjWithComplexProperty() { I = 2, S = "b", Contact = new Contact()}); ClassicAssert.AreEqual(false, result.IsValid); @@ -114,7 +114,7 @@ public void Should_Work_When_NotValid_ForSubObjWithSimpleConditionForComplexProp .AddProperty(o => o.S) .WithValidation(o => o.MaximumLength(1)) .AddProperty(o => o.Contacts) - .WithValidation(o => o.ForEach(o1 => o1.SetValidator(new ContactValidator()))) + .WithValidation(o => o.ForEach(o1 => o1.SetValidator(new SimpleContactValidator()))) .Build() .Validate(new SubObjWithComplexCollectionProperty() { I = 1, S = "b"}); ClassicAssert.AreEqual(false, result.IsValid); @@ -130,7 +130,7 @@ public void Should_Work_When_NotValid_ForSubObjWithSimpleConditionForComplexProp .AddProperty(o => o.S) .WithValidation(o => o.MaximumLength(1)) .AddProperty(o => o.Contacts) - .WithValidation(o => o.NotEmpty().ForEach(o1 => o1.SetValidator(new ContactValidator()))) + .WithValidation(o => o.NotEmpty().ForEach(o1 => o1.SetValidator(new SimpleContactValidator()))) .Build() .Validate(new SubObjWithComplexCollectionProperty() { I = 1, S = "b", Contacts = new List() { new Contact(), new Contact() } }); @@ -147,7 +147,7 @@ public void Should_Work_When_Valid_ForSubObjWithSimpleConditionForComplexPropert .AddProperty(o => o.S) .WithValidation(o => o.MaximumLength(1)) .AddProperty(o => o.Contacts) - .WithValidation(o => o.ForEach(o1 => o1.SetValidator(new ContactValidator()))) + .WithValidation(o => o.ForEach(o1 => o1.SetValidator(new SimpleContactValidator()))) .Build() .Validate(new SubObjWithComplexCollectionProperty() { diff --git a/tests/ExpressValidator.Tests/ExpressValidatorWithOptionsTests.cs b/tests/ExpressValidator.Tests/ExpressValidatorWithOptionsTests.cs index 640a220..249278a 100644 --- a/tests/ExpressValidator.Tests/ExpressValidatorWithOptionsTests.cs +++ b/tests/ExpressValidator.Tests/ExpressValidatorWithOptionsTests.cs @@ -220,6 +220,34 @@ public void Should_AddFunc_Preserve_Property_Name(SetPropertyNameType setPropert } } + [Test] + public void Should_Invoke_SuccessValidationHandler_When_IsValid() + { + int percentSum = 0; + var options = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 0, IGreaterThanValue2 = 100 }; + var result = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum", (p) => percentSum = p) + .WithValidation((topt, o) => o.InclusiveBetween(topt.IGreaterThanValue, topt.IGreaterThanValue2)) + .Build(options) + .Validate(new ObjWithTwoPublicProps() { PercentValue1 = 20, PercentValue2 = 80 }); + Assert.That(percentSum, Is.EqualTo(100)); + Assert.That(result.IsValid, Is.True); + } + + [Test] + public void Should_Not_Invoke_SuccessValidationHandler_When_IsNotValid() + { + int percentSum = 0; + var options = new ObjWithTwoPublicPropsOptions() { IGreaterThanValue = 0, IGreaterThanValue2 = 100}; + var result = new ExpressValidatorBuilder() + .AddFunc(o => o.PercentValue1 + o.PercentValue2, "percentSum", (p) => percentSum = p) + .WithValidation((topt, o) => o.InclusiveBetween(topt.IGreaterThanValue, topt.IGreaterThanValue2)) + .Build(options) + .Validate(new ObjWithTwoPublicProps() { PercentValue1 = 21, PercentValue2 = 83 }); + Assert.That(percentSum, Is.EqualTo(0)); + Assert.That(result.IsValid, Is.False); + } + [Test] [TestCase(true)] [TestCase(false)] diff --git a/tests/ExpressValidator.Tests/ObjectsToTests.cs b/tests/ExpressValidator.Tests/ObjectsToTests.cs index 30bd8db..1ba9ed2 100644 --- a/tests/ExpressValidator.Tests/ObjectsToTests.cs +++ b/tests/ExpressValidator.Tests/ObjectsToTests.cs @@ -36,16 +36,30 @@ public class ObjWithTwoPublicPropsOptions public int PercentSumMaxValue { get; set; } } - public class ContactValidator : AbstractValidator + public class SimpleContactValidator : AbstractValidator { - public ContactValidator() + public SimpleContactValidator() { RuleFor(x => x.Name).NotNull(); RuleFor(x => x.Email).NotNull(); } } - public class Contact + public class ContactValidator : AbstractValidator + { + public ContactValidator() + { + RuleFor(x => x.Name) + .NotEmpty() + .MaximumLength(100); + + RuleFor(x => x.Email) + .NotEmpty() + .EmailAddress(); + } + } + + public class Contact { public string Name { get; set; } public string Email { get; set; } @@ -53,7 +67,27 @@ public class Contact public string K { get; set; } } - internal class ObjWithNullable + public class ContactNullableStructValidator : AbstractValidator + { + public ContactNullableStructValidator() + { + RuleFor(x => x.Value.Name) + .NotEmpty() + .MaximumLength(100); + + RuleFor(x => x.Value.Email) + .NotEmpty() + .EmailAddress(); + } + } + + public struct ContactStruct + { + public string Name { get; set; } + public string Email { get; set; } + } + + internal class ObjWithNullable { public string Value { get; set; } = "Test"; } diff --git a/tests/ExpressValidator.Tests/PropertyValidationProcessorTests.cs b/tests/ExpressValidator.Tests/PropertyValidationProcessorTests.cs new file mode 100644 index 0000000..e4b68f8 --- /dev/null +++ b/tests/ExpressValidator.Tests/PropertyValidationProcessorTests.cs @@ -0,0 +1,85 @@ +using FluentValidation; +using NUnit.Framework; +using System; +using System.Reflection; +using System.Threading.Tasks; + +namespace ExpressValidator.Tests +{ + internal class PropertyValidationProcessorTests + { + [Test] + [TestCase("t", true, false)] + [TestCase("t", true, true)] + [TestCase("tt", false, null)] + public void Should_Validate_When_IsAsync_False(string whatToTest, bool result, bool? useHandler) + { + string handlerResult = null; + void successHandler(string s) { handlerResult = s; } + + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + var validator = new TypeValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + + var processor = new PropertyValidationProcessor(o => o.Value, validator, useHandler == true ? successHandler : null); + + var (IsValid, Failures) = processor.Validate(new ObjWithNullable() { Value = whatToTest }); + Assert.That (IsValid, Is.EqualTo(result)); + if (!result) + { + Assert.That(Failures.Count, Is.EqualTo(1)); + } + else + { + Assert.That(handlerResult, Is.EqualTo(useHandler == true ? whatToTest : null)); + } + } + + [Test] + [TestCase("t")] + [TestCase("tt")] + public void Should_Throw_On_Validate_When_IsAsync_True(string whatToTest) + { + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + var validator = new TypeAsyncValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + + var processor = new PropertyValidationProcessor(o => o.Value, validator, null); + + Assert.Throws(() => processor.Validate(new ObjWithNullable() { Value = whatToTest })); + } + + [Test] + [TestCase("t", true, false, true)] + [TestCase("t", true, true, true)] + [TestCase("tt", false, null, true)] + [TestCase("t", true, false, false)] + [TestCase("t", true, true, false)] + [TestCase("tt", false, null, false)] + public async Task Should_ValidateAsync_Do_Not_Depend_On_TypeValidator_Sync_Type(string whatToTest, bool result, bool? useHandler, bool isAsync) + { + string handlerResult = null; + void successHandler(string s) { handlerResult = s; } + + MemberInfoParser.TryParse(o => o.Value, MemberTypes.Property, out MemberInfo propertyInfo); + + TypeValidatorBase validator = isAsync ? new TypeAsyncValidator() : new TypeValidator(); + validator.SetValidation(o => o.MaximumLength(1), propertyInfo.Name); + + var processor = new PropertyValidationProcessor(o => o.Value, validator, useHandler == true ? successHandler : null); + + var (IsValid, Failures) = await processor.ValidateAsync(new ObjWithNullable() { Value = whatToTest }); + Assert.That(IsValid, Is.EqualTo(result)); + if (!result) + { + Assert.That(Failures.Count, Is.EqualTo(1)); + } + else + { + Assert.That(handlerResult, Is.EqualTo(useHandler == true ? whatToTest : null)); + } + } + } +} diff --git a/tests/ExpressValidator.Tests/UtilitiesTests.cs b/tests/ExpressValidator.Tests/UtilitiesTests.cs index f856d18..20f1d78 100644 --- a/tests/ExpressValidator.Tests/UtilitiesTests.cs +++ b/tests/ExpressValidator.Tests/UtilitiesTests.cs @@ -53,5 +53,57 @@ public void Should_GetTypedValue_Work() _ = MemberInfoParser.TryParse(s => s._sField, MemberTypes.Field, out MemberInfo memberInfoF); Assert.That(memberInfoF.GetTypedValue(objToTest), Is.EqualTo("TestField")); } + + [Test] + public void Should_ReturnFalse_ForNonNullableValueType_WhenCheckingIsValueNull() + { + Assert.That(TypeTraits.CanBeNull, Is.False); + } + + [Test] + public void Should_ReturnTrue_ForNullableValueType_WhenCheckingIsValueNull_AndValueIsNull() + { + Assert.That(TypeTraits.CanBeNull, Is.True); + } + + [Test] + public void Should_ReturnTrue_ForReferenceType_WhenCheckingIsValueNull_AndValueIsNull() + { + Assert.That(TypeTraits.CanBeNull, Is.True); + } + + [Test] + public void Should_TypeHelper_ReturnFalse_ForNonNullableValueType_WhenCheckingIsValueNull() + { + Assert.That(TypeHelper.IsNull(0), Is.False); + } + + [Test] + public void Should_TypeHelper_ReturnTrue_ForNullableValueType_WhenCheckingIsValueNull_AndValueIsNull() + { + int? value = null; + Assert.That(TypeHelper.IsNull(value), Is.True); + } + + [Test] + public void Should_TypeHelper_ReturnFalse_ForNullableValueType_WhenCheckingIsValueNull_AndValueIsNotNull() + { + int? value = 5; + Assert.That(TypeHelper.IsNull(value), Is.False); + } + + [Test] + public void Should_TypeHelper_ReturnTrue_ForReferenceType_WhenCheckingIsValueNull_AndValueIsNull() + { + string value = null; + Assert.That(TypeHelper.IsNull(value), Is.True); + } + + [Test] + public void Should_TypeHelper_ReturnFalse_ForReferenceType_WhenCheckingIsValueNull_AndValueIsNotNull() + { + string value = "hello"; + Assert.That(TypeHelper.IsNull(value), Is.False); + } } } diff --git a/tests/ExpressValidator.Tests/ValidationProfileTests.cs b/tests/ExpressValidator.Tests/ValidationProfileTests.cs new file mode 100644 index 0000000..62ea28e --- /dev/null +++ b/tests/ExpressValidator.Tests/ValidationProfileTests.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; + +namespace ExpressValidator.Tests +{ + [TestFixture] + internal class ValidationProfileTests + { + [Test] + public void Should_AllowDerivedClassesToConfigureValidator_WhenCreatingValidator() + { + // Arrange + var customProfile = new CustomValidatorProfile(); + + // Act + customProfile.Configure(new ExpressValidatorBuilder()); + + // Assert + Assert.That(customProfile.CustomConfigurationWasApplied, Is.True); + } + } + + internal class CustomValidatorProfile : ValidationProfile + { + public bool CustomConfigurationWasApplied { get; private set; } + + public override void Configure(ExpressValidatorBuilder expressValidatorBuilder) + { + CustomConfigurationWasApplied = true; + } + } +} diff --git a/tests/ExpressValidator.Tests/packages.config b/tests/ExpressValidator.Tests/packages.config index 3e49123..38137b1 100644 --- a/tests/ExpressValidator.Tests/packages.config +++ b/tests/ExpressValidator.Tests/packages.config @@ -8,5 +8,5 @@ - + \ No newline at end of file