Merge pull request #109 from TomPallister/feature/clear-cache

Feature/clear cache
This commit is contained in:
Tom Pallister 2017-06-29 06:54:01 +01:00 committed by GitHub
commit 0139452fbf
22 changed files with 397 additions and 34 deletions

View File

@ -1,11 +1,13 @@
using System; using System;
using System.Collections.Generic;
namespace Ocelot.Cache namespace Ocelot.Cache
{ {
public interface IOcelotCache<T> public interface IOcelotCache<T>
{ {
void Add(string key, T value, TimeSpan ttl); void Add(string key, T value, TimeSpan ttl, string region);
void AddAndDelete(string key, T value, TimeSpan ttl); void AddAndDelete(string key, T value, TimeSpan ttl, string region);
T Get(string key); T Get(string key, string region);
void ClearRegion(string region);
} }
} }

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Cache
{
public interface IRegionCreator
{
string Create(FileReRoute reRoute);
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -13,16 +14,19 @@ namespace Ocelot.Cache.Middleware
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly IOcelotLogger _logger; private readonly IOcelotLogger _logger;
private readonly IOcelotCache<HttpResponseMessage> _outputCache; private readonly IOcelotCache<HttpResponseMessage> _outputCache;
private readonly IRegionCreator _regionCreator;
public OutputCacheMiddleware(RequestDelegate next, public OutputCacheMiddleware(RequestDelegate next,
IOcelotLoggerFactory loggerFactory, IOcelotLoggerFactory loggerFactory,
IRequestScopedDataRepository scopedDataRepository, IRequestScopedDataRepository scopedDataRepository,
IOcelotCache<HttpResponseMessage> outputCache) IOcelotCache<HttpResponseMessage> outputCache,
IRegionCreator regionCreator)
:base(scopedDataRepository) :base(scopedDataRepository)
{ {
_next = next; _next = next;
_outputCache = outputCache; _outputCache = outputCache;
_logger = loggerFactory.CreateLogger<OutputCacheMiddleware>(); _logger = loggerFactory.CreateLogger<OutputCacheMiddleware>();
_regionCreator = regionCreator;
} }
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
@ -33,11 +37,11 @@ namespace Ocelot.Cache.Middleware
return; return;
} }
var downstreamUrlKey = DownstreamRequest.RequestUri.OriginalString; var downstreamUrlKey = $"{DownstreamRequest.Method.Method}-{DownstreamRequest.RequestUri.OriginalString}";
_logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey); _logger.LogDebug("started checking cache for {downstreamUrlKey}", downstreamUrlKey);
var cached = _outputCache.Get(downstreamUrlKey); var cached = _outputCache.Get(downstreamUrlKey, DownstreamRoute.ReRoute.CacheOptions.Region);
if (cached != null) if (cached != null)
{ {
@ -63,7 +67,7 @@ namespace Ocelot.Cache.Middleware
var response = HttpResponseMessage; var response = HttpResponseMessage;
_outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.FileCacheOptions.TtlSeconds)); _outputCache.Add(downstreamUrlKey, response, TimeSpan.FromSeconds(DownstreamRoute.ReRoute.CacheOptions.TtlSeconds), DownstreamRoute.ReRoute.CacheOptions.Region);
_logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey); _logger.LogDebug("finished response added to cache for {downstreamUrlKey}", downstreamUrlKey);
} }

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using CacheManager.Core; using CacheManager.Core;
namespace Ocelot.Cache namespace Ocelot.Cache
@ -12,12 +14,12 @@ namespace Ocelot.Cache
_cacheManager = cacheManager; _cacheManager = cacheManager;
} }
public void Add(string key, T value, TimeSpan ttl) public void Add(string key, T value, TimeSpan ttl, string region)
{ {
_cacheManager.Add(new CacheItem<T>(key, value, ExpirationMode.Absolute, ttl)); _cacheManager.Add(new CacheItem<T>(key, region, value, ExpirationMode.Absolute, ttl));
} }
public void AddAndDelete(string key, T value, TimeSpan ttl) public void AddAndDelete(string key, T value, TimeSpan ttl, string region)
{ {
var exists = _cacheManager.Get(key); var exists = _cacheManager.Get(key);
@ -26,12 +28,17 @@ namespace Ocelot.Cache
_cacheManager.Remove(key); _cacheManager.Remove(key);
} }
_cacheManager.Add(new CacheItem<T>(key, value, ExpirationMode.Absolute, ttl)); Add(key, value, ttl, region);
} }
public T Get(string key) public T Get(string key, string region)
{ {
return _cacheManager.Get<T>(key); return _cacheManager.Get<T>(key, region);
}
public void ClearRegion(string region)
{
_cacheManager.ClearRegion(region);
} }
} }
} }

View File

@ -0,0 +1,24 @@
using System.Linq;
using Ocelot.Configuration;
using Ocelot.Configuration.File;
namespace Ocelot.Cache
{
public class RegionCreator : IRegionCreator
{
public string Create(FileReRoute reRoute)
{
if(!string.IsNullOrEmpty(reRoute?.FileCacheOptions?.Region))
{
return reRoute?.FileCacheOptions?.Region;
}
var methods = string.Join("", reRoute.UpstreamHttpMethod.Select(m => m));
var region = $"{methods}{reRoute.UpstreamPathTemplate.Replace("/", "")}";
return region;
}
}
}

View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace Ocelot.Cache
{
public class Regions
{
public Regions(List<string> value)
{
Value = value;
}
public List<string> Value {get;private set;}
}
}

View File

@ -2,11 +2,13 @@
{ {
public class CacheOptions public class CacheOptions
{ {
public CacheOptions(int ttlSeconds) public CacheOptions(int ttlSeconds, string region)
{ {
TtlSeconds = ttlSeconds; TtlSeconds = ttlSeconds;
Region = region;
} }
public int TtlSeconds { get; private set; } public int TtlSeconds { get; private set; }
public string Region {get;private set;}
} }
} }

View File

@ -4,6 +4,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Ocelot.Cache;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.Configuration.Parser; using Ocelot.Configuration.Parser;
@ -36,6 +37,7 @@ namespace Ocelot.Configuration.Creator
private IQoSOptionsCreator _qosOptionsCreator; private IQoSOptionsCreator _qosOptionsCreator;
private IReRouteOptionsCreator _fileReRouteOptionsCreator; private IReRouteOptionsCreator _fileReRouteOptionsCreator;
private IRateLimitOptionsCreator _rateLimitOptionsCreator; private IRateLimitOptionsCreator _rateLimitOptionsCreator;
private IRegionCreator _regionCreator;
public FileOcelotConfigurationCreator( public FileOcelotConfigurationCreator(
IOptions<FileConfiguration> options, IOptions<FileConfiguration> options,
@ -52,9 +54,11 @@ namespace Ocelot.Configuration.Creator
IServiceProviderConfigurationCreator serviceProviderConfigCreator, IServiceProviderConfigurationCreator serviceProviderConfigCreator,
IQoSOptionsCreator qosOptionsCreator, IQoSOptionsCreator qosOptionsCreator,
IReRouteOptionsCreator fileReRouteOptionsCreator, IReRouteOptionsCreator fileReRouteOptionsCreator,
IRateLimitOptionsCreator rateLimitOptionsCreator IRateLimitOptionsCreator rateLimitOptionsCreator,
IRegionCreator regionCreator
) )
{ {
_regionCreator = regionCreator;
_rateLimitOptionsCreator = rateLimitOptionsCreator; _rateLimitOptionsCreator = rateLimitOptionsCreator;
_requestIdKeyCreator = requestIdKeyCreator; _requestIdKeyCreator = requestIdKeyCreator;
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator; _upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
@ -137,6 +141,8 @@ namespace Ocelot.Configuration.Creator
var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting); var rateLimitOption = _rateLimitOptionsCreator.Create(fileReRoute, globalConfiguration, fileReRouteOptions.EnableRateLimiting);
var region = _regionCreator.Create(fileReRoute);
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate) .WithDownstreamPathTemplate(fileReRoute.DownstreamPathTemplate)
.WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate) .WithUpstreamPathTemplate(fileReRoute.UpstreamPathTemplate)
@ -151,7 +157,7 @@ namespace Ocelot.Configuration.Creator
.WithClaimsToQueries(claimsToQueries) .WithClaimsToQueries(claimsToQueries)
.WithRequestIdKey(requestIdKey) .WithRequestIdKey(requestIdKey)
.WithIsCached(fileReRouteOptions.IsCached) .WithIsCached(fileReRouteOptions.IsCached)
.WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds)) .WithCacheOptions(new CacheOptions(fileReRoute.FileCacheOptions.TtlSeconds, region))
.WithDownstreamScheme(fileReRoute.DownstreamScheme) .WithDownstreamScheme(fileReRoute.DownstreamScheme)
.WithLoadBalancer(fileReRoute.LoadBalancer) .WithLoadBalancer(fileReRoute.LoadBalancer)
.WithDownstreamHost(fileReRoute.DownstreamHost) .WithDownstreamHost(fileReRoute.DownstreamHost)

View File

@ -3,5 +3,6 @@
public class FileCacheOptions public class FileCacheOptions
{ {
public int TtlSeconds { get; set; } public int TtlSeconds { get; set; }
public string Region {get; set;}
} }
} }

View File

@ -46,7 +46,7 @@ namespace Ocelot.Configuration
IsAuthorised = isAuthorised; IsAuthorised = isAuthorised;
RequestIdKey = requestIdKey; RequestIdKey = requestIdKey;
IsCached = isCached; IsCached = isCached;
FileCacheOptions = fileCacheOptions; CacheOptions = fileCacheOptions;
ClaimsToQueries = claimsToQueries ClaimsToQueries = claimsToQueries
?? new List<ClaimToThing>(); ?? new List<ClaimToThing>();
ClaimsToClaims = claimsToClaims ClaimsToClaims = claimsToClaims
@ -74,7 +74,7 @@ namespace Ocelot.Configuration
public Dictionary<string, string> RouteClaimsRequirement { get; private set; } public Dictionary<string, string> RouteClaimsRequirement { get; private set; }
public string RequestIdKey { get; private set; } public string RequestIdKey { get; private set; }
public bool IsCached { get; private set; } public bool IsCached { get; private set; }
public CacheOptions FileCacheOptions { get; private set; } public CacheOptions CacheOptions { get; private set; }
public string DownstreamScheme {get;private set;} public string DownstreamScheme {get;private set;}
public bool IsQos { get; private set; } public bool IsQos { get; private set; }
public QoSOptions QosOptionsOptions { get; private set; } public QoSOptions QosOptionsOptions { get; private set; }

View File

@ -30,7 +30,7 @@ namespace Ocelot.Configuration.Repository
public async Task<Response<IOcelotConfiguration>> Get() public async Task<Response<IOcelotConfiguration>> Get()
{ {
var config = _cache.Get(_ocelotConfiguration); var config = _cache.Get(_ocelotConfiguration, _ocelotConfiguration);
if (config != null) if (config != null)
{ {
@ -68,7 +68,7 @@ namespace Ocelot.Configuration.Repository
if (result.Response) if (result.Response)
{ {
_cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3)); _cache.AddAndDelete(_ocelotConfiguration, ocelotConfiguration, TimeSpan.FromSeconds(3), _ocelotConfiguration);
return new OkResponse(); return new OkResponse();
} }

View File

@ -0,0 +1,29 @@
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ocelot.Cache;
using Ocelot.Configuration.Provider;
namespace Ocelot.Controllers
{
[Authorize]
[Route("outputcache")]
public class OutputCacheController : Controller
{
private IOcelotCache<HttpResponseMessage> _cache;
public OutputCacheController(IOcelotCache<HttpResponseMessage> cache)
{
_cache = cache;
}
[HttpDelete]
[Route("{region}")]
public IActionResult Delete(string region)
{
_cache.ClearRegion(region);
return new NoContentResult();
}
}
}

View File

@ -145,6 +145,7 @@ namespace Ocelot.DependencyInjection
.AddJsonFormatters(); .AddJsonFormatters();
services.AddLogging(); services.AddLogging();
services.TryAddSingleton<IRegionCreator, RegionCreator>();
services.TryAddSingleton<IFileConfigurationRepository, FileConfigurationRepository>(); services.TryAddSingleton<IFileConfigurationRepository, FileConfigurationRepository>();
services.TryAddSingleton<IFileConfigurationSetter, FileConfigurationSetter>(); services.TryAddSingleton<IFileConfigurationSetter, FileConfigurationSetter>();
services.TryAddSingleton<IFileConfigurationProvider, FileConfigurationProvider>(); services.TryAddSingleton<IFileConfigurationProvider, FileConfigurationProvider>();

View File

@ -30,6 +30,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta2-build1317" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta2-build1317" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.1" />

View File

@ -7,6 +7,7 @@ using System.Net.Http.Headers;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ocelot.Cache;
using Ocelot.Configuration.File; using Ocelot.Configuration.File;
using Ocelot.ManualTest; using Ocelot.ManualTest;
using Shouldly; using Shouldly;
@ -120,7 +121,12 @@ namespace Ocelot.IntegrationTests
DownstreamScheme = "https", DownstreamScheme = "https",
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" }, UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/" UpstreamPathTemplate = "/",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10,
Region = "Geoff"
}
}, },
new FileReRoute() new FileReRoute()
{ {
@ -129,7 +135,12 @@ namespace Ocelot.IntegrationTests
DownstreamScheme = "https", DownstreamScheme = "https",
DownstreamPathTemplate = "/", DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" }, UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/test" UpstreamPathTemplate = "/test",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10,
Region = "Dave"
}
} }
} }
}; };
@ -218,6 +229,57 @@ namespace Ocelot.IntegrationTests
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_clear_region()
{
var initialConfiguration = new FileConfiguration
{
GlobalConfiguration = new FileGlobalConfiguration
{
AdministrationPath = "/administration"
},
ReRoutes = new List<FileReRoute>()
{
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10
}
},
new FileReRoute()
{
DownstreamHost = "localhost",
DownstreamPort = 80,
DownstreamScheme = "https",
DownstreamPathTemplate = "/",
UpstreamHttpMethod = new List<string> { "get" },
UpstreamPathTemplate = "/test",
FileCacheOptions = new FileCacheOptions
{
TtlSeconds = 10
}
}
}
};
var regionToClear = "gettest";
this.Given(x => GivenThereIsAConfiguration(initialConfiguration))
.And(x => GivenOcelotIsRunning())
.And(x => GivenIHaveAnOcelotToken("/administration"))
.And(x => GivenIHaveAddedATokenToMyRequest())
.When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}"))
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent))
.BDDfy();
}
private void GivenAnotherOcelotIsRunning(string baseUrl) private void GivenAnotherOcelotIsRunning(string baseUrl)
{ {
_httpClientTwo.BaseAddress = new Uri(baseUrl); _httpClientTwo.BaseAddress = new Uri(baseUrl);
@ -256,6 +318,13 @@ namespace Ocelot.IntegrationTests
_response = _httpClient.PostAsync(url, content).Result; _response = _httpClient.PostAsync(url, content).Result;
} }
private void ThenTheResponseShouldBe(List<string> expected)
{
var content = _response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<Regions>(content);
result.Value.ShouldBe(expected);
}
private void ThenTheResponseShouldBe(FileConfiguration expected) private void ThenTheResponseShouldBe(FileConfiguration expected)
{ {
var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result); var response = JsonConvert.DeserializeObject<FileConfiguration>(_response.Content.ReadAsStringAsync().Result);
@ -353,6 +422,11 @@ namespace Ocelot.IntegrationTests
_response = _httpClient.GetAsync(url).Result; _response = _httpClient.GetAsync(url).Result;
} }
private void WhenIDeleteOnTheApiGateway(string url)
{
_response = _httpClient.DeleteAsync(url).Result;
}
private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
{ {
_response.StatusCode.ShouldBe(expectedHttpStatusCode); _response.StatusCode.ShouldBe(expectedHttpStatusCode);

View File

@ -30,6 +30,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="1.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0-preview-20170106-08" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta2-build1317" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta2-build1317" />
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.1" />

View File

@ -22,6 +22,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.MiddlewareAnalysis" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.1" /> <PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.1.1" />

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using CacheManager.Core; using CacheManager.Core;
using Moq; using Moq;
using Ocelot.Cache; using Ocelot.Cache;
@ -16,20 +17,22 @@ namespace Ocelot.UnitTests.Cache
private string _value; private string _value;
private string _resultGet; private string _resultGet;
private TimeSpan _ttlSeconds; private TimeSpan _ttlSeconds;
private List<string> _resultKeys;
private string _region;
public CacheManagerCacheTests() public CacheManagerCacheTests()
{ {
_mockCacheManager = new Mock<ICacheManager<string>>(); _mockCacheManager = new Mock<ICacheManager<string>>();
_ocelotOcelotCacheManager = new OcelotCacheManagerCache<string>(_mockCacheManager.Object); _ocelotOcelotCacheManager = new OcelotCacheManagerCache<string>(_mockCacheManager.Object);
} }
[Fact] [Fact]
public void should_get_from_cache() public void should_get_from_cache()
{ {
this.Given(x => x.GivenTheFollowingIsCached("someKey", "someValue")) this.Given(x => x.GivenTheFollowingIsCached("someKey", "someRegion", "someValue"))
.When(x => x.WhenIGetFromTheCache()) .When(x => x.WhenIGetFromTheCache())
.Then(x => x.ThenTheResultIs("someValue")) .Then(x => x.ThenTheResultIs("someValue"))
.BDDfy(); .BDDfy();
} }
[Fact] [Fact]
@ -40,13 +43,37 @@ namespace Ocelot.UnitTests.Cache
.BDDfy(); .BDDfy();
} }
[Fact]
public void should_delete_key_from_cache()
{
this.Given(_ => GivenTheFollowingRegion("fookey"))
.When(_ => WhenIDeleteTheRegion("fookey"))
.Then(_ => ThenTheRegionIsDeleted("fookey"))
.BDDfy();
}
private void WhenIDeleteTheRegion(string region)
{
_ocelotOcelotCacheManager.ClearRegion(region);
}
private void ThenTheRegionIsDeleted(string region)
{
_mockCacheManager
.Verify(x => x.ClearRegion(region), Times.Once);
}
private void GivenTheFollowingRegion(string key)
{
_ocelotOcelotCacheManager.Add(key, "doesnt matter", TimeSpan.FromSeconds(10), "region");
}
private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds) private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds)
{ {
_key = key; _key = key;
_value = value; _value = value;
_ttlSeconds = ttlSeconds; _ttlSeconds = ttlSeconds;
_ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds, "region");
_ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds);
} }
private void ThenTheCacheIsCalledCorrectly() private void ThenTheCacheIsCalledCorrectly()
@ -62,15 +89,16 @@ namespace Ocelot.UnitTests.Cache
private void WhenIGetFromTheCache() private void WhenIGetFromTheCache()
{ {
_resultGet = _ocelotOcelotCacheManager.Get(_key); _resultGet = _ocelotOcelotCacheManager.Get(_key, _region);
} }
private void GivenTheFollowingIsCached(string key, string value) private void GivenTheFollowingIsCached(string key, string region, string value)
{ {
_key = key; _key = key;
_value = value; _value = value;
_region = region;
_mockCacheManager _mockCacheManager
.Setup(x => x.Get<string>(It.IsAny<string>())) .Setup(x => x.Get<string>(It.IsAny<string>(), It.IsAny<string>()))
.Returns(value); .Returns(value);
} }
} }

View File

@ -5,6 +5,7 @@ using System.Net.Http;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Moq; using Moq;
using Ocelot.Cache; using Ocelot.Cache;
using Ocelot.Cache.Middleware; using Ocelot.Cache.Middleware;
@ -43,6 +44,7 @@ namespace Ocelot.UnitTests.Cache
x.AddLogging(); x.AddLogging();
x.AddSingleton(_cacheManager.Object); x.AddSingleton(_cacheManager.Object);
x.AddSingleton(_scopedRepo.Object); x.AddSingleton(_scopedRepo.Object);
x.AddSingleton<IRegionCreator, RegionCreator>();
}) })
.UseUrls(_url) .UseUrls(_url)
.UseKestrel() .UseKestrel()
@ -90,7 +92,7 @@ namespace Ocelot.UnitTests.Cache
{ {
var reRoute = new ReRouteBuilder() var reRoute = new ReRouteBuilder()
.WithIsCached(true) .WithIsCached(true)
.WithCacheOptions(new CacheOptions(100)) .WithCacheOptions(new CacheOptions(100, "kanken"))
.WithUpstreamHttpMethod(new List<string> { "Get" }) .WithUpstreamHttpMethod(new List<string> { "Get" })
.Build(); .Build();
@ -118,13 +120,13 @@ namespace Ocelot.UnitTests.Cache
private void ThenTheCacheGetIsCalledCorrectly() private void ThenTheCacheGetIsCalledCorrectly()
{ {
_cacheManager _cacheManager
.Verify(x => x.Get(It.IsAny<string>()), Times.Once); .Verify(x => x.Get(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
} }
private void ThenTheCacheAddIsCalledCorrectly() private void ThenTheCacheAddIsCalledCorrectly()
{ {
_cacheManager _cacheManager
.Verify(x => x.Add(It.IsAny<string>(), It.IsAny<HttpResponseMessage>(), It.IsAny<TimeSpan>()), Times.Once); .Verify(x => x.Add(It.IsAny<string>(), It.IsAny<HttpResponseMessage>(), It.IsAny<TimeSpan>(), It.IsAny<string>()), Times.Once);
} }
private void GivenResponseIsNotCached() private void GivenResponseIsNotCached()
@ -138,7 +140,7 @@ namespace Ocelot.UnitTests.Cache
{ {
_response = response; _response = response;
_cacheManager _cacheManager
.Setup(x => x.Get(It.IsAny<string>())) .Setup(x => x.Get(It.IsAny<string>(), It.IsAny<string>()))
.Returns(_response); .Returns(_response);
} }

View File

@ -0,0 +1,65 @@
using System.Collections.Generic;
using Ocelot.Cache;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Cache
{
public class RegionCreatorTests
{
private string _result;
private FileReRoute _reRoute;
[Fact]
public void should_create_region()
{
var reRoute = new FileReRoute
{
UpstreamHttpMethod = new List<string> { "Get" },
UpstreamPathTemplate = "/testdummy"
};
this.Given(_ => GivenTheReRoute(reRoute))
.When(_ => WhenICreateTheRegion())
.Then(_ => ThenTheRegionIs("Gettestdummy"))
.BDDfy();
}
[Fact]
public void should_use_region()
{
var reRoute = new FileReRoute
{
FileCacheOptions = new FileCacheOptions
{
Region = "region"
}
};
this.Given(_ => GivenTheReRoute(reRoute))
.When(_ => WhenICreateTheRegion())
.Then(_ => ThenTheRegionIs("region"))
.BDDfy();
}
private void GivenTheReRoute(FileReRoute reRoute)
{
_reRoute = reRoute;
}
private void WhenICreateTheRegion()
{
RegionCreator regionCreator = new RegionCreator();
_result = regionCreator.Create(_reRoute);
}
private void ThenTheRegionIs(string expected)
{
_result.ShouldBe(expected);
}
}
}

View File

@ -2,6 +2,7 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Moq; using Moq;
using Ocelot.Cache;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Builder; using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator; using Ocelot.Configuration.Creator;
@ -39,6 +40,7 @@ namespace Ocelot.UnitTests.Configuration
private Mock<IQoSOptionsCreator> _qosOptionsCreator; private Mock<IQoSOptionsCreator> _qosOptionsCreator;
private Mock<IReRouteOptionsCreator> _fileReRouteOptionsCreator; private Mock<IReRouteOptionsCreator> _fileReRouteOptionsCreator;
private Mock<IRateLimitOptionsCreator> _rateLimitOptions; private Mock<IRateLimitOptionsCreator> _rateLimitOptions;
private Mock<IRegionCreator> _regionCreator;
public FileConfigurationCreatorTests() public FileConfigurationCreatorTests()
{ {
@ -59,6 +61,7 @@ namespace Ocelot.UnitTests.Configuration
_qosOptionsCreator = new Mock<IQoSOptionsCreator>(); _qosOptionsCreator = new Mock<IQoSOptionsCreator>();
_fileReRouteOptionsCreator = new Mock<IReRouteOptionsCreator>(); _fileReRouteOptionsCreator = new Mock<IReRouteOptionsCreator>();
_rateLimitOptions = new Mock<IRateLimitOptionsCreator>(); _rateLimitOptions = new Mock<IRateLimitOptionsCreator>();
_regionCreator = new Mock<IRegionCreator>();
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
_fileConfig.Object, _validator.Object, _logger.Object, _fileConfig.Object, _validator.Object, _logger.Object,
@ -66,7 +69,51 @@ namespace Ocelot.UnitTests.Configuration
_qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object,
_authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object,
_serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object, _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object,
_rateLimitOptions.Object); _rateLimitOptions.Object, _regionCreator.Object);
}
[Fact]
public void should_call_region_creator()
{
var reRouteOptions = new ReRouteOptionsBuilder()
.Build();
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
DownstreamHost = "127.0.0.1",
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = new List<string> { "Get" },
FileCacheOptions = new FileCacheOptions
{
Region = "region"
}
}
},
}))
.And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheFollowingRegionIsReturned("region"))
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheRegionCreatorIsCalledCorrectly("region"))
.BDDfy();
}
private void GivenTheFollowingRegionIsReturned(string region)
{
_regionCreator
.Setup(x => x.Create(It.IsAny<FileReRoute>()))
.Returns(region);
}
private void ThenTheRegionCreatorIsCalledCorrectly(string expected)
{
_regionCreator
.Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once);
} }
[Fact] [Fact]

View File

@ -0,0 +1,46 @@
using Xunit;
using Shouldly;
using TestStack.BDDfy;
using Ocelot.Controllers;
using System;
using Moq;
using Ocelot.Cache;
using System.Net.Http;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace Ocelot.UnitTests.Controllers
{
public class OutputCacheControllerTests
{
private OutputCacheController _controller;
private Mock<IOcelotCache<HttpResponseMessage>> _cache;
private IActionResult _result;
public OutputCacheControllerTests()
{
_cache = new Mock<IOcelotCache<HttpResponseMessage>>();
_controller = new OutputCacheController(_cache.Object);
}
[Fact]
public void should_delete_key()
{
this.When(_ => WhenIDeleteTheKey("a"))
.Then(_ => ThenTheKeyIsDeleted("a"))
.BDDfy();
}
private void ThenTheKeyIsDeleted(string key)
{
_result.ShouldBeOfType<NoContentResult>();
_cache
.Verify(x => x.ClearRegion(key), Times.Once);
}
private void WhenIDeleteTheKey(string key)
{
_result = _controller.Delete(key);
}
}
}