diff --git a/src/Ocelot/Cache/CacheObject.cs b/src/Ocelot/Cache/CacheObject.cs new file mode 100644 index 00000000..adee3ed8 --- /dev/null +++ b/src/Ocelot/Cache/CacheObject.cs @@ -0,0 +1,16 @@ +namespace Ocelot.Cache +{ + using System; + + class CacheObject + { + public CacheObject(T value, DateTime expires) + { + Value = value; + Expires = expires; + } + + public T Value { get; } + public DateTime Expires { get; } + } +} diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index e70afec1..c2054f6e 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -5,28 +5,7 @@ namespace Ocelot.Cache public interface IOcelotCache { void Add(string key, T value, TimeSpan ttl, string region); - void AddAndDelete(string key, T value, TimeSpan ttl, string region); T Get(string key, string region); void ClearRegion(string region); - } - - public class NoCache : IOcelotCache - { - public void Add(string key, T value, TimeSpan ttl, string region) - { - } - - public void AddAndDelete(string key, T value, TimeSpan ttl, string region) - { - } - - public void ClearRegion(string region) - { - } - - public T Get(string key, string region) - { - return default(T); - } } } diff --git a/src/Ocelot/Cache/InMemoryCache.cs b/src/Ocelot/Cache/InMemoryCache.cs new file mode 100644 index 00000000..fe1c8791 --- /dev/null +++ b/src/Ocelot/Cache/InMemoryCache.cs @@ -0,0 +1,71 @@ +namespace Ocelot.Cache +{ + using System; + using System.Collections.Generic; + + public class InMemoryCache : IOcelotCache + { + private readonly Dictionary> _cache; + private readonly Dictionary> _regions; + + public InMemoryCache() + { + _cache = new Dictionary>(); + _regions = new Dictionary>(); + } + + public void Add(string key, T value, TimeSpan ttl, string region) + { + if (ttl.TotalMilliseconds <= 0) + { + return; + } + + var expires = DateTime.UtcNow.Add(ttl); + + _cache.Add(key, new CacheObject(value, expires)); + + if (_regions.ContainsKey(region)) + { + var current = _regions[region]; + if (!current.Contains(key)) + { + current.Add(key); + } + } + else + { + _regions.Add(region, new List{ key }); + } + } + + public void ClearRegion(string region) + { + if (_regions.ContainsKey(region)) + { + var keys = _regions[region]; + foreach (var key in keys) + { + _cache.Remove(key); + } + } + } + + public T Get(string key, string region) + { + if (_cache.ContainsKey(key)) + { + var cached = _cache[key]; + + if (cached.Expires > DateTime.UtcNow) + { + return cached.Value; + } + + _cache.Remove(key); + } + + return default(T); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs index 30adaed2..6d0af409 100644 --- a/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/QoSOptionsCreator.cs @@ -17,8 +17,8 @@ namespace Ocelot.Configuration.Creator public QoSOptions Create(FileQoSOptions options, string pathTemplate, string[] httpMethods) { - var key = CreateKey(pathTemplate, httpMethods); - + var key = CreateKey(pathTemplate, httpMethods); + return Map(key, options.TimeoutValue, options.DurationOfBreak, options.ExceptionsAllowedBeforeBreaking); } diff --git a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs index 27f1f2ae..8d1ca3f5 100644 --- a/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileGlobalConfiguration.cs @@ -23,7 +23,7 @@ public FileLoadBalancerOptions LoadBalancerOptions { get; set; } - public string DownstreamScheme { get; set; } + public string DownstreamScheme { get; set; } public FileHttpHandlerOptions HttpHandlerOptions { get; set; } } diff --git a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs index d0c6de0e..ebac0a30 100644 --- a/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/FileConfigurationFluentValidator.cs @@ -9,13 +9,20 @@ using System.Threading.Tasks; namespace Ocelot.Configuration.Validator { + using System; + using Microsoft.Extensions.DependencyInjection; + using Requester; + public class FileConfigurationFluentValidator : AbstractValidator, IConfigurationValidator { - public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider) + private readonly IServiceProvider _provider; + + public FileConfigurationFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider provider) { + _provider = provider; RuleFor(configuration => configuration.ReRoutes) - .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider)); - + .SetCollectionValidator(new ReRouteFluentValidator(authenticationSchemeProvider, provider)); + RuleForEach(configuration => configuration.ReRoutes) .Must((config, reRoute) => IsNotDuplicateIn(reRoute, config.ReRoutes)) .WithMessage((config, reRoute) => $"{nameof(reRoute)} {reRoute.UpstreamPathTemplate} has duplicate"); diff --git a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs index c2f4dc62..c54fc2aa 100644 --- a/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs +++ b/src/Ocelot/Configuration/Validator/ReRouteFluentValidator.cs @@ -1,19 +1,24 @@ -using FluentValidation; -using Microsoft.AspNetCore.Authentication; -using Ocelot.Configuration.File; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.Validator +namespace Ocelot.Configuration.Validator { + using FluentValidation; + using Microsoft.AspNetCore.Authentication; + using Ocelot.Configuration.File; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System; + using Microsoft.Extensions.DependencyInjection; + using Requester; + public class ReRouteFluentValidator : AbstractValidator { private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; + private readonly IServiceProvider _serviceProvider; - public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider) + public ReRouteFluentValidator(IAuthenticationSchemeProvider authenticationSchemeProvider, IServiceProvider serviceProvider) { _authenticationSchemeProvider = authenticationSchemeProvider; + _serviceProvider = serviceProvider; RuleFor(reRoute => reRoute.DownstreamPathTemplate) .Must(path => path.StartsWith("/")) @@ -48,11 +53,13 @@ namespace Ocelot.Configuration.Validator .WithMessage("{PropertyValue} is unsupported authentication provider"); When(reRoute => reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.ServiceName).NotEmpty().WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); + RuleFor(r => r.ServiceName).NotEmpty() + .WithMessage("ServiceName cannot be empty or null when using service discovery or Ocelot cannot look up your service!"); }); When(reRoute => !reRoute.UseServiceDiscovery, () => { - RuleFor(r => r.DownstreamHostAndPorts).NotEmpty().WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); + RuleFor(r => r.DownstreamHostAndPorts).NotEmpty() + .WithMessage("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!"); }); When(reRoute => !reRoute.UseServiceDiscovery, () => { diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 62adf1fb..90907465 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -45,13 +45,10 @@ namespace Ocelot.DependencyInjection { Configuration = configurationRoot; Services = services; - Services.Configure(configurationRoot); - - //default no caches... - Services.TryAddSingleton, NoCache>(); - Services.TryAddSingleton, NoCache>(); + Services.TryAddSingleton, InMemoryCache>(); + Services.TryAddSingleton, InMemoryCache>(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); @@ -105,6 +102,18 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.AddMemoryCache(); Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.AddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); //add asp.net services.. var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; @@ -118,19 +127,6 @@ namespace Ocelot.DependencyInjection Services.AddLogging(); Services.AddMiddlewareAnalysis(); Services.AddWebEncoders(); - - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.AddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); - Services.TryAddSingleton(); } public IOcelotBuilder AddSingletonDefinedAggregator() diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs index 89de2840..45858787 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs @@ -4,6 +4,7 @@ namespace Ocelot.Requester using System.Collections.Generic; using System.Linq; using System.Net.Http; + using Logging; using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; using Ocelot.Responses; @@ -14,18 +15,21 @@ namespace Ocelot.Requester private readonly ITracingHandlerFactory _tracingFactory; private readonly IQoSFactory _qoSFactory; private readonly IServiceProvider _serviceProvider; + private readonly IOcelotLogger _logger; public DelegatingHandlerHandlerFactory( ITracingHandlerFactory tracingFactory, IQoSFactory qoSFactory, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + IOcelotLoggerFactory loggerFactory) { + _logger = loggerFactory.CreateLogger(); _serviceProvider = serviceProvider; _tracingFactory = tracingFactory; _qoSFactory = qoSFactory; } - public Response>> Get(DownstreamReRoute request) + public Response>> Get(DownstreamReRoute downstreamReRoute) { var globalDelegatingHandlers = _serviceProvider .GetServices() @@ -39,7 +43,7 @@ namespace Ocelot.Requester foreach (var handler in globalDelegatingHandlers) { - if (GlobalIsInHandlersConfig(request, handler)) + if (GlobalIsInHandlersConfig(downstreamReRoute, handler)) { reRouteSpecificHandlers.Add(handler.DelegatingHandler); } @@ -49,9 +53,9 @@ namespace Ocelot.Requester } } - if (request.DelegatingHandlers.Any()) + if (downstreamReRoute.DelegatingHandlers.Any()) { - var sorted = SortByConfigOrder(request, reRouteSpecificHandlers); + var sorted = SortByConfigOrder(downstreamReRoute, reRouteSpecificHandlers); foreach (var handler in sorted) { @@ -59,14 +63,14 @@ namespace Ocelot.Requester } } - if (request.HttpHandlerOptions.UseTracing) + if (downstreamReRoute.HttpHandlerOptions.UseTracing) { handlers.Add(() => (DelegatingHandler)_tracingFactory.Get()); } - if (request.QosOptions.UseQos) + if (downstreamReRoute.QosOptions.UseQos) { - var handler = _qoSFactory.Get(request); + var handler = _qoSFactory.Get(downstreamReRoute); if (handler != null && !handler.IsError) { @@ -74,7 +78,8 @@ namespace Ocelot.Requester } else { - return new ErrorResponse>>(handler?.Errors); + _logger.LogWarning($"ReRoute {downstreamReRoute.UpstreamPathTemplate} specifies use QoS but no QosHandler found in DI container. Will use not use a QosHandler, please check your setup!"); + handlers.Add(() => new NoQosDelegatingHandler()); } } diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs index 53b6a73c..4d905c35 100644 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs +++ b/src/Ocelot/Requester/IDelegatingHandlerHandlerFactory.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Configuration; -using Ocelot.Responses; - namespace Ocelot.Requester { + using System; + using System.Collections.Generic; + using System.Net.Http; + using Ocelot.Configuration; + using Ocelot.Responses; + public interface IDelegatingHandlerHandlerFactory { - Response>> Get(DownstreamReRoute request); + Response>> Get(DownstreamReRoute downstreamReRoute); } } diff --git a/src/Ocelot/Requester/NoQosDelegatingHandler.cs b/src/Ocelot/Requester/NoQosDelegatingHandler.cs new file mode 100644 index 00000000..995b50ed --- /dev/null +++ b/src/Ocelot/Requester/NoQosDelegatingHandler.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Requester +{ + using System.Net.Http; + + public class NoQosDelegatingHandler : DelegatingHandler + { + } +} diff --git a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs index e555ade7..5ecedd1a 100644 --- a/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs +++ b/test/Ocelot.AcceptanceTests/CannotStartOcelotTests.cs @@ -1,11 +1,11 @@ -using System; -using System.Collections.Generic; -using Ocelot.Configuration.File; -using Shouldly; -using Xunit; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using Ocelot.Configuration.File; + using Shouldly; + using Xunit; + public class CannotStartOcelotTests : IDisposable { private readonly Steps _steps; @@ -42,6 +42,7 @@ namespace Ocelot.AcceptanceTests } exception.ShouldNotBeNull(); + exception.Message.ShouldBe("One or more errors occurred. (Unable to start Ocelot, errors are: Downstream Path Template test doesnt start with forward slash,Upstream Path Template api doesnt start with forward slash,When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!)"); } public void Dispose() diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 26ccd1cd..7615e0b4 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Net; + using Microsoft.AspNetCore.Http; using Ocelot.Configuration.File; using TestStack.BDDfy; using Xunit; diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 5fa7bcff..23cb19dd 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -523,13 +523,7 @@ namespace Ocelot.AcceptanceTests } }, UpstreamPathTemplate = "/products/{productId}", - UpstreamHttpMethod = new List { "Get" }, - QoSOptions = new FileQoSOptions() - { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 5, - TimeoutValue = 5000 - } + UpstreamHttpMethod = new List { "Get" } } } }; diff --git a/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs b/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs new file mode 100644 index 00000000..8b2e58b9 --- /dev/null +++ b/test/Ocelot.UnitTests/Cache/InMemoryCacheTests.cs @@ -0,0 +1,69 @@ +namespace Ocelot.UnitTests.Cache +{ + using System; + using System.Threading; + using Ocelot.Cache; + using Shouldly; + using Xunit; + + public class InMemoryCacheTests + { + private readonly InMemoryCache _cache; + + public InMemoryCacheTests() + { + _cache = new InMemoryCache(); + } + + [Fact] + public void should_cache() + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromSeconds(100), "region"); + var result = _cache.Get("1", "region"); + result.ShouldBe(fake); + fake.Value.ShouldBe(1); + } + + [Fact] + public void should_clear_region() + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromSeconds(100), "region"); + _cache.ClearRegion("region"); + var result = _cache.Get("1", "region"); + result.ShouldBeNull(); + } + + [Fact] + public void should_clear_key_if_ttl_expired() + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromMilliseconds(50), "region"); + Thread.Sleep(200); + var result = _cache.Get("1", "region"); + result.ShouldBeNull(); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void should_not_add_to_cache_if_timespan_empty(int ttl) + { + var fake = new Fake(1); + _cache.Add("1", fake, TimeSpan.FromSeconds(ttl), "region"); + var result = _cache.Get("1", "region"); + result.ShouldBeNull(); + } + + class Fake + { + public Fake(int value) + { + Value = value; + } + + public int Value { get; } + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs index d33bb6f4..da8d0e75 100644 --- a/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs +++ b/test/Ocelot.UnitTests/Configuration/ConfigurationFluentValidationTests.cs @@ -1,23 +1,26 @@ -using System.Collections.Generic; -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Moq; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Validator; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration +namespace Ocelot.UnitTests.Configuration { + using System.Collections.Generic; + using System.Security.Claims; + using System.Text.Encodings.Web; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authentication; + using Microsoft.Extensions.Logging; + using Microsoft.Extensions.Options; + using Moq; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Validator; + using Ocelot.Responses; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Requester; + using Requester; + public class ConfigurationFluentValidationTests { - private readonly IConfigurationValidator _configurationValidator; + private IConfigurationValidator _configurationValidator; private FileConfiguration _fileConfiguration; private Response _result; private readonly Mock _provider; @@ -25,8 +28,10 @@ namespace Ocelot.UnitTests.Configuration public ConfigurationFluentValidationTests() { _provider = new Mock(); - _configurationValidator = new FileConfigurationFluentValidator(_provider.Object); - } + var provider = new ServiceCollection() + .BuildServiceProvider(); + _configurationValidator = new FileConfigurationFluentValidator(_provider.Object, provider); + } [Fact] public void configuration_is_valid_if_aggregates_are_valid() @@ -1046,6 +1051,15 @@ namespace Ocelot.UnitTests.Configuration }); } + private void GivenAQosDelegate() + { + var services = new ServiceCollection(); + QosDelegatingHandlerDelegate del = (a, b) => new FakeDelegatingHandler(); + services.AddSingleton(del); + var provider = services.BuildServiceProvider(); + _configurationValidator = new FileConfigurationFluentValidator(_provider.Object, provider); + } + private class TestOptions : AuthenticationSchemeOptions { } diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index 679b051c..f63c2f34 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -22,7 +22,8 @@ namespace Ocelot.UnitTests.Requester { private DelegatingHandlerHandlerFactory _factory; private readonly Mock _loggerFactory; - private DownstreamReRoute _request; + private readonly Mock _logger; + private DownstreamReRoute _downstreamReRoute; private Response>> _result; private readonly Mock _qosFactory; private readonly Mock _tracingFactory; @@ -36,6 +37,8 @@ namespace Ocelot.UnitTests.Requester _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); } @@ -300,7 +303,7 @@ namespace Ocelot.UnitTests.Requester } [Fact] - public void should_return_error() + public void should_log_error_and_return_no_qos_provider_delegate_when_qos_factory_returns_error() { var qosOptions = new QoSOptionsBuilder() .WithTimeoutValue(1) @@ -310,16 +313,60 @@ namespace Ocelot.UnitTests.Requester var reRoute = new DownstreamReRouteBuilder() .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false, true)).WithLoadBalancerKey("").Build(); + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true)) + .WithLoadBalancerKey("") + .Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosFactoryReturnsError()) - .And(x => GivenTheServiceProviderReturnsNothing()) + .And(x => GivenTheTracingFactoryReturns()) + .And(x => GivenTheServiceProviderReturnsGlobalDelegatingHandlers()) + .And(x => GivenTheServiceProviderReturnsSpecificDelegatingHandlers()) .When(x => WhenIGet()) - .Then(x => ThenAnErrorIsReturned()) + .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 reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, true, true)) + .WithLoadBalancerKey("") + .Build(); + + this.Given(x => GivenTheFollowingRequest(reRoute)) + .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($"ReRoute {_downstreamReRoute.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 { @@ -396,6 +443,13 @@ namespace Ocelot.UnitTests.Requester .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; @@ -411,14 +465,14 @@ namespace Ocelot.UnitTests.Requester private void GivenTheFollowingRequest(DownstreamReRoute request) { - _request = request; + _downstreamReRoute = request; } private void WhenIGet() { _serviceProvider = _services.BuildServiceProvider(); - _factory = new DelegatingHandlerHandlerFactory(_tracingFactory.Object, _qosFactory.Object, _serviceProvider); - _result = _factory.Get(_request); + _factory = new DelegatingHandlerHandlerFactory(_tracingFactory.Object, _qosFactory.Object, _serviceProvider, _loggerFactory.Object); + _result = _factory.Get(_downstreamReRoute); } private void ThenNoDelegatesAreInTheProvider() diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index 429c5a90..1b0d17eb 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -89,22 +89,6 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } - private void GivenARealCache() - { - _realCache = new MemoryHttpClientCache(); - _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); - } - - private void ThenTheHttpClientIsFromTheCache() - { - _againHttpClient.ShouldBe(_firstHttpClient); - } - - private void WhenISave() - { - _builder.Save(); - } - [Fact] public void should_log_if_ignoring_ssl_errors() { @@ -214,9 +198,25 @@ namespace Ocelot.UnitTests.Requester .BDDfy(); } + private void GivenARealCache() + { + _realCache = new MemoryHttpClientCache(); + _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); + } + + private void ThenTheHttpClientIsFromTheCache() + { + _againHttpClient.ShouldBe(_firstHttpClient); + } + + private void WhenISave() + { + _builder.Save(); + } + private void GivenCacheIsCalledWithExpectedKey(string expectedKey) { - this._cacheHandlers.Verify(x => x.Get(It.Is(p => p.Equals(expectedKey, StringComparison.OrdinalIgnoreCase))), Times.Once); + _cacheHandlers.Verify(x => x.Get(It.Is(p => p.Equals(expectedKey, StringComparison.OrdinalIgnoreCase))), Times.Once); } private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged()