From 48b5a326768b695f988e105967e39d77f45e3811 Mon Sep 17 00:00:00 2001 From: Charalampos Chomenidis Date: Sat, 25 Nov 2017 16:47:17 +0200 Subject: [PATCH] Implement mapping of HttpResponseMessage to CachedResponse to fix #152 (#153) * changed name to cache options to fix issue #146 * Add acceptance test that exposes JSON deserialization bug from issue #146 - Create InMemoryJsonHandle for CacheManager that mimics DictionaryHandle but uses ICacheSerializer to serialize/deserialize values instead of saving references - Add CacheManager.Serialization.Json package - Add StartupWithCustomCacheHandle class that extends Startup and overrides ConfigureServices to register InMemoryJsonHandle - Add GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache method to initiate Ocelot with StartupWithCustomCacheHandle - Add test should_return_response_200_with_simple_url_when_using_jsonserialized_cache * Create Acceptance test that exposes issue #152 - Add GivenOcelotIsRunningUsingJsonSerializedCache() that initializes Ocelot with InMemoryJsonHandle - Add should_return_cached_response_when_using_jsonserialized_cache test * Change Consul port to 9502 on should_return_response_200_with_simple_url_when_using_jsonserialized_cache() test * Implement mapping of HttpResponseMessage to CachedResponse to enable distributed caching - Add CachedResponse class that holds HttpResponse data - Add mapping methods in OutputCacheMiddleware to create HttpResponseMessage from CachedResponse and vice versa - Replace HttpResponseMessage with CachedResponse in services registrations - Replace HttpResponseMessage with CachedResponse in OutputCacheController's IOcelotCache * Fix unit tests for OutputCacheMiddleware and OutputCacheController by replacing HttpResponseMessage with CachedResponse * Add .editorconfig with default identation settings (spaces with size 4) * Re-format broken files with new identation settings * Add Startup_WithConsul_And_CustomCacheHandle class - Use Startup_WithConsul_And_CustomCacheHandle in GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache step * Do minor cleanups - Rename StartupWithCustomCacheHandle to Startup_WithCustomCacheHandle for better readability - Remove cachemanager settings Action in Startup since it is not used anymore * Drop Task in CreateHttpResponseMessage - unnecessary overhead * Make setters private in CachedResponse - Rework CreateCachedResponse to use new CachedResponse constructor --- .editorconfig | 9 ++ src/Ocelot/Cache/CachedResponse.cs | 25 ++++ .../Cache/Middleware/OutputCacheMiddleware.cs | 55 ++++++- src/Ocelot/Configuration/ReRoute.cs | 4 +- .../Controllers/OutputCacheController.cs | 4 +- .../DependencyInjection/OcelotBuilder.cs | 12 +- .../Caching/InMemoryJsonHandle.cs | 137 ++++++++++++++++++ test/Ocelot.AcceptanceTests/CachingTests.cs | 36 +++++ .../ConfigurationInConsulTests.cs | 43 +++++- .../Ocelot.AcceptanceTests.csproj | 1 + test/Ocelot.AcceptanceTests/Startup.cs | 47 +++++- test/Ocelot.AcceptanceTests/Steps.cs | 51 +++++-- .../Cache/OutputCacheMiddlewareTests.cs | 13 +- .../Controllers/OutputCacheControllerTests.cs | 4 +- .../DependencyInjection/OcelotBuilderTests.cs | 6 +- 15 files changed, 401 insertions(+), 46 deletions(-) create mode 100644 .editorconfig create mode 100644 src/Ocelot/Cache/CachedResponse.cs create mode 100644 test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..beb5aacf4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = crlf +insert_final_newline = true + +[*.cs] +indent_style = space +indent_size = 4 diff --git a/src/Ocelot/Cache/CachedResponse.cs b/src/Ocelot/Cache/CachedResponse.cs new file mode 100644 index 000000000..122841fd1 --- /dev/null +++ b/src/Ocelot/Cache/CachedResponse.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Net; + +namespace Ocelot.Cache +{ + public class CachedResponse + { + public CachedResponse( + HttpStatusCode statusCode = HttpStatusCode.OK, + Dictionary> headers = null, + string body = null + ) + { + StatusCode = statusCode; + Headers = headers ?? new Dictionary>(); + Body = body ?? ""; + } + + public HttpStatusCode StatusCode { get; private set; } + + public Dictionary> Headers { get; private set; } + + public string Body { get; private set; } + } +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 9c6dcf328..513577ff0 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -6,6 +6,7 @@ using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; +using System.IO; namespace Ocelot.Cache.Middleware { @@ -13,15 +14,15 @@ public class OutputCacheMiddleware : OcelotMiddleware { private readonly RequestDelegate _next; private readonly IOcelotLogger _logger; - private readonly IOcelotCache _outputCache; + private readonly IOcelotCache _outputCache; private readonly IRegionCreator _regionCreator; public OutputCacheMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IRequestScopedDataRepository scopedDataRepository, - IOcelotCache outputCache, + IOcelotCache outputCache, IRegionCreator regionCreator) - :base(scopedDataRepository) + : base(scopedDataRepository) { _next = next; _outputCache = outputCache; @@ -40,14 +41,15 @@ public async Task Invoke(HttpContext context) var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}"; _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); - + var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region); if (cached != null) { _logger.LogDebug("cache entry exists for {downstreamUrlKey}", downstreamUrlKey); - SetHttpResponseMessageThisRequest(cached); + var response = CreateHttpResponseMessage(cached); + SetHttpResponseMessageThisRequest(response); _logger.LogDebug("finished returned cached response for {downstreamUrlKey}", downstreamUrlKey); @@ -65,11 +67,50 @@ public async Task Invoke(HttpContext context) return; } - var response = HttpResponseMessage; + cached = await CreateCachedResponse(HttpResponseMessage); - _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region); + _outputCache.Add(downstreamUrlKey, cached, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region); _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); } + + internal HttpResponseMessage CreateHttpResponseMessage(CachedResponse cached) + { + if (cached == null) + { + return null; + } + + var response = new HttpResponseMessage(cached.StatusCode); + foreach (var header in cached.Headers) + { + response.Headers.Add(header.Key, header.Value); + } + var content = new MemoryStream(Convert.FromBase64String(cached.Body)); + response.Content = new StreamContent(content); + + return response; + } + + internal async Task CreateCachedResponse(HttpResponseMessage response) + { + if (response == null) + { + return null; + } + + var statusCode = response.StatusCode; + var headers = response.Headers.ToDictionary(v => v.Key, v => v.Value); + string body = null; + + if (response.Content != null) + { + var content = await response.Content.ReadAsByteArrayAsync(); + body = Convert.ToBase64String(content); + } + + var cached = new CachedResponse(statusCode, headers, body); + return cached; + } } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index 57f8fcc50..0d373425f 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -19,7 +19,7 @@ public ReRoute(PathTemplate downstreamPathTemplate, List claimsToQueries, string requestIdKey, bool isCached, - CacheOptions fileCacheOptions, + CacheOptions cacheOptions, string downstreamScheme, string loadBalancer, string downstreamHost, @@ -49,7 +49,7 @@ public ReRoute(PathTemplate downstreamPathTemplate, IsAuthorised = isAuthorised; RequestIdKey = requestIdKey; IsCached = isCached; - CacheOptions = fileCacheOptions; + CacheOptions = cacheOptions; ClaimsToQueries = claimsToQueries ?? new List(); ClaimsToClaims = claimsToClaims diff --git a/src/Ocelot/Controllers/OutputCacheController.cs b/src/Ocelot/Controllers/OutputCacheController.cs index a68201a15..8d5189c7c 100644 --- a/src/Ocelot/Controllers/OutputCacheController.cs +++ b/src/Ocelot/Controllers/OutputCacheController.cs @@ -11,9 +11,9 @@ namespace Ocelot.Controllers [Route("outputcache")] public class OutputCacheController : Controller { - private IOcelotCache _cache; + private IOcelotCache _cache; - public OutputCacheController(IOcelotCache cache) + public OutputCacheController(IOcelotCache cache) { _cache = cache; } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index c561b04dd..9eb6821e4 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -161,13 +161,13 @@ public IOcelotBuilder AddStoreOcelotConfigurationInConsul() public IOcelotBuilder AddCacheManager(Action settings) { - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); - var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); + var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(cacheManagerOutputCache); - _services.AddSingleton>(ocelotOutputCacheManager); + _services.RemoveAll(typeof(ICacheManager)); + _services.RemoveAll(typeof(IOcelotCache)); + _services.AddSingleton>(cacheManagerOutputCache); + _services.AddSingleton>(ocelotOutputCacheManager); var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); diff --git a/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs b/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs new file mode 100644 index 000000000..4ab61427c --- /dev/null +++ b/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs @@ -0,0 +1,137 @@ +using CacheManager.Core; +using CacheManager.Core.Internal; +using CacheManager.Core.Logging; +using System; +using System.Collections.Concurrent; +using System.Linq; +using static CacheManager.Core.Utility.Guard; + +namespace Ocelot.AcceptanceTests.Caching +{ + public class InMemoryJsonHandle : BaseCacheHandle + { + private readonly ICacheSerializer _serializer; + private readonly ConcurrentDictionary> _cache; + + public InMemoryJsonHandle( + ICacheManagerConfiguration managerConfiguration, + CacheHandleConfiguration configuration, + ICacheSerializer serializer, + ILoggerFactory loggerFactory) : base(managerConfiguration, configuration) + { + _cache = new ConcurrentDictionary>(); + _serializer = serializer; + Logger = loggerFactory.CreateLogger(this); + } + + public override int Count => _cache.Count; + + protected override ILogger Logger { get; } + + public override void Clear() => _cache.Clear(); + + public override void ClearRegion(string region) + { + NotNullOrWhiteSpace(region, nameof(region)); + + var key = string.Concat(region, ":"); + foreach (var item in _cache.Where(p => p.Key.StartsWith(key, StringComparison.OrdinalIgnoreCase))) + { + _cache.TryRemove(item.Key, out Tuple val); + } + } + + public override bool Exists(string key) + { + NotNullOrWhiteSpace(key, nameof(key)); + + return _cache.ContainsKey(key); + } + + public override bool Exists(string key, string region) + { + NotNullOrWhiteSpace(region, nameof(region)); + var fullKey = GetKey(key, region); + return _cache.ContainsKey(fullKey); + } + + protected override bool AddInternalPrepared(CacheItem item) + { + NotNull(item, nameof(item)); + + var key = GetKey(item.Key, item.Region); + + var serializedItem = _serializer.SerializeCacheItem(item); + + return _cache.TryAdd(key, new Tuple(item.Value.GetType(), serializedItem)); + } + + protected override CacheItem GetCacheItemInternal(string key) => GetCacheItemInternal(key, null); + + protected override CacheItem GetCacheItemInternal(string key, string region) + { + var fullKey = GetKey(key, region); + + CacheItem deserializedResult = null; + + if (_cache.TryGetValue(fullKey, out Tuple result)) + { + deserializedResult = _serializer.DeserializeCacheItem(result.Item2, result.Item1); + + if (deserializedResult.ExpirationMode != ExpirationMode.None && IsExpired(deserializedResult, DateTime.UtcNow)) + { + _cache.TryRemove(fullKey, out Tuple removeResult); + TriggerCacheSpecificRemove(key, region, CacheItemRemovedReason.Expired, deserializedResult.Value); + return null; + } + } + + return deserializedResult; + } + + protected override void PutInternalPrepared(CacheItem item) + { + NotNull(item, nameof(item)); + + var serializedItem = _serializer.SerializeCacheItem(item); + + _cache[GetKey(item.Key, item.Region)] = new Tuple(item.Value.GetType(), serializedItem); + } + + protected override bool RemoveInternal(string key) => RemoveInternal(key, null); + + protected override bool RemoveInternal(string key, string region) + { + var fullKey = GetKey(key, region); + return _cache.TryRemove(fullKey, out Tuple val); + } + + private static string GetKey(string key, string region) + { + NotNullOrWhiteSpace(key, nameof(key)); + + if (string.IsNullOrWhiteSpace(region)) + { + return key; + } + + return string.Concat(region, ":", key); + } + + private static bool IsExpired(CacheItem item, DateTime now) + { + if (item.ExpirationMode == ExpirationMode.Absolute + && item.CreatedUtc.Add(item.ExpirationTimeout) < now) + { + return true; + } + else if (item.ExpirationMode == ExpirationMode.Sliding + && item.LastAccessedUtc.Add(item.ExpirationTimeout) < now) + { + return true; + } + + return false; + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs index 888cf1517..1c45135a8 100644 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ b/test/Ocelot.AcceptanceTests/CachingTests.cs @@ -58,6 +58,42 @@ public void should_return_cached_response() .BDDfy(); } + [Fact] + public void should_return_cached_response_when_using_jsonserialized_cache() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamPort = 51879, + DownstreamScheme = "http", + DownstreamHost = "localhost", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + FileCacheOptions = new FileCacheOptions + { + TtlSeconds = 100 + } + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingJsonSerializedCache()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .Given(x => x.GivenTheServiceNowReturns("http://localhost:51879", 200, "Hello from Tom")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_not_return_cached_response_as_ttl_expires() { diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs index b36563bc8..5a65050fa 100644 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs @@ -68,6 +68,45 @@ public void should_return_response_200_with_simple_url() .BDDfy(); } + [Fact] + public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHost = "localhost", + DownstreamPort = 51779, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + }, + GlobalConfiguration = new FileGlobalConfiguration() + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() + { + Host = "localhost", + Port = 9502 + } + } + }; + + var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; + + this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) + .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .BDDfy(); + } + [Fact] public void should_load_configuration_out_of_consul() { @@ -236,7 +275,7 @@ private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) var kvp = new FakeConsulGetResponse(base64); - await context.Response.WriteJsonAsync(new FakeConsulGetResponse[]{kvp}); + await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); } else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/OcelotConfiguration") @@ -311,4 +350,4 @@ public void Dispose() _steps.Dispose(); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index 6380b5f9e..6f4e1f9a1 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -30,6 +30,7 @@ + diff --git a/test/Ocelot.AcceptanceTests/Startup.cs b/test/Ocelot.AcceptanceTests/Startup.cs index 411203dbe..9c8a6e777 100644 --- a/test/Ocelot.AcceptanceTests/Startup.cs +++ b/test/Ocelot.AcceptanceTests/Startup.cs @@ -8,6 +8,7 @@ using Ocelot.DependencyInjection; using Ocelot.Middleware; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; +using Ocelot.AcceptanceTests.Caching; namespace Ocelot.AcceptanceTests { @@ -27,13 +28,8 @@ public Startup(IHostingEnvironment env) public IConfigurationRoot Configuration { get; } - public void ConfigureServices(IServiceCollection services) + public virtual void ConfigureServices(IServiceCollection services) { - Action settings = (x) => - { - x.WithDictionaryHandle(); - }; - services.AddOcelot(Configuration); } @@ -44,4 +40,43 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerF app.UseOcelot().Wait(); } } + + public class Startup_WithCustomCacheHandle : Startup + { + public Startup_WithCustomCacheHandle(IHostingEnvironment env) : base(env) { } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddOcelot(Configuration) + .AddCacheManager((x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithJsonSerializer() + .WithHandle(typeof(InMemoryJsonHandle<>)); + }); + } + } + + public class Startup_WithConsul_And_CustomCacheHandle : Startup + { + public Startup_WithConsul_And_CustomCacheHandle(IHostingEnvironment env) : base(env) { } + + public override void ConfigureServices(IServiceCollection services) + { + services.AddOcelot(Configuration) + .AddCacheManager((x) => + { + x.WithMicrosoftLogging(log => + { + log.AddConsole(LogLevel.Debug); + }) + .WithJsonSerializer() + .WithHandle(typeof(InMemoryJsonHandle<>)); + }) + .AddStoreOcelotConfigurationInConsul(); + } + } } diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 641252274..83fe84565 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -23,6 +23,7 @@ using Ocelot.ServiceDiscovery; using Shouldly; using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; +using Ocelot.AcceptanceTests.Caching; namespace Ocelot.AcceptanceTests { @@ -74,9 +75,9 @@ public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration, stri /// public void GivenOcelotIsRunning() { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder.ConfigureServices(s => + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => { s.AddSingleton(_webHostBuilder); }); @@ -107,6 +108,21 @@ public void GivenOcelotIsRunning(Action opt _ocelotClient = _ocelotServer.CreateClient(); } + public void GivenOcelotIsRunningUsingJsonSerializedCache() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + public void GivenOcelotIsRunningUsingConsulToStoreConfig() { _webHostBuilder = new WebHostBuilder(); @@ -122,16 +138,31 @@ public void GivenOcelotIsRunningUsingConsulToStoreConfig() _ocelotClient = _ocelotServer.CreateClient(); } + public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder.ConfigureServices(s => + { + s.AddSingleton(_webHostBuilder); + }); + + _ocelotServer = new TestServer(_webHostBuilder + .UseStartup()); + + _ocelotClient = _ocelotServer.CreateClient(); + } + internal void ThenTheResponseShouldBe(FileConfiguration expected) { var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - + response.GlobalConfiguration.AdministrationPath.ShouldBe(expected.GlobalConfiguration.AdministrationPath); response.GlobalConfiguration.RequestIdKey.ShouldBe(expected.GlobalConfiguration.RequestIdKey); response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Host); response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expected.GlobalConfiguration.ServiceDiscoveryProvider.Port); - for(var i = 0; i < response.ReRoutes.Count; i++) + for (var i = 0; i < response.ReRoutes.Count; i++) { response.ReRoutes[i].DownstreamHost.ShouldBe(expected.ReRoutes[i].DownstreamHost); response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expected.ReRoutes[i].DownstreamPathTemplate); @@ -155,7 +186,7 @@ public void GivenOcelotIsRunning(OcelotMiddlewareConfiguration ocelotMiddlewareC var configuration = builder.Build(); _webHostBuilder = new WebHostBuilder(); - _webHostBuilder.ConfigureServices(s => + _webHostBuilder.ConfigureServices(s => { s.AddSingleton(_webHostBuilder); }); @@ -172,7 +203,7 @@ public void GivenOcelotIsRunning(OcelotMiddlewareConfiguration ocelotMiddlewareC }) .WithDictionaryHandle(); }; - + s.AddOcelot(configuration); }) .ConfigureLogging(l => @@ -299,12 +330,12 @@ public void WhenIGetUrlOnTheApiGateway(string url) 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)); + Thread.Sleep(_random.Next(40, 60)); } Task.WaitAll(tasks); @@ -327,7 +358,7 @@ public void WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit(string url, int request.Headers.Add("ClientId", clientId); _response = _ocelotClient.SendAsync(request).Result; } - } + } public void WhenIGetUrlOnTheApiGateway(string url, string requestId) { diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index 11c15d8dc..9b78b3cc3 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -19,12 +19,12 @@ public class OutputCacheMiddlewareTests : ServerHostedMiddlewareTest { - private readonly Mock> _cacheManager; - private HttpResponseMessage _response; + private readonly Mock> _cacheManager; + private CachedResponse _response; public OutputCacheMiddlewareTests() { - _cacheManager = new Mock>(); + _cacheManager = new Mock>(); ScopedRepository .Setup(sr => sr.Get("DownstreamRequest")) @@ -36,7 +36,8 @@ public OutputCacheMiddlewareTests() [Fact] public void should_returned_cached_item_when_it_is_in_cache() { - this.Given(x => x.GivenThereIsACachedResponse(new HttpResponseMessage())) + var cachedResponse = new CachedResponse(); + this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) .And(x => x.GivenTheDownstreamRouteIs()) .And(x => x.GivenThereIsADownstreamUrl()) .When(x => x.WhenICallTheMiddleware()) @@ -70,7 +71,7 @@ protected override void GivenTheTestServerPipelineIsConfigured(IApplicationBuild app.UseOutputCacheMiddleware(); } - private void GivenThereIsACachedResponse(HttpResponseMessage response) + private void GivenThereIsACachedResponse(CachedResponse response) { _response = response; _cacheManager @@ -123,7 +124,7 @@ private void ThenTheCacheGetIsCalledCorrectly() private void ThenTheCacheAddIsCalledCorrectly() { _cacheManager - .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } } diff --git a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs index 100597a01..d449d280c 100644 --- a/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/OutputCacheControllerTests.cs @@ -14,12 +14,12 @@ namespace Ocelot.UnitTests.Controllers public class OutputCacheControllerTests { private OutputCacheController _controller; - private Mock> _cache; + private Mock> _cache; private IActionResult _result; public OutputCacheControllerTests() { - _cache = new Mock>(); + _cache = new Mock>(); _controller = new OutputCacheController(_cache.Object); } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 311957230..138ea496c 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -72,9 +72,9 @@ public void should_set_up_consul() private void OnlyOneVersionOfEachCacheIsRegistered() { - var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - var thing = (CacheManager.Core.ICacheManager)outputCacheManager.ImplementationInstance; + var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); + var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); + var thing = (CacheManager.Core.ICacheManager)outputCacheManager.ImplementationInstance; thing.Configuration.MaxRetries.ShouldBe(_maxRetries); var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache));