diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index a6f2e4e9..b3108534 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -4,28 +4,21 @@ Delegating Handers Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ and I decided that it was going to be useful in various ways. Usage -^^^^^^ +^^^^^ In order to add delegating handlers to the HttpClient transport you need to do the following. -.. code-block:: csharp - - services.AddOcelot() - .AddDelegatingHandler(() => new FakeHandler()) - .AddDelegatingHandler(() => new FakeHandler()); - -Or for singleton like behaviour.. +This will register the Handlers as singletons. Because Ocelot caches the HttpClient for the downstream services to avoid +socket exhaustion (well known http client issue) you can only register singleton handlers. .. code-block:: csharp - var handlerOne = new FakeHandler(); - var handlerTwo = new FakeHandler(); - services.AddOcelot() - .AddDelegatingHandler(() => handlerOne) - .AddDelegatingHandler(() => handlerTwo); + .AddDelegatingHandler() + .AddDelegatingHandler() -You can have as many DelegatingHandlers as you want and they are run in a first in first out order. If you are using Ocelot's QoS functionality then that will always be run after your last delegating handler. +You can have as many DelegatingHandlers as you want and they are run in a first in first out order. If you are using Ocelot's QoS functionality then that will always be run after your last delegating handler. If you are also registering handlers in DI these will be +run first. In order to create a class that can be used a delegating handler it must look as follows diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 48db5ef7..ad1e4777 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -3,16 +3,22 @@ using CacheManager.Core; using System; using System.Net.Http; using IdentityServer4.AccessTokenValidation; +using Ocelot.Requester; namespace Ocelot.DependencyInjection { public interface IOcelotBuilder { IOcelotBuilder AddStoreOcelotConfigurationInConsul(); + IOcelotBuilder AddCacheManager(Action settings); + IOcelotBuilder AddOpenTracing(Action settings); + IOcelotAdministrationBuilder AddAdministration(string path, string secret); + IOcelotAdministrationBuilder AddAdministration(string path, Action configOptions); - IOcelotBuilder AddDelegatingHandler(Func delegatingHandler); + + IOcelotBuilder AddDelegatingHandler() where T : DelegatingHandler; } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index e1ef6b42..75dddb1d 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -57,7 +57,6 @@ namespace Ocelot.DependencyInjection { private readonly IServiceCollection _services; private readonly IConfiguration _configurationRoot; - private readonly IDelegatingHandlerHandlerProvider _provider; public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) { @@ -120,8 +119,7 @@ namespace Ocelot.DependencyInjection _services.TryAddSingleton(); _services.TryAddSingleton(); _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); + _services.TryAddSingleton(); // see this for why we register this as singleton http://stackoverflow.com/questions/37371264/invalidoperationexception-unable-to-resolve-service-for-type-microsoft-aspnetc // could maybe use a scoped data repository @@ -144,9 +142,6 @@ namespace Ocelot.DependencyInjection _services.AddWebEncoders(); _services.AddSingleton(new NullAdministrationPath()); - //these get picked out later and added to http request - _provider = new DelegatingHandlerHandlerProvider(); - _services.TryAddSingleton(_provider); _services.TryAddSingleton(); _services.TryAddSingleton(); _services.AddSingleton(); @@ -187,9 +182,10 @@ namespace Ocelot.DependencyInjection return new OcelotAdministrationBuilder(_services, _configurationRoot); } - public IOcelotBuilder AddDelegatingHandler(Func delegatingHandler) + public IOcelotBuilder AddDelegatingHandler() + where THandler : DelegatingHandler { - _provider.Add(delegatingHandler); + _services.AddSingleton(); return this; } diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs deleted file mode 100644 index eec395fb..00000000 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerHouse.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Responses; - -namespace Ocelot.Requester -{ - public class DelegatingHandlerHandlerHouse : IDelegatingHandlerHandlerHouse - { - private readonly IDelegatingHandlerHandlerProviderFactory _factory; - private readonly ConcurrentDictionary _housed; - - public DelegatingHandlerHandlerHouse(IDelegatingHandlerHandlerProviderFactory factory) - { - _factory = factory; - _housed = new ConcurrentDictionary(); - } - - public Response Get(DownstreamReRoute request) - { - try - { - if (_housed.TryGetValue(request.ReRouteKey, out var provider)) - { - //todo once day we might need a check here to see if we need to create a new provider - provider = _housed[request.ReRouteKey]; - return new OkResponse(provider); - } - - //todo - unit test for this - var providerResponse = _factory.Get(request); - - if (providerResponse.IsError) - { - return new ErrorResponse(providerResponse.Errors); - } - - provider = providerResponse.Data; - AddHoused(request.ReRouteKey, provider); - return new OkResponse(provider); - } - catch (Exception ex) - { - return new ErrorResponse(new List() - { - new UnableToFindDelegatingHandlerProviderError($"unabe to find delegating handler provider for {request.ReRouteKey} exception is {ex}") - }); - } - } - - private void AddHoused(string key, IDelegatingHandlerHandlerProvider provider) - { - _housed.AddOrUpdate(key, provider, (k, v) => provider); - } - } -} diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerProvider.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerProvider.cs deleted file mode 100644 index 71684c52..00000000 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; - -namespace Ocelot.Requester -{ - public class DelegatingHandlerHandlerProvider : IDelegatingHandlerHandlerProvider - { - private readonly Dictionary> _handlers; - - public DelegatingHandlerHandlerProvider() - { - _handlers = new Dictionary>(); - } - - public void Add(Func handler) - { - var key = _handlers.Count == 0 ? 0 : _handlers.Count + 1; - _handlers[key] = handler; - } - - public List> Get() - { - return _handlers.Count > 0 ? _handlers.OrderBy(x => x.Key).Select(x => x.Value).ToList() : new List>(); - } - } -} diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs index 468b6013..4e242137 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerProviderFactory.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; using Ocelot.Logging; using Ocelot.Requester.QoS; @@ -7,38 +9,38 @@ using Ocelot.Responses; namespace Ocelot.Requester { - public class DelegatingHandlerHandlerProviderFactory : IDelegatingHandlerHandlerProviderFactory + public class DelegatingHandlerHandlerFactory : IDelegatingHandlerHandlerFactory { private readonly ITracingHandlerFactory _factory; private readonly IOcelotLoggerFactory _loggerFactory; - private readonly IDelegatingHandlerHandlerProvider _allRoutesProvider; private readonly IQosProviderHouse _qosProviderHouse; + private readonly IServiceProvider _serviceProvider; - public DelegatingHandlerHandlerProviderFactory(IOcelotLoggerFactory loggerFactory, - IDelegatingHandlerHandlerProvider allRoutesProvider, + public DelegatingHandlerHandlerFactory(IOcelotLoggerFactory loggerFactory, ITracingHandlerFactory factory, - IQosProviderHouse qosProviderHouse) + IQosProviderHouse qosProviderHouse, + IServiceProvider serviceProvider) { + _serviceProvider = serviceProvider; _factory = factory; _loggerFactory = loggerFactory; - _allRoutesProvider = allRoutesProvider; _qosProviderHouse = qosProviderHouse; } - public Response Get(DownstreamReRoute request) + public Response>> Get(DownstreamReRoute request) { - var handlersAppliedToAll = _allRoutesProvider.Get(); + var handlersAppliedToAll = _serviceProvider.GetServices(); - var provider = new DelegatingHandlerHandlerProvider(); + var handlers = new List>(); foreach (var handler in handlersAppliedToAll) { - provider.Add(handler); + handlers.Add(() => handler); } if (request.HttpHandlerOptions.UseTracing) { - provider.Add(() => (DelegatingHandler)_factory.Get()); + handlers.Add(() => (DelegatingHandler)_factory.Get()); } if (request.IsQos) @@ -47,13 +49,13 @@ namespace Ocelot.Requester if (qosProvider.IsError) { - return new ErrorResponse(qosProvider.Errors); + return new ErrorResponse>>(qosProvider.Errors); } - provider.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory)); + handlers.Add(() => new PollyCircuitBreakingDelegatingHandler(qosProvider.Data, _loggerFactory)); } - return new OkResponse(provider); + return new OkResponse>>(handlers); } } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index 2d3a0f36..b5604081 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -6,11 +6,11 @@ namespace Ocelot.Requester { public class HttpClientBuilder : IHttpClientBuilder { - private readonly IDelegatingHandlerHandlerHouse _house; + private readonly IDelegatingHandlerHandlerFactory _factory; - public HttpClientBuilder(IDelegatingHandlerHandlerHouse house) + public HttpClientBuilder(IDelegatingHandlerHandlerFactory house) { - _house = house; + _factory = house; } public IHttpClient Create(DownstreamReRoute request) @@ -24,11 +24,9 @@ namespace Ocelot.Requester private HttpMessageHandler CreateHttpMessageHandler(HttpMessageHandler httpMessageHandler, DownstreamReRoute request) { - var provider = _house.Get(request); - - var handlers = provider.Data.Get(); - //todo handle error + var handlers = _factory.Get(request).Data; + handlers .Select(handler => handler) .Reverse() diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index de9ea1c6..f2d32733 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -13,20 +13,20 @@ namespace Ocelot.Requester { private readonly IHttpClientCache _cacheHandlers; private readonly IOcelotLogger _logger; - private readonly IDelegatingHandlerHandlerHouse _house; + private readonly IDelegatingHandlerHandlerFactory _factory; public HttpClientHttpRequester(IOcelotLoggerFactory loggerFactory, IHttpClientCache cacheHandlers, - IDelegatingHandlerHandlerHouse house) + IDelegatingHandlerHandlerFactory house) { _logger = loggerFactory.CreateLogger(); _cacheHandlers = cacheHandlers; - _house = house; + _factory = house; } public async Task> GetResponse(DownstreamContext request) { - var builder = new HttpClientBuilder(_house); + var builder = new HttpClientBuilder(_factory); var cacheKey = GetCacheKey(request); diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs deleted file mode 100644 index b236ed16..00000000 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerHouse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ocelot.Configuration; -using Ocelot.Responses; - -namespace Ocelot.Requester -{ - public interface IDelegatingHandlerHandlerHouse - { - Response Get(DownstreamReRoute request); - } -} diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerProvider.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerProvider.cs deleted file mode 100644 index addaaeb7..00000000 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerProvider.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; - -namespace Ocelot.Requester -{ - public interface IDelegatingHandlerHandlerProvider - { - void Add(Func handler); - List> Get(); - } -} diff --git a/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs b/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs index c77a62bd..53b6a73c 100644 --- a/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs +++ b/src/Ocelot/Requester/IDelegatingHandlerHandlerProviderFactory.cs @@ -1,10 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; using Ocelot.Configuration; using Ocelot.Responses; namespace Ocelot.Requester { - public interface IDelegatingHandlerHandlerProviderFactory + public interface IDelegatingHandlerHandlerFactory { - Response Get(DownstreamReRoute request); + Response>> Get(DownstreamReRoute request); } } diff --git a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs index c54294cd..72f1f341 100644 --- a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs @@ -26,8 +26,9 @@ namespace Ocelot.AcceptanceTests _steps = new Steps(); } + [Fact] - public void should_call_handlers() + public void should_call_di_handlers() { var configuration = new FileConfiguration { @@ -42,7 +43,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 61879, + Port = 7187, } }, UpstreamPathTemplate = "/", @@ -51,27 +52,98 @@ namespace Ocelot.AcceptanceTests } }; - var handlerOne = new FakeHandler(); - var handlerTwo = new FakeHandler(); - - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:61879", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:7187", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningWithHandlers(handlerOne, handlerTwo)) + .And(x => _steps.GivenOcelotIsRunningWithHandlersRegisteredInDi()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(x => ThenTheHandlersAreCalledCorrectly(handlerOne, handlerTwo)) + .And(x => ThenTheHandlersAreCalledCorrectly()) .BDDfy(); } - private void ThenTheHandlersAreCalledCorrectly(FakeHandler one, FakeHandler two) + + [Fact] + public void should_call_di_handlers_with_dependency() { - one.TimeCalled.ShouldBeLessThan(two.TimeCalled); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 7188, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + var dependency = new FakeDependency(); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:7188", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithHandlersRegisteredInDi(dependency)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => ThenTheDependencyIsCalled(dependency)) + .BDDfy(); } + private void ThenTheDependencyIsCalled(FakeDependency dependency) + { + dependency.Called.ShouldBeTrue(); + } + + private void ThenTheHandlersAreCalledCorrectly() + { + FakeHandler.TimeCalled.ShouldBeLessThan(FakeHandlerTwo.TimeCalled); + } + + public class FakeDependency + { + public bool Called; + } + + class FakeHandlerWithDependency : DelegatingHandler + { + private FakeDependency _dependency; + + public FakeHandlerWithDependency(FakeDependency dependency) + { + _dependency = dependency; + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + _dependency.Called = true; + return await base.SendAsync(request, cancellationToken); + } + } + class FakeHandler : DelegatingHandler { - public DateTime TimeCalled { get; private set; } + public static DateTime TimeCalled { get; private set; } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return await base.SendAsync(request, cancellationToken); + } + } + class FakeHandlerTwo : DelegatingHandler + { + public static DateTime TimeCalled { get; private set; } protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 755dd1a1..0ea0e6b6 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -24,6 +24,7 @@ using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBui using Ocelot.AcceptanceTests.Caching; using System.IO.Compression; using System.Text; +using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; namespace Ocelot.AcceptanceTests { @@ -174,10 +175,9 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } - /// - /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. - /// - public void GivenOcelotIsRunningWithHandlers(DelegatingHandler handlerOne, DelegatingHandler handlerTwo) + public void GivenOcelotIsRunningWithHandlersRegisteredInDi() + where TOne : DelegatingHandler + where TWo : DelegatingHandler { _webHostBuilder = new WebHostBuilder(); @@ -195,8 +195,40 @@ namespace Ocelot.AcceptanceTests { s.AddSingleton(_webHostBuilder); s.AddOcelot() - .AddDelegatingHandler(() => handlerOne) - .AddDelegatingHandler(() => handlerTwo); + .AddDelegatingHandler() + .AddDelegatingHandler(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithHandlersRegisteredInDi(FakeDependency dependency) + where TOne : DelegatingHandler + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddSingleton(dependency); + s.AddOcelot() + .AddDelegatingHandler(); }) .Configure(a => { diff --git a/test/Ocelot.AcceptanceTests/Steps.cs.orig b/test/Ocelot.AcceptanceTests/Steps.cs.orig new file mode 100644 index 00000000..84c119ab --- /dev/null +++ b/test/Ocelot.AcceptanceTests/Steps.cs.orig @@ -0,0 +1,722 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using CacheManager.Core; +using IdentityServer4.AccessTokenValidation; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; +using Shouldly; +using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; +using Ocelot.AcceptanceTests.Caching; +<<<<<<< HEAD +using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; +||||||| merged common ancestors +======= +using System.IO.Compression; +using System.Text; +>>>>>>> develop + +namespace Ocelot.AcceptanceTests +{ + public class Steps : IDisposable + { + private TestServer _ocelotServer; + private HttpClient _ocelotClient; + private HttpResponseMessage _response; + private HttpContent _postContent; + private BearerToken _token; + public HttpClient OcelotClient => _ocelotClient; + public string RequestIdKey = "OcRequestId"; + private readonly Random _random; + private IWebHostBuilder _webHostBuilder; + + public Steps() + { + _random = new Random(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = TestConfiguration.ConfigurationPath; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath) + { + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + /// + /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. + /// + public void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + internal void GivenOcelotIsRunningUsingButterfly(string butterflyUrl) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot() + .AddOpenTracing(option => + { + //this is the url that the butterfly collector server is running on... + option.CollectorUrl = butterflyUrl; + option.Service = "Ocelot"; + }); + }) + .Configure(app => + { + app.Use(async (context, next) => + { + await next.Invoke(); + }); + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + +/* + public void GivenIHaveAddedXForwardedForHeader(string value) + { + _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation("X-Forwarded-For", value); + }*/ + + public void GivenOcelotIsRunningWithMiddleareBeforePipeline(Func callback) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(app => + { + app.UseMiddleware(callback); + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithHandlersRegisteredInDi() + where TOne : DelegatingHandler + where TWo : DelegatingHandler + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddOcelot() + .AddDelegatingHandler() + .AddDelegatingHandler(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithHandlersRegisteredInDi(FakeDependency dependency) + where TOne : DelegatingHandler + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddSingleton(dependency); + s.AddOcelot() + .AddDelegatingHandler(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + /// + /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. + /// + public void GivenOcelotIsRunning(Action options, string authenticationProviderKey) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + s.AddAuthentication() + .AddIdentityServerAuthentication(authenticationProviderKey, options); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void ThenTheResponseHeaderIs(string key, string value) + { + var header = _response.Headers.GetValues(key); + header.First().ShouldBe(value); + } + + public void GivenOcelotIsRunningUsingJsonSerializedCache() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot() + .AddCacheManager((x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithJsonSerializer() + .WithHandle(typeof(InMemoryJsonHandle<>)); + }); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningUsingConsulToStoreConfig() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot().AddStoreOcelotConfigurationInConsul(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + config.AddJsonFile("configuration.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot() + .AddCacheManager((x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithJsonSerializer() + .WithHandle(typeof(InMemoryJsonHandle<>)); + }) + .AddStoreOcelotConfigurationInConsul(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + internal void ThenTheResponseShouldBe(FileConfiguration expecteds) + { + var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); + + response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for (var i = 0; i < response.ReRoutes.Count; i++) + { + for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); + response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); + } + } + + /// + /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. + /// + public void GivenOcelotIsRunning(OcelotPipelineConfiguration ocelotPipelineConfig) + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile("configuration.json") + .AddEnvironmentVariables(); + + var configuration = builder.Build(); + _webHostBuilder = new WebHostBuilder(); + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseConfiguration(configuration) + .ConfigureServices(s => + { + Action settings = (x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithDictionaryHandle(); + }; + + s.AddOcelot(configuration); + }) + .ConfigureLogging(l => + { + l.AddConsole(); + l.AddDebug(); + }) + .Configure(a => + { + a.UseOcelot(ocelotPipelineConfig).Wait(); + })); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenIHaveAddedATokenToMyRequest() + { + _ocelotClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); + } + + public void GivenIHaveAToken(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveATokenForApiReadOnlyScope(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api.readOnly"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveATokenForApi2(string url) + { + var tokenUrl = $"{url}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "client"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "api2"), + new KeyValuePair("username", "test"), + new KeyValuePair("password", "test"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + using (var httpClient = new HttpClient()) + { + var response = httpClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + } + + public void GivenIHaveAnOcelotToken(string adminPath) + { + var tokenUrl = $"{adminPath}/connect/token"; + var formData = new List> + { + new KeyValuePair("client_id", "admin"), + new KeyValuePair("client_secret", "secret"), + new KeyValuePair("scope", "admin"), + new KeyValuePair("username", "admin"), + new KeyValuePair("password", "admin"), + new KeyValuePair("grant_type", "password") + }; + var content = new FormUrlEncodedContent(formData); + + var response = _ocelotClient.PostAsync(tokenUrl, content).Result; + var responseContent = response.Content.ReadAsStringAsync().Result; + response.EnsureSuccessStatusCode(); + _token = JsonConvert.DeserializeObject(responseContent); + } + + public void VerifyIdentiryServerStarted(string url) + { + using (var httpClient = new HttpClient()) + { + var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; + response.EnsureSuccessStatusCode(); + } + } + + public void WhenIGetUrlOnTheApiGateway(string url) + { + _response = _ocelotClient.GetAsync(url).Result; + } + + public void GivenIAddAHeader(string key, string value) + { + _ocelotClient.DefaultRequestHeaders.Add(key, value); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + tasks[i] = GetForServiceDiscoveryTest(urlCopy); + Thread.Sleep(_random.Next(40, 60)); + } + + Task.WaitAll(tasks); + } + + private async Task GetForServiceDiscoveryTest(string url) + { + var response = await _ocelotClient.GetAsync(url); + var content = await response.Content.ReadAsStringAsync(); + int count = int.Parse(content); + count.ShouldBeGreaterThan(0); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit(string url, int times) + { + for (int i = 0; i < times; i++) + { + var clientId = "ocelotclient1"; + var request = new HttpRequestMessage(new HttpMethod("GET"), url); + request.Headers.Add("ClientId", clientId); + _response = _ocelotClient.SendAsync(request).Result; + } + } + + public void WhenIGetUrlOnTheApiGateway(string url, string requestId) + { + _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(RequestIdKey, requestId); + + _response = _ocelotClient.GetAsync(url).Result; + } + + public void WhenIPostUrlOnTheApiGateway(string url) + { + _response = _ocelotClient.PostAsync(url, _postContent).Result; + } + + public void GivenThePostHasContent(string postcontent) + { + _postContent = new StringContent(postcontent); + } + + public void GivenThePostHasGzipContent(object input) + { + string json = JsonConvert.SerializeObject(input); + byte[] jsonBytes = Encoding.UTF8.GetBytes(json); + MemoryStream ms = new MemoryStream(); + using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true)) + { + gzip.Write(jsonBytes, 0, jsonBytes.Length); + } + ms.Position = 0; + StreamContent content = new StreamContent(ms); + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + content.Headers.ContentEncoding.Add("gzip"); + _postContent = content; + } + + public void ThenTheResponseBodyShouldBe(string expectedBody) + { + _response.Content.ReadAsStringAsync().Result.ShouldBe(expectedBody); + } + + public void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) + { + _response.StatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void ThenTheStatusCodeShouldBe(int expectedHttpStatusCode) + { + var responseStatusCode = (int)_response.StatusCode; + responseStatusCode.ShouldBe(expectedHttpStatusCode); + } + + public void Dispose() + { + _ocelotClient?.Dispose(); + _ocelotServer?.Dispose(); + } + + public void ThenTheRequestIdIsReturned() + { + _response.Headers.GetValues(RequestIdKey).First().ShouldNotBeNullOrEmpty(); + } + + public void ThenTheRequestIdIsReturned(string expected) + { + _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); + } + + public void ThenTheContentLengthIs(int expected) + { + _response.Content.Headers.ContentLength.ShouldBe(expected); + } + + public void WhenIMakeLotsOfDifferentRequestsToTheApiGateway() + { + int numberOfRequests = 100; + var aggregateUrl = "/"; + var aggregateExpected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; + var tomUrl = "/tom"; + var tomExpected = "{Hello from Tom}"; + var lauraUrl = "/laura"; + var lauraExpected = "{Hello from Laura}"; + var random = new Random(); + + var aggregateTasks = new Task[numberOfRequests]; + + for (int i = 0; i < numberOfRequests; i++) + { + aggregateTasks[i] = Fire(aggregateUrl, aggregateExpected, random); + } + + var tomTasks = new Task[numberOfRequests]; + + for (int i = 0; i < numberOfRequests; i++) + { + tomTasks[i] = Fire(tomUrl, tomExpected, random); + } + + var lauraTasks = new Task[numberOfRequests]; + + for (int i = 0; i < numberOfRequests; i++) + { + lauraTasks[i] = Fire(lauraUrl, lauraExpected, random); + } + + Task.WaitAll(lauraTasks); + Task.WaitAll(tomTasks); + Task.WaitAll(aggregateTasks); + } + + private async Task Fire(string url, string expectedBody, Random random) + { + var request = new HttpRequestMessage(new HttpMethod("GET"), url); + await Task.Delay(random.Next(0, 2)); + var response = await _ocelotClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + content.ShouldBe(expectedBody); + } + } +} diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index c2f35990..4b4e328b 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -41,15 +41,13 @@ namespace Ocelot.UnitTests.DependencyInjection private Exception _ex; [Fact] - public void should_add_delegating_handlers() + public void should_add_delegating_handlers_with_di() { - var fakeOne = new FakeDelegatingHandler(0); - var fakeTwo = new FakeDelegatingHandler(1); this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddDelegate(fakeOne)) - .And(x => AddDelegate(fakeTwo)) - .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .When(x => AddDelegate()) + .And(x => AddDelegate()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) .BDDfy(); } @@ -164,15 +162,12 @@ namespace Ocelot.UnitTests.DependencyInjection path.Path.ShouldBe("/administration"); } - private void ThenTheProviderIsRegisteredAndReturnsHandlers() + private void ThenTheProviderIsRegisteredAndReturnsHandlers() { _serviceProvider = _services.BuildServiceProvider(); - var provider = _serviceProvider.GetService(); - var handlers = provider.Get(); - var handler = (FakeDelegatingHandler)handlers[0].Invoke(); - handler.Order.ShouldBe(0); - handler = (FakeDelegatingHandler)handlers[1].Invoke(); - handler.Order.ShouldBe(1); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); } private void OnlyOneVersionOfEachCacheIsRegistered() @@ -213,9 +208,9 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void AddDelegate(DelegatingHandler handler) + private void AddDelegate() where T : DelegatingHandler { - _ocelotBuilder.AddDelegatingHandler(() => handler); + _ocelotBuilder.AddDelegatingHandler(); } private void ThenAnOcelotBuilderIsReturned() diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs deleted file mode 100644 index 8df95ec1..00000000 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerHouseTests.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.Net.Http; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Errors; -using Ocelot.Requester; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester -{ - public class DelegatingHandlerHandlerHouseTests - { - private readonly DelegatingHandlerHandlerHouse _house; - private Mock _factory; - private readonly Mock _provider; - private DownstreamReRoute _request; - private Response _result; - - public DelegatingHandlerHandlerHouseTests() - { - _provider = new Mock(); - _factory = new Mock(); - _house = new DelegatingHandlerHandlerHouse(_factory.Object); - } - - [Fact] - public void should_create_and_store_provider() - { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - - this.Given(x => GivenTheRequest(reRoute)) - .And(x => GivenTheProviderReturns()) - .When(x => WhenIGet()) - .Then(x => ThenTheFactoryIsCalled(1)) - .And(x => ThenTheProviderIsNotNull()) - .BDDfy(); - } - - [Fact] - public void should_get_provider() - { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - - this.Given(x => GivenTheRequest(reRoute)) - .And(x => GivenTheProviderReturns()) - .And(x => WhenIGet()) - .And(x => GivenTheFactoryIsCleared()) - .When(x => WhenIGet()) - .Then(x => ThenTheFactoryIsCalled(0)) - .And(x => ThenTheProviderIsNotNull()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - - this.Given(x => GivenTheRequest(reRoute)) - .And(x => GivenTheProviderThrows()) - .When(x => WhenIGet()) - .And(x => ThenAnErrorIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_factory_errors() - { - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) - .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("key").Build(); - - this.Given(x => GivenTheRequest(reRoute)) - .And(x => GivenTheProviderReturnsError()) - .When(x => WhenIGet()) - .Then(x => ThenAnUnknownErrorIsReturned()) - .BDDfy(); - } - - private void ThenAnUnknownErrorIsReturned() - { - _result.IsError.ShouldBeTrue(); - } - - private void ThenAnErrorIsReturned() - { - _result.IsError.ShouldBeTrue(); - _result.Errors[0].ShouldBeOfType(); - } - - private void GivenTheProviderThrows() - { - _factory.Setup(x => x.Get(It.IsAny())).Throws(); - } - - private void GivenTheFactoryIsCleared() - { - _factory = new Mock(); - } - - private void ThenTheProviderIsNotNull() - { - _result.Data.ShouldBe(_provider.Object); - } - - private void WhenIGet() - { - _result = _house.Get(_request); - } - - private void GivenTheRequest(DownstreamReRoute request) - { - _request = request; - } - - private void GivenTheProviderReturns() - { - _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse(_provider.Object)); - } - - private void GivenTheProviderReturnsError() - { - _factory.Setup(x => x.Get(It.IsAny())).Returns(new ErrorResponse(It.IsAny())); - } - - private void ThenTheFactoryIsCalled(int times) - { - _factory.Verify(x => x.Get(_request), Times.Exactly(times)); - } - } -} diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs index c63e04d2..793f3601 100644 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderFactoryTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; @@ -17,50 +18,35 @@ namespace Ocelot.UnitTests.Requester { public class DelegatingHandlerHandlerProviderFactoryTests { - private readonly DelegatingHandlerHandlerProviderFactory _factory; + private DelegatingHandlerHandlerFactory _factory; private Mock _loggerFactory; private DownstreamReRoute _request; - private Response _provider; - private readonly Mock _allRoutesProvider; + private Response>> _provider; private readonly Mock _qosProviderHouse; private readonly Mock _tracingFactory; + private IServiceProvider _serviceProvider; public DelegatingHandlerHandlerProviderFactoryTests() { _tracingFactory = new Mock(); _qosProviderHouse = new Mock(); - _allRoutesProvider = new Mock(); _loggerFactory = new Mock(); - _factory = new DelegatingHandlerHandlerProviderFactory(_loggerFactory.Object, _allRoutesProvider.Object, _tracingFactory.Object, _qosProviderHouse.Object); - } - - private void GivenTheQosProviderHouseReturns(Response qosProvider) - { - _qosProviderHouse - .Setup(x => x.Get(It.IsAny())) - .Returns(qosProvider); } [Fact] public void should_all_from_all_routes_provider_and_qos() { - var handlers = new List> - { - () => new FakeDelegatingHandler(0), - () => new FakeDelegatingHandler(1) - }; - var reRoute = new DownstreamReRouteBuilder().WithIsQos(true) .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("").Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) - .And(x => GivenTheAllRoutesProviderReturns(handlers)) + .And(x => GivenTheServiceProviderReturns()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(3)) .And(x => ThenTheDelegatesAreAddedCorrectly()) .And(x => ThenItIsPolly(2)) - .BDDfy(); + .BDDfy(); } [Fact] @@ -70,7 +56,7 @@ namespace Ocelot.UnitTests.Requester .WithHttpHandlerOptions(new HttpHandlerOptions(true, true, false)).WithReRouteKey("").Build(); this.Given(x => GivenTheFollowingRequest(reRoute)) - .And(x => GivenTheAllRoutesProviderReturns()) + .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenNoDelegatesAreInTheProvider()) .BDDfy(); @@ -84,7 +70,7 @@ namespace Ocelot.UnitTests.Requester this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosProviderHouseReturns(new OkResponse(It.IsAny()))) - .And(x => GivenTheAllRoutesProviderReturns()) + .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenThereIsDelegatesInProvider(1)) .And(x => ThenItIsPolly(0)) @@ -99,12 +85,28 @@ namespace Ocelot.UnitTests.Requester this.Given(x => GivenTheFollowingRequest(reRoute)) .And(x => GivenTheQosProviderHouseReturns(new ErrorResponse(It.IsAny()))) - .And(x => GivenTheAllRoutesProviderReturns()) + .And(x => GivenTheServiceProviderReturnsNothing()) .When(x => WhenIGet()) .Then(x => ThenAnErrorIsReturned()) .BDDfy(); } + private void GivenTheServiceProviderReturns() + where TOne : DelegatingHandler + where TTwo : DelegatingHandler + { + IServiceCollection services = new ServiceCollection(); + services.AddSingleton(); + services.AddSingleton(); + _serviceProvider = services.BuildServiceProvider(); + } + + private void GivenTheServiceProviderReturnsNothing() + { + IServiceCollection services = new ServiceCollection(); + _serviceProvider = services.BuildServiceProvider(); + } + private void ThenAnErrorIsReturned() { _provider.IsError.ShouldBeTrue(); @@ -112,29 +114,27 @@ namespace Ocelot.UnitTests.Requester private void ThenTheDelegatesAreAddedCorrectly() { - var delegates = _provider.Data.Get(); + var delegates = _provider.Data; + var del = delegates[0].Invoke(); var handler = (FakeDelegatingHandler) del; - handler.Order.ShouldBe(0); + handler.Order.ShouldBe(1); del = delegates[1].Invoke(); - handler = (FakeDelegatingHandler)del; - handler.Order.ShouldBe(1); + var handlerTwo = (FakeDelegatingHandlerTwo) del; + handlerTwo.Order.ShouldBe(2); } - private void GivenTheAllRoutesProviderReturns() + private void GivenTheQosProviderHouseReturns(Response qosProvider) { - _allRoutesProvider.Setup(x => x.Get()).Returns(new List>()); - } - - private void GivenTheAllRoutesProviderReturns(List> handlers) - { - _allRoutesProvider.Setup(x => x.Get()).Returns(handlers); + _qosProviderHouse + .Setup(x => x.Get(It.IsAny())) + .Returns(qosProvider); } private void ThenItIsPolly(int i) { - var delegates = _provider.Data.Get(); + var delegates = _provider.Data; var del = delegates[i].Invoke(); del.ShouldBeOfType(); } @@ -142,7 +142,7 @@ namespace Ocelot.UnitTests.Requester private void ThenThereIsDelegatesInProvider(int count) { _provider.ShouldNotBeNull(); - _provider.Data.Get().Count.ShouldBe(count); + _provider.Data.Count.ShouldBe(count); } private void GivenTheFollowingRequest(DownstreamReRoute request) @@ -152,13 +152,14 @@ namespace Ocelot.UnitTests.Requester private void WhenIGet() { + _factory = new DelegatingHandlerHandlerFactory(_loggerFactory.Object, _tracingFactory.Object, _qosProviderHouse.Object, _serviceProvider); _provider = _factory.Get(_request); } private void ThenNoDelegatesAreInTheProvider() { _provider.ShouldNotBeNull(); - _provider.Data.Get().Count.ShouldBe(0); + _provider.Data.Count.ShouldBe(0); } } } diff --git a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderTests.cs b/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderTests.cs deleted file mode 100644 index d93e291a..00000000 --- a/test/Ocelot.UnitTests/Requester/DelegatingHandlerHandlerProviderTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using Ocelot.Requester; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester -{ - public class DelegatingHandlerHandlerProviderTests - { - private readonly DelegatingHandlerHandlerProvider _provider; - private List> _handlers; - - public DelegatingHandlerHandlerProviderTests() - { - _provider = new DelegatingHandlerHandlerProvider(); - } - - [Fact] - public void should_return_empty_list() - { - this.When(x => WhenIGet()) - .Then(x => ThenAnEmptyListIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_get_delegating_handlers_in_order_first_in_first_out() - { - this.Given(x => GivenTheHandlers()) - .When(x => WhenIGet()) - .Then(x => ThenTheHandlersAreReturnedInOrder()) - .BDDfy(); - } - - private void ThenAnEmptyListIsReturned() - { - _handlers.Count.ShouldBe(0); - } - - private void ThenTheHandlersAreReturnedInOrder() - { - var handler = (FakeDelegatingHandler)_handlers[0].Invoke(); - handler.Order.ShouldBe(0); - handler = (FakeDelegatingHandler)_handlers[1].Invoke(); - handler.Order.ShouldBe(1); - } - - private void WhenIGet() - { - _handlers = _provider.Get(); - } - - private void GivenTheHandlers() - { - _provider.Add(() => new FakeDelegatingHandler(0)); - _provider.Add(() => new FakeDelegatingHandler(1)); - } - } -} diff --git a/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs b/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs index 0db47a8f..df68978b 100644 --- a/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs +++ b/test/Ocelot.UnitTests/Requester/FakeDelegatingHandler.cs @@ -9,6 +9,7 @@ namespace Ocelot.UnitTests.Requester { public FakeDelegatingHandler() { + Order = 1; } public FakeDelegatingHandler(int order) @@ -26,4 +27,21 @@ namespace Ocelot.UnitTests.Requester return Task.FromResult(new HttpResponseMessage()); } } + + public class FakeDelegatingHandlerTwo : DelegatingHandler + { + public FakeDelegatingHandlerTwo() + { + Order = 2; + } + + public int Order {get;private set;} + public DateTime TimeCalled {get;private set;} + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + TimeCalled = DateTime.Now; + return new HttpResponseMessage(); + } + } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index b788c826..c4ab1341 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -15,25 +15,22 @@ namespace Ocelot.UnitTests.Requester public class HttpClientBuilderTests { private readonly HttpClientBuilder _builder; - private readonly Mock _house; - private readonly Mock _provider; + private readonly Mock _factory; private IHttpClient _httpClient; private HttpResponseMessage _response; private DownstreamReRoute _request; public HttpClientBuilderTests() { - _provider = new Mock(); - _house = new Mock(); - _builder = new HttpClientBuilder(_house.Object); + _factory = new Mock(); + _builder = new HttpClientBuilder(_factory.Object); } [Fact] public void should_build_http_client() { - this.Given(x => GivenTheProviderReturns()) + this.Given(x => GivenTheFactoryReturns()) .And(x => GivenARequest()) - .And(x => GivenTheHouseReturns()) .When(x => WhenIBuild()) .Then(x => ThenTheHttpClientShouldNotBeNull()) .BDDfy(); @@ -51,9 +48,8 @@ namespace Ocelot.UnitTests.Requester () => fakeTwo }; - this.Given(x => GivenTheProviderReturns(handlers)) + this.Given(x => GivenTheFactoryReturns(handlers)) .And(x => GivenARequest()) - .And(x => GivenTheHouseReturns()) .And(x => WhenIBuild()) .When(x => WhenICallTheClient()) .Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo)) @@ -69,13 +65,6 @@ namespace Ocelot.UnitTests.Requester _request = reRoute; } - private void GivenTheHouseReturns() - { - _house - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse(_provider.Object)); - } - private void ThenSomethingIsReturned() { _response.ShouldNotBeNull(); @@ -91,18 +80,20 @@ namespace Ocelot.UnitTests.Requester fakeOne.TimeCalled.ShouldBeGreaterThan(fakeTwo.TimeCalled); } - private void GivenTheProviderReturns() + private void GivenTheFactoryReturns() { - _provider - .Setup(x => x.Get()) - .Returns(new List>(){ () => new FakeDelegatingHandler()}); + var handlers = new List>(){ () => new FakeDelegatingHandler()}; + + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); } - private void GivenTheProviderReturns(List> handlers) + private void GivenTheFactoryReturns(List> handlers) { - _provider - .Setup(x => x.Get()) - .Returns(handlers); + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); } private void WhenIBuild() diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index 6b01a8c1..f2c1f3a0 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -18,8 +18,7 @@ namespace Ocelot.UnitTests.Requester public class HttpClientHttpRequesterTest { private readonly Mock _cacheHandlers; - private Mock _house; - private Mock _provider; + private Mock _house; private Response _response; private readonly HttpClientHttpRequester _httpClientRequester; private DownstreamContext _request; @@ -28,10 +27,8 @@ namespace Ocelot.UnitTests.Requester public HttpClientHttpRequesterTest() { - _provider = new Mock(); - _provider.Setup(x => x.Get()).Returns(new List>()); - _house = new Mock(); - _house.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse(_provider.Object)); + _house = new Mock(); + _house.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(new List>())); _logger = new Mock(); _loggerFactory = new Mock(); _loggerFactory