namespace Ocelot.UnitTests.Configuration.Validation { using FluentValidation.Results; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Configuration.File; using Ocelot.Configuration.Validator; using Ocelot.Requester; using Shouldly; using System; using System.Collections.Generic; using System.Threading.Tasks; using TestStack.BDDfy; using Xunit; public class RouteFluentValidatorTests { private readonly RouteFluentValidator _validator; private readonly Mock _authProvider; private QosDelegatingHandlerDelegate _qosDelegatingHandler; private Mock _serviceProvider; private FileRoute _route; private ValidationResult _result; public RouteFluentValidatorTests() { _authProvider = new Mock(); _serviceProvider = new Mock(); // Todo - replace with mocks _validator = new RouteFluentValidator(_authProvider.Object, new HostAndPortValidator(), new FileQoSOptionsFluentValidator(_serviceProvider.Object)); } [Fact] public void downstream_path_template_should_not_be_empty() { var fileRoute = new FileRoute(); this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("Downstream Path Template cannot be empty")) .BDDfy(); } [Fact] public void upstream_path_template_should_not_be_empty() { var fileRoute = new FileRoute { DownstreamPathTemplate = "test" }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("Upstream Path Template cannot be empty")) .BDDfy(); } [Fact] public void downstream_path_template_should_start_with_forward_slash() { var fileRoute = new FileRoute { DownstreamPathTemplate = "test" }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("Downstream Path Template test doesnt start with forward slash")) .BDDfy(); } [Fact] public void downstream_path_template_should_not_contain_double_forward_slash() { var fileRoute = new FileRoute { DownstreamPathTemplate = "//test" }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("Downstream Path Template //test contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) .BDDfy(); } [Theory] [InlineData("https://test")] [InlineData("http://test")] [InlineData("/test/http://")] [InlineData("/test/https://")] public void downstream_path_template_should_not_contain_scheme(string downstreamPathTemplate) { var fileRoute = new FileRoute { DownstreamPathTemplate = downstreamPathTemplate }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains($"Downstream Path Template {downstreamPathTemplate} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) .BDDfy(); } [Fact] public void upstream_path_template_should_start_with_forward_slash() { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "test" }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("Upstream Path Template test doesnt start with forward slash")) .BDDfy(); } [Fact] public void upstream_path_template_should_not_contain_double_forward_slash() { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "//test" }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("Upstream Path Template //test contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) .BDDfy(); } [Theory] [InlineData("https://test")] [InlineData("http://test")] [InlineData("/test/http://")] [InlineData("/test/https://")] public void upstream_path_template_should_not_contain_scheme(string upstreamPathTemplate) { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = upstreamPathTemplate }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains($"Upstream Path Template {upstreamPathTemplate} contains double forward slash, Ocelot does not support this at the moment. Please raise an issue in GitHib if you need this feature.")) .BDDfy(); } [Fact] public void should_not_be_valid_if_enable_rate_limiting_true_and_period_is_empty() { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", RateLimitOptions = new FileRateLimitRule { EnableRateLimiting = true } }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("RateLimitOptions.Period is empty")) .BDDfy(); } [Fact] public void should_not_be_valid_if_enable_rate_limiting_true_and_period_has_value() { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", RateLimitOptions = new FileRateLimitRule { EnableRateLimiting = true, Period = "test" } }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("RateLimitOptions.Period does not contain integer then s (second), m (minute), h (hour), d (day) e.g. 1m for 1 minute period")) .BDDfy(); } [Fact] public void should_not_be_valid_if_specified_authentication_provider_isnt_registered() { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", AuthenticationOptions = new FileAuthenticationOptions { AuthenticationProviderKey = "JwtLads" } }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains($"Authentication Options AuthenticationProviderKey:JwtLads,AllowedScopes:[] is unsupported authentication provider")) .BDDfy(); } [Fact] public void should_not_be_valid_if_not_using_service_discovery_and_no_host_and_ports() { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("When not using service discovery DownstreamHostAndPorts must be set and not empty or Ocelot cannot find your service!")) .BDDfy(); } [Fact] public void should_be_valid_if_using_service_discovery_and_no_host_and_ports() { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", ServiceName = "Lads" }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsValid()) .BDDfy(); } [Fact] public void should_be_valid_re_route_using_host_and_port_and_paths() { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", DownstreamHostAndPorts = new List { new FileHostAndPort { Host = "localhost", Port = 5000 } } }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsValid()) .BDDfy(); } [Fact] public void should_be_valid_if_specified_authentication_provider_is_registered() { const string key = "JwtLads"; var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", AuthenticationOptions = new FileAuthenticationOptions { AuthenticationProviderKey = key }, DownstreamHostAndPorts = new List { new FileHostAndPort { Host = "localhost", Port = 5000 } } }; this.Given(_ => GivenThe(fileRoute)) .And(_ => GivenAnAuthProvider(key)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsValid()) .BDDfy(); } [Theory] [InlineData("1.0")] [InlineData("1.1")] [InlineData("2.0")] [InlineData("1,0")] [InlineData("1,1")] [InlineData("2,0")] [InlineData("1")] [InlineData("2")] [InlineData("")] [InlineData(null)] public void should_be_valid_re_route_using_downstream_http_version(string version) { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", DownstreamHostAndPorts = new List { new FileHostAndPort { Host = "localhost", Port = 5000, }, }, DownstreamHttpVersion = version, }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsValid()) .BDDfy(); } [Theory] [InlineData("retg1.1")] [InlineData("re2.0")] [InlineData("1,0a")] [InlineData("a1,1")] [InlineData("12,0")] [InlineData("asdf")] public void should_be_invalid_re_route_using_downstream_http_version(string version) { var fileRoute = new FileRoute { DownstreamPathTemplate = "/test", UpstreamPathTemplate = "/test", DownstreamHostAndPorts = new List { new FileHostAndPort { Host = "localhost", Port = 5000, }, }, DownstreamHttpVersion = version, }; this.Given(_ => GivenThe(fileRoute)) .When(_ => WhenIValidate()) .Then(_ => ThenTheResultIsInvalid()) .And(_ => ThenTheErrorsContains("'Downstream Http Version' is not in the correct format.")) .BDDfy(); } private void GivenAnAuthProvider(string key) { var schemes = new List { new AuthenticationScheme(key, key, typeof(FakeAutheHandler)) }; _authProvider .Setup(x => x.GetAllSchemesAsync()) .ReturnsAsync(schemes); } private void ThenTheResultIsValid() { _result.IsValid.ShouldBeTrue(); } private void GivenThe(FileRoute route) { _route = route; } private void WhenIValidate() { _result = _validator.Validate(_route); } private void ThenTheResultIsInvalid() { _result.IsValid.ShouldBeFalse(); } private void ThenTheErrorsContains(string expected) { _result.Errors.ShouldContain(x => x.ErrorMessage == expected); } private class FakeAutheHandler : IAuthenticationHandler { public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) { throw new System.NotImplementedException(); } public Task AuthenticateAsync() { throw new System.NotImplementedException(); } public Task ChallengeAsync(AuthenticationProperties properties) { throw new System.NotImplementedException(); } public Task ForbidAsync(AuthenticationProperties properties) { throw new System.NotImplementedException(); } } } }