namespace Ocelot.UnitTests.Requester { using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Logging; using Ocelot.Requester; using Ocelot.Requester.QoS; using Ocelot.Responses; using Responder; using Shouldly; using System; using System.Collections.Generic; using System.Net.Http; using TestStack.BDDfy; using Xunit; public class DelegatingHandlerHandlerProviderFactoryTests { private DelegatingHandlerHandlerFactory _factory; private readonly Mock _loggerFactory; private readonly Mock _logger; private DownstreamRoute _downstreamRoute; private Response>> _result; private readonly Mock _qosFactory; private readonly Mock _tracingFactory; private IServiceProvider _serviceProvider; private readonly IServiceCollection _services; private readonly QosDelegatingHandlerDelegate _qosDelegate; public DelegatingHandlerHandlerProviderFactoryTests() { _qosDelegate = (a, b) => new FakeQoSHandler(); _tracingFactory = new Mock(); _qosFactory = new Mock(); _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _services = new ServiceCollection(); _services.AddSingleton(_qosDelegate); } [Fact] public void should_follow_ordering_add_specifics() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true, int.MaxValue)) .WithDelegatingHandlers(new List { "FakeDelegatingHandler", "FakeDelegatingHandlerTwo" }) .WithLoadBalancerKey("") .Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(6)) .And(x => ThenHandlerAtPositionIs(0)) .And(x => ThenHandlerAtPositionIs(1)) .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) .And(x => ThenHandlerAtPositionIs(4)) .And(x => ThenHandlerAtPositionIs(5)) .BDDfy(); } [Fact] public void should_follow_ordering_order_specifics_and_globals() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true, int.MaxValue)) .WithDelegatingHandlers(new List { "FakeDelegatingHandlerTwo", "FakeDelegatingHandler", "FakeDelegatingHandlerFour" }) .WithLoadBalancerKey("") .Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(6)) .And(x => ThenHandlerAtPositionIs(0)) //first because global not in config .And(x => ThenHandlerAtPositionIs(1)) //first from config .And(x => ThenHandlerAtPositionIs(2)) //second from config .And(x => ThenHandlerAtPositionIs(3)) //third from config (global) .And(x => ThenHandlerAtPositionIs(4)) .And(x => ThenHandlerAtPositionIs(5)) .BDDfy(); } [Fact] public void should_follow_ordering_order_specifics() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true, int.MaxValue)) .WithDelegatingHandlers(new List { "FakeDelegatingHandlerTwo", "FakeDelegatingHandler" }) .WithLoadBalancerKey("") .Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(6)) .And(x => ThenHandlerAtPositionIs(0)) .And(x => ThenHandlerAtPositionIs(1)) .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) .And(x => ThenHandlerAtPositionIs(4)) .And(x => ThenHandlerAtPositionIs(5)) .BDDfy(); } [Fact] public void should_follow_ordering_order_and_only_add_specifics_in_config() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true, int.MaxValue)) .WithDelegatingHandlers(new List { "FakeDelegatingHandler", }) .WithLoadBalancerKey("") .Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(5)) .And(x => ThenHandlerAtPositionIs(0)) .And(x => ThenHandlerAtPositionIs(1)) .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) .And(x => ThenHandlerAtPositionIs(4)) .BDDfy(); } [Fact] public void should_follow_ordering_dont_add_specifics() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true, int.MaxValue)) .WithLoadBalancerKey("") .Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheTracingFactoryReturns()) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(4)) .And(x => ThenHandlerAtPositionIs(0)) .And(x => ThenHandlerAtPositionIs(1)) .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) .BDDfy(); } [Fact] public void should_apply_re_route_specific() { var qosOptions = new QoSOptionsBuilder() .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true, int.MaxValue)) .WithDelegatingHandlers(new List { "FakeDelegatingHandler", "FakeDelegatingHandlerTwo" }) .WithLoadBalancerKey("") .Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(2)) .And(x => ThenTheDelegatesAreAddedCorrectly()) .BDDfy(); } [Fact] public void should_all_from_all_routes_provider_and_qos() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true, int.MaxValue)).WithLoadBalancerKey("").Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(3)) .And(x => ThenTheDelegatesAreAddedCorrectly()) .And(x => ThenItIsQosHandler(2)) .BDDfy(); } [Fact] public void should_return_provider_with_no_delegates() { var qosOptions = new QoSOptionsBuilder() .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true, int.MaxValue)).WithLoadBalancerKey("").Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenNoDelegatesAreInTheProvider()) .BDDfy(); } [Fact] public void should_return_provider_with_qos_delegate() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true, int.MaxValue)).WithLoadBalancerKey("").Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(1)) .And(x => ThenItIsQosHandler(0)) .BDDfy(); } [Fact] public void should_return_provider_with_qos_delegate_when_timeout_value_set() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true, int.MaxValue)).WithLoadBalancerKey("").Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturns(new FakeQoSHandler())) .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(1)) .And(x => ThenItIsQosHandler(0)) .BDDfy(); } [Fact] public void should_log_error_and_return_no_qos_provider_delegate_when_qos_factory_returns_error() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true, int.MaxValue)) .WithLoadBalancerKey("") .Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturnsError()) .And(x => GivenTheTracingFactoryReturns()) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(4)) .And(x => ThenHandlerAtPositionIs(0)) .And(x => ThenHandlerAtPositionIs(1)) .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) .And(_ => ThenTheWarningIsLogged()) .BDDfy(); } [Fact] public void should_log_error_and_return_no_qos_provider_delegate_when_qos_factory_returns_null() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .Build(); var route = new DownstreamRouteBuilder() .WithQosOptions(qosOptions) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true, int.MaxValue)) .WithLoadBalancerKey("") .Build(); this.Given(x => GivenTheFollowingRequest(route)) .And(x => GivenTheQosFactoryReturnsNull()) .And(x => GivenTheTracingFactoryReturns()) .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(4)) .And(x => ThenHandlerAtPositionIs(0)) .And(x => ThenHandlerAtPositionIs(1)) .And(x => ThenHandlerAtPositionIs(2)) .And(x => ThenHandlerAtPositionIs(3)) .And(_ => ThenTheWarningIsLogged()) .BDDfy(); } private void ThenTheWarningIsLogged() { _logger.Verify(x => x.LogWarning($"Route {_downstreamRoute.UpstreamPathTemplate} specifies use QoS but no QosHandler found in DI container. Will use not use a QosHandler, please check your setup!"), Times.Once); } private void ThenHandlerAtPositionIs(int pos) where T : DelegatingHandler { var delegates = _result.Data; var del = delegates[pos].Invoke(); del.ShouldBeOfType(); } private void GivenTheTracingFactoryReturns() { _tracingFactory .Setup(x => x.Get()) .Returns(new FakeTracingHandler()); } private void GivenTheServiceProviderReturnsGlobalDelegatingHandlers() where TOne : DelegatingHandler where TTwo : DelegatingHandler { _services.AddTransient(); _services.AddTransient(s => { var service = s.GetService(); return new GlobalDelegatingHandler(service); }); _services.AddTransient(); _services.AddTransient(s => { var service = s.GetService(); return new GlobalDelegatingHandler(service); }); } private void GivenTheServiceProviderReturnsSpecificDelegatingHandlers() where TOne : DelegatingHandler where TTwo : DelegatingHandler { _services.AddTransient(); _services.AddTransient(); } private void GivenTheServiceProviderReturnsNothing() { _serviceProvider = _services.BuildServiceProvider(); } private void ThenAnErrorIsReturned() { _result.IsError.ShouldBeTrue(); } private void ThenTheDelegatesAreAddedCorrectly() { var delegates = _result.Data; var del = delegates[0].Invoke(); var handler = (FakeDelegatingHandler)del; handler.Order.ShouldBe(1); del = delegates[1].Invoke(); var handlerTwo = (FakeDelegatingHandlerTwo)del; handlerTwo.Order.ShouldBe(2); } private void GivenTheQosFactoryReturns(DelegatingHandler handler) { _qosFactory .Setup(x => x.Get(It.IsAny())) .Returns(new OkResponse(handler)); } private void GivenTheQosFactoryReturnsError() { _qosFactory .Setup(x => x.Get(It.IsAny())) .Returns(new ErrorResponse(new AnyError())); } private void GivenTheQosFactoryReturnsNull() { _qosFactory .Setup(x => x.Get(It.IsAny())) .Returns((ErrorResponse)null); } private void ThenItIsQosHandler(int i) { var delegates = _result.Data; var del = delegates[i].Invoke(); del.ShouldBeOfType(); } private void ThenThereIsDelegatesInProvider(int count) { _result.ShouldNotBeNull(); _result.Data.Count.ShouldBe(count); } private void GivenTheFollowingRequest(DownstreamRoute request) { _downstreamRoute = request; } private void WhenIGet() { _serviceProvider = _services.BuildServiceProvider(); _factory = new DelegatingHandlerHandlerFactory(_tracingFactory.Object, _qosFactory.Object, _serviceProvider, _loggerFactory.Object); _result = _factory.Get(_downstreamRoute); } private void ThenNoDelegatesAreInTheProvider() { _result.ShouldNotBeNull(); _result.Data.Count.ShouldBe(0); } } internal class FakeTracingHandler : DelegatingHandler, ITracingHandler { } internal class FakeQoSHandler : DelegatingHandler { } }