// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Constraints; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; using Xunit; namespace Microsoft.AspNetCore.Routing.Tests { public class DefaultInlineConstraintResolverTest { private IInlineConstraintResolver _constraintResolver; public DefaultInlineConstraintResolverTest() { var routeOptions = new RouteOptions(); _constraintResolver = GetInlineConstraintResolver(routeOptions); } [Fact] public void ResolveConstraint_RequiredConstraint_ResolvesCorrectly() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("required"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_IntConstraint_ResolvesCorrectly() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("int"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_IntConstraintWithArgument_Throws() { // Arrange, Act & Assert var ex = Assert.Throws( () => _constraintResolver.ResolveConstraint("int(5)")); Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" + " with the following number of parameters: 1.", ex.Message); } [Fact] public void ResolveConstraint_AlphaConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("alpha"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_RegexInlineConstraint_WithAComma_PassesAsASingleArgument() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("regex(ab,1)"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_RegexInlineConstraint_WithCurlyBraces_Balanced() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint( @"regex(\\b(?\\d{1,2})/(?\\d{1,2})/(?\\d{2,4})\\b)"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_BoolConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("bool"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_CompositeConstraintIsNotRegistered() { // Arrange, Act & Assert Assert.Null(_constraintResolver.ResolveConstraint("composite")); } [Fact] public void ResolveConstraint_DateTimeConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("datetime"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_DecimalConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("decimal"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_DoubleConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("double"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_FloatConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("float"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_GuidConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("guid"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_IntConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("int"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_LengthConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("length(5)"); // Assert Assert.IsType(constraint); Assert.Equal(5, ((LengthRouteConstraint)constraint).MinLength); Assert.Equal(5, ((LengthRouteConstraint)constraint).MaxLength); } [Fact] public void ResolveConstraint_LengthRangeConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("length(5, 10)"); // Assert var lengthConstraint = Assert.IsType(constraint); Assert.Equal(5, lengthConstraint.MinLength); Assert.Equal(10, lengthConstraint.MaxLength); } [Fact] public void ResolveConstraint_LongRangeConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("long"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_MaxConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("max(10)"); // Assert Assert.IsType(constraint); Assert.Equal(10, ((MaxRouteConstraint)constraint).Max); } [Fact] public void ResolveConstraint_MaxLengthConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("maxlength(10)"); // Assert Assert.IsType(constraint); Assert.Equal(10, ((MaxLengthRouteConstraint)constraint).MaxLength); } [Fact] public void ResolveConstraint_MinConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("min(3)"); // Assert Assert.IsType(constraint); Assert.Equal(3, ((MinRouteConstraint)constraint).Min); } [Fact] public void ResolveConstraint_MinLengthConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("minlength(3)"); // Assert Assert.IsType(constraint); Assert.Equal(3, ((MinLengthRouteConstraint)constraint).MinLength); } [Fact] public void ResolveConstraint_RangeConstraint() { // Arrange & Act var constraint = _constraintResolver.ResolveConstraint("range(5, 10)"); // Assert Assert.IsType(constraint); var rangeConstraint = (RangeRouteConstraint)constraint; Assert.Equal(5, rangeConstraint.Min); Assert.Equal(10, rangeConstraint.Max); } [Fact] public void ResolveConstraint_SupportsCustomConstraints() { // Arrange var routeOptions = new RouteOptions(); routeOptions.ConstraintMap.Add("custom", typeof(CustomRouteConstraint)); var resolver = GetInlineConstraintResolver(routeOptions); // Act var constraint = resolver.ResolveConstraint("custom(argument)"); // Assert Assert.IsType(constraint); } [Fact] public void ResolveConstraint_CustomConstraintThatDoesNotImplementIRouteConstraint_Throws() { // Arrange var routeOptions = new RouteOptions(); routeOptions.ConstraintMap.Add("custom", typeof(string)); var resolver = GetInlineConstraintResolver(routeOptions); // Act & Assert var ex = Assert.Throws(() => resolver.ResolveConstraint("custom")); Assert.Equal("The constraint type 'System.String' which is mapped to constraint key 'custom'" + " must implement the 'IRouteConstraint' interface.", ex.Message); } [Fact] public void ResolveConstraint_AmbiguousConstructors_Throws() { // Arrange var routeOptions = new RouteOptions(); routeOptions.ConstraintMap.Add("custom", typeof(MultiConstructorRouteConstraint)); var resolver = GetInlineConstraintResolver(routeOptions); // Act & Assert var ex = Assert.Throws(() => resolver.ResolveConstraint("custom(5,6)")); Assert.Equal("The constructor to use for activating the constraint type 'MultiConstructorRouteConstraint' is ambiguous." + " Multiple constructors were found with the following number of parameters: 2.", ex.Message); } // These are cases which parsing does not catch and we'll end up here [Theory] [InlineData("regex(abc")] [InlineData("int/")] [InlineData("in{t")] public void ResolveConstraint_Invalid_Throws(string constraint) { // Arrange var routeOptions = new RouteOptions(); var resolver = GetInlineConstraintResolver(routeOptions); // Act & Assert Assert.Null(resolver.ResolveConstraint(constraint)); } [Fact] public void ResolveConstraint_NoMatchingConstructor_Throws() { // Arrange // Act & Assert var ex = Assert.Throws(() => _constraintResolver.ResolveConstraint("int(5,6)")); Assert.Equal("Could not find a constructor for constraint type 'IntRouteConstraint'" + " with the following number of parameters: 2.", ex.Message); } private IInlineConstraintResolver GetInlineConstraintResolver(RouteOptions routeOptions) { var optionsAccessor = new Mock>(); optionsAccessor.SetupGet(o => o.Value).Returns(routeOptions); return new DefaultInlineConstraintResolver(optionsAccessor.Object); } private class MultiConstructorRouteConstraint : IRouteConstraint { public MultiConstructorRouteConstraint(string pattern, int intArg) { } public MultiConstructorRouteConstraint(int intArg, string pattern) { } public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { return true; } } private class CustomRouteConstraint : IRouteConstraint { public CustomRouteConstraint(string pattern) { Pattern = pattern; } public string Pattern { get; private set; } public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { return true; } } } }