diff --git a/src/Ocelot/Infrastructure/DelayedMessage.cs b/src/Ocelot/Infrastructure/DelayedMessage.cs new file mode 100644 index 00000000..f7b5d4f8 --- /dev/null +++ b/src/Ocelot/Infrastructure/DelayedMessage.cs @@ -0,0 +1,15 @@ +namespace Ocelot.Infrastructure +{ + internal class DelayedMessage + { + public DelayedMessage(T message, int delay) + { + Delay = delay; + Message = message; + } + + public T Message { get; set; } + + public int Delay { get; set; } + } +} diff --git a/src/Ocelot/Infrastructure/IBus.cs b/src/Ocelot/Infrastructure/IBus.cs index 7d4e82d1..0fc227dd 100644 --- a/src/Ocelot/Infrastructure/IBus.cs +++ b/src/Ocelot/Infrastructure/IBus.cs @@ -1,14 +1,10 @@ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; namespace Ocelot.Infrastructure { public interface IBus { void Subscribe(Action action); - Task Publish(T message, int delay); + void Publish(T message, int delay); } } diff --git a/src/Ocelot/Infrastructure/InMemoryBus.cs b/src/Ocelot/Infrastructure/InMemoryBus.cs index 7d2b6836..c4dcc7a2 100644 --- a/src/Ocelot/Infrastructure/InMemoryBus.cs +++ b/src/Ocelot/Infrastructure/InMemoryBus.cs @@ -8,15 +8,15 @@ namespace Ocelot.Infrastructure { public class InMemoryBus : IBus { - private readonly BlockingCollection _queue; + private readonly BlockingCollection> _queue; private readonly List> _subscriptions; private Thread _processing; public InMemoryBus() { - _queue = new BlockingCollection(); + _queue = new BlockingCollection>(); _subscriptions = new List>(); - _processing = new Thread(Process); + _processing = new Thread(async () => await Process()); _processing.Start(); } @@ -25,19 +25,21 @@ namespace Ocelot.Infrastructure _subscriptions.Add(action); } - public async Task Publish(T message, int delay) + public void Publish(T message, int delay) { - await Task.Delay(delay); - _queue.Add(message); + var delayed = new DelayedMessage(message, delay); + _queue.Add(delayed); } - private void Process() + private async Task Process() { - foreach(var message in _queue.GetConsumingEnumerable()) + foreach(var delayedMessage in _queue.GetConsumingEnumerable()) { - foreach(var subscription in _subscriptions) + await Task.Delay(delayedMessage.Delay); + + foreach (var subscription in _subscriptions) { - subscription(message); + subscription(delayedMessage.Message); } } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs index 25411e88..b64c4b43 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs @@ -3,7 +3,6 @@ namespace Ocelot.LoadBalancer.LoadBalancers using System; using System.Collections.Concurrent; using System.Collections.Generic; - using System.Linq; using System.Threading; using System.Threading.Tasks; using Ocelot.Infrastructure; @@ -11,13 +10,13 @@ namespace Ocelot.LoadBalancer.LoadBalancers using Responses; using Values; - public class CookieStickySessions : ILoadBalancer, IDisposable + public class CookieStickySessions : ILoadBalancer { private readonly int _keyExpiryInMs; private readonly string _key; private readonly ILoadBalancer _loadBalancer; private readonly ConcurrentDictionary _stored; - private IBus _bus; + private readonly IBus _bus; private readonly object _lock = new object(); public CookieStickySessions(ILoadBalancer loadBalancer, string key, int keyExpiryInMs, IBus bus) @@ -27,12 +26,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers _keyExpiryInMs = keyExpiryInMs; _loadBalancer = loadBalancer; _stored = new ConcurrentDictionary(); - _bus.Subscribe(ss => { - if(_stored.TryGetValue(ss.Key, out var stickySession)) + _bus.Subscribe(ss => + { + //todo - get test coverage for this. + if (_stored.TryGetValue(ss.Key, out var stickySession)) { - lock(_lock) + lock (_lock) { - if(stickySession.Expiry < DateTime.Now) + if (stickySession.Expiry < DateTime.UtcNow) { _stored.Remove(stickySession.Key, out _); _loadBalancer.Release(stickySession.HostAndPort); @@ -42,26 +43,24 @@ namespace Ocelot.LoadBalancer.LoadBalancers }); } - public void Dispose() - { - _timer?.Dispose(); - } - public async Task> Lease(DownstreamContext context) { var key = context.HttpContext.Request.Cookies[_key]; - - if (!string.IsNullOrEmpty(key) && _stored.ContainsKey(key)) + + lock (_lock) { - var cached = _stored[key]; + if (!string.IsNullOrEmpty(key) && _stored.ContainsKey(key)) + { + var cached = _stored[key]; - var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key); + var updated = new StickySession(cached.HostAndPort, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key); - _stored[key] = updated; + _stored[key] = updated; - await _bus.Publish(updated, _keyExpiryInMs); + _bus.Publish(updated, _keyExpiryInMs); - return new OkResponse(updated.HostAndPort); + return new OkResponse(updated.HostAndPort); + } } var next = await _loadBalancer.Lease(context); @@ -71,11 +70,14 @@ namespace Ocelot.LoadBalancer.LoadBalancers return new ErrorResponse(next.Errors); } - if (!string.IsNullOrEmpty(key) && !_stored.ContainsKey(key)) + lock (_lock) { - var ss = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key); - _stored[key] = ss; - await _bus.Publish(ss, _keyExpiryInMs); + if (!string.IsNullOrEmpty(key) && !_stored.ContainsKey(key)) + { + var ss = new StickySession(next.Data, DateTime.UtcNow.AddMilliseconds(_keyExpiryInMs), key); + _stored[key] = ss; + _bus.Publish(ss, _keyExpiryInMs); + } } return new OkResponse(next.Data); diff --git a/src/Ocelot/Raft/HttpPeer.cs b/src/Ocelot/Raft/HttpPeer.cs index 18ecdee1..224a4474 100644 --- a/src/Ocelot/Raft/HttpPeer.cs +++ b/src/Ocelot/Raft/HttpPeer.cs @@ -99,7 +99,7 @@ namespace Ocelot.Raft var response = _httpClient.PostAsync($"{_hostAndPort}/administration/raft/command", content).GetAwaiter().GetResult(); if(response.IsSuccessStatusCode) { - var okResponse = JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings); + var okResponse = JsonConvert.DeserializeObject>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), _jsonSerializerSettings); return new OkResponse((T)okResponse.Command); } else diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 2742aa60..72fad48b 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -1,865 +1,865 @@ -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; -using System.IO.Compression; -using System.Text; -using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; -using Ocelot.Requester; -using Ocelot.Middleware.Multiplexer; - -namespace Ocelot.AcceptanceTests -{ - using Microsoft.Net.Http.Headers; - using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; - - 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; - private WebHostBuilder _ocelotBuilder; - private IWebHost _ocelotHost; - - public Steps() - { - _random = new Random(); - } - - public async Task StartFakeOcelotWithWebSockets() - { - _ocelotBuilder = new WebHostBuilder(); - _ocelotBuilder.ConfigureServices(s => - { - s.AddSingleton(_ocelotBuilder); - s.AddOcelot(); - }); - _ocelotBuilder.UseKestrel() - .UseUrls("http://localhost:5000") - .UseContentRoot(Directory.GetCurrentDirectory()) - .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("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .Configure(app => - { - app.UseWebSockets(); - app.UseOcelot().Wait(); - }) - .UseIISIntegration(); - _ocelotHost = _ocelotBuilder.Build(); - await _ocelotHost.StartAsync(); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = TestConfiguration.ConfigurationPath; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath) - { - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); - - 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("ocelot.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("ocelot.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 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("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot(); - }) - .Configure(app => - { - app.UseMiddleware(callback); - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningWithSpecficHandlersRegisteredInDi() - 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("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - s.AddOcelot() - .AddSingletonDelegatingHandler() - .AddSingletonDelegatingHandler(); - }) - .Configure(a => - { - a.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi() - where TAggregator : class, IDefinedAggregator - where TDepedency : class - { - _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("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - s.AddSingleton(); - s.AddOcelot() - .AddSingletonDefinedAggregator(); - }) - .Configure(a => - { - a.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi() - 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("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - s.AddOcelot() - .AddSingletonDelegatingHandler(true) - .AddSingletonDelegatingHandler(true); - }) - .Configure(a => - { - a.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi(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("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddSingleton(_webHostBuilder); - s.AddSingleton(dependency); - s.AddOcelot() - .AddSingletonDelegatingHandler(true); - }) - .Configure(a => - { - a.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - internal void GivenIAddCookieToMyRequest(string cookie) - { +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; +using System.IO.Compression; +using System.Text; +using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; +using Ocelot.Requester; +using Ocelot.Middleware.Multiplexer; + +namespace Ocelot.AcceptanceTests +{ + using Microsoft.Net.Http.Headers; + using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; + + 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; + private WebHostBuilder _ocelotBuilder; + private IWebHost _ocelotHost; + + public Steps() + { + _random = new Random(); + } + + public async Task StartFakeOcelotWithWebSockets() + { + _ocelotBuilder = new WebHostBuilder(); + _ocelotBuilder.ConfigureServices(s => + { + s.AddSingleton(_ocelotBuilder); + s.AddOcelot(); + }); + _ocelotBuilder.UseKestrel() + .UseUrls("http://localhost:5000") + .UseContentRoot(Directory.GetCurrentDirectory()) + .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("ocelot.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .Configure(app => + { + app.UseWebSockets(); + app.UseOcelot().Wait(); + }) + .UseIISIntegration(); + _ocelotHost = _ocelotBuilder.Build(); + await _ocelotHost.StartAsync(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = TestConfiguration.ConfigurationPath; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, string configurationPath) + { + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); + + 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("ocelot.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("ocelot.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 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("ocelot.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(app => + { + app.UseMiddleware(callback); + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithSpecficHandlersRegisteredInDi() + 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("ocelot.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddOcelot() + .AddSingletonDelegatingHandler() + .AddSingletonDelegatingHandler(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi() + where TAggregator : class, IDefinedAggregator + where TDepedency : class + { + _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("ocelot.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddSingleton(); + s.AddOcelot() + .AddSingletonDefinedAggregator(); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi() + 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("ocelot.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddOcelot() + .AddSingletonDelegatingHandler(true) + .AddSingletonDelegatingHandler(true); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + public void GivenOcelotIsRunningWithGlobalHandlersRegisteredInDi(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("ocelot.json"); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + s.AddSingleton(dependency); + s.AddOcelot() + .AddSingletonDelegatingHandler(true); + }) + .Configure(a => + { + a.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + + internal void GivenIAddCookieToMyRequest(string cookie) + { _ocelotClient.DefaultRequestHeaders.Add("Set-Cookie", cookie); - } - - /// - /// 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("ocelot.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 ThenTheTraceHeaderIsSet(string key) - { - var header = _response.Headers.GetValues(key); - header.First().ShouldNotBeNullOrEmpty(); - } - - 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("ocelot.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("ocelot.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("ocelot.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("ocelot.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 WhenIGetUrlOnTheApiGateway(string url, string cookie, string value) - { - var request = _ocelotServer.CreateRequest(url); - request.And(x => { x.Headers.Add("Cookie", new CookieHeaderValue(cookie, value).ToString()); }); - var response = request.GetAsync().Result; - _response = response; - } - - 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); - } - - public async Task WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times, string cookie, string value) - { - var tasks = new Task[times]; - - for (int i = 0; i < times; i++) - { - var urlCopy = url; - tasks[i] = GetForServiceDiscoveryTest(urlCopy, cookie, value); - Thread.Sleep(_random.Next(40, 60)); - } - - Task.WaitAll(tasks); - } - - private async Task GetForServiceDiscoveryTest(string url, string cookie, string value) - { - var request = _ocelotServer.CreateRequest(url); - request.And(x => { x.Headers.Add("Cookie", new CookieHeaderValue(cookie, value).ToString()); }); - var response = await request.GetAsync(); - var content = await response.Content.ReadAsStringAsync(); - int count = int.Parse(content); - count.ShouldBeGreaterThan(0); - } - - 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) - { - var json = JsonConvert.SerializeObject(input); - var jsonBytes = Encoding.UTF8.GetBytes(json); - var ms = new MemoryStream(); - using (var gzip = new GZipStream(ms, CompressionMode.Compress, true)) - { - gzip.Write(jsonBytes, 0, jsonBytes.Length); - } - - ms.Position = 0; - var 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(); - _ocelotHost?.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); - } - } -} + } + + /// + /// 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("ocelot.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 ThenTheTraceHeaderIsSet(string key) + { + var header = _response.Headers.GetValues(key); + header.First().ShouldNotBeNullOrEmpty(); + } + + 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("ocelot.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("ocelot.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("ocelot.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("ocelot.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 WhenIGetUrlOnTheApiGateway(string url, string cookie, string value) + { + var request = _ocelotServer.CreateRequest(url); + request.And(x => { x.Headers.Add("Cookie", new CookieHeaderValue(cookie, value).ToString()); }); + var response = request.GetAsync().Result; + _response = response; + } + + 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); + } + + public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times, string cookie, string value) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + tasks[i] = GetForServiceDiscoveryTest(urlCopy, cookie, value); + Thread.Sleep(_random.Next(40, 60)); + } + + Task.WaitAll(tasks); + } + + private async Task GetForServiceDiscoveryTest(string url, string cookie, string value) + { + var request = _ocelotServer.CreateRequest(url); + request.And(x => { x.Headers.Add("Cookie", new CookieHeaderValue(cookie, value).ToString()); }); + var response = await request.GetAsync(); + var content = await response.Content.ReadAsStringAsync(); + int count = int.Parse(content); + count.ShouldBeGreaterThan(0); + } + + 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) + { + var json = JsonConvert.SerializeObject(input); + var jsonBytes = Encoding.UTF8.GetBytes(json); + var ms = new MemoryStream(); + using (var gzip = new GZipStream(ms, CompressionMode.Compress, true)) + { + gzip.Write(jsonBytes, 0, jsonBytes.Length); + } + + ms.Position = 0; + var 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(); + _ocelotHost?.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.Benchmarks/AllTheThingsBenchmarks.cs b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs index 55e7a435..053ac7ea 100644 --- a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs +++ b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs @@ -76,16 +76,16 @@ namespace Ocelot.Benchmarks response.EnsureSuccessStatusCode(); } - // * Summary* - // BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] - // Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores - //.NET Core SDK = 2.1.4 +/* * Summary* + BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] + Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores + .NET Core SDK = 2.1.4 - // [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - // DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - // Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | - // --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| - // Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB | + [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | + --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| + Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |*/ private void GivenOcelotIsRunning(string url) { diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs index bbb8ff41..706ae913 100644 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ b/test/Ocelot.IntegrationTests/RaftTests.cs @@ -280,11 +280,6 @@ namespace Ocelot.IntegrationTests content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); _response = _httpClient.PostAsync(url, content).Result; var responseContent = _response.Content.ReadAsStringAsync().Result; - - //Console.ForegroundColor = ConsoleColor.Green; - //Console.WriteLine(responseContent); - //Console.WriteLine(_response.StatusCode); - //Console.ForegroundColor = ConsoleColor.White; if(responseContent == "There was a problem. This error message sucks raise an issue in GitHub.") { @@ -330,12 +325,13 @@ namespace Ocelot.IntegrationTests { return false; } + _token = JsonConvert.DeserializeObject(responseContent); var configPath = $"{adminPath}/.well-known/openid-configuration"; response = _httpClient.GetAsync(configPath).Result; return response.IsSuccessStatusCode; } - catch(Exception e) + catch(Exception) { return false; } @@ -343,7 +339,6 @@ namespace Ocelot.IntegrationTests var addToken = WaitFor(20000).Until(() => AddToken()); addToken.ShouldBeTrue(); - } private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) diff --git a/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs b/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs index 81cae7c6..35849dbf 100644 --- a/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs @@ -7,11 +7,11 @@ namespace Ocelot.UnitTests.Infrastructure { public class InMemoryBusTests { - private InMemoryBus _bus; + private readonly InMemoryBus _bus; public InMemoryBusTests() { - _bus = new InMemoryBus(); + _bus = new InMemoryBus(); } [Fact] @@ -21,26 +21,20 @@ namespace Ocelot.UnitTests.Infrastructure _bus.Subscribe(x => { called = true; }); - await _bus.Publish(new Message(), 1); + _bus.Publish(new object(), 1); await Task.Delay(10); called.ShouldBeTrue(); } [Fact] - public async Task should_not_be_publish_yet_as_no_delay_in_caller() + public void should_not_be_publish_yet_as_no_delay_in_caller() { var called = false; _bus.Subscribe(x => { called = true; }); - await _bus.Publish(new Message(), 1); + _bus.Publish(new object(), 1); called.ShouldBeFalse(); } - - - class Message - { - - } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs b/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs index 0d6437cb..ce5f4b2b 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs @@ -1,5 +1,6 @@ namespace Ocelot.UnitTests.LoadBalancer { + using System; using System.Threading.Tasks; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Responses; @@ -25,13 +26,13 @@ namespace Ocelot.UnitTests.LoadBalancer private Response _result; private Response _firstHostAndPort; private Response _secondHostAndPort; - private IBus _bus; + private readonly FakeBus _bus; public CookieStickySessionsTests() { - _bus = new InMemoryBus(); + _bus = new FakeBus(); _loadBalancer = new Mock(); - _defaultExpiryInMs = 100; + _defaultExpiryInMs = 0; _stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", _defaultExpiryInMs, _bus); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); } @@ -52,6 +53,7 @@ namespace Ocelot.UnitTests.LoadBalancer .And(_ => GivenTheDownstreamRequestHasSessionId("321")) .When(_ => WhenILeaseTwiceInARow()) .Then(_ => ThenTheFirstAndSecondResponseAreTheSame()) + .And(_ => ThenTheStickySessionWillTimeout()) .BDDfy(); } @@ -73,94 +75,12 @@ namespace Ocelot.UnitTests.LoadBalancer .BDDfy(); } - [Fact] - public void should_expire_sticky_session() - { - this.Given(_ => GivenTheLoadBalancerReturnsSequence()) - .When(_ => WhenTheStickySessionExpires()) - .Then(_ => ThenANewHostAndPortIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_refresh_sticky_session() - { - this.Given(_ => GivenTheLoadBalancerReturnsSequence()) - .When(_ => WhenIMakeRequestsToKeepRefreshingTheSession()) - .Then(_ => ThenTheSessionIsRefreshed()) - .BDDfy(); - } - - [Fact] - public void should_dispose() - { - _stickySessions.Dispose(); - } - [Fact] public void should_release() { _stickySessions.Release(new ServiceHostAndPort("", 0)); } - private async Task ThenTheSessionIsRefreshed() - { - var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext); - postExpireHostAndPort.Data.DownstreamHost.ShouldBe("one"); - postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80); - - _loadBalancer - .Verify(x => x.Lease(It.IsAny()), Times.Once); - } - - private async Task WhenIMakeRequestsToKeepRefreshingTheSession() - { - var context = new DefaultHttpContext(); - var cookies = new FakeCookies(); - cookies.AddCookie("sessionid", "321"); - context.Request.Cookies = cookies; - _downstreamContext = new DownstreamContext(context); - - var firstHostAndPort = await _stickySessions.Lease(_downstreamContext); - firstHostAndPort.Data.DownstreamHost.ShouldBe("one"); - firstHostAndPort.Data.DownstreamPort.ShouldBe(80); - - Thread.Sleep(80); - - var secondHostAndPort = await _stickySessions.Lease(_downstreamContext); - secondHostAndPort.Data.DownstreamHost.ShouldBe("one"); - secondHostAndPort.Data.DownstreamPort.ShouldBe(80); - - Thread.Sleep(80); - } - - private async Task ThenANewHostAndPortIsReturned() - { - var postExpireHostAndPort = await _stickySessions.Lease(_downstreamContext); - postExpireHostAndPort.Data.DownstreamHost.ShouldBe("two"); - postExpireHostAndPort.Data.DownstreamPort.ShouldBe(80); - } - - private async Task WhenTheStickySessionExpires() - { - var context = new DefaultHttpContext(); - var cookies = new FakeCookies(); - cookies.AddCookie("sessionid", "321"); - context.Request.Cookies = cookies; - _downstreamContext = new DownstreamContext(context); - - var firstHostAndPort = await _stickySessions.Lease(_downstreamContext); - var secondHostAndPort = await _stickySessions.Lease(_downstreamContext); - - firstHostAndPort.Data.DownstreamHost.ShouldBe("one"); - firstHostAndPort.Data.DownstreamPort.ShouldBe(80); - - secondHostAndPort.Data.DownstreamHost.ShouldBe("one"); - secondHostAndPort.Data.DownstreamPort.ShouldBe(80); - - Thread.Sleep(300); - } - private void ThenAnErrorIsReturned() { _result.IsError.ShouldBeTrue(); @@ -240,9 +160,14 @@ namespace Ocelot.UnitTests.LoadBalancer { _result.Data.ShouldNotBeNull(); } + + private void ThenTheStickySessionWillTimeout() + { + _bus.Messages.Count.ShouldBe(2); + } } - - class FakeCookies : IRequestCookieCollection + + internal class FakeCookies : IRequestCookieCollection { private readonly Dictionary _cookies = new Dictionary(); @@ -277,4 +202,23 @@ namespace Ocelot.UnitTests.LoadBalancer return _cookies.GetEnumerator(); } } + + internal class FakeBus : IBus + { + public FakeBus() + { + Messages = new List(); + } + + public List Messages { get; } + + public void Subscribe(Action action) + { + } + + public void Publish(T message, int delay) + { + Messages.Add(message); + } + } }