refactoring ocelot config creation process

This commit is contained in:
Tom Gardham-Pallister 2017-03-01 07:54:07 +00:00
parent 1d216f6863
commit c85ea41951
12 changed files with 517 additions and 280 deletions

View File

@ -0,0 +1,20 @@
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public class AuthenticationOptionsCreator : IAuthenticationOptionsCreator
{
public AuthenticationOptions Create(FileReRoute fileReRoute)
{
return new AuthenticationOptionsBuilder()
.WithProvider(fileReRoute.AuthenticationOptions?.Provider)
.WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl)
.WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName)
.WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps)
.WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes)
.WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret)
.Build();
}
}
}

View File

@ -0,0 +1,41 @@
using System.Collections.Generic;
using Ocelot.Configuration.Parser;
using Ocelot.Logging;
namespace Ocelot.Configuration.Creator
{
public class ClaimsToThingCreator : IClaimsToThingCreator
{
private readonly IClaimToThingConfigurationParser _claimToThingConfigParser;
private readonly IOcelotLogger _logger;
public ClaimsToThingCreator(IClaimToThingConfigurationParser claimToThingConfigurationParser,
IOcelotLoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<ClaimsToThingCreator>();
_claimToThingConfigParser = claimToThingConfigurationParser;
}
public List<ClaimToThing> Create(Dictionary<string,string> inputToBeParsed)
{
var claimsToThings = new List<ClaimToThing>();
foreach (var input in inputToBeParsed)
{
var claimToThing = _claimToThingConfigParser.Extract(input.Key, input.Value);
if (claimToThing.IsError)
{
_logger.LogDebug("ClaimsToThingCreator.BuildAddThingsToRequest",
$"Unable to extract configuration for key: {input.Key} and value: {input.Value} your configuration file is incorrect");
}
else
{
claimsToThings.Add(claimToThing.Data);
}
}
return claimsToThings;
}
}
}

View File

@ -22,36 +22,38 @@ namespace Ocelot.Configuration.Creator
{
private readonly IOptions<FileConfiguration> _options;
private readonly IConfigurationValidator _configurationValidator;
private const string RegExMatchEverything = ".*";
private const string RegExMatchEndString = "$";
private const string RegExIgnoreCase = "(?i)";
private const string RegExForwardSlashOnly = "^/$";
private readonly IClaimToThingConfigurationParser _claimToThingConfigurationParser;
private readonly ILogger<FileOcelotConfigurationCreator> _logger;
private readonly ILoadBalancerFactory _loadBalanceFactory;
private readonly ILoadBalancerHouse _loadBalancerHouse;
private readonly IQoSProviderFactory _qoSProviderFactory;
private readonly IQosProviderHouse _qosProviderHouse;
private readonly IClaimsToThingCreator _claimsToThingCreator;
private readonly IAuthenticationOptionsCreator _authOptionsCreator;
private IUpstreamTemplatePatternCreator _upstreamTemplatePatternCreator;
public FileOcelotConfigurationCreator(
IOptions<FileConfiguration> options,
IConfigurationValidator configurationValidator,
IClaimToThingConfigurationParser claimToThingConfigurationParser,
ILogger<FileOcelotConfigurationCreator> logger,
ILoadBalancerFactory loadBalancerFactory,
ILoadBalancerHouse loadBalancerHouse,
IQoSProviderFactory qoSProviderFactory,
IQosProviderHouse qosProviderHouse)
IQosProviderHouse qosProviderHouse,
IClaimsToThingCreator claimsToThingCreator,
IAuthenticationOptionsCreator authOptionsCreator,
IUpstreamTemplatePatternCreator upstreamTemplatePatternCreator)
{
_upstreamTemplatePatternCreator = upstreamTemplatePatternCreator;
_authOptionsCreator = authOptionsCreator;
_loadBalanceFactory = loadBalancerFactory;
_loadBalancerHouse = loadBalancerHouse;
_qoSProviderFactory = qoSProviderFactory;
_qosProviderHouse = qosProviderHouse;
_options = options;
_configurationValidator = configurationValidator;
_claimToThingConfigurationParser = claimToThingConfigurationParser;
_logger = logger;
_claimsToThingCreator = claimsToThingCreator;
}
public async Task<Response<IOcelotConfiguration>> Create()
@ -107,19 +109,19 @@ namespace Ocelot.Configuration.Creator
var reRouteKey = BuildReRouteKey(fileReRoute);
var upstreamTemplatePattern = BuildUpstreamTemplatePattern(fileReRoute);
var upstreamTemplatePattern = _upstreamTemplatePatternCreator.Create(fileReRoute);
var isQos = IsQoS(fileReRoute);
var serviceProviderConfiguration = BuildServiceProviderConfiguration(fileReRoute, globalConfiguration);
var authOptionsForRoute = BuildAuthenticationOptions(fileReRoute);
var authOptionsForRoute = _authOptionsCreator.Create(fileReRoute);
var claimsToHeaders = BuildAddThingsToRequest(fileReRoute.AddHeadersToRequest);
var claimsToHeaders = _claimsToThingCreator.Create(fileReRoute.AddHeadersToRequest);
var claimsToClaims = BuildAddThingsToRequest(fileReRoute.AddClaimsToRequest);
var claimsToClaims = _claimsToThingCreator.Create(fileReRoute.AddClaimsToRequest);
var claimsToQueries = BuildAddThingsToRequest(fileReRoute.AddQueriesToRequest);
var claimsToQueries = _claimsToThingCreator.Create(fileReRoute.AddQueriesToRequest);
var qosOptions = BuildQoSOptions(fileReRoute);
@ -153,6 +155,7 @@ namespace Ocelot.Configuration.Creator
.WithEnableRateLimiting(enableRateLimiting)
.WithRateLimitOptions(rateLimitOption)
.Build();
await SetupLoadBalancer(reRoute);
SetupQosProvider(reRoute);
return reRoute;
@ -225,18 +228,6 @@ namespace Ocelot.Configuration.Creator
return loadBalancerKey;
}
private AuthenticationOptions BuildAuthenticationOptions(FileReRoute fileReRoute)
{
return new AuthenticationOptionsBuilder()
.WithProvider(fileReRoute.AuthenticationOptions?.Provider)
.WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl)
.WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName)
.WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps)
.WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes)
.WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret)
.Build();
}
private async Task SetupLoadBalancer(ReRoute reRoute)
{
var loadBalancer = await _loadBalanceFactory.Get(reRoute);
@ -267,63 +258,6 @@ namespace Ocelot.Configuration.Creator
.Build();
}
private string BuildUpstreamTemplatePattern(FileReRoute reRoute)
{
var upstreamTemplate = reRoute.UpstreamPathTemplate;
upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/');
var placeholders = new List<string>();
for (var i = 0; i < upstreamTemplate.Length; i++)
{
if (IsPlaceHolder(upstreamTemplate, i))
{
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
var variableName = upstreamTemplate.Substring(i, difference);
placeholders.Add(variableName);
}
}
foreach (var placeholder in placeholders)
{
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
}
if (upstreamTemplate == "/")
{
return RegExForwardSlashOnly;
}
var route = reRoute.ReRouteIsCaseSensitive
? $"{upstreamTemplate}{RegExMatchEndString}"
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
return route;
}
private List<ClaimToThing> BuildAddThingsToRequest(Dictionary<string,string> thingBeingAdded)
{
var claimsToTHings = new List<ClaimToThing>();
foreach (var add in thingBeingAdded)
{
var claimToHeader = _claimToThingConfigurationParser.Extract(add.Key, add.Value);
if (claimToHeader.IsError)
{
_logger.LogCritical(new EventId(1, "Application Failed to start"),
$"Unable to extract configuration for key: {add.Key} and value: {add.Value} your configuration file is incorrect");
throw new Exception(claimToHeader.Errors[0].Message);
}
claimsToTHings.Add(claimToHeader.Data);
}
return claimsToTHings;
}
private bool IsPlaceHolder(string upstreamTemplate, int i)
{
return upstreamTemplate[i] == '{';

View File

@ -0,0 +1,9 @@
using Ocelot.Configuration.File;
namespace Ocelot.Configuration.Creator
{
public interface IAuthenticationOptionsCreator
{
AuthenticationOptions Create(FileReRoute fileReRoute);
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Ocelot.Configuration.Creator
{
public interface IClaimsToThingCreator
{
List<ClaimToThing> Create(Dictionary<string,string> thingsBeingAdded);
}
}

View File

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

View File

@ -0,0 +1,56 @@
using System.Collections.Generic;
using Ocelot.Configuration.File;
using Ocelot.Utilities;
namespace Ocelot.Configuration.Creator
{
public class UpstreamTemplatePatternCreator : IUpstreamTemplatePatternCreator
{
private const string RegExMatchEverything = ".*";
private const string RegExMatchEndString = "$";
private const string RegExIgnoreCase = "(?i)";
private const string RegExForwardSlashOnly = "^/$";
public string Create(FileReRoute reRoute)
{
var upstreamTemplate = reRoute.UpstreamPathTemplate;
upstreamTemplate = upstreamTemplate.SetLastCharacterAs('/');
var placeholders = new List<string>();
for (var i = 0; i < upstreamTemplate.Length; i++)
{
if (IsPlaceHolder(upstreamTemplate, i))
{
var postitionOfPlaceHolderClosingBracket = upstreamTemplate.IndexOf('}', i);
var difference = postitionOfPlaceHolderClosingBracket - i + 1;
var variableName = upstreamTemplate.Substring(i, difference);
placeholders.Add(variableName);
}
}
foreach (var placeholder in placeholders)
{
upstreamTemplate = upstreamTemplate.Replace(placeholder, RegExMatchEverything);
}
if (upstreamTemplate == "/")
{
return RegExForwardSlashOnly;
}
var route = reRoute.ReRouteIsCaseSensitive
? $"{upstreamTemplate}{RegExMatchEndString}"
: $"{RegExIgnoreCase}{upstreamTemplate}{RegExMatchEndString}";
return route;
}
private bool IsPlaceHolder(string upstreamTemplate, int i)
{
return upstreamTemplate[i] == '{';
}
}
}

View File

@ -60,6 +60,9 @@ namespace Ocelot.DependencyInjection
services.AddSingleton<IOcelotConfigurationRepository, InMemoryOcelotConfigurationRepository>();
services.AddSingleton<IConfigurationValidator, FileConfigurationValidator>();
services.AddSingleton<IBaseUrlFinder, BaseUrlFinder>();
services.AddSingleton<IClaimsToThingCreator, ClaimsToThingCreator>();
services.AddSingleton<IAuthenticationOptionsCreator, AuthenticationOptionsCreator>();
services.AddSingleton<IUpstreamTemplatePatternCreator, UpstreamTemplatePatternCreator>();
var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration();

View File

@ -0,0 +1,74 @@
using System.Collections.Generic;
using Ocelot.Configuration;
using Ocelot.Configuration.Builder;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class AuthenticationOptionsCreatorTests
{
private AuthenticationOptionsCreator _authOptionsCreator;
private FileReRoute _fileReRoute;
private AuthenticationOptions _result;
public AuthenticationOptionsCreatorTests()
{
_authOptionsCreator = new AuthenticationOptionsCreator();
}
[Fact]
public void should_return_auth_options()
{
var fileReRoute = new FileReRoute()
{
AuthenticationOptions = new FileAuthenticationOptions
{
Provider = "Geoff",
ProviderRootUrl = "http://www.bbc.co.uk/",
ScopeName = "Laura",
RequireHttps = true,
AdditionalScopes = new List<string> {"cheese"},
ScopeSecret = "secret"
}
};
var expected = new AuthenticationOptionsBuilder()
.WithProvider(fileReRoute.AuthenticationOptions?.Provider)
.WithProviderRootUrl(fileReRoute.AuthenticationOptions?.ProviderRootUrl)
.WithScopeName(fileReRoute.AuthenticationOptions?.ScopeName)
.WithRequireHttps(fileReRoute.AuthenticationOptions.RequireHttps)
.WithAdditionalScopes(fileReRoute.AuthenticationOptions?.AdditionalScopes)
.WithScopeSecret(fileReRoute.AuthenticationOptions?.ScopeSecret)
.Build();
this.Given(x => x.GivenTheFollowing(fileReRoute))
.When(x => x.WhenICreateTheAuthenticationOptions())
.Then(x => x.ThenTheFollowingIsReturned(expected))
.BDDfy();
}
private void GivenTheFollowing(FileReRoute fileReRoute)
{
_fileReRoute = fileReRoute;
}
private void WhenICreateTheAuthenticationOptions()
{
_result = _authOptionsCreator.Create(_fileReRoute);
}
private void ThenTheFollowingIsReturned(AuthenticationOptions expected)
{
_result.AdditionalScopes.ShouldBe(expected.AdditionalScopes);
_result.Provider.ShouldBe(expected.Provider);
_result.ProviderRootUrl.ShouldBe(expected.ProviderRootUrl);
_result.RequireHttps.ShouldBe(expected.RequireHttps);
_result.ScopeName.ShouldBe(expected.ScopeName);
_result.ScopeSecret.ShouldBe(expected.ScopeSecret);
}
}
}

View File

@ -0,0 +1,110 @@
using System.Collections.Generic;
using System.Linq;
using Moq;
using Ocelot.Configuration;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.Parser;
using Ocelot.Errors;
using Ocelot.Logging;
using Ocelot.Responses;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class ClaimsToThingCreatorTests
{
private readonly Mock<IClaimToThingConfigurationParser> _configParser;
private Dictionary<string,string> _claimsToThings;
private ClaimsToThingCreator _claimsToThingsCreator;
private Mock<IOcelotLoggerFactory> _loggerFactory;
private List<ClaimToThing> _result;
private Mock<IOcelotLogger> _logger;
public ClaimsToThingCreatorTests()
{
_loggerFactory = new Mock<IOcelotLoggerFactory>();
_logger = new Mock<IOcelotLogger>();
_loggerFactory
.Setup(x => x.CreateLogger<ClaimsToThingCreator>())
.Returns(_logger.Object);
_configParser = new Mock<IClaimToThingConfigurationParser>();
_claimsToThingsCreator = new ClaimsToThingCreator(_configParser.Object, _loggerFactory.Object);
}
[Fact]
public void should_return_claims_to_things()
{
var userInput = new Dictionary<string,string>()
{
{"CustomerId", "Claims[CustomerId] > value"}
};
var claimsToThing = new OkResponse<ClaimToThing>(new ClaimToThing("CustomerId", "CustomerId", "", 0));
this.Given(x => x.GivenTheFollowingDictionary(userInput))
.And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing))
.When(x => x.WhenIGetTheThings())
.Then(x => x.ThenTheConfigParserIsCalledCorrectly())
.And(x => x.ThenClaimsToThingsAreReturned())
.BDDfy();
}
[Fact]
public void should_log_error_if_cannot_parse_claim_to_thing()
{
var userInput = new Dictionary<string,string>()
{
{"CustomerId", "Claims[CustomerId] > value"}
};
var claimsToThing = new ErrorResponse<ClaimToThing>(It.IsAny<Error>());
this.Given(x => x.GivenTheFollowingDictionary(userInput))
.And(x => x.GivenTheConfigHeaderExtractorReturns(claimsToThing))
.When(x => x.WhenIGetTheThings())
.Then(x => x.ThenTheConfigParserIsCalledCorrectly())
.And(x => x.ThenNoClaimsToThingsAreReturned())
.BDDfy();
}
private void ThenTheLoggerIsCalledCorrectly()
{
_logger
.Verify(x => x.LogDebug(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
private void ThenClaimsToThingsAreReturned()
{
_result.Count.ShouldBeGreaterThan(0);
}
private void GivenTheFollowingDictionary(Dictionary<string,string> claimsToThings)
{
_claimsToThings = claimsToThings;
}
private void GivenTheConfigHeaderExtractorReturns(Response<ClaimToThing> expected)
{
_configParser
.Setup(x => x.Extract(It.IsAny<string>(), It.IsAny<string>()))
.Returns(expected);
}
private void ThenNoClaimsToThingsAreReturned()
{
_result.Count.ShouldBe(0);
}
private void WhenIGetTheThings()
{
_result = _claimsToThingsCreator.Create(_claimsToThings);
}
private void ThenTheConfigParserIsCalledCorrectly()
{
_configParser
.Verify(x => x.Extract(_claimsToThings.First().Key, _claimsToThings.First().Value), Times.Once);
}
}
}

View File

@ -23,7 +23,6 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<IConfigurationValidator> _validator;
private Response<IOcelotConfiguration> _config;
private FileConfiguration _fileConfiguration;
private readonly Mock<IClaimToThingConfigurationParser> _configParser;
private readonly Mock<ILogger<FileOcelotConfigurationCreator>> _logger;
private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator;
private readonly Mock<ILoadBalancerFactory> _loadBalancerFactory;
@ -32,6 +31,9 @@ namespace Ocelot.UnitTests.Configuration
private readonly Mock<IQoSProviderFactory> _qosProviderFactory;
private readonly Mock<IQosProviderHouse> _qosProviderHouse;
private readonly Mock<IQoSProvider> _qosProvider;
private Mock<IClaimsToThingCreator> _claimsToThingCreator;
private Mock<IAuthenticationOptionsCreator> _authOptionsCreator;
private Mock<IUpstreamTemplatePatternCreator> _upstreamTemplatePatternCreator;
public FileConfigurationCreatorTests()
{
@ -39,16 +41,20 @@ namespace Ocelot.UnitTests.Configuration
_qosProviderHouse = new Mock<IQosProviderHouse>();
_qosProvider = new Mock<IQoSProvider>();
_logger = new Mock<ILogger<FileOcelotConfigurationCreator>>();
_configParser = new Mock<IClaimToThingConfigurationParser>();
_validator = new Mock<IConfigurationValidator>();
_fileConfig = new Mock<IOptions<FileConfiguration>>();
_loadBalancerFactory = new Mock<ILoadBalancerFactory>();
_loadBalancerHouse = new Mock<ILoadBalancerHouse>();
_loadBalancer = new Mock<ILoadBalancer>();
_claimsToThingCreator = new Mock<IClaimsToThingCreator>();
_authOptionsCreator = new Mock<IAuthenticationOptionsCreator>();
_upstreamTemplatePatternCreator = new Mock<IUpstreamTemplatePatternCreator>();
_ocelotConfigurationCreator = new FileOcelotConfigurationCreator(
_fileConfig.Object, _validator.Object, _configParser.Object, _logger.Object,
_fileConfig.Object, _validator.Object, _logger.Object,
_loadBalancerFactory.Object, _loadBalancerHouse.Object,
_qosProviderFactory.Object, _qosProviderHouse.Object);
_qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object,
_authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object);
}
[Fact]
@ -130,7 +136,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build()
}))
.BDDfy();
@ -161,7 +166,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build()
}))
.BDDfy();
@ -200,7 +204,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder()
.WithUseServiceDiscovery(true)
.WithServiceDiscoveryProvider("consul")
@ -236,7 +239,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.WithServiceProviderConfiguraion(new ServiceProviderConfiguraionBuilder()
.WithUseServiceDiscovery(false)
.Build())
@ -246,7 +248,7 @@ namespace Ocelot.UnitTests.Configuration
}
[Fact]
public void should_use_reroute_case_sensitivity_value()
public void should_call_template_pattern_creator_correctly()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
@ -262,6 +264,7 @@ namespace Ocelot.UnitTests.Configuration
}
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$"))
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
@ -275,65 +278,6 @@ namespace Ocelot.UnitTests.Configuration
.BDDfy();
}
[Fact]
public void should_set_upstream_template_pattern_to_ignore_case_sensitivity()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get"
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("(?i)/api/products/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_set_upstream_template_pattern_to_respect_case_sensitivity()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_set_global_request_id_key()
{
@ -362,43 +306,12 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.WithRequestIdKey("blahhhh")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_anything_to_end_of_string()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_create_with_headers_to_extract()
{
@ -417,7 +330,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.WithAuthenticationOptions(authenticationOptions)
.WithClaimsToHeaders(new List<ClaimToThing>
{
@ -453,20 +365,17 @@ namespace Ocelot.UnitTests.Configuration
}
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheConfigHeaderExtractorReturns(new ClaimToThing("CustomerId", "CustomerId", "", 0)))
.And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions))
.And(x => x.GivenTheClaimsToThingCreatorReturns(new List<ClaimToThing>{new ClaimToThing("CustomerId", "CustomerId", "", 0)}))
.And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(expected))
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
.And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly())
.BDDfy();
}
private void GivenTheConfigHeaderExtractorReturns(ClaimToThing expected)
{
_configParser
.Setup(x => x.Extract(It.IsAny<string>(), It.IsAny<string>()))
.Returns(new OkResponse<ClaimToThing>(expected));
}
[Fact]
public void should_create_with_authentication_properties()
@ -486,7 +395,6 @@ namespace Ocelot.UnitTests.Configuration
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/$")
.WithAuthenticationOptions(authenticationOptions)
.Build()
};
@ -514,100 +422,12 @@ namespace Ocelot.UnitTests.Configuration
}
}))
.And(x => x.GivenTheConfigIsValid())
.And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions))
.And(x => x.GivenTheLoadBalancerFactoryReturns())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(expected))
.And(x => x.ThenTheAuthenticationOptionsAre(expected))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_more_than_one_placeholder()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/",
DownstreamPathTemplate = "/products/{productId}",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/products/{productId}")
.WithUpstreamPathTemplate("/api/products/{productId}/variants/{variantId}/")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("/api/products/.*/variants/.*/$")
.Build()
}))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_to_end_of_string()
{
this.Given(x => x.GivenTheConfigIs(new FileConfiguration
{
ReRoutes = new List<FileReRoute>
{
new FileReRoute
{
UpstreamPathTemplate = "/",
DownstreamPathTemplate = "/api/products/",
UpstreamHttpMethod = "Get",
ReRouteIsCaseSensitive = true
}
}
}))
.And(x => x.GivenTheConfigIsValid())
.When(x => x.WhenICreateTheConfig())
.Then(x => x.ThenTheReRoutesAre(new List<ReRoute>
{
new ReRouteBuilder()
.WithDownstreamPathTemplate("/api/products/")
.WithUpstreamPathTemplate("/")
.WithUpstreamHttpMethod("Get")
.WithUpstreamTemplatePattern("^/$")
.Build()
}))
.And(x => x.ThenTheAuthOptionsCreatorIsCalledCorrectly())
.BDDfy();
}
@ -642,6 +462,9 @@ namespace Ocelot.UnitTests.Configuration
result.UpstreamHttpMethod.ShouldBe(expected.UpstreamHttpMethod);
result.UpstreamPathTemplate.Value.ShouldBe(expected.UpstreamPathTemplate.Value);
result.UpstreamTemplatePattern.ShouldBe(expected.UpstreamTemplatePattern);
result.ClaimsToClaims.Count.ShouldBe(expected.ClaimsToClaims.Count);
result.ClaimsToHeaders.Count.ShouldBe(expected.ClaimsToHeaders.Count);
result.ClaimsToQueries.Count.ShouldBe(expected.ClaimsToQueries.Count);
}
}
@ -699,5 +522,32 @@ namespace Ocelot.UnitTests.Configuration
_qosProviderHouse
.Verify(x => x.Add(It.IsAny<string>(), _qosProvider.Object), Times.Once);
}
private void GivenTheClaimsToThingCreatorReturns(List<ClaimToThing> claimsToThing)
{
_claimsToThingCreator
.Setup(x => x.Create(_fileConfiguration.ReRoutes[0].AddHeadersToRequest))
.Returns(claimsToThing);
}
private void GivenTheAuthOptionsCreatorReturns(AuthenticationOptions authOptions)
{
_authOptionsCreator
.Setup(x => x.Create(It.IsAny<FileReRoute>()))
.Returns(authOptions);
}
private void ThenTheAuthOptionsCreatorIsCalledCorrectly()
{
_authOptionsCreator
.Verify(x => x.Create(_fileConfiguration.ReRoutes[0]), Times.Once);
}
private void GivenTheUpstreamTemplatePatternCreatorReturns(string pattern)
{
_upstreamTemplatePatternCreator
.Setup(x => x.Create(It.IsAny<FileReRoute>()))
.Returns(pattern);
}
}
}

View File

@ -0,0 +1,122 @@
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Shouldly;
using TestStack.BDDfy;
using Xunit;
namespace Ocelot.UnitTests.Configuration
{
public class UpstreamTemplatePatternCreatorTests
{
private FileReRoute _fileReRoute;
private UpstreamTemplatePatternCreator _creator;
private string _result;
public UpstreamTemplatePatternCreatorTests()
{
_creator = new UpstreamTemplatePatternCreator();
}
[Fact]
public void should_set_upstream_template_pattern_to_ignore_case_sensitivity()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/PRODUCTS/{productId}",
ReRouteIsCaseSensitive = false
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("(?i)/PRODUCTS/.*/$"))
.BDDfy();
}
[Fact]
public void should_set_upstream_template_pattern_to_respect_case_sensitivity()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/PRODUCTS/{productId}",
ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("/PRODUCTS/.*/$"))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_anything_to_end_of_string()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}",
ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/$"))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_more_than_one_placeholder()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}",
ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/variants/.*/$"))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_more_than_one_placeholder_with_trailing_slash()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/api/products/{productId}/variants/{variantId}/",
ReRouteIsCaseSensitive = true
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("/api/products/.*/variants/.*/$"))
.BDDfy();
}
[Fact]
public void should_create_template_pattern_that_matches_to_end_of_string()
{
var fileReRoute = new FileReRoute
{
UpstreamPathTemplate = "/"
};
this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute))
.When(x => x.WhenICreateTheTemplatePattern())
.Then(x => x.ThenTheFollowingIsReturned("^/$"))
.BDDfy();
}
private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute)
{
_fileReRoute = fileReRoute;
}
private void WhenICreateTheTemplatePattern()
{
_result = _creator.Create(_fileReRoute);
}
private void ThenTheFollowingIsReturned(string expected)
{
_result.ShouldBe(expected);
}
}
}