File: DependencyInjection\PollyHttpClientBuilderExtensionsTest.cs
Web Access
Project: src\src\HttpClientFactory\Polly\test\Microsoft.Extensions.Http.Polly.Tests.csproj (Microsoft.Extensions.Http.Polly.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 System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Http.Logging;
using Polly;
using Polly.Registry;
using Xunit;
 
namespace Microsoft.Extensions.DependencyInjection;
 
// These are integration tests that verify basic end-to-ends.
public class PollyHttpClientBuilderExtensionsTest
{
    public PollyHttpClientBuilderExtensionsTest()
    {
        PrimaryHandler = new FaultyMessageHandler();
 
        NoOpPolicy = Policy.NoOpAsync<HttpResponseMessage>();
        RetryPolicy = Policy.Handle<OverflowException>().OrResult<HttpResponseMessage>(r => false).RetryAsync();
    }
 
    private FaultyMessageHandler PrimaryHandler { get; }
 
    // Allows the exception from our handler to propegate
    private IAsyncPolicy<HttpResponseMessage> NoOpPolicy { get; }
 
    // Matches what our client handler does
    private IAsyncPolicy<HttpResponseMessage> RetryPolicy { get; }
 
    [Fact]
    public async Task AddPolicyHandler_Policy_AddsPolicyHandler()
    {
        var serviceCollection = new ServiceCollection();
 
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
            .AddPolicyHandler(RetryPolicy)
            .ConfigurePrimaryHttpMessageHandler(() => PrimaryHandler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("example.com");
 
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var response = await client.SendAsync(new HttpRequestMessage());
 
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
    }
 
    [Fact]
    public async Task AddPolicyHandler_PolicySelector_AddsPolicyHandler()
    {
        var serviceCollection = new ServiceCollection();
 
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
            .AddPolicyHandler((req) => req.RequestUri.AbsolutePath == "/" ? RetryPolicy : NoOpPolicy)
            .ConfigurePrimaryHttpMessageHandler(() => PrimaryHandler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("example.com");
 
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var response = await client.SendAsync(new HttpRequestMessage());
 
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
 
        // Act 4
        await Assert.ThrowsAsync<OverflowException>(() => client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/throw")));
    }
 
    [Fact]
    public async Task AddPolicyHandler_PolicySelectorWithServices_AddsPolicyHandler()
    {
        var serviceCollection = new ServiceCollection();
 
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
            .AddPolicyHandler((req) => req.RequestUri.AbsolutePath == "/" ? RetryPolicy : NoOpPolicy)
            .ConfigurePrimaryHttpMessageHandler(() => PrimaryHandler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("example.com");
 
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var response = await client.SendAsync(new HttpRequestMessage());
 
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
 
        // Act 4
        await Assert.ThrowsAsync<OverflowException>(() => client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/throw")));
    }
 
    [Fact]
    public async Task AddPolicyHandlerFromRegistry_Name_AddsPolicyHandler()
    {
        var serviceCollection = new ServiceCollection();
 
        var registry = serviceCollection.AddPolicyRegistry();
        registry.Add<IAsyncPolicy<HttpResponseMessage>>("retry", RetryPolicy);
 
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
            .AddPolicyHandlerFromRegistry("retry")
            .ConfigurePrimaryHttpMessageHandler(() => PrimaryHandler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("example.com");
 
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var response = await client.SendAsync(new HttpRequestMessage());
 
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
    }
 
    [Fact]
    public async Task AddPolicyHandlerFromRegistry_Dynamic_AddsPolicyHandler()
    {
        var serviceCollection = new ServiceCollection();
 
        var registry = serviceCollection.AddPolicyRegistry();
        registry.Add<IAsyncPolicy<HttpResponseMessage>>("noop", NoOpPolicy);
        registry.Add<IAsyncPolicy<HttpResponseMessage>>("retry", RetryPolicy);
 
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
            .AddPolicyHandlerFromRegistry((reg, req) =>
            {
                return req.RequestUri.AbsolutePath == "/" ?
                    reg.Get<IAsyncPolicy<HttpResponseMessage>>("retry") :
                    reg.Get<IAsyncPolicy<HttpResponseMessage>>("noop");
            })
            .ConfigurePrimaryHttpMessageHandler(() => PrimaryHandler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("example.com");
 
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var response = await client.SendAsync(new HttpRequestMessage());
 
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
 
        // Act 4
        await Assert.ThrowsAsync<OverflowException>(() => client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/throw")));
    }
 
    [Theory]
    [InlineData(HttpStatusCode.RequestTimeout)]
    [InlineData((HttpStatusCode)500)]
    [InlineData((HttpStatusCode)501)]
    [InlineData((HttpStatusCode)502)]
    [InlineData((HttpStatusCode)503)]
    public async Task AddTransientHttpErrorPolicy_AddsPolicyHandler_HandlesStatusCode(HttpStatusCode statusCode)
    {
        // Arrange
        using var handler = new SequenceMessageHandler()
        {
            Responses =
                {
                    (req) => new HttpResponseMessage(statusCode),
                    (req) => new HttpResponseMessage(HttpStatusCode.OK),
                },
        };
 
        var serviceCollection = new ServiceCollection();
 
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
            .AddTransientHttpErrorPolicy(b => b.RetryAsync(5))
            .ConfigurePrimaryHttpMessageHandler(() => handler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("example.com");
 
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/"));
 
        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
 
    [Fact]
    public async Task AddTransientHttpErrorPolicy_AddsPolicyHandler_HandlesHttpRequestException()
    {
        // Arrange
        using var handler = new SequenceMessageHandler()
        {
            Responses =
                {
                    (req) => { throw new HttpRequestException("testing..."); },
                    (req) => new HttpResponseMessage(HttpStatusCode.OK),
                },
        };
 
        var serviceCollection = new ServiceCollection();
 
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
            .AddTransientHttpErrorPolicy(b => b.RetryAsync(5))
            .ConfigurePrimaryHttpMessageHandler(() => handler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("example.com");
 
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/"));
 
        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
 
    [Fact]
    public async Task AddPolicyHandlerFromRegistry_PolicySelectorWithKey_AddsPolicyHandler()
    {
        var serviceCollection = new ServiceCollection();
        var registry = serviceCollection.AddPolicyRegistry();
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("Service")
            .AddPolicyHandler(
            (sp, req, key) =>
            {
                return RetryPolicy;
            },
            (r) =>
            {
                return r.RequestUri.Host;
            }
            )
            .ConfigurePrimaryHttpMessageHandler(() => PrimaryHandler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("Service");
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var request = new HttpRequestMessage(HttpMethod.Get, "http://host1/Service1/");
        var response = await client.SendAsync(request);
 
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        Assert.True(registry.ContainsKey("host1"));
        Assert.Equal(1, registry.Count);
 
        // Act 4
        request = new HttpRequestMessage(HttpMethod.Get, "http://host1/Service1/");
        response = await client.SendAsync(request);
 
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        Assert.True(registry.ContainsKey("host1"));
        Assert.Equal(1, registry.Count);
 
        // Act 4
        request = new HttpRequestMessage(HttpMethod.Get, "http://host2/Service1/");
        response = await client.SendAsync(request);
 
        // Assert policy count
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
        Assert.Equal(2, registry.Count);
        Assert.True(registry.ContainsKey("host1"));
        Assert.True(registry.ContainsKey("host2"));
    }
 
    [Fact]
    public async Task AddPolicyHandlerFromRegistry_WithConfigureDelegate_AddsPolicyHandler()
    {
        var options = new PollyPolicyOptions()
        {
            PolicyName = "retrypolicy"
        };
 
        var serviceCollection = new ServiceCollection();
 
        serviceCollection.AddSingleton(options);
 
        serviceCollection.AddPolicyRegistry((serviceProvider, registry) =>
        {
            string policyName = serviceProvider.GetRequiredService<PollyPolicyOptions>().PolicyName;
 
            registry.Add<IAsyncPolicy<HttpResponseMessage>>(policyName, RetryPolicy);
        });
 
        IList<DelegatingHandler> additionalHandlers = null;
 
        // Act1
        serviceCollection.AddHttpClient("example.com", c => c.BaseAddress = new Uri("http://example.com"))
            .AddPolicyHandlerFromRegistry(options.PolicyName)
            .ConfigurePrimaryHttpMessageHandler(() => PrimaryHandler)
            .ConfigureAdditionalHttpMessageHandlers((handlers, _) => additionalHandlers = handlers);
 
        var services = serviceCollection.BuildServiceProvider();
        var factory = services.GetRequiredService<IHttpClientFactory>();
 
        // Act2
        var client = factory.CreateClient("example.com");
 
        // Assert
        Assert.NotNull(client);
 
        Assert.Collection(
            additionalHandlers,
            h => Assert.IsType<LoggingScopeHttpMessageHandler>(h),
            h => Assert.IsType<PolicyHttpMessageHandler>(h),
            h => Assert.IsType<LoggingHttpMessageHandler>(h));
 
        // Act 3
        var response = await client.SendAsync(new HttpRequestMessage());
 
        // Assert
        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
    }
 
    [Fact]
    public void AddPolicyHandlerFromRegistry_WithoutConfigureDelegate_AddsPolicyRegistries()
    {
        var serviceCollection = new ServiceCollection();
 
        // Act
        serviceCollection.AddPolicyRegistry();
 
        var services = serviceCollection.BuildServiceProvider();
        var registry = services.GetService<IPolicyRegistry<string>>();
 
        // Assert
        Assert.NotNull(registry);
        Assert.Same(registry, services.GetService<IConcurrentPolicyRegistry<string>>());
        Assert.Same(registry, services.GetService<IReadOnlyPolicyRegistry<string>>());
    }
 
    [Fact]
    public void AddPolicyHandlerFromRegistry_WithRegistry_AddsPolicyRegistries()
    {
        var serviceCollection = new ServiceCollection();
        var registry = new PolicyRegistry();
 
        // Act
        serviceCollection.AddPolicyRegistry(registry);
 
        var services = serviceCollection.BuildServiceProvider();
 
        // Assert
        Assert.Same(registry, services.GetService<IConcurrentPolicyRegistry<string>>());
        Assert.Same(registry, services.GetService<IPolicyRegistry<string>>());
        Assert.Same(registry, services.GetService<IReadOnlyPolicyRegistry<string>>());
    }
 
    [Fact]
    public void AddPolicyHandlerFromRegistry_WithConfigureDelegate_AddsPolicyRegistries()
    {
        var serviceCollection = new ServiceCollection();
 
        // Act
        serviceCollection.AddPolicyRegistry((serviceProvider, registry) =>
        {
            // No-op
        });
 
        var services = serviceCollection.BuildServiceProvider();
        var registry = services.GetService<IPolicyRegistry<string>>();
 
        // Assert
        Assert.NotNull(registry);
        Assert.Same(registry, services.GetService<IConcurrentPolicyRegistry<string>>());
        Assert.Same(registry, services.GetService<IReadOnlyPolicyRegistry<string>>());
    }
 
    [Fact]
    public void AddPolicyRegistry_DoesNotOverrideOrAddExtraRegistrations()
    {
        // Arrange
        var serviceCollection = new ServiceCollection();
 
        // Act 1
        var existingRegistry = serviceCollection.AddPolicyRegistry();
 
        // Act 2
        var registry = serviceCollection.AddPolicyRegistry();
        var services = serviceCollection.BuildServiceProvider();
 
        // Assert
        Assert.NotNull(existingRegistry);
        Assert.Same(existingRegistry, registry);
 
        Assert.Same(existingRegistry, services.GetService<IPolicyRegistry<string>>());
        Assert.Same(existingRegistry, services.GetService<IConcurrentPolicyRegistry<string>>());
        Assert.Same(existingRegistry, services.GetService<IReadOnlyPolicyRegistry<string>>());
 
        Assert.Single(serviceCollection, sd => sd.ServiceType == typeof(IPolicyRegistry<string>));
        Assert.Single(serviceCollection, sd => sd.ServiceType == typeof(IReadOnlyPolicyRegistry<string>));
        Assert.Single(serviceCollection, sd => sd.ServiceType == typeof(IConcurrentPolicyRegistry<string>));
    }
 
    [Theory]
    [InlineData(typeof(IPolicyRegistry<string>))]
    [InlineData(typeof(IReadOnlyPolicyRegistry<string>))]
    [InlineData(typeof(IConcurrentPolicyRegistry<string>))]
    public void AddPolicyRegistry_AddsOnlyMissingRegistrations(Type missingType)
    {
        // Arrange
        var serviceCollection = new ServiceCollection();
        var registry = new PolicyRegistry();
        var policyTypes = new List<Type>
        {
            typeof(IPolicyRegistry<string>),
            typeof(IReadOnlyPolicyRegistry<string>),
            typeof(IConcurrentPolicyRegistry<string>)
        };
 
        // Act 1
        foreach (var policyType in policyTypes.Where(x => x != missingType))
        {
            serviceCollection.AddSingleton(policyType, registry);
        }
 
        // Act 2
        serviceCollection.AddPolicyRegistry();
 
        // Assert
        Assert.Single(serviceCollection, sd => sd.ServiceType == typeof(IPolicyRegistry<string>));
        Assert.Single(serviceCollection, sd => sd.ServiceType == typeof(IReadOnlyPolicyRegistry<string>));
        Assert.Single(serviceCollection, sd => sd.ServiceType == typeof(IConcurrentPolicyRegistry<string>));
    }
 
    // Throws an exception or fails on even numbered requests, otherwise succeeds.
    private class FaultyMessageHandler : DelegatingHandler
    {
        public int CallCount { get; private set; }
 
        public Func<Exception> CreateException { get; set; } = () => new OverflowException();
 
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            if (CallCount++ % 2 == 0)
            {
                throw CreateException();
            }
            else
            {
                return Task.FromResult(new HttpResponseMessage(HttpStatusCode.Created));
            }
        }
    }
 
    private class SequenceMessageHandler : DelegatingHandler
    {
        public int CallCount { get; private set; }
 
        public List<Func<HttpRequestMessage, HttpResponseMessage>> Responses { get; } = new List<Func<HttpRequestMessage, HttpResponseMessage>>();
 
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            var func = Responses[CallCount++ % Responses.Count];
            return Task.FromResult(func(request));
        }
    }
 
    private class PollyPolicyOptions
    {
        public string PolicyName { get; set; }
    }
}