diff --git a/src/OptionalValues/OptionalValue.cs b/src/OptionalValues/OptionalValue.cs index 8e20956..5208888 100644 --- a/src/OptionalValues/OptionalValue.cs +++ b/src/OptionalValues/OptionalValue.cs @@ -75,6 +75,7 @@ public OptionalValue(T value) "Design", "CA1000:Do not declare static members on generic types", Justification = "Having a static class for Unspecified is not adding value at the moment")] + [JsonIgnore] public static OptionalValue Unspecified => new(); /// diff --git a/test/OptionalValues.DataAnnotations.Tests/MinimalApiValidationTest.cs b/test/OptionalValues.DataAnnotations.Tests/MinimalApiValidationTest.cs new file mode 100644 index 0000000..6cf99cc --- /dev/null +++ b/test/OptionalValues.DataAnnotations.Tests/MinimalApiValidationTest.cs @@ -0,0 +1,133 @@ +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Net.Http.Json; + +using Shouldly; + +#if NET10_0_OR_GREATER +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +#endif + +namespace OptionalValues.DataAnnotations.Tests; + +/// +/// Tests that verify OptionalValues DataAnnotations work with .NET 10's minimal API validation support. +/// In .NET 10, minimal APIs automatically validate models when AddValidation() is called. +/// See: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-10.0#validation-support-in-minimal-apis +/// +/// These tests create an actual minimal API with host builder and use AddValidation() to verify +/// that OptionalValues DataAnnotations work correctly with automatic validation. +/// +#if NET10_0_OR_GREATER +public class MinimalApiValidationTest : IAsyncLifetime +{ + private WebApplication? _app; + private HttpClient? _client; + + public class TestModel + { + [RequiredValue] + public OptionalValue RequiredName { get; init; } + + [OptionalRange(1, 100)] + public OptionalValue RangeValue { get; init; } + + [Specified] + public OptionalValue SpecifiedField { get; init; } + } + + public async Task InitializeAsync() + { + // Create a minimal API host with AddValidation() + var builder = WebApplication.CreateBuilder(); + + // Configure JSON options for OptionalValue support + builder.Services.ConfigureHttpJsonOptions(options => + { + options.SerializerOptions.AddOptionalValueSupport(); + }); + + // Add validation support for minimal APIs in .NET 10 + builder.Services.AddValidation(); + + // Use test server + builder.WebHost.UseTestServer(); + + _app = builder.Build(); + + // Create minimal API endpoint with automatic validation + _app.MapPost("/test", (TestModel model) => Results.Ok(model)) + .WithName("TestEndpoint"); + + await _app.StartAsync(); + _client = _app.GetTestClient(); + } + + public async Task DisposeAsync() + { + if (_app != null) + { + await _app.DisposeAsync(); + } + _client?.Dispose(); + } + + [Fact] + public async Task ValidModel_ShouldPass_MinimalApiValidation() + { + // Arrange - Send valid JSON + var json = """{"RequiredName":"TestName","RangeValue":50,"SpecifiedField":"value"}"""; + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + // Act - Post to minimal API which uses AddValidation() + var response = await _client!.PostAsync("/test", content); + + // Assert + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + [Fact] + public async Task InvalidModel_ShouldFail_MinimalApiValidation() + { + // Arrange - Send invalid JSON (out of range value) + var json = """{"RequiredName":"Test","RangeValue":150,"SpecifiedField":"value"}"""; + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + // Act - Post to minimal API which uses AddValidation() + var response = await _client!.PostAsync("/test", content); + + // Assert - Minimal API validation should return BadRequest + response.StatusCode.ShouldBe(HttpStatusCode.BadRequest); + } + + [Fact] + public async Task UnspecifiedOptionalFields_ShouldBeValid_MinimalApiValidation() + { + // Arrange - Don't send unspecified fields in JSON + var json = """{"RequiredName":"Test","SpecifiedField":null}"""; + var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); + + // Act - Post to minimal API which uses AddValidation() + var response = await _client!.PostAsync("/test", content); + + // Assert + response.StatusCode.ShouldBe(HttpStatusCode.OK); + } +} +#else +public class MinimalApiValidationTest +{ + [Fact] + public void MinimalApiValidation_OnlyAvailableInNet10() + { + // Minimal API validation with AddValidation() is only available in .NET 10+ + true.ShouldBeTrue(); + } +} +#endif + diff --git a/test/OptionalValues.DataAnnotations.Tests/OptionalValues.DataAnnotations.Tests.csproj b/test/OptionalValues.DataAnnotations.Tests/OptionalValues.DataAnnotations.Tests.csproj index d27e15e..8acc73c 100644 --- a/test/OptionalValues.DataAnnotations.Tests/OptionalValues.DataAnnotations.Tests.csproj +++ b/test/OptionalValues.DataAnnotations.Tests/OptionalValues.DataAnnotations.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0 @@ -20,6 +20,11 @@ + + + + + diff --git a/version.json b/version.json index 50492cc..d91304a 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.8", + "version": "0.9", "publicReleaseRefSpec": [ "^refs/heads/main$", "^refs/heads/v\\d+(?:\\.\\d+)?$",