using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using ZB.MOM.WW.Configuration; namespace ZB.MOM.WW.Configuration.Tests; public sealed class AddValidatedOptionsTests { private sealed class NodeOptions { public int Port { get; set; } public string? Name { get; set; } } private sealed class NodeValidator : OptionsValidatorBase { protected override void Validate(ValidationBuilder v, NodeOptions o) { v.Port(o.Port, "Node:Port"); v.Required(o.Name, "Node:Name"); } } private static IHost BuildHost(Dictionary config) { var builder = Host.CreateApplicationBuilder(); builder.Configuration.AddInMemoryCollection(config); builder.Services.AddValidatedOptions(builder.Configuration, "Node"); return builder.Build(); } [Fact] public async Task Bad_config_throws_at_startup() { using var host = BuildHost(new() { ["Node:Port"] = "0", ["Node:Name"] = "" }); await Assert.ThrowsAsync(() => host.StartAsync()); } [Fact] public async Task Good_config_starts_and_binds() { using var host = BuildHost(new() { ["Node:Port"] = "8080", ["Node:Name"] = "central" }); await host.StartAsync(); var opts = host.Services.GetRequiredService>().Value; Assert.Equal(8080, opts.Port); Assert.Equal("central", opts.Name); await host.StopAsync(); } [Fact] public void Calling_twice_registers_validator_once() { var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Node:Port"] = "0", ["Node:Name"] = "" }) .Build(); var services = new ServiceCollection(); services.AddValidatedOptions(config, "Node"); services.AddValidatedOptions(config, "Node"); using var provider = services.BuildServiceProvider(); var validators = provider.GetServices>().ToArray(); Assert.Single(validators); // Resolving the options surfaces each accumulated failure exactly once, not doubled. var ex = Assert.Throws( () => provider.GetRequiredService>().Value); Assert.Equal(2, ex.Failures.Count()); } }