Skip to content

Implement runtime-based IValidatableTypeInfoResolver for minimal API validation #62497

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

Closed
wants to merge 10 commits into from
Closed
Prev Previous commit
Next Next commit
Add integration tests for RuntimeValidatableTypeInfoResolver registra…
…tion

Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com>
  • Loading branch information
Copilot and captainsafia committed Jun 27, 2025
commit 658d475918be47e5e7fe138e6fcb4fd1ac954cc1
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#pragma warning disable ASP0029 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Validation.Tests;

public class ValidationServiceCollectionExtensionsIntegrationTests
{
[Fact]
public void AddValidation_RegistersRuntimeValidatableTypeInfoResolver()
{
// Arrange
var services = new ServiceCollection();

// Act
services.AddValidation();
var serviceProvider = services.BuildServiceProvider();
var validationOptions = serviceProvider.GetRequiredService<IOptions<ValidationOptions>>().Value;

// Assert
Assert.NotEmpty(validationOptions.Resolvers);
Assert.Contains(validationOptions.Resolvers, r => r is RuntimeValidatableTypeInfoResolver);
Assert.Contains(validationOptions.Resolvers, r => r is RuntimeValidatableParameterInfoResolver);
}

[Fact]
public void AddValidation_RuntimeTypeResolverCanResolveComplexTypes()
{
// Arrange
var services = new ServiceCollection();
services.AddValidation();
var serviceProvider = services.BuildServiceProvider();
var validationOptions = serviceProvider.GetRequiredService<IOptions<ValidationOptions>>().Value;

// Act
var result = validationOptions.TryGetValidatableTypeInfo(typeof(TestPoco), out var validatableInfo);

// Assert
Assert.True(result);
Assert.NotNull(validatableInfo);
Assert.IsType<RuntimeValidatableTypeInfoResolver.RuntimeValidatableTypeInfo>(validatableInfo);
}

[Fact]
public void AddValidation_RuntimeTypeResolverReturnsNullForPrimitiveTypes()
{
// Arrange
var services = new ServiceCollection();
services.AddValidation();
var serviceProvider = services.BuildServiceProvider();
var validationOptions = serviceProvider.GetRequiredService<IOptions<ValidationOptions>>().Value;

// Act
var result = validationOptions.TryGetValidatableTypeInfo(typeof(int), out var validatableInfo);

// Assert
Assert.False(result);
Assert.Null(validatableInfo);
}

[Fact]
public void AddValidation_ResolversAreInCorrectOrder()
{
// Arrange
var services = new ServiceCollection();
services.AddValidation();
var serviceProvider = services.BuildServiceProvider();
var validationOptions = serviceProvider.GetRequiredService<IOptions<ValidationOptions>>().Value;

// Assert - RuntimeValidatableParameterInfoResolver should come first, then RuntimeValidatableTypeInfoResolver
var parameterResolverIndex = -1;
var typeResolverIndex = -1;

for (int i = 0; i < validationOptions.Resolvers.Count; i++)
{
if (validationOptions.Resolvers[i] is RuntimeValidatableParameterInfoResolver)
{
parameterResolverIndex = i;
}
else if (validationOptions.Resolvers[i] is RuntimeValidatableTypeInfoResolver)
{
typeResolverIndex = i;
}
}

Assert.True(parameterResolverIndex >= 0, "RuntimeValidatableParameterInfoResolver should be registered");
Assert.True(typeResolverIndex >= 0, "RuntimeValidatableTypeInfoResolver should be registered");
Assert.True(parameterResolverIndex < typeResolverIndex, "Parameter resolver should come before type resolver");
}

public class TestPoco
{
[Required]
public string Name { get; set; } = string.Empty;

[Range(0, 100)]
public int Age { get; set; }
}
}
Loading