315 lines
10 KiB
C#
315 lines
10 KiB
C#
// 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 System.Linq;
|
|
using Microsoft.AspNetCore.Blazor.Routing;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Blazor.Test.Routing
|
|
{
|
|
public class RouteTableTests
|
|
{
|
|
[Fact]
|
|
public void CanMatchRootTemplate()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/").Build();
|
|
var context = new RouteContext("/");
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanMatchLiteralTemplate()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/literal").Build();
|
|
var context = new RouteContext("/literal/");
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanMatchTemplateWithMultipleLiterals()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/some/awesome/route/").Build();
|
|
var context = new RouteContext("/some/awesome/route");
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
}
|
|
|
|
[Fact]
|
|
public void RouteMatchingIsCaseInsensitive()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/some/AWESOME/route/").Build();
|
|
var context = new RouteContext("/Some/awesome/RouTe");
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
}
|
|
|
|
[Fact]
|
|
public void DoesNotMatchIfSegmentsDontMatch()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/some/AWESOME/route/").Build();
|
|
var context = new RouteContext("/some/brilliant/route");
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.Null(context.Handler);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("/{value:bool}", "/maybe")]
|
|
[InlineData("/{value:datetime}", "/1955-01-32")]
|
|
[InlineData("/{value:decimal}", "/hello")]
|
|
[InlineData("/{value:double}", "/0.1.2")]
|
|
[InlineData("/{value:float}", "/0.1.2")]
|
|
[InlineData("/{value:guid}", "/not-a-guid")]
|
|
[InlineData("/{value:int}", "/3.141")]
|
|
[InlineData("/{value:long}", "/3.141")]
|
|
public void DoesNotMatchIfConstraintDoesNotMatch(string template, string contextUrl)
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute(template).Build();
|
|
var context = new RouteContext(contextUrl);
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.Null(context.Handler);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("/some")]
|
|
[InlineData("/some/awesome/route/with/extra/segments")]
|
|
public void DoesNotMatchIfDifferentNumberOfSegments(string path)
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/some/awesome/route/").Build();
|
|
var context = new RouteContext(path);
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.Null(context.Handler);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("/value1", "value1")]
|
|
[InlineData("/value2/", "value2")]
|
|
public void CanMatchParameterTemplate(string path, string expectedValue)
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/{parameter}").Build();
|
|
var context = new RouteContext(path);
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
Assert.Single(context.Parameters, p => p.Key == "parameter" && (string)p.Value == expectedValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanMatchTemplateWithMultipleParameters()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/{some}/awesome/{route}/").Build();
|
|
var context = new RouteContext("/an/awesome/path");
|
|
|
|
var expectedParameters = new Dictionary<string, object>
|
|
{
|
|
["some"] = "an",
|
|
["route"] = "path"
|
|
};
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
Assert.Equal(expectedParameters, context.Parameters);
|
|
}
|
|
|
|
public static IEnumerable<object[]> CanMatchParameterWithConstraintCases() => new object[][]
|
|
{
|
|
new object[] { "/{value:bool}", "/true", true },
|
|
new object[] { "/{value:bool}", "/false", false },
|
|
new object[] { "/{value:datetime}", "/1955-01-30", new DateTime(1955, 1, 30) },
|
|
new object[] { "/{value:decimal}", "/5.3", 5.3m },
|
|
new object[] { "/{value:double}", "/0.1", 0.1d },
|
|
new object[] { "/{value:float}", "/0.1", 0.1f },
|
|
new object[] { "/{value:guid}", "/1FCEF085-884F-416E-B0A1-71B15F3E206B", Guid.Parse("1FCEF085-884F-416E-B0A1-71B15F3E206B") },
|
|
new object[] { "/{value:int}", "/123", 123 },
|
|
new object[] { "/{value:long}", "/9223372036854775807", long.MaxValue },
|
|
};
|
|
|
|
[Theory]
|
|
[MemberData(nameof(CanMatchParameterWithConstraintCases))]
|
|
public void CanMatchParameterWithConstraint(string template, string contextUrl, object convertedValue)
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute(template).Build();
|
|
var context = new RouteContext(contextUrl);
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
if (context.Handler == null)
|
|
{
|
|
// Make it easier to track down failing tests when using MemberData
|
|
throw new InvalidOperationException($"Failed to match template '{template}'.");
|
|
}
|
|
Assert.Equal(context.Parameters, new Dictionary<string, object>
|
|
{
|
|
{ "value", convertedValue }
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void CanMatchSegmentWithMultipleConstraints()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder().AddRoute("/{value:double:int}/").Build();
|
|
var context = new RouteContext("/15");
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
Assert.Equal(context.Parameters, new Dictionary<string, object>
|
|
{
|
|
{ "value", 15 } // Final constraint's convertedValue is used
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void PrefersLiteralTemplateOverTemplateWithParameters()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder()
|
|
.AddRoute("/an/awesome/path")
|
|
.AddRoute("/{some}/awesome/{route}/").Build();
|
|
var context = new RouteContext("/an/awesome/path");
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
Assert.Null(context.Parameters);
|
|
}
|
|
|
|
[Fact]
|
|
public void PrefersShorterRoutesOverLongerRoutes()
|
|
{
|
|
// Arrange & Act
|
|
var handler = typeof(int);
|
|
var routeTable = new TestRouteTableBuilder()
|
|
.AddRoute("/an/awesome/path")
|
|
.AddRoute("/an/awesome/", handler).Build();
|
|
|
|
// Act
|
|
Assert.Equal("an/awesome", routeTable.Routes[0].Template.TemplateText);
|
|
}
|
|
|
|
[Fact]
|
|
public void PrefersMoreConstraintsOverFewer()
|
|
{
|
|
// Arrange
|
|
var routeTable = new TestRouteTableBuilder()
|
|
.AddRoute("/products/{id}")
|
|
.AddRoute("/products/{id:int}").Build();
|
|
var context = new RouteContext("/products/456");
|
|
|
|
// Act
|
|
routeTable.Route(context);
|
|
|
|
// Assert
|
|
Assert.NotNull(context.Handler);
|
|
Assert.Equal(context.Parameters, new Dictionary<string, object>
|
|
{
|
|
{ "id", 456 }
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void ProducesAStableOrderForNonAmbiguousRoutes()
|
|
{
|
|
// Arrange & Act
|
|
var handler = typeof(int);
|
|
var routeTable = new TestRouteTableBuilder()
|
|
.AddRoute("/an/awesome/", handler)
|
|
.AddRoute("/a/brilliant/").Build();
|
|
|
|
// Act
|
|
Assert.Equal("a/brilliant", routeTable.Routes[0].Template.TemplateText);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("/literal", "/Literal/")]
|
|
[InlineData("/{parameter}", "/{parameter}/")]
|
|
[InlineData("/literal/{parameter}", "/Literal/{something}")]
|
|
[InlineData("/{parameter}/literal/{something}", "{param}/Literal/{else}")]
|
|
public void DetectsAmbigousRoutes(string left, string right)
|
|
{
|
|
// Arrange
|
|
var expectedMessage = $@"The following routes are ambiguous:
|
|
'{left.Trim('/')}' in '{typeof(object).FullName}'
|
|
'{right.Trim('/')}' in '{typeof(object).FullName}'
|
|
";
|
|
// Act
|
|
var exception = Assert.Throws<InvalidOperationException>(() => new TestRouteTableBuilder()
|
|
.AddRoute(left)
|
|
.AddRoute(right).Build());
|
|
|
|
Assert.Equal(expectedMessage, exception.Message);
|
|
}
|
|
|
|
private class TestRouteTableBuilder
|
|
{
|
|
IList<(string, Type)> _routeTemplates = new List<(string, Type)>();
|
|
Type _handler = typeof(object);
|
|
|
|
public TestRouteTableBuilder AddRoute(string template, Type handler = null)
|
|
{
|
|
_routeTemplates.Add((template, handler ?? _handler));
|
|
return this;
|
|
}
|
|
|
|
public RouteTable Build() => new RouteTable(_routeTemplates
|
|
.Select(rt => new RouteEntry(TemplateParser.ParseTemplate(rt.Item1), rt.Item2))
|
|
.OrderBy(id => id, RouteTable.RoutePrecedence)
|
|
.ToArray());
|
|
}
|
|
}
|
|
}
|