-
Notifications
You must be signed in to change notification settings - Fork 3
Add async version of TryValidateObjectRecursive method and tests #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
a8007a7
Add async version of TryValidateObjectRecursive method and tests
tgharold cd8abf7
Use Task.Run for async validation methods to improve thread utilization
tgharold f6ef487
Let GitHub Copilot suggest the XML comment
tgharold a3c89f6
Add deadlock prevention tests for async validation methods
tgharold File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
src/RecursiveDataAnnotationsValidation/IAsyncRecursiveDataAnnotationValidator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| using System.Collections.Generic; | ||
| using System.ComponentModel.DataAnnotations; | ||
| using System.Threading.Tasks; | ||
|
|
||
| namespace RecursiveDataAnnotationsValidation | ||
| { | ||
| /// <summary>Async interface for the RecursiveDataAnnotationValidator. Useful if you need | ||
| /// to swap in a different approach, or to mock the methods.</summary> | ||
| public interface IAsyncRecursiveDataAnnotationValidator | ||
| { | ||
| /// <summary>Runs async validation on an object.</summary> | ||
| /// <param name="obj">The object being validated.</param> | ||
| /// <param name="validationContext">Validation context.</param> | ||
| /// <param name="validationResults">A collection that will be populated if validation errors occur.</param> | ||
| /// <returns>Returns true if all validation passes.</returns> | ||
| Task<bool> TryValidateObjectRecursiveAsync( | ||
| object obj, | ||
| ValidationContext validationContext, | ||
| List<ValidationResult> validationResults | ||
| ); | ||
|
|
||
| /// <summary>Runs async validation on an object.</summary> | ||
| /// <param name="obj">The object being validated.</param> | ||
| /// <param name="validationResults">A collection that will be populated if validation errors occur.</param> | ||
| /// <param name="validationContextItems">Validation context items.</param> | ||
| /// <returns>Returns true if all validation passes.</returns> | ||
| Task<bool> TryValidateObjectRecursiveAsync( | ||
| object obj, | ||
| List<ValidationResult> validationResults, | ||
| IDictionary<object, object> validationContextItems = null | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
46 changes: 46 additions & 0 deletions
46
test/RecursiveDataAnnotationsValidation.Tests/AsyncCollectionTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| using System.Collections.Generic; | ||
| using System.ComponentModel.DataAnnotations; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
| using RecursiveDataAnnotationsValidation.Tests.TestModels; | ||
| using Xunit; | ||
|
|
||
| namespace RecursiveDataAnnotationsValidation.Tests | ||
| { | ||
| public class AsyncCollectionTests | ||
| { | ||
| private readonly IAsyncRecursiveDataAnnotationValidator _sut = new RecursiveDataAnnotationValidator(); | ||
|
|
||
| [Fact] | ||
| public async Task Validates_collections_recursively() | ||
| { | ||
| var model = new ItemWithListExample | ||
| { | ||
| ItemWithListName = "Parent", | ||
| Claims = new List<string> { "Claim1", "Claim2" } | ||
| }; | ||
|
|
||
| var validationResults = new List<ValidationResult>(); | ||
| var result = await _sut.TryValidateObjectRecursiveAsync(model, validationResults); | ||
|
|
||
| Assert.True(result); | ||
| Assert.Empty(validationResults); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Fails_when_collection_item_has_validation_errors() | ||
| { | ||
| var model = new ItemWithListExample | ||
| { | ||
| ItemWithListName = "Parent", | ||
| Claims = new List<string> { null } // This should fail validation due to [Required] | ||
| }; | ||
|
|
||
| var validationResults = new List<ValidationResult>(); | ||
| var result = await _sut.TryValidateObjectRecursiveAsync(model, validationResults); | ||
|
|
||
| Assert.False(result); | ||
| Assert.NotEmpty(validationResults); | ||
| } | ||
| } | ||
| } |
75 changes: 75 additions & 0 deletions
75
test/RecursiveDataAnnotationsValidation.Tests/AsyncRecursionTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| using System.Collections.Generic; | ||
| using System.ComponentModel.DataAnnotations; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
| using RecursiveDataAnnotationsValidation.Tests.TestModels; | ||
| using Xunit; | ||
|
|
||
| namespace RecursiveDataAnnotationsValidation.Tests | ||
| { | ||
| public class AsyncRecursionTests | ||
| { | ||
| private readonly IAsyncRecursiveDataAnnotationValidator _sut = new RecursiveDataAnnotationValidator(); | ||
|
|
||
| // This test verifies that async version handles recursive structures correctly | ||
| [Fact] | ||
| public async Task Handles_recursive_structures_without_infinite_loop() | ||
| { | ||
| var recursiveModel = new RecursionExample | ||
| { | ||
| Name = "Recursion1-pass", | ||
| BooleanA = false, | ||
| Recursion = new RecursionExample | ||
| { | ||
| Name = "Recursion1-pass.Inner1", | ||
| BooleanA = true, | ||
| Recursion = null | ||
| } | ||
| }; | ||
| recursiveModel.Recursion.Recursion = recursiveModel; | ||
|
|
||
| var model = new RecursionExample | ||
| { | ||
| Name = "SUT", | ||
| BooleanA = true, | ||
| Recursion = recursiveModel | ||
| }; | ||
|
|
||
| var validationResults = new List<ValidationResult>(); | ||
| var result = await _sut.TryValidateObjectRecursiveAsync(model, validationResults); | ||
|
|
||
| Assert.True(result); | ||
| Assert.Empty(validationResults); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Fails_when_recursive_property_has_validation_errors() | ||
| { | ||
| var recursiveModel = new RecursionExample | ||
| { | ||
| Name = "Recursion1-fail", | ||
| BooleanA = false, | ||
| Recursion = new RecursionExample | ||
| { | ||
| Name = "Recursion1-fail.Inner1", | ||
| BooleanA = null, // This should fail validation due to [Required] | ||
| Recursion = null | ||
| } | ||
| }; | ||
| recursiveModel.Recursion.Recursion = recursiveModel; | ||
|
|
||
| var model = new RecursionExample | ||
| { | ||
| Name = "SUT", | ||
| BooleanA = true, | ||
| Recursion = recursiveModel | ||
| }; | ||
|
|
||
| var validationResults = new List<ValidationResult>(); | ||
| var result = await _sut.TryValidateObjectRecursiveAsync(model, validationResults); | ||
|
|
||
| Assert.False(result); | ||
| Assert.NotEmpty(validationResults); | ||
| } | ||
| } | ||
| } |
73 changes: 73 additions & 0 deletions
73
test/RecursiveDataAnnotationsValidation.Tests/AsyncValidatorTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| using System.Collections.Generic; | ||
| using System.ComponentModel.DataAnnotations; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
| using RecursiveDataAnnotationsValidation.Tests.TestModels; | ||
| using Xunit; | ||
|
|
||
| namespace RecursiveDataAnnotationsValidation.Tests | ||
| { | ||
| public class AsyncValidatorTests | ||
| { | ||
| private readonly IAsyncRecursiveDataAnnotationValidator _sut = new RecursiveDataAnnotationValidator(); | ||
|
|
||
| [Fact] | ||
| public async Task Pass_all_validation() | ||
| { | ||
| var model = new SimpleExample | ||
| { | ||
| IntegerA = 100, | ||
| StringB = "test-100", | ||
| BoolC = true, | ||
| ExampleEnumD = ExampleEnum.ValueB | ||
| }; | ||
|
|
||
| var validationContext = new ValidationContext(model); | ||
| var validationResults = new List<ValidationResult>(); | ||
| var result = await _sut.TryValidateObjectRecursiveAsync(model, validationContext, validationResults); | ||
|
|
||
| Assert.True(result); | ||
| Assert.Empty(validationResults); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Indicate_that_IntegerA_is_missing() | ||
| { | ||
| var model = new SimpleExample | ||
| { | ||
| IntegerA = null, | ||
| StringB = "test-101", | ||
| BoolC = false, | ||
| ExampleEnumD = ExampleEnum.ValueC | ||
| }; | ||
|
|
||
| const string fieldName = nameof(SimpleExample.IntegerA); | ||
| var validationContext = new ValidationContext(model); | ||
| var validationResults = new List<ValidationResult>(); | ||
| var result = await _sut.TryValidateObjectRecursiveAsync(model, validationContext, validationResults); | ||
|
|
||
| Assert.False(result); | ||
| Assert.NotEmpty(validationResults); | ||
| Assert.NotNull(validationResults | ||
| .FirstOrDefault(x => x.MemberNames.Contains(fieldName))); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task Pass_all_validation_without_context() | ||
| { | ||
| var model = new SimpleExample | ||
| { | ||
| IntegerA = 100, | ||
| StringB = "test-100", | ||
| BoolC = true, | ||
| ExampleEnumD = ExampleEnum.ValueB | ||
| }; | ||
|
|
||
| var validationResults = new List<ValidationResult>(); | ||
| var result = await _sut.TryValidateObjectRecursiveAsync(model, validationResults); | ||
|
|
||
| Assert.True(result); | ||
| Assert.Empty(validationResults); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.