using System.Collections.Generic; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.Creator; using Ocelot.Configuration.File; using Ocelot.Configuration.Validator; using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.Requester.QoS; using Ocelot.Responses; using Shouldly; using TestStack.BDDfy; using Xunit; namespace Ocelot.UnitTests.Configuration { public class FileConfigurationCreatorTests { private readonly Mock> _fileConfig; private readonly Mock _validator; private Response _config; private FileConfiguration _fileConfiguration; private readonly Mock _logger; private readonly FileOcelotConfigurationCreator _ocelotConfigurationCreator; private readonly Mock _loadBalancerFactory; private readonly Mock _loadBalancerHouse; private readonly Mock _loadBalancer; private readonly Mock _qosProviderFactory; private readonly Mock _qosProviderHouse; private readonly Mock _qosProvider; private Mock _claimsToThingCreator; private Mock _authOptionsCreator; private Mock _upstreamTemplatePatternCreator; private Mock _requestIdKeyCreator; private Mock _serviceProviderConfigCreator; private Mock _qosOptionsCreator; private Mock _fileReRouteOptionsCreator; private Mock _rateLimitOptions; public FileConfigurationCreatorTests() { _qosProviderFactory = new Mock(); _qosProviderHouse = new Mock(); _qosProvider = new Mock(); _logger = 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(); _requestIdKeyCreator = new Mock(); _serviceProviderConfigCreator = new Mock(); _qosOptionsCreator = new Mock(); _fileReRouteOptionsCreator = new Mock(); _rateLimitOptions = new Mock(); _ocelotConfigurationCreator = new FileOcelotConfigurationCreator( _fileConfig.Object, _validator.Object, _logger.Object, _loadBalancerFactory.Object, _loadBalancerHouse.Object, _qosProviderFactory.Object, _qosProviderHouse.Object, _claimsToThingCreator.Object, _authOptionsCreator.Object, _upstreamTemplatePatternCreator.Object, _requestIdKeyCreator.Object, _serviceProviderConfigCreator.Object, _qosOptionsCreator.Object, _fileReRouteOptionsCreator.Object, _rateLimitOptions.Object); } [Fact] public void should_call_rate_limit_options_creator() { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamHost = "127.0.0.1", UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheRateLimitOptionsCreatorIsCalledCorrectly()) .BDDfy(); } [Fact] public void should_call_qos_options_creator() { var expected = new QoSOptionsBuilder() .WithDurationOfBreak(1) .WithExceptionsAllowedBeforeBreaking(1) .WithTimeoutValue(1) .Build(); var serviceOptions = new ReRouteOptionsBuilder() .WithIsQos(true) .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamHost = "127.0.0.1", UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", QoSOptions = new FileQoSOptions { TimeoutValue = 1, DurationOfBreak = 1, ExceptionsAllowedBeforeBreaking = 1 } } }, })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(serviceOptions)) .And(x => x.GivenTheQosProviderFactoryReturns()) .And(x => x.GivenTheQosOptionsCreatorReturns(expected)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheQosOptionsAre(expected)) .And(x => x.TheQosProviderFactoryIsCalledCorrectly()) .And(x => x.ThenTheQosProviderHouseIsCalledCorrectly()) .BDDfy(); } [Fact] public void should_create_load_balancer() { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamHost = "127.0.0.1", UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheLoadBalancerFactoryReturns()) .When(x => x.WhenICreateTheConfig()) .Then(x => x.TheLoadBalancerFactoryIsCalledCorrectly()) .And(x => x.ThenTheLoadBalancerHouseIsCalledCorrectly()) .BDDfy(); } [Fact] public void should_use_downstream_host() { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamHost = "127.0.0.1", UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() .WithDownstreamHost("127.0.0.1") .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .Build() })) .BDDfy(); } [Fact] public void should_use_downstream_scheme() { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { DownstreamScheme = "https", UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", } }, })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() .WithDownstreamScheme("https") .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .Build() })) .BDDfy(); } [Fact] public void should_use_service_discovery_for_downstream_service_host() { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, ServiceName = "ProductService" } }, GlobalConfiguration = new FileGlobalConfiguration { ServiceDiscoveryProvider = new FileServiceDiscoveryProvider { Provider = "consul", Host = "127.0.0.1" } } })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder() .WithUseServiceDiscovery(true) .WithServiceDiscoveryProvider("consul") .WithServiceDiscoveryProviderHost("127.0.0.1") .WithServiceName("ProductService") .Build()) .Build() })) .BDDfy(); } [Fact] public void should_not_use_service_discovery_for_downstream_host_url_when_no_service_name() { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false, } } })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithServiceProviderConfiguraion(new ServiceProviderConfigurationBuilder() .WithUseServiceDiscovery(false) .Build()) .Build() })) .BDDfy(); } [Fact] public void should_call_template_pattern_creator_correctly() { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = false } } })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheUpstreamTemplatePatternCreatorReturns("(?i)/api/products/.*/$")) .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_call_request_id_creator() { var reRouteOptions = new ReRouteOptionsBuilder() .Build(); this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true } }, GlobalConfiguration = new FileGlobalConfiguration { RequestIdKey = "blahhhh" } })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheRequestIdCreatorReturns("blahhhh")) .When(x => x.WhenICreateTheConfig()) .Then(x => x.ThenTheReRoutesAre(new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithRequestIdKey("blahhhh") .Build() })) .And(x => x.ThenTheRequestIdKeyCreatorIsCalledCorrectly()) .BDDfy(); } [Fact] public void should_create_with_headers_to_extract() { var reRouteOptions = new ReRouteOptionsBuilder() .WithIsAuthenticated(true) .Build(); var authenticationOptions = new AuthenticationOptionsBuilder() .WithProvider("IdentityServer") .WithProviderRootUrl("http://localhost:51888") .WithRequireHttps(false) .WithApiSecret("secret") .WithApiName("api") .WithAllowedScopes(new List()) .Build(); var expected = new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithAuthenticationOptions(authenticationOptions) .WithClaimsToHeaders(new List { new ClaimToThing("CustomerId", "CustomerId", "", 0), }) .Build() }; this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions { AllowedScopes= new List(), Provider = "IdentityServer", ProviderRootUrl = "http://localhost:51888", RequireHttps = false, ApiName= "api", ApiSecret = "secret" }, AddHeadersToRequest = { {"CustomerId", "Claims[CustomerId] > value"}, } } } })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .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(); } [Fact] public void should_create_with_authentication_properties() { var reRouteOptions = new ReRouteOptionsBuilder() .WithIsAuthenticated(true) .Build(); var authenticationOptions = new AuthenticationOptionsBuilder() .WithProvider("IdentityServer") .WithProviderRootUrl("http://localhost:51888") .WithRequireHttps(false) .WithApiSecret("secret") .WithApiName("api") .WithAllowedScopes(new List()) .Build(); var expected = new List { new ReRouteBuilder() .WithDownstreamPathTemplate("/products/{productId}") .WithUpstreamPathTemplate("/api/products/{productId}") .WithUpstreamHttpMethod("Get") .WithAuthenticationOptions(authenticationOptions) .Build() }; this.Given(x => x.GivenTheConfigIs(new FileConfiguration { ReRoutes = new List { new FileReRoute { UpstreamPathTemplate = "/api/products/{productId}", DownstreamPathTemplate = "/products/{productId}", UpstreamHttpMethod = "Get", ReRouteIsCaseSensitive = true, AuthenticationOptions = new FileAuthenticationOptions { AllowedScopes = new List(), Provider = "IdentityServer", ProviderRootUrl = "http://localhost:51888", RequireHttps = false, ApiName= "api", ApiSecret = "secret" } } } })) .And(x => x.GivenTheConfigIsValid()) .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) .And(x => x.GivenTheAuthOptionsCreatorReturns(authenticationOptions)) .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 GivenTheFollowingOptionsAreReturned(ReRouteOptions fileReRouteOptions) { _fileReRouteOptionsCreator .Setup(x => x.Create(It.IsAny())) .Returns(fileReRouteOptions); } private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() { _rateLimitOptions .Verify(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } private void GivenTheConfigIsValid() { _validator .Setup(x => x.IsValid(It.IsAny())) .Returns(new OkResponse(new ConfigurationValidationResult(false))); } private void GivenTheConfigIs(FileConfiguration fileConfiguration) { _fileConfiguration = fileConfiguration; _fileConfig .Setup(x => x.Value) .Returns(_fileConfiguration); } private void WhenICreateTheConfig() { _config = _ocelotConfigurationCreator.Create().Result; } private void ThenTheReRoutesAre(List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) { var result = _config.Data.ReRoutes[i]; var expected = expectedReRoutes[i]; result.DownstreamPathTemplate.Value.ShouldBe(expected.DownstreamPathTemplate.Value); 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); result.RequestIdKey.ShouldBe(expected.RequestIdKey); } } private void ThenTheServiceConfigurationIs(ServiceProviderConfiguration expected) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) { var result = _config.Data.ReRoutes[i]; result.ServiceProviderConfiguraion.DownstreamHost.ShouldBe(expected.DownstreamHost); result.ServiceProviderConfiguraion.DownstreamPort.ShouldBe(expected.DownstreamPort); result.ServiceProviderConfiguraion.ServiceDiscoveryProvider.ShouldBe(expected.ServiceDiscoveryProvider); result.ServiceProviderConfiguraion.ServiceName.ShouldBe(expected.ServiceName); result.ServiceProviderConfiguraion.ServiceProviderHost.ShouldBe(expected.ServiceProviderHost); result.ServiceProviderConfiguraion.ServiceProviderPort.ShouldBe(expected.ServiceProviderPort); } } private void ThenTheAuthenticationOptionsAre(List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) { var result = _config.Data.ReRoutes[i].AuthenticationOptions; var expected = expectedReRoutes[i].AuthenticationOptions; result.AllowedScopes.ShouldBe(expected.AllowedScopes); result.Provider.ShouldBe(expected.Provider); result.ProviderRootUrl.ShouldBe(expected.ProviderRootUrl); result.RequireHttps.ShouldBe(expected.RequireHttps); result.ApiName.ShouldBe(expected.ApiName); result.ApiSecret.ShouldBe(expected.ApiSecret); } } private void GivenTheLoadBalancerFactoryReturns() { _loadBalancerFactory .Setup(x => x.Get(It.IsAny())) .ReturnsAsync(_loadBalancer.Object); } private void TheLoadBalancerFactoryIsCalledCorrectly() { _loadBalancerFactory .Verify(x => x.Get(It.IsAny()), Times.Once); } private void ThenTheLoadBalancerHouseIsCalledCorrectly() { _loadBalancerHouse .Verify(x => x.Add(It.IsAny(), _loadBalancer.Object), Times.Once); } private void GivenTheQosProviderFactoryReturns() { _qosProviderFactory .Setup(x => x.Get(It.IsAny())) .Returns(_qosProvider.Object); } private void TheQosProviderFactoryIsCalledCorrectly() { _qosProviderFactory .Verify(x => x.Get(It.IsAny()), Times.Once); } private void ThenTheQosProviderHouseIsCalledCorrectly() { _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); } private void ThenTheRequestIdKeyCreatorIsCalledCorrectly() { _requestIdKeyCreator .Verify(x => x.Create(_fileConfiguration.ReRoutes[0], _fileConfiguration.GlobalConfiguration), Times.Once); } private void GivenTheRequestIdCreatorReturns(string requestId) { _requestIdKeyCreator .Setup(x => x.Create(It.IsAny(), It.IsAny())) .Returns(requestId); } private void GivenTheQosOptionsCreatorReturns(QoSOptions qosOptions) { _qosOptionsCreator .Setup(x => x.Create(_fileConfiguration.ReRoutes[0])) .Returns(qosOptions); } private void ThenTheQosOptionsAre(QoSOptions qosOptions) { _config.Data.ReRoutes[0].QosOptionsOptions.DurationOfBreak.ShouldBe(qosOptions.DurationOfBreak); _config.Data.ReRoutes[0].QosOptionsOptions.ExceptionsAllowedBeforeBreaking.ShouldBe(qosOptions.ExceptionsAllowedBeforeBreaking); _config.Data.ReRoutes[0].QosOptionsOptions.TimeoutValue.ShouldBe(qosOptions.TimeoutValue); } } }