| File: Routing\RoutingStrategyTest.cs | Web Access |
| Project: src\test\Libraries\Microsoft.Extensions.Http.Resilience.Tests\Microsoft.Extensions.Http.Resilience.Tests.csproj (Microsoft.Extensions.Http.Resilience.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Linq; using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Http.Resilience.Internal; using Microsoft.Extensions.Http.Resilience.Routing.Internal; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.Options; using Moq; using Xunit; namespace Microsoft.Extensions.Http.Resilience.Test.Routing; public abstract class RoutingStrategyTest { public const string RoutingName = "dummy-routing"; protected RoutingStrategyTest() { Builder = new RoutingStrategyBuilder(RoutingName, new ServiceCollection()); Builder.Services.TryAddSingleton(Randomizer.Object); } public IRoutingStrategyBuilder Builder { get; set; } internal Mock<Randomizer> Randomizer { get; } = new Mock<Randomizer>(MockBehavior.Strict); public virtual bool CompareOrder => true; [Fact] public void CreateStrategy_EnsurePooled() { SetupRandomizer(60d); SetupRandomizer(60); Configure(Builder); var factory = CreateRoutingFactory(); var strategies = new HashSet<RequestRoutingStrategy>(); for (int i = 0; i < 10; i++) { using var strategy = factory(); strategies.Add(strategy); } // assert that some strategies were pooled Assert.True(strategies.Count < 5); } [Fact] public virtual void MinRoutes_Ok() { SetupRandomizer(0); SetupRandomizer(0d); var routes = ConfigureMinRoutes(Builder).ToArray(); var urls = CollectUrls(CreateStrategy()).ToArray(); Assert.Equal(routes.Length, urls.Length); if (CompareOrder) { urls.Should().Equal(routes); } else { urls.Should().BeEquivalentTo(routes); } } [Fact] public void InvalidRoutes_ValidationException() { foreach (var action in ConfigureInvalidRoutes()) { Builder = new RoutingStrategyBuilder(RoutingName, new ServiceCollection()); Builder.Services.TryAddSingleton(Randomizer.Object); action(Builder); Assert.Throws<OptionsValidationException>(() => CreateStrategy()); } } [Fact] public void TryGetNextRoute_NotInitialized_Throws() { Assert.Throws<InvalidOperationException>(() => CreateEmptyStrategy().TryGetNextRoute(out _)); } [Fact] public void TryGetNextRoute_AfterReset_Throws() { SetupRandomizer(0); Builder = new RoutingStrategyBuilder(RoutingName, new ServiceCollection()); Builder.Services.TryAddSingleton(Randomizer.Object); Configure(Builder); var strategy = CreateStrategy(); _ = ((IResettable)strategy).TryReset(); Assert.Throws<InvalidOperationException>(() => strategy.TryGetNextRoute(out _)); } protected void ReloadHelper( Action<IRoutingStrategyBuilder, IConfiguration> configure, Dictionary<string, string?> config1, Dictionary<string, string?> config2, string[] urls1, string[] urls2) { var provider = new ReloadableConfiguration(); provider.Reload(config1); var builder = new ConfigurationBuilder(); builder.Add(provider); configure(Builder, builder.Build()); CollectUrls(CreateStrategy()).Should().Equal(urls1); // empty data -> failure Assert.Throws<AggregateException>(() => provider.Reload([])); provider.Reload(config2); CollectUrls(CreateStrategy()).Should().Equal(urls2); } internal void StrategyResultHelper(params string[] expectedUrls) { Builder = new RoutingStrategyBuilder(RoutingName, new ServiceCollection()); Builder.Services.TryAddSingleton(Randomizer.Object); Configure(Builder); var factory = CreateRoutingFactory(); CollectUrls(factory()).Should().Equal(expectedUrls); // intentionally, we check that the output on subsequent calls is the same CollectUrls(factory()).Should().Equal(expectedUrls); } internal RequestRoutingStrategy CreateStrategy(string? name = null) => CreateRoutingFactory(name)(); internal Func<RequestRoutingStrategy> CreateRoutingFactory(string? name = null) { return Builder.Services .BuildServiceProvider() .GetRequiredService<IOptionsMonitor<RequestRoutingOptions>>() .Get(name ?? Builder.Name).RoutingStrategyProvider!; } private static IEnumerable<string> CollectUrls(RequestRoutingStrategy strategy) { while (strategy.TryGetNextRoute(out var route)) { yield return route.ToString(); } } protected static IConfigurationSection GetSection(IDictionary<string, string> values) { return new ConfigurationBuilder().AddInMemoryCollection(values.Select(pair => new KeyValuePair<string, string?>("section:" + pair.Key, pair.Value))).Build().GetSection("section"); } protected abstract void Configure(IRoutingStrategyBuilder routingBuilder); protected abstract IEnumerable<string> ConfigureMinRoutes(IRoutingStrategyBuilder routingBuilder); protected abstract IEnumerable<Action<IRoutingStrategyBuilder>> ConfigureInvalidRoutes(); internal abstract RequestRoutingStrategy CreateEmptyStrategy(); protected void SetupRandomizer(double result) => Randomizer.Setup(r => r.NextDouble(It.IsAny<double>())).Returns(result); protected void SetupRandomizer(int result) => Randomizer.Setup(r => r.NextInt(It.IsAny<int>())).Returns(result); private class ReloadableConfiguration : ConfigurationProvider, IConfigurationSource { public IConfigurationProvider Build(IConfigurationBuilder builder) { return this; } public void Reload(Dictionary<string, string?> data) { Data = new Dictionary<string, string?>(data, StringComparer.OrdinalIgnoreCase); OnReload(); } } }