Adding Tests for InlineRouting Support.

This commit is contained in:
harshgMSFT 2014-05-22 14:43:24 -07:00
parent 2e96ba5862
commit 5dc8267920
9 changed files with 537 additions and 14 deletions

View File

@ -1,8 +1,7 @@
using System.Text.RegularExpressions;
using Microsoft.AspNet;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Constraints;
namespace RoutingSample.Web
{

View File

@ -24,6 +24,7 @@
<Compile Include="BuilderExtensions.cs" />
<Compile Include="Constraints\CompositeRouteConstraint.cs" />
<Compile Include="Constraints\IntRouteConstraint.cs" />
<Compile Include="Constraints\RegexConstraint.cs" />
<Compile Include="DefaultInlineConstraintResolver.cs" />
<Compile Include="IInlineConstraintResolver.cs" />
<Compile Include="INamedRouter.cs" />
@ -33,7 +34,6 @@
<Compile Include="IRouter.cs" />
<Compile Include="NotNullAttribute.cs" />
<Compile Include="Properties\Resources.Designer.cs" />
<Compile Include="RegexConstraint.cs" />
<Compile Include="RouteCollection.cs" />
<Compile Include="RouteCollectionExtensions.cs" />
<Compile Include="RouteConstraintBuilder.cs" />
@ -52,4 +52,4 @@
<Compile Include="VirtualPathContext.cs" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
</Project>

View File

@ -196,10 +196,6 @@ namespace Microsoft.AspNet.Routing.Template
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
return false;
}
// A workaround is to add it as a separate entry in the defaults argument.
context.Error = Resources.TemplateRoute_OptionalCannotHaveDefaultValue;
return false;
}
var parameterName = templatePart.Name;
if (IsValidParameterName(context, parameterName))

View File

@ -43,13 +43,13 @@ namespace Microsoft.AspNet.Routing.Template
_routeTemplate = routeTemplate ?? string.Empty;
Name = routeName;
_defaults = defaults ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
_constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate) ??
_constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate) ??
new Dictionary<string, IRouteConstraint>();
// The parser will throw for invalid routes.
_parsedTemplate = TemplateParser.Parse(RouteTemplate, inlineConstraintResolver);
UpdateInlineDefaultValuesAndConstraints();
_matcher = new TemplateMatcher(_parsedTemplate);
_binder = new TemplateBinder(_parsedTemplate, _defaults);
}
@ -184,14 +184,13 @@ namespace Microsoft.AspNet.Routing.Template
IRouteConstraint constraint;
if (_constraints.TryGetValue(parameter.Name, out constraint))
{
_constraints[parameter.Name] =
new CompositeRouteConstraint(new []{ constraint, parameter.InlineConstraint });
_constraints[parameter.Name] =
new CompositeRouteConstraint(new[] { constraint, parameter.InlineConstraint });
}
else
{
_constraints[parameter.Name] = parameter.InlineConstraint;
}
}
}
if (parameter.DefaultValue != null)
@ -210,4 +209,4 @@ namespace Microsoft.AspNet.Routing.Template
}
}
}
}
}

View File

@ -0,0 +1,81 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Http;
using Microsoft.AspNet.Routing.Constraints;
using Xunit;
namespace Microsoft.AspNet.Routing.Tests
{
public class DefaultInlineConstraintResolverTest
{
[Fact]
public void ResolveConstraint_IntConstraint_ResolvesCorrectly()
{
// Arrange & Act
var constraint = new DefaultInlineConstraintResolver().ResolveConstraint("int");
// Assert
Assert.IsType<IntRouteConstraint>(constraint);
}
[Fact]
public void ResolveConstraint_IntConstraintWithArgument_Throws()
{
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(
() => new DefaultInlineConstraintResolver().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_SupportsCustomConstraints()
{
// Arrange
var resolver = new DefaultInlineConstraintResolver();
resolver.ConstraintMap.Add("custom", typeof(CustomRouteConstraint));
// Act
var constraint = resolver.ResolveConstraint("custom(argument)");
// Assert
Assert.IsType<CustomRouteConstraint>(constraint);
}
[Fact]
public void ResolveConstraint_CustomConstraintThatDoesNotImplementIRouteConstraint_Throws()
{
// Arrange
var resolver = new DefaultInlineConstraintResolver();
resolver.ConstraintMap.Add("custom", typeof(string));
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => resolver.ResolveConstraint("custom"));
Assert.Equal("The constraint type 'System.String' which is mapped to constraint key 'custom'"+
" must implement the 'IRouteConstraint' interface.",
ex.Message);
}
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,
IDictionary<string, object> values,
RouteDirection routeDirection)
{
return true;
}
}
}
}

View File

@ -0,0 +1,304 @@
// Copyright (c) Microsoft Open Technologies, Inc. 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 System.Linq;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Constraints;
using Microsoft.AspNet.Routing.Template;
using Xunit;
namespace Microsoft.AspNet.Routing.Tests
{
public class InlineRouteParameterParserTests
{
[Fact]
public void ParseRouteParameter_ConstraintAndDefault_ParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter("param:int=111111");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.Equal("111111", templatePart.DefaultValue);
Assert.IsType<IntRouteConstraint>(templatePart.InlineConstraint);
}
[Fact]
public void ParseRouteParameter_ConstraintWithArgumentsAndDefault_ParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(\d+)=111111");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.Equal("111111", templatePart.DefaultValue);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@"\d+", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Fact]
public void ParseRouteParameter_ConstraintAndOptional_ParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:int?");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.True(templatePart.IsOptional);
Assert.IsType<IntRouteConstraint>(templatePart.InlineConstraint);
}
[Fact]
public void ParseRouteParameter_ConstraintWithArgumentsAndOptional_ParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(\d+)?");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.True(templatePart.IsOptional);
Assert.Equal(@"\d+", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Fact]
public void ParseRouteParameter_ChainedConstraints_ParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(\d+):test(\w+)");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.IsType<CompositeRouteConstraint>(templatePart.InlineConstraint);
var constraint = (CompositeRouteConstraint)templatePart.InlineConstraint;
Assert.Equal(@"\d+", ((TestRouteConstraint)constraint.Constraints.ElementAt(0)).Pattern);
Assert.Equal(@"\w+", ((TestRouteConstraint)constraint.Constraints.ElementAt(1)).Pattern);
}
[Fact]
public void ParseRouteTemplate_ConstraintsDefaultsAndOptionalsInMultipleSections_ParsedCorrectly()
{
// Arrange & Act
var template = ParseRouteTemplate(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}");
// Assert
var parameters = template.Parameters.ToArray();
var param1 = parameters[0];
Assert.Equal("p1", param1.Name);
Assert.Equal("hello", param1.DefaultValue);
Assert.False(param1.IsOptional);
Assert.IsType<CompositeRouteConstraint>(param1.InlineConstraint);
var constraint = (CompositeRouteConstraint)param1.InlineConstraint;
Assert.IsType<IntRouteConstraint>(constraint.Constraints.ElementAt(0));
Assert.IsType<TestRouteConstraint>(constraint.Constraints.ElementAt(1));
var param2 = parameters[1];
Assert.Equal("p2", param2.Name);
Assert.Equal("abc", param2.DefaultValue);
Assert.False(param2.IsOptional);
var param3 = parameters[2];
Assert.Equal("p3", param3.Name);
Assert.True(param3.IsOptional);
}
[Fact]
public void ParseRouteParameter_NoTokens_ParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter("world");
// Assert
Assert.Equal("world", templatePart.Name);
}
[Fact]
public void ParseRouteParameter_ParamDefault_ParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter("param=world");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.Equal("world", templatePart.DefaultValue);
}
[Fact]
public void ParseRouteParameter_ConstraintWithClosingBraceInPattern_ClosingBraceIsParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(\})");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@"\}", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Fact]
public void ParseRouteParameter_ConstraintWithClosingParenInPattern_ClosingParenIsParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(\))");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@"\)", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Fact]
public void ParseRouteParameter_ConstraintWithColonInPattern_ColonIsParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(:)");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@":", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Fact]
public void ParseRouteParameter_ConstraintWithCommaInPattern_PatternIsParsedCorrectly()
{
// Arrange
var templatePart = ParseParameter(@"param:test(\w,\w)");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@"\w,\w", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Theory]
[InlineData(",")]
[InlineData("(")]
[InlineData(")")]
[InlineData("}")]
[InlineData("{")]
public void ParseRouteParameter_MisplacedSpecialCharacterInParameter_Throws(string character)
{
// Arrange
var unresolvedConstraint = character + @"test(\w,\w)";
var parameter = "param:" + unresolvedConstraint;
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => ParseParameter(parameter));
Assert.Equal(@"The inline constraint resolver of type 'DefaultInlineConstraintResolver'"+
" was unable to resolve the following inline constraint: '"+ unresolvedConstraint + "'.",
ex.Message);
}
[Fact]
public void ParseRouteParameter_ConstraintWithEqualsSignInPattern_PatternIsParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(=)");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.Null(templatePart.DefaultValue);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@"=", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Fact]
public void ParseRouteParameter_ConstraintWithOpenBraceInPattern_PatternIsParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(\{)");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@"\{", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Fact]
public void ParseRouteParameter_ConstraintWithOpenParenInPattern_PatternIsParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(\()");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@"\(", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Fact]
public void ParseRouteParameter_ConstraintWithQuestionMarkInPattern_PatternIsParsedCorrectly()
{
// Arrange & Act
var templatePart = ParseParameter(@"param:test(\?)");
// Assert
Assert.Equal("param", templatePart.Name);
Assert.Null(templatePart.DefaultValue);
Assert.False(templatePart.IsOptional);
Assert.IsType<TestRouteConstraint>(templatePart.InlineConstraint);
Assert.Equal(@"\?", ((TestRouteConstraint)templatePart.InlineConstraint).Pattern);
}
[Theory]
[InlineData("", "")]
[InlineData("?", "")]
[InlineData("*", "")]
[InlineData(" ", " ")]
[InlineData("\t", "\t")]
[InlineData("#!@#$%Q@#@%", "#!@#$%Q@#@%")]
[InlineData(",,,", ",,,")]
public void ParseRouteParameter_ParameterWithoutInlineConstraint_ReturnsTemplatePartWithEmptyInlineValues(
string parameter,
string expectedParameterName)
{
// Arrange & Act
var templatePart = ParseParameter(parameter);
// Assert
Assert.Equal(expectedParameterName, templatePart.Name);
Assert.Null(templatePart.InlineConstraint);
Assert.Null(templatePart.DefaultValue);
}
private TemplatePart ParseParameter(string routeParameter)
{
var constraintResolver = new DefaultInlineConstraintResolver();
// TODO: This will be removed once this is supported in product code.
constraintResolver.ConstraintMap.Add("test", typeof(TestRouteConstraint));
var templatePart = InlineRouteParameterParser.ParseRouteParameter(routeParameter, constraintResolver);
return templatePart;
}
private static Template.Template ParseRouteTemplate(string template)
{
var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("test", typeof(TestRouteConstraint));
return TemplateParser.Parse(template, constraintResolver);
}
private class TestRouteConstraint : IRouteConstraint
{
public TestRouteConstraint(string pattern)
{
Pattern = pattern;
}
public string Pattern { get; private set; }
public bool Match(HttpContext httpContext,
IRouter route,
string routeKey,
IDictionary<string, object> values,
RouteDirection routeDirection)
{
throw new NotImplementedException();
}
}
}
}

View File

@ -22,8 +22,11 @@
<ItemGroup>
<Compile Include="ConstraintMatcherTests.cs" />
<Compile Include="ConstraintsBuilderTests.cs" />
<Compile Include="DefaultInlineConstraintResolverTest.cs" />
<Compile Include="RegexConstraintTests.cs" />
<Compile Include="RouteCollectionTest.cs" />
<Compile Include="InlineRouteParameterParserTests.cs" />
<Compile Include="RouteConstraintsTests.cs" />
<Compile Include="RouteValueDictionaryTests.cs" />
<Compile Include="Template\TemplateBinderTests.cs" />
<Compile Include="Template\TemplateMatcherTests.cs" />

View File

@ -0,0 +1,97 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if NET45
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Constraints;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Routing.Tests
{
public class RouteConstraintsTests
{
[Theory]
[InlineData(42, true)]
[InlineData("42", true)]
[InlineData(3.14, false)]
[InlineData("43.567", false)]
[InlineData("42a", false)]
public void IntRouteConstraint_Match_AppliesConstraint(object parameterValue, bool expected)
{
// Arrange
var constraint = new IntRouteConstraint();
// Act
var actual = TestValue(constraint, parameterValue);
// Assert
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(true, true, true)]
[InlineData(true, false, false)]
[InlineData(false, true, false)]
[InlineData(false, false, false)]
public void CompoundRouteConstraint_Match_CallsMatchOnInnerConstraints(bool inner1Result,
bool inner2Result,
bool expected)
{
// Arrange
var inner1 = MockConstraintWithResult(inner1Result);
var inner2 = MockConstraintWithResult(inner2Result);
// Act
var constraint = new CompositeRouteConstraint(new[] { inner1.Object, inner2.Object });
var actual = TestValue(constraint, null);
// Assert
Assert.Equal(expected, actual);
}
static Expression<Func<IRouteConstraint, bool>> ConstraintMatchMethodExpression =
c => c.Match(It.IsAny<HttpContext>(),
It.IsAny<IRouter>(),
It.IsAny<string>(),
It.IsAny<Dictionary<string, object>>(),
It.IsAny<RouteDirection>());
private static Mock<IRouteConstraint> MockConstraintWithResult(bool result)
{
var mock = new Mock<IRouteConstraint>();
mock.Setup(ConstraintMatchMethodExpression)
.Returns(result)
.Verifiable();
return mock;
}
private static void AssertMatchWasCalled(Mock<IRouteConstraint> mock, Times times)
{
mock.Verify(ConstraintMatchMethodExpression, times);
}
private static bool TestValue(IRouteConstraint constraint, object value, Action<IRouter> routeConfig = null)
{
var context = new Mock<HttpContext>();
IRouter route = new RouteCollection();
if (routeConfig != null)
{
routeConfig(route);
}
var parameterName = "fake";
var values = new Dictionary<string, object>() { { parameterName, value } };
var routeDirection = RouteDirection.IncomingRequest;
return constraint.Match(context.Object, route, parameterName, values, routeDirection);
}
}
}
#endif

View File

@ -4,6 +4,7 @@
#if NET45
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing.Constraints;
@ -425,6 +426,49 @@ namespace Microsoft.AspNet.Routing.Template.Tests
Assert.Equal(mockConstraint, constraints["action"]);
}
[Fact]
public void RegisteringRouteWithOneInlineConstraintAndOneUsingConstraintArgument()
{
// Arrange
var collection = new RouteCollection();
collection.DefaultHandler = new Mock<IRouter>().Object;
collection.InlineConstraintResolver = new DefaultInlineConstraintResolver();
collection.MapRoute("mockName",
"{controller}/{action}/{id:int}",
defaults: null,
constraints: new { id = "1*" });
var constraints = ((TemplateRoute)collection[0]).Constraints;
// Assert
Assert.Equal(1, constraints.Count);
var constraint = (CompositeRouteConstraint)constraints["id"];
Assert.IsType<CompositeRouteConstraint>(constraint);
Assert.IsType<RegexConstraint>(constraint.Constraints.ElementAt(0));
Assert.IsType<IntRouteConstraint>(constraint.Constraints.ElementAt(1));
}
[Fact]
public void RegisteringRoute_WithOneInlineConstraint_AddsItToConstraintCollection()
{
// Arrange
var collection = new RouteCollection();
collection.DefaultHandler = new Mock<IRouter>().Object;
collection.InlineConstraintResolver = new DefaultInlineConstraintResolver();
collection.MapRoute("mockName",
"{controller}/{action}/{id:int}",
defaults: null,
constraints: null);
var constraints = ((TemplateRoute)collection[0]).Constraints;
// Assert
Assert.Equal(1, constraints.Count);
Assert.IsType<IntRouteConstraint>(constraints["id"]);
}
[Fact]
public void RegisteringRouteWithRouteName_WithNullDefaults_AddsTheRoute()
{