diff options
Diffstat (limited to 'src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs')
-rw-r--r-- | src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs | 1535 |
1 files changed, 767 insertions, 768 deletions
diff --git a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs index bdd1850671..bf9af68b30 100644 --- a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs +++ b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs @@ -14,1033 +14,1032 @@ using Microsoft.Extensions.Logging.Testing; using Moq; using Xunit; -namespace Microsoft.AspNetCore.Routing.Matching +namespace Microsoft.AspNetCore.Routing.Matching; + +// Many of these are integration tests that exercise the system end to end, +// so we're reusing the services here. +public class DfaMatcherTest { - // Many of these are integration tests that exercise the system end to end, - // so we're reusing the services here. - public class DfaMatcherTest + private RouteEndpoint CreateEndpoint(string template, int order, object defaults = null, object requiredValues = null, object policies = null) { - private RouteEndpoint CreateEndpoint(string template, int order, object defaults = null, object requiredValues = null, object policies = null) - { - return EndpointFactory.CreateRouteEndpoint(template, defaults, policies, requiredValues, order, displayName: template); - } - - private DataSourceDependentMatcher CreateDfaMatcher( - EndpointDataSource dataSource, - MatcherPolicy[] policies = null, - EndpointSelector endpointSelector = null, - ILoggerFactory loggerFactory = null) - { - var serviceCollection = new ServiceCollection() - .AddLogging() - .AddOptions() - .AddRouting(options => - { - options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); - }); - - if (policies != null) - { - for (var i = 0; i < policies.Length; i++) - { - serviceCollection.AddSingleton<MatcherPolicy>(policies[i]); - } - } + return EndpointFactory.CreateRouteEndpoint(template, defaults, policies, requiredValues, order, displayName: template); + } - if (endpointSelector != null) + private DataSourceDependentMatcher CreateDfaMatcher( + EndpointDataSource dataSource, + MatcherPolicy[] policies = null, + EndpointSelector endpointSelector = null, + ILoggerFactory loggerFactory = null) + { + var serviceCollection = new ServiceCollection() + .AddLogging() + .AddOptions() + .AddRouting(options => { - serviceCollection.AddSingleton<EndpointSelector>(endpointSelector); - } + options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); + }); - if (loggerFactory != null) + if (policies != null) + { + for (var i = 0; i < policies.Length; i++) { - serviceCollection.AddSingleton<ILoggerFactory>(loggerFactory); + serviceCollection.AddSingleton<MatcherPolicy>(policies[i]); } + } - var services = serviceCollection.BuildServiceProvider(); - - var factory = services.GetRequiredService<MatcherFactory>(); - return Assert.IsType<DataSourceDependentMatcher>(factory.CreateMatcher(dataSource)); + if (endpointSelector != null) + { + serviceCollection.AddSingleton<EndpointSelector>(endpointSelector); } - [Fact] - public async Task MatchAsync_ValidRouteConstraint_EndpointMatched() + if (loggerFactory != null) { - // Arrange - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + serviceCollection.AddSingleton<ILoggerFactory>(loggerFactory); + } + + var services = serviceCollection.BuildServiceProvider(); + + var factory = services.GetRequiredService<MatcherFactory>(); + return Assert.IsType<DataSourceDependentMatcher>(factory.CreateMatcher(dataSource)); + } + + [Fact] + public async Task MatchAsync_ValidRouteConstraint_EndpointMatched() + { + // Arrange + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/{p:int}", 0) }); - var matcher = CreateDfaMatcher(endpointDataSource); + var matcher = CreateDfaMatcher(endpointDataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = "/1"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/1"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.NotNull(httpContext.GetEndpoint()); - } + // Assert + Assert.NotNull(httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_InvalidRouteConstraint_NoEndpointMatched() - { - // Arrange - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_InvalidRouteConstraint_NoEndpointMatched() + { + // Arrange + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/{p:int}", 0) }); - var matcher = CreateDfaMatcher(endpointDataSource); + var matcher = CreateDfaMatcher(endpointDataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = "/One"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/One"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Null(httpContext.GetEndpoint()); - } + // Assert + Assert.Null(httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_RequireValuesAndDefaultValues_EndpointMatched() - { - // Arrange - var endpoint = CreateEndpoint( - "{controller=Home}/{action=Index}/{id?}", - 0, - requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null }); + [Fact] + public async Task MatchAsync_RequireValuesAndDefaultValues_EndpointMatched() + { + // Arrange + var endpoint = CreateEndpoint( + "{controller=Home}/{action=Index}/{id?}", + 0, + requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null }); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint }); - var matcher = CreateDfaMatcher(dataSource); + var matcher = CreateDfaMatcher(dataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = "/"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Same(endpoint, httpContext.GetEndpoint()); + // Assert + Assert.Same(endpoint, httpContext.GetEndpoint()); - Assert.Collection( - httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key), - (kvp) => - { - Assert.Equal("action", kvp.Key); - Assert.Equal("Index", kvp.Value); - }, - (kvp) => - { - Assert.Equal("controller", kvp.Key); - Assert.Equal("Home", kvp.Value); - }); - } + Assert.Collection( + httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key), + (kvp) => + { + Assert.Equal("action", kvp.Key); + Assert.Equal("Index", kvp.Value); + }, + (kvp) => + { + Assert.Equal("controller", kvp.Key); + Assert.Equal("Home", kvp.Value); + }); + } - [Fact] - public async Task MatchAsync_RequireValuesAndDifferentPath_NoEndpointMatched() - { - // Arrange - var endpoint = CreateEndpoint( - "{controller}/{action}", - 0, - requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null }); + [Fact] + public async Task MatchAsync_RequireValuesAndDifferentPath_NoEndpointMatched() + { + // Arrange + var endpoint = CreateEndpoint( + "{controller}/{action}", + 0, + requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null }); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint }); - var matcher = CreateDfaMatcher(dataSource); + var matcher = CreateDfaMatcher(dataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = "/Login/Index"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/Login/Index"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Null(httpContext.GetEndpoint()); - } + // Assert + Assert.Null(httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_RequireValuesAndOptionalParameter_EndpointMatched() - { - // Arrange - var endpoint = CreateEndpoint( - "{controller}/{action}/{id?}", - 0, - requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null }); + [Fact] + public async Task MatchAsync_RequireValuesAndOptionalParameter_EndpointMatched() + { + // Arrange + var endpoint = CreateEndpoint( + "{controller}/{action}/{id?}", + 0, + requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null }); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint }); - var matcher = CreateDfaMatcher(dataSource); - - var httpContext = CreateContext(); - httpContext.Request.Path = "/Home/Index/123"; - - // Act - await matcher.MatchAsync(httpContext); - - // Assert - Assert.Same(endpoint, httpContext.GetEndpoint()); - - Assert.Collection( - httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key), - (kvp) => - { - Assert.Equal("action", kvp.Key); - Assert.Equal("Index", kvp.Value); - }, - (kvp) => - { - Assert.Equal("controller", kvp.Key); - Assert.Equal("Home", kvp.Value); - }, - (kvp) => - { - Assert.Equal("id", kvp.Key); - Assert.Equal("123", kvp.Value); - }); - } + var matcher = CreateDfaMatcher(dataSource); - [Theory] - [InlineData("/")] - [InlineData("/TestController")] - [InlineData("/TestController/TestAction")] - [InlineData("/TestController/TestAction/17")] - [InlineData("/TestController/TestAction/17/catchAll")] - public async Task MatchAsync_ShortenedPattern_EndpointMatched(string path) - { - // Arrange - var endpoint = CreateEndpoint( - "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}", - 0, - requiredValues: new { controller = "TestController", action = "TestAction", area = (string)null, page = (string)null }); + var httpContext = CreateContext(); + httpContext.Request.Path = "/Home/Index/123"; + + // Act + await matcher.MatchAsync(httpContext); + + // Assert + Assert.Same(endpoint, httpContext.GetEndpoint()); + + Assert.Collection( + httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key), + (kvp) => + { + Assert.Equal("action", kvp.Key); + Assert.Equal("Index", kvp.Value); + }, + (kvp) => + { + Assert.Equal("controller", kvp.Key); + Assert.Equal("Home", kvp.Value); + }, + (kvp) => + { + Assert.Equal("id", kvp.Key); + Assert.Equal("123", kvp.Value); + }); + } - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Theory] + [InlineData("/")] + [InlineData("/TestController")] + [InlineData("/TestController/TestAction")] + [InlineData("/TestController/TestAction/17")] + [InlineData("/TestController/TestAction/17/catchAll")] + public async Task MatchAsync_ShortenedPattern_EndpointMatched(string path) + { + // Arrange + var endpoint = CreateEndpoint( + "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}", + 0, + requiredValues: new { controller = "TestController", action = "TestAction", area = (string)null, page = (string)null }); + + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint }); - var matcher = CreateDfaMatcher(dataSource); + var matcher = CreateDfaMatcher(dataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = path; + var httpContext = CreateContext(); + httpContext.Request.Path = path; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Same(endpoint, httpContext.GetEndpoint()); + // Assert + Assert.Same(endpoint, httpContext.GetEndpoint()); - Assert.Equal("TestAction", httpContext.Request.RouteValues["action"]); - Assert.Equal("TestController", httpContext.Request.RouteValues["controller"]); - Assert.Equal("17", httpContext.Request.RouteValues["id"]); - } + Assert.Equal("TestAction", httpContext.Request.RouteValues["action"]); + Assert.Equal("TestController", httpContext.Request.RouteValues["controller"]); + Assert.Equal("17", httpContext.Request.RouteValues["id"]); + } - [Fact] - public async Task MatchAsync_MultipleEndpointsWithDifferentRequiredValues_EndpointMatched() - { - // Arrange - var endpoint1 = CreateEndpoint( - "{controller}/{action}/{id?}", - 0, - requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null }); - var endpoint2 = CreateEndpoint( - "{controller}/{action}/{id?}", - 0, - requiredValues: new { controller = "Login", action = "Index", area = (string)null, page = (string)null }); - - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_MultipleEndpointsWithDifferentRequiredValues_EndpointMatched() + { + // Arrange + var endpoint1 = CreateEndpoint( + "{controller}/{action}/{id?}", + 0, + requiredValues: new { controller = "Home", action = "Index", area = (string)null, page = (string)null }); + var endpoint2 = CreateEndpoint( + "{controller}/{action}/{id?}", + 0, + requiredValues: new { controller = "Login", action = "Index", area = (string)null, page = (string)null }); + + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = CreateDfaMatcher(dataSource); + var matcher = CreateDfaMatcher(dataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = "/Home/Index/123"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/Home/Index/123"; - // Act 1 - await matcher.MatchAsync(httpContext); + // Act 1 + await matcher.MatchAsync(httpContext); - // Assert 1 - Assert.Same(endpoint1, httpContext.GetEndpoint()); + // Assert 1 + Assert.Same(endpoint1, httpContext.GetEndpoint()); - httpContext.Request.Path = "/Login/Index/123"; + httpContext.Request.Path = "/Login/Index/123"; - // Act 2 - await matcher.MatchAsync(httpContext); + // Act 2 + await matcher.MatchAsync(httpContext); - // Assert 2 - Assert.Same(endpoint2, httpContext.GetEndpoint()); - } + // Assert 2 + Assert.Same(endpoint2, httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_ParameterTransformer_EndpointMatched() - { - // Arrange - var endpoint = CreateEndpoint( - "ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}", - 0, - requiredValues: new { controller = "ConventionalTransformer", action = "Index", area = (string)null, page = (string)null }); + [Fact] + public async Task MatchAsync_ParameterTransformer_EndpointMatched() + { + // Arrange + var endpoint = CreateEndpoint( + "ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}", + 0, + requiredValues: new { controller = "ConventionalTransformer", action = "Index", area = (string)null, page = (string)null }); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint }); - var matcher = CreateDfaMatcher(dataSource); + var matcher = CreateDfaMatcher(dataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = "/ConventionalTransformerRoute/conventional-transformer/Index"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/ConventionalTransformerRoute/conventional-transformer/Index"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Same(endpoint, httpContext.GetEndpoint()); + // Assert + Assert.Same(endpoint, httpContext.GetEndpoint()); - Assert.Collection( - httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key), - (kvp) => - { - Assert.Equal("action", kvp.Key); - Assert.Equal("Index", kvp.Value); - }, - (kvp) => - { - Assert.Equal("controller", kvp.Key); - Assert.Equal("ConventionalTransformer", kvp.Value); - }); - } + Assert.Collection( + httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key), + (kvp) => + { + Assert.Equal("action", kvp.Key); + Assert.Equal("Index", kvp.Value); + }, + (kvp) => + { + Assert.Equal("controller", kvp.Key); + Assert.Equal("ConventionalTransformer", kvp.Value); + }); + } - [Fact] - public void MatchAsync_ConstrainedParameter_EndpointMatched() - { - // Arrange - var endpoint1 = CreateEndpoint("a/c", 0); - var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0); + [Fact] + public void MatchAsync_ConstrainedParameter_EndpointMatched() + { + // Arrange + var endpoint1 = CreateEndpoint("a/c", 0); + var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; - var buffer = new PathSegment[3]; - var (context, path, count) = CreateMatchingContext("/aa/b/c", buffer); + var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; + var buffer = new PathSegment[3]; + var (context, path, count) = CreateMatchingContext("/aa/b/c", buffer); - // Act - var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); + // Act + var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); - // Assert - // We expect endpoint2 to match here since we trimmed the branch for the parameter based on `a` not meeting - // the constraints. - var candidate = Assert.Single(set.candidates); - Assert.Same(endpoint2, candidate.Endpoint); - } + // Assert + // We expect endpoint2 to match here since we trimmed the branch for the parameter based on `a` not meeting + // the constraints. + var candidate = Assert.Single(set.candidates); + Assert.Same(endpoint2, candidate.Endpoint); + } - [Fact] - public void MatchAsync_ConstrainedParameter_EndpointNotMatched() - { - // Arrange - var endpoint1 = CreateEndpoint("a/c", 0); - var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0); + [Fact] + public void MatchAsync_ConstrainedParameter_EndpointNotMatched() + { + // Arrange + var endpoint1 = CreateEndpoint("a/c", 0); + var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; - var buffer = new PathSegment[3]; - var (context, path, count) = CreateMatchingContext("/a/b/c", buffer); + var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; + var buffer = new PathSegment[3]; + var (context, path, count) = CreateMatchingContext("/a/b/c", buffer); - // Act - var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); + // Act + var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); - // Assert - // We expect no candidates here, since the path on the tree (a -> b -> c = ({param:length(2)}/b/c)) for not meeting the length(2) constraint. - Assert.Empty(set.candidates); - } + // Assert + // We expect no candidates here, since the path on the tree (a -> b -> c = ({param:length(2)}/b/c)) for not meeting the length(2) constraint. + Assert.Empty(set.candidates); + } - [Fact] - public void MatchAsync_ConstrainedParameter_EndpointMatched_WhenExplicitRouteExists() - { - // Arrange - // Note that there is now an explicit branch created by the first endpoint, however endpoint 2 will - // be filtered out of the candidates list because it didn't meet the constraint. - var endpoint1 = CreateEndpoint("a/b/c", 0); - var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0); + [Fact] + public void MatchAsync_ConstrainedParameter_EndpointMatched_WhenExplicitRouteExists() + { + // Arrange + // Note that there is now an explicit branch created by the first endpoint, however endpoint 2 will + // be filtered out of the candidates list because it didn't meet the constraint. + var endpoint1 = CreateEndpoint("a/b/c", 0); + var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; - var buffer = new PathSegment[3]; - var (context, path, count) = CreateMatchingContext("/a/b/c", buffer); + var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; + var buffer = new PathSegment[3]; + var (context, path, count) = CreateMatchingContext("/a/b/c", buffer); - // Act - var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); + // Act + var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); - // Assert - // We expect only one candidate, since the path on the tree (a -> b -> c = ({param:length(2)}/b/c)) does not meet the length(2) constraint. - var candidate = Assert.Single(set.candidates); - Assert.Same(endpoint1, candidate.Endpoint); - } + // Assert + // We expect only one candidate, since the path on the tree (a -> b -> c = ({param:length(2)}/b/c)) does not meet the length(2) constraint. + var candidate = Assert.Single(set.candidates); + Assert.Same(endpoint1, candidate.Endpoint); + } - [Fact] - public void MatchAsync_ConstrainedParameter_EndpointMatchedWithTwoCandidates_WhenLiteralMeetsConstraint() - { - // Arrange - // Note that the literal now meets the constraint, so there will be an explicit branch and two candidates - var endpoint1 = CreateEndpoint("aa/b/c", 0); - var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0); - var endpoints = new List<Endpoint> + [Fact] + public void MatchAsync_ConstrainedParameter_EndpointMatchedWithTwoCandidates_WhenLiteralMeetsConstraint() + { + // Arrange + // Note that the literal now meets the constraint, so there will be an explicit branch and two candidates + var endpoint1 = CreateEndpoint("aa/b/c", 0); + var endpoint2 = CreateEndpoint("{param:length(2)}/b/c", 0); + var endpoints = new List<Endpoint> { endpoint2, endpoint1, }; - var dataSource = new DefaultEndpointDataSource(endpoints); + var dataSource = new DefaultEndpointDataSource(endpoints); - var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; - var buffer = new PathSegment[3]; - var (context, path, count) = CreateMatchingContext("/aa/b/c", buffer); + var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; + var buffer = new PathSegment[3]; + var (context, path, count) = CreateMatchingContext("/aa/b/c", buffer); - // Act - var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); + // Act + var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); - // Assert - // We expect 2 candidates, since the path on the tree (aa -> b -> c = ({param:length(2)}/b/c)) meets the length(2) constraint. - Assert.Equal(endpoints.ToArray(), set.candidates.Select(e => e.Endpoint).OrderBy(e => ((RouteEndpoint)e).RoutePattern.RawText).ToArray()); - } + // Assert + // We expect 2 candidates, since the path on the tree (aa -> b -> c = ({param:length(2)}/b/c)) meets the length(2) constraint. + Assert.Equal(endpoints.ToArray(), set.candidates.Select(e => e.Endpoint).OrderBy(e => ((RouteEndpoint)e).RoutePattern.RawText).ToArray()); + } - [Fact] - public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatched() - { - // Arrange - var endpoint1 = CreateEndpoint("a/b/c", 0); - var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0); + [Fact] + public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatched() + { + // Arrange + var endpoint1 = CreateEndpoint("a/b/c", 0); + var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; - var buffer = new PathSegment[3]; - var (context, path, count) = CreateMatchingContext("/a/bb/c", buffer); + var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; + var buffer = new PathSegment[3]; + var (context, path, count) = CreateMatchingContext("/a/bb/c", buffer); - // Act - var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); + // Act + var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); - // Assert - // We expect endpoint2 to match here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint. - var candidate = Assert.Single(set.candidates); - Assert.Same(endpoint2, candidate.Endpoint); - } + // Assert + // We expect endpoint2 to match here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint. + var candidate = Assert.Single(set.candidates); + Assert.Same(endpoint2, candidate.Endpoint); + } - [Fact] - public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointNotMatched() - { - // Arrange - var endpoint1 = CreateEndpoint("a/b/d", 0); - var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0); + [Fact] + public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointNotMatched() + { + // Arrange + var endpoint1 = CreateEndpoint("a/b/d", 0); + var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; - var buffer = new PathSegment[3]; - var (context, path, count) = CreateMatchingContext("/a/b/c", buffer); + var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; + var buffer = new PathSegment[3]; + var (context, path, count) = CreateMatchingContext("/a/b/c", buffer); - // Act - var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); + // Act + var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); - // Assert - // We expect no candidates here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint. - Assert.Empty(set.candidates); - } + // Assert + // We expect no candidates here since we trimmed the branch (a -> b -> c = (a/{param:length(2)}/c)) for the parameter based on `b` not meeting the length(2) constraint. + Assert.Empty(set.candidates); + } - [Fact] - public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatched_WhenExplicitRouteExists() - { - // Arrange - // Note that there is now an explicit branch created by the first endpoint, however endpoint 2 will - // be filtered out of the candidates list because it didn't meet the constraint. - var endpoint1 = CreateEndpoint("a/b/c", 0); - var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0); + [Fact] + public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatched_WhenExplicitRouteExists() + { + // Arrange + // Note that there is now an explicit branch created by the first endpoint, however endpoint 2 will + // be filtered out of the candidates list because it didn't meet the constraint. + var endpoint1 = CreateEndpoint("a/b/c", 0); + var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; - var buffer = new PathSegment[3]; - var (context, path, count) = CreateMatchingContext("/a/b/c", buffer); + var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; + var buffer = new PathSegment[3]; + var (context, path, count) = CreateMatchingContext("/a/b/c", buffer); - // Act - var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); + // Act + var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); - // Assert - // We expect only one candidate, since the path on the tree (a -> b -> c = (a/{param:length(2)}/c)) does not meet the length(2) constraint. - var candidate = Assert.Single(set.candidates); - Assert.Same(endpoint1, candidate.Endpoint); - } + // Assert + // We expect only one candidate, since the path on the tree (a -> b -> c = (a/{param:length(2)}/c)) does not meet the length(2) constraint. + var candidate = Assert.Single(set.candidates); + Assert.Same(endpoint1, candidate.Endpoint); + } - [Fact] - public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatchedWithTwoCandidates_WhenLiteralMeetsConstraint() - { - // Arrange - // Note that the literal now meets the constraint, so there will be an explicit branch and two candidates - var endpoint1 = CreateEndpoint("a/bb/c", 0); - var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0); - var endpoints = new List<Endpoint> + [Fact] + public void MatchAsync_ConstrainedParameter_MiddleSegment_EndpointMatchedWithTwoCandidates_WhenLiteralMeetsConstraint() + { + // Arrange + // Note that the literal now meets the constraint, so there will be an explicit branch and two candidates + var endpoint1 = CreateEndpoint("a/bb/c", 0); + var endpoint2 = CreateEndpoint("a/{param:length(2)}/c", 0); + var endpoints = new List<Endpoint> { endpoint2, endpoint1, }; - var dataSource = new DefaultEndpointDataSource(endpoints); + var dataSource = new DefaultEndpointDataSource(endpoints); - var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; - var buffer = new PathSegment[3]; - var (context, path, count) = CreateMatchingContext("/a/bb/c", buffer); + var matcher = (DfaMatcher)CreateDfaMatcher(dataSource).CurrentMatcher; + var buffer = new PathSegment[3]; + var (context, path, count) = CreateMatchingContext("/a/bb/c", buffer); - // Act - var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); + // Act + var set = matcher.FindCandidateSet(context, path, buffer.AsSpan().Slice(0, count)); - // Assert - // We expect 2 candidates, since the path on the tree (aa -> b -> c = ({param:length(2)}/b/c)) meets the length(2) constraint. - Assert.Equal(endpoints.ToArray(), set.candidates.Select(e => e.Endpoint).OrderBy(e => ((RouteEndpoint)e).RoutePattern.RawText).ToArray()); - } + // Assert + // We expect 2 candidates, since the path on the tree (aa -> b -> c = ({param:length(2)}/b/c)) meets the length(2) constraint. + Assert.Equal(endpoints.ToArray(), set.candidates.Select(e => e.Endpoint).OrderBy(e => ((RouteEndpoint)e).RoutePattern.RawText).ToArray()); + } - private (HttpContext context, string path, int count) CreateMatchingContext(string requestPath, PathSegment[] buffer) - { - var context = CreateContext(); - context.Request.Path = requestPath; + private (HttpContext context, string path, int count) CreateMatchingContext(string requestPath, PathSegment[] buffer) + { + var context = CreateContext(); + context.Request.Path = requestPath; - // First tokenize the path into series of segments. - var count = FastPathTokenizer.Tokenize(requestPath, buffer); - return (context, requestPath, count); - } + // First tokenize the path into series of segments. + var count = FastPathTokenizer.Tokenize(requestPath, buffer); + return (context, requestPath, count); + } - [Fact] - public async Task MatchAsync_DifferentDefaultCase_RouteValueUsesDefaultCase() - { - // Arrange - var endpoint = CreateEndpoint( - "{controller}/{action=TESTACTION}/{id?}", - 0, - requiredValues: new { controller = "TestController", action = "TestAction" }); + [Fact] + public async Task MatchAsync_DifferentDefaultCase_RouteValueUsesDefaultCase() + { + // Arrange + var endpoint = CreateEndpoint( + "{controller}/{action=TESTACTION}/{id?}", + 0, + requiredValues: new { controller = "TestController", action = "TestAction" }); - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint }); - var matcher = CreateDfaMatcher(dataSource); + var matcher = CreateDfaMatcher(dataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = "/TestController"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/TestController"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Same(endpoint, httpContext.GetEndpoint()); + // Assert + Assert.Same(endpoint, httpContext.GetEndpoint()); - Assert.Collection( - httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key), - (kvp) => - { - Assert.Equal("action", kvp.Key); - Assert.Equal("TESTACTION", kvp.Value); - }, - (kvp) => - { - Assert.Equal("controller", kvp.Key); - Assert.Equal("TestController", kvp.Value); - }); - } + Assert.Collection( + httpContext.Request.RouteValues.OrderBy(kvp => kvp.Key), + (kvp) => + { + Assert.Equal("action", kvp.Key); + Assert.Equal("TESTACTION", kvp.Value); + }, + (kvp) => + { + Assert.Equal("controller", kvp.Key); + Assert.Equal("TestController", kvp.Value); + }); + } - [Fact] - public async Task MatchAsync_DuplicateTemplatesAndDifferentOrder_LowerOrderEndpointMatched() - { - // Arrange - var higherOrderEndpoint = CreateEndpoint("/Teams", 1); - var lowerOrderEndpoint = CreateEndpoint("/Teams", 0); + [Fact] + public async Task MatchAsync_DuplicateTemplatesAndDifferentOrder_LowerOrderEndpointMatched() + { + // Arrange + var higherOrderEndpoint = CreateEndpoint("/Teams", 1); + var lowerOrderEndpoint = CreateEndpoint("/Teams", 0); - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { higherOrderEndpoint, lowerOrderEndpoint }); - var matcher = CreateDfaMatcher(endpointDataSource); + var matcher = CreateDfaMatcher(endpointDataSource); - var httpContext = CreateContext(); - httpContext.Request.Path = "/Teams"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/Teams"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Equal(lowerOrderEndpoint, httpContext.GetEndpoint()); - } + // Assert + Assert.Equal(lowerOrderEndpoint, httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled() - { - // Arrange - var endpoint1 = CreateEndpoint("/Teams", 0); - var endpoint2 = CreateEndpoint("/Teams", 1); - - var endpointSelector = new Mock<EndpointSelector>(); - endpointSelector - .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) - .Callback<HttpContext, CandidateSet>((c, cs) => - { - Assert.Equal(2, cs.Count); - - Assert.Same(endpoint1, cs[0].Endpoint); - Assert.True(cs.IsValidCandidate(0)); - Assert.Equal(0, cs[0].Score); - Assert.Null(cs[0].Values); - - Assert.Same(endpoint2, cs[1].Endpoint); - Assert.True(cs.IsValidCandidate(1)); - Assert.Equal(1, cs[1].Score); - Assert.Null(cs[1].Values); - - c.SetEndpoint(endpoint2); - }) - .Returns(Task.CompletedTask); - - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled() + { + // Arrange + var endpoint1 = CreateEndpoint("/Teams", 0); + var endpoint2 = CreateEndpoint("/Teams", 1); + + var endpointSelector = new Mock<EndpointSelector>(); + endpointSelector + .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) + .Callback<HttpContext, CandidateSet>((c, cs) => + { + Assert.Equal(2, cs.Count); + + Assert.Same(endpoint1, cs[0].Endpoint); + Assert.True(cs.IsValidCandidate(0)); + Assert.Equal(0, cs[0].Score); + Assert.Null(cs[0].Values); + + Assert.Same(endpoint2, cs[1].Endpoint); + Assert.True(cs.IsValidCandidate(1)); + Assert.Equal(1, cs[1].Score); + Assert.Null(cs[1].Values); + + c.SetEndpoint(endpoint2); + }) + .Returns(Task.CompletedTask); + + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object); + var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object); - var httpContext = CreateContext(); - httpContext.Request.Path = "/Teams"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/Teams"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Equal(endpoint2, httpContext.GetEndpoint()); - } + // Assert + Assert.Equal(endpoint2, httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled_AllocatesDictionaryForRouteParameter() - { - // Arrange - var endpoint1 = CreateEndpoint("/Teams/{x?}", 0); - var endpoint2 = CreateEndpoint("/Teams/{x?}", 1); - - var endpointSelector = new Mock<EndpointSelector>(); - endpointSelector - .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) - .Callback<HttpContext, CandidateSet>((c, cs) => - { - Assert.Equal(2, cs.Count); - - Assert.Same(endpoint1, cs[0].Endpoint); - Assert.True(cs.IsValidCandidate(0)); - Assert.Equal(0, cs[0].Score); - Assert.Empty(cs[0].Values); - - Assert.Same(endpoint2, cs[1].Endpoint); - Assert.True(cs.IsValidCandidate(1)); - Assert.Equal(1, cs[1].Score); - Assert.Empty(cs[1].Values); - - c.SetEndpoint(endpoint2); - }) - .Returns(Task.CompletedTask); - - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled_AllocatesDictionaryForRouteParameter() + { + // Arrange + var endpoint1 = CreateEndpoint("/Teams/{x?}", 0); + var endpoint2 = CreateEndpoint("/Teams/{x?}", 1); + + var endpointSelector = new Mock<EndpointSelector>(); + endpointSelector + .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) + .Callback<HttpContext, CandidateSet>((c, cs) => + { + Assert.Equal(2, cs.Count); + + Assert.Same(endpoint1, cs[0].Endpoint); + Assert.True(cs.IsValidCandidate(0)); + Assert.Equal(0, cs[0].Score); + Assert.Empty(cs[0].Values); + + Assert.Same(endpoint2, cs[1].Endpoint); + Assert.True(cs.IsValidCandidate(1)); + Assert.Equal(1, cs[1].Score); + Assert.Empty(cs[1].Values); + + c.SetEndpoint(endpoint2); + }) + .Returns(Task.CompletedTask); + + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object); + var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object); - var httpContext = CreateContext(); - httpContext.Request.Path = "/Teams"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/Teams"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Equal(endpoint2, httpContext.GetEndpoint()); - } + // Assert + Assert.Equal(endpoint2, httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled_AllocatesDictionaryForRouteConstraint() - { - // Arrange - var constraint = new OptionalRouteConstraint(new IntRouteConstraint()); - var endpoint1 = CreateEndpoint("/Teams", 0, policies: new { x = constraint, }); - var endpoint2 = CreateEndpoint("/Teams", 1, policies: new { x = constraint, }); - - var endpointSelector = new Mock<EndpointSelector>(); - endpointSelector - .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) - .Callback<HttpContext, CandidateSet>((c, cs) => - { - Assert.Equal(2, cs.Count); - - Assert.Same(endpoint1, cs[0].Endpoint); - Assert.True(cs.IsValidCandidate(0)); - Assert.Equal(0, cs[0].Score); - Assert.Empty(cs[0].Values); - - Assert.Same(endpoint2, cs[1].Endpoint); - Assert.True(cs.IsValidCandidate(1)); - Assert.Equal(1, cs[1].Score); - Assert.Empty(cs[1].Values); - - c.SetEndpoint(endpoint2); - }) - .Returns(Task.CompletedTask); - - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_MultipleMatches_EndpointSelectorCalled_AllocatesDictionaryForRouteConstraint() + { + // Arrange + var constraint = new OptionalRouteConstraint(new IntRouteConstraint()); + var endpoint1 = CreateEndpoint("/Teams", 0, policies: new { x = constraint, }); + var endpoint2 = CreateEndpoint("/Teams", 1, policies: new { x = constraint, }); + + var endpointSelector = new Mock<EndpointSelector>(); + endpointSelector + .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) + .Callback<HttpContext, CandidateSet>((c, cs) => + { + Assert.Equal(2, cs.Count); + + Assert.Same(endpoint1, cs[0].Endpoint); + Assert.True(cs.IsValidCandidate(0)); + Assert.Equal(0, cs[0].Score); + Assert.Empty(cs[0].Values); + + Assert.Same(endpoint2, cs[1].Endpoint); + Assert.True(cs.IsValidCandidate(1)); + Assert.Equal(1, cs[1].Score); + Assert.Empty(cs[1].Values); + + c.SetEndpoint(endpoint2); + }) + .Returns(Task.CompletedTask); + + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { endpoint1, endpoint2 }); - var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object); + var matcher = CreateDfaMatcher(endpointDataSource, endpointSelector: endpointSelector.Object); - var httpContext = CreateContext(); - httpContext.Request.Path = "/Teams"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/Teams"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Equal(endpoint2, httpContext.GetEndpoint()); - } + // Assert + Assert.Equal(endpoint2, httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_NoCandidates_Logging() - { - // Arrange - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_NoCandidates_Logging() + { + // Arrange + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/{p:int}", 0) }); - var sink = new TestSink(); - var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true)); + var sink = new TestSink(); + var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true)); - var httpContext = CreateContext(); - httpContext.Request.Path = "/"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Null(httpContext.GetEndpoint()); + // Assert + Assert.Null(httpContext.GetEndpoint()); - Assert.Collection( - sink.Writes, - (log) => - { - Assert.Equal(1000, log.EventId); - Assert.Equal("No candidates found for the request path '/'", log.Message); - }); - } + Assert.Collection( + sink.Writes, + (log) => + { + Assert.Equal(1000, log.EventId); + Assert.Equal("No candidates found for the request path '/'", log.Message); + }); + } - [Fact] - public async Task MatchAsync_ConstraintRejectsEndpoint_Logging() - { - // Arrange - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_ConstraintRejectsEndpoint_Logging() + { + // Arrange + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/{p:int}", 0) }); - var sink = new TestSink(); - var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true)); - - var httpContext = CreateContext(); - httpContext.Request.Path = "/One"; - - // Act - await matcher.MatchAsync(httpContext); - - // Assert - Assert.Null(httpContext.GetEndpoint()); - - Assert.Collection( - sink.Writes, - (log) => - { - Assert.Equal(1001, log.EventId); - Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1003, log.EventId); - Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1004, log.EventId); - Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message); - }); - } + var sink = new TestSink(); + var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true)); - [Fact] - public async Task MatchAsync_ComplexSegmentRejectsEndpoint_Logging() - { - // Arrange - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + var httpContext = CreateContext(); + httpContext.Request.Path = "/One"; + + // Act + await matcher.MatchAsync(httpContext); + + // Assert + Assert.Null(httpContext.GetEndpoint()); + + Assert.Collection( + sink.Writes, + (log) => + { + Assert.Equal(1001, log.EventId); + Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1003, log.EventId); + Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1004, log.EventId); + Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message); + }); + } + + [Fact] + public async Task MatchAsync_ComplexSegmentRejectsEndpoint_Logging() + { + // Arrange + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/x-{id}-y", 0) }); - var sink = new TestSink(); - var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true)); - - var httpContext = CreateContext(); - httpContext.Request.Path = "/One"; - - // Act - await matcher.MatchAsync(httpContext); - - // Assert - Assert.Null(httpContext.GetEndpoint()); - - Assert.Collection( - sink.Writes, - (log) => - { - Assert.Equal(1001, log.EventId); - Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1002, log.EventId); - Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1004, log.EventId); - Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message); - }); - } + var sink = new TestSink(); + var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true)); - [Fact] - public async Task MatchAsync_MultipleCandidates_Logging() - { - // Arrange - var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> + var httpContext = CreateContext(); + httpContext.Request.Path = "/One"; + + // Act + await matcher.MatchAsync(httpContext); + + // Assert + Assert.Null(httpContext.GetEndpoint()); + + Assert.Collection( + sink.Writes, + (log) => + { + Assert.Equal(1001, log.EventId); + Assert.Equal("1 candidate(s) found for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1002, log.EventId); + Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1004, log.EventId); + Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message); + }); + } + + [Fact] + public async Task MatchAsync_MultipleCandidates_Logging() + { + // Arrange + var endpointDataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/{one}", 0), CreateEndpoint("/{p:int}", 1), CreateEndpoint("/x-{id}-y", 2), }); - var sink = new TestSink(); - var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true)); - - var httpContext = CreateContext(); - httpContext.Request.Path = "/One"; - - // Act - await matcher.MatchAsync(httpContext); - - // Assert - Assert.Same(endpointDataSource.Endpoints[0], httpContext.GetEndpoint()); - - Assert.Collection( - sink.Writes, - (log) => - { - Assert.Equal(1001, log.EventId); - Assert.Equal("3 candidate(s) found for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1005, log.EventId); - Assert.Equal("Endpoint '/{one}' with route pattern '/{one}' is valid for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1003, log.EventId); - Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1004, log.EventId); - Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1002, log.EventId); - Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message); - }, - (log) => - { - Assert.Equal(1004, log.EventId); - Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message); - }); - } + var sink = new TestSink(); + var matcher = CreateDfaMatcher(endpointDataSource, loggerFactory: new TestLoggerFactory(sink, enabled: true)); - [Fact] - public async Task MatchAsync_RunsApplicableEndpointSelectorPolicies() - { - // Arrange - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + var httpContext = CreateContext(); + httpContext.Request.Path = "/One"; + + // Act + await matcher.MatchAsync(httpContext); + + // Assert + Assert.Same(endpointDataSource.Endpoints[0], httpContext.GetEndpoint()); + + Assert.Collection( + sink.Writes, + (log) => + { + Assert.Equal(1001, log.EventId); + Assert.Equal("3 candidate(s) found for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1005, log.EventId); + Assert.Equal("Endpoint '/{one}' with route pattern '/{one}' is valid for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1003, log.EventId); + Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' was rejected by constraint 'p':'Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint' with value 'One' for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1004, log.EventId); + Assert.Equal("Endpoint '/{p:int}' with route pattern '/{p:int}' is not valid for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1002, log.EventId); + Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' was rejected by complex segment 'x-{id}-y' for the request path '/One'", log.Message); + }, + (log) => + { + Assert.Equal(1004, log.EventId); + Assert.Equal("Endpoint '/x-{id}-y' with route pattern '/x-{id}-y' is not valid for the request path '/One'", log.Message); + }); + } + + [Fact] + public async Task MatchAsync_RunsApplicableEndpointSelectorPolicies() + { + // Arrange + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/test/{id:alpha}", 0), CreateEndpoint("/test/{id:int}", 0), CreateEndpoint("/test/{id}", 0), }); - var policy = new Mock<MatcherPolicy>(); - policy - .As<IEndpointSelectorPolicy>() - .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true); - policy - .As<IEndpointSelectorPolicy>() - .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) - .Returns<HttpContext, CandidateSet>((c, cs) => - { - cs.SetValidity(1, false); - return Task.CompletedTask; - }); + var policy = new Mock<MatcherPolicy>(); + policy + .As<IEndpointSelectorPolicy>() + .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true); + policy + .As<IEndpointSelectorPolicy>() + .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) + .Returns<HttpContext, CandidateSet>((c, cs) => + { + cs.SetValidity(1, false); + return Task.CompletedTask; + }); - var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, }); + var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, }); - var httpContext = CreateContext(); - httpContext.Request.Path = "/test/17"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/test/17"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Same(dataSource.Endpoints[2], httpContext.GetEndpoint()); - } + // Assert + Assert.Same(dataSource.Endpoints[2], httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_SkipsNonApplicableEndpointSelectorPolicies() - { - // Arrange - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_SkipsNonApplicableEndpointSelectorPolicies() + { + // Arrange + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/test/{id:alpha}", 0), CreateEndpoint("/test/{id:int}", 0), CreateEndpoint("/test/{id}", 0), }); - var policy = new Mock<MatcherPolicy>(); - policy - .As<IEndpointSelectorPolicy>() - .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(false); - policy - .As<IEndpointSelectorPolicy>() - .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) - .Returns<HttpContext, CandidateSet>((c, cs) => - { - throw null; // Won't be called. + var policy = new Mock<MatcherPolicy>(); + policy + .As<IEndpointSelectorPolicy>() + .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(false); + policy + .As<IEndpointSelectorPolicy>() + .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) + .Returns<HttpContext, CandidateSet>((c, cs) => + { + throw null; // Won't be called. }); - var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, }); + var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy.Object, }); - var httpContext = CreateContext(); - httpContext.Request.Path = "/test/17"; + var httpContext = CreateContext(); + httpContext.Request.Path = "/test/17"; - // Act - await matcher.MatchAsync(httpContext); + // Act + await matcher.MatchAsync(httpContext); - // Assert - Assert.Same(dataSource.Endpoints[1], httpContext.GetEndpoint()); - } + // Assert + Assert.Same(dataSource.Endpoints[1], httpContext.GetEndpoint()); + } - [Fact] - public async Task MatchAsync_RunsEndpointSelectorPolicies_CanShortCircuit() - { - // Arrange - var dataSource = new DefaultEndpointDataSource(new List<Endpoint> + [Fact] + public async Task MatchAsync_RunsEndpointSelectorPolicies_CanShortCircuit() + { + // Arrange + var dataSource = new DefaultEndpointDataSource(new List<Endpoint> { CreateEndpoint("/test/{id:alpha}", 0), CreateEndpoint("/test/{id:int}", 0), CreateEndpoint("/test/{id}", 0), }); - var policy1 = new Mock<MatcherPolicy>(); - policy1 - .As<IEndpointSelectorPolicy>() - .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true); - policy1 - .As<IEndpointSelectorPolicy>() - .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) - .Returns<HttpContext, CandidateSet>((c, cs) => - { - c.SetEndpoint(cs[0].Endpoint); - return Task.CompletedTask; - }); + var policy1 = new Mock<MatcherPolicy>(); + policy1 + .As<IEndpointSelectorPolicy>() + .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true); + policy1 + .As<IEndpointSelectorPolicy>() + .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) + .Returns<HttpContext, CandidateSet>((c, cs) => + { + c.SetEndpoint(cs[0].Endpoint); + return Task.CompletedTask; + }); - // This should never run, it's after policy1 which short circuits - var policy2 = new Mock<MatcherPolicy>(); - policy2 - .SetupGet(p => p.Order) - .Returns(1000); - policy2 - .As<IEndpointSelectorPolicy>() - .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true); - policy2 - .As<IEndpointSelectorPolicy>() - .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) - .Throws(new InvalidOperationException()); - - var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy1.Object, policy2.Object, }); - - var httpContext = CreateContext(); - httpContext.Request.Path = "/test/17"; - - // Act - await matcher.MatchAsync(httpContext); - - // Assert - Assert.Same(dataSource.Endpoints[0], httpContext.GetEndpoint()); - } + // This should never run, it's after policy1 which short circuits + var policy2 = new Mock<MatcherPolicy>(); + policy2 + .SetupGet(p => p.Order) + .Returns(1000); + policy2 + .As<IEndpointSelectorPolicy>() + .Setup(p => p.AppliesToEndpoints(It.IsAny<IReadOnlyList<Endpoint>>())).Returns(true); + policy2 + .As<IEndpointSelectorPolicy>() + .Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>())) + .Throws(new InvalidOperationException()); + + var matcher = CreateDfaMatcher(dataSource, policies: new[] { policy1.Object, policy2.Object, }); + + var httpContext = CreateContext(); + httpContext.Request.Path = "/test/17"; + + // Act + await matcher.MatchAsync(httpContext); + + // Assert + Assert.Same(dataSource.Endpoints[0], httpContext.GetEndpoint()); + } - private HttpContext CreateContext() - { - return new DefaultHttpContext(); - } + private HttpContext CreateContext() + { + return new DefaultHttpContext(); } } |