From c85ea41951409a4fc1a0a38077847edb38e9492b Mon Sep 17 00:00:00 2001 From: Tom Gardham-Pallister Date: Wed, 1 Mar 2017 07:54:07 +0000 Subject: [PATCH] refactoring ocelot config creation process --- .../Creator/AuthenticationOptionsCreator.cs | 20 ++ .../Creator/ClaimsToThingCreator.cs | 41 +++ .../Creator/FileOcelotConfigurationCreator.cs | 98 ++----- .../Creator/IAuthenticationOptionsCreator.cs | 9 + .../Creator/IClaimsToThingCreator.cs | 9 + .../IUpstreamTemplatePatternCreator.cs | 9 + .../Creator/UpstreamTemplatePatternCreator.cs | 56 ++++ .../ServiceCollectionExtensions.cs | 3 + .../AuthenticationOptionsCreatorTests.cs | 74 ++++++ .../ClaimsToThingCreatorTests.cs | 110 ++++++++ .../FileConfigurationCreatorTests.cs | 246 ++++-------------- .../UpstreamTemplatePatternCreatorTests.cs | 122 +++++++++ 12 files changed, 517 insertions(+), 280 deletions(-) create mode 100644 src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs create mode 100644 src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs create mode 100644 test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs create mode 100644 test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs diff --git a/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs new file mode 100644 index 00000000..85219296 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/AuthenticationOptionsCreator.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs b/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs new file mode 100644 index 00000000..27f52a5f --- /dev/null +++ b/src/Ocelot/Configuration/Creator/ClaimsToThingCreator.cs @@ -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(); + _claimToThingConfigParser = claimToThingConfigurationParser; + } + + public List Create(Dictionary inputToBeParsed) + { + var claimsToThings = new List(); + + 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; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs index b4abdec8..0d853f94 100644 --- a/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileOcelotConfigurationCreator.cs @@ -22,36 +22,38 @@ namespace Ocelot.Configuration.Creator { private readonly IOptions _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 _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 options, IConfigurationValidator configurationValidator, - IClaimToThingConfigurationParser claimToThingConfigurationParser, ILogger 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> 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(); - - 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 BuildAddThingsToRequest(Dictionary thingBeingAdded) - { - var claimsToTHings = new List(); - - 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] == '{'; diff --git a/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs new file mode 100644 index 00000000..e5e82ca8 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IAuthenticationOptionsCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IAuthenticationOptionsCreator + { + AuthenticationOptions Create(FileReRoute fileReRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs b/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs new file mode 100644 index 00000000..54ff8ddc --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IClaimsToThingCreator.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Ocelot.Configuration.Creator +{ + public interface IClaimsToThingCreator + { + List Create(Dictionary thingsBeingAdded); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs new file mode 100644 index 00000000..ae62c47a --- /dev/null +++ b/src/Ocelot/Configuration/Creator/IUpstreamTemplatePatternCreator.cs @@ -0,0 +1,9 @@ +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IUpstreamTemplatePatternCreator + { + string Create(FileReRoute reRoute); + } +} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs new file mode 100644 index 00000000..95b339a9 --- /dev/null +++ b/src/Ocelot/Configuration/Creator/UpstreamTemplatePatternCreator.cs @@ -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(); + + 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] == '{'; + } + } +} \ No newline at end of file diff --git a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs index c076f367..54fef846 100644 --- a/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs @@ -60,6 +60,9 @@ namespace Ocelot.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); var identityServerConfiguration = IdentityServerConfigurationCreator.GetIdentityServerConfiguration(); diff --git a/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs new file mode 100644 index 00000000..9273caf7 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/AuthenticationOptionsCreatorTests.cs @@ -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 {"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); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs new file mode 100644 index 00000000..467835d0 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/ClaimsToThingCreatorTests.cs @@ -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 _configParser; + private Dictionary _claimsToThings; + private ClaimsToThingCreator _claimsToThingsCreator; + private Mock _loggerFactory; + private List _result; + private Mock _logger; + + public ClaimsToThingCreatorTests() + { + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory + .Setup(x => x.CreateLogger()) + .Returns(_logger.Object); + _configParser = new Mock(); + _claimsToThingsCreator = new ClaimsToThingCreator(_configParser.Object, _loggerFactory.Object); + } + + [Fact] + public void should_return_claims_to_things() + { + var userInput = new Dictionary() + { + {"CustomerId", "Claims[CustomerId] > value"} + }; + + var claimsToThing = new OkResponse(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() + { + {"CustomerId", "Claims[CustomerId] > value"} + }; + + var claimsToThing = new ErrorResponse(It.IsAny()); + + 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(), It.IsAny()), Times.Once); + } + + private void ThenClaimsToThingsAreReturned() + { + _result.Count.ShouldBeGreaterThan(0); + } + private void GivenTheFollowingDictionary(Dictionary claimsToThings) + { + _claimsToThings = claimsToThings; + } + + private void GivenTheConfigHeaderExtractorReturns(Response expected) + { + _configParser + .Setup(x => x.Extract(It.IsAny(), It.IsAny())) + .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); + } + } +} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs index fa1bbda3..bafea3cd 100644 --- a/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationCreatorTests.cs @@ -23,7 +23,6 @@ namespace Ocelot.UnitTests.Configuration private readonly Mock _validator; private Response _config; private FileConfiguration _fileConfiguration; - private readonly Mock _configParser; private readonly Mock> _logger; private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; private readonly Mock _loadBalancerFactory; @@ -32,6 +31,9 @@ namespace Ocelot.UnitTests.Configuration private readonly Mock _qosProviderFactory; private readonly Mock _qosProviderHouse; private readonly Mock _qosProvider; + private Mock _claimsToThingCreator; + private Mock _authOptionsCreator; + private Mock _upstreamTemplatePatternCreator; public FileConfigurationCreatorTests() { @@ -39,16 +41,20 @@ namespace Ocelot.UnitTests.Configuration _qosProviderHouse = new Mock(); _qosProvider = new Mock(); _logger = new Mock>(); - _configParser = new Mock(); _validator = new Mock(); _fileConfig = new Mock>(); _loadBalancerFactory = new Mock(); _loadBalancerHouse = new Mock(); _loadBalancer = new Mock(); + _claimsToThingCreator = new Mock(); + _authOptionsCreator = new Mock(); + _upstreamTemplatePatternCreator = new Mock(); + _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 { @@ -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 - { - 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 - { - 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 - { - 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 - { - 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 - { - 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 - { - 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 { @@ -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{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(), It.IsAny())) - .Returns(new OkResponse(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 - { - 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 - { - 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 - { - 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 - { - 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 - { - new FileReRoute - { - UpstreamPathTemplate = "/", - DownstreamPathTemplate = "/api/products/", - UpstreamHttpMethod = "Get", - ReRouteIsCaseSensitive = true - } - } - })) - .And(x => x.GivenTheConfigIsValid()) - .When(x => x.WhenICreateTheConfig()) - .Then(x => x.ThenTheReRoutesAre(new List - { - 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(), _qosProvider.Object), Times.Once); } + + private void GivenTheClaimsToThingCreatorReturns(List claimsToThing) + { + _claimsToThingCreator + .Setup(x => x.Create(_fileConfiguration.ReRoutes[0].AddHeadersToRequest)) + .Returns(claimsToThing); + } + + private void GivenTheAuthOptionsCreatorReturns(AuthenticationOptions authOptions) + { + _authOptionsCreator + .Setup(x => x.Create(It.IsAny())) + .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())) + .Returns(pattern); + } } } diff --git a/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs new file mode 100644 index 00000000..34db1232 --- /dev/null +++ b/test/Ocelot.UnitTests/Configuration/UpstreamTemplatePatternCreatorTests.cs @@ -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); + } + } +} \ No newline at end of file