329 lines
12 KiB
C#
329 lines
12 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 System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Routing.Matchers
|
|
{
|
|
public abstract class MatcherConformanceTest
|
|
{
|
|
internal abstract Matcher CreateMatcher(MatcherEndpoint endpoint);
|
|
|
|
[Fact]
|
|
public virtual async Task Match_SingleLiteralSegment()
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher("/simple");
|
|
var (httpContext, feature) = CreateContext("/simple");
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint);
|
|
}
|
|
|
|
[Fact]
|
|
public virtual async Task Match_SingleLiteralSegment_TrailingSlash()
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher("/simple");
|
|
var (httpContext, feature) = CreateContext("/simple/");
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("/simple")]
|
|
[InlineData("/sImpLe")]
|
|
[InlineData("/SIMPLE")]
|
|
public virtual async Task Match_SingleLiteralSegment_CaseInsensitive(string path)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher("/Simple");
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint);
|
|
}
|
|
|
|
// Some matchers will optimize for the ASCII case
|
|
[Theory]
|
|
[InlineData("/SÏmple", "/SÏmple")]
|
|
[InlineData("/ab\uD834\uDD1Ecd", "/ab\uD834\uDD1Ecd")] // surrogate pair
|
|
public virtual async Task Match_SingleLiteralSegment_Unicode(string template, string path)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher(template);
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint);
|
|
}
|
|
|
|
// Matchers should operate on the decoded representation - a matcher that calls
|
|
// `httpContext.Request.Path.ToString()` will break this test.
|
|
[Theory]
|
|
[InlineData("/S%mple", "/S%mple")]
|
|
[InlineData("/S\\imple", "/S\\imple")] // surrogate pair
|
|
public virtual async Task Match_SingleLiteralSegment_PercentEncoded(string template, string path)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher(template);
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint);
|
|
}
|
|
|
|
|
|
[Theory]
|
|
[InlineData("/")]
|
|
[InlineData("/imple")]
|
|
[InlineData("/siple")]
|
|
[InlineData("/simple1")]
|
|
[InlineData("/simple/not-simple")]
|
|
[InlineData("/simple/a/b/c")]
|
|
public virtual async Task NotMatch_SingleLiteralSegment(string path)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher("/simple");
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertNotMatch(feature);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("simple")]
|
|
[InlineData("/simple")]
|
|
[InlineData("~/simple")]
|
|
public virtual async Task Match_Sanitizies_Template(string template)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher(template);
|
|
var (httpContext, feature) = CreateContext("/simple");
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint);
|
|
}
|
|
|
|
// Matchers do their own 'splitting' of the path into segments, so including
|
|
// some extra variation here
|
|
[Theory]
|
|
[InlineData("/a/b", "/a/b")]
|
|
[InlineData("/a/b", "/A/B")]
|
|
[InlineData("/a/b", "/a/b/")]
|
|
[InlineData("/a/b/c", "/a/b/c")]
|
|
[InlineData("/a/b/c", "/a/b/c/")]
|
|
[InlineData("/a/b/c/d", "/a/b/c/d")]
|
|
[InlineData("/a/b/c/d", "/a/b/c/d/")]
|
|
public virtual async Task Match_MultipleLiteralSegments(string template, string path)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher(template);
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint);
|
|
}
|
|
|
|
// Matchers do their own 'splitting' of the path into segments, so including
|
|
// some extra variation here
|
|
[Theory]
|
|
[InlineData("/a/b", "/")]
|
|
[InlineData("/a/b", "/a")]
|
|
[InlineData("/a/b", "/a/")]
|
|
[InlineData("/a/b", "/a//")]
|
|
[InlineData("/a/b", "/aa/")]
|
|
[InlineData("/a/b", "/a/bb")]
|
|
[InlineData("/a/b", "/a/bb/")]
|
|
[InlineData("/a/b/c", "/aa/b/c")]
|
|
[InlineData("/a/b/c", "/a/bb/c/")]
|
|
[InlineData("/a/b/c", "/a/b/cab")]
|
|
[InlineData("/a/b/c", "/d/b/c/")]
|
|
[InlineData("/a/b/c", "//b/c")]
|
|
[InlineData("/a/b/c", "/a/b//")]
|
|
[InlineData("/a/b/c", "/a/b/c/d")]
|
|
[InlineData("/a/b/c", "/a/b/c/d/e")]
|
|
public virtual async Task NotMatch_MultipleLiteralSegments(string template, string path)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher(template);
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertNotMatch(feature);
|
|
}
|
|
|
|
[Fact]
|
|
public virtual async Task Match_SingleParameter()
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher("/{p}");
|
|
var (httpContext, feature) = CreateContext("/14");
|
|
var values = new RouteValueDictionary(new { p = "14", });
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
|
}
|
|
|
|
[Fact]
|
|
public virtual async Task Match_SingleParameter_TrailingSlash()
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher("/{p}");
|
|
var (httpContext, feature) = CreateContext("/14/");
|
|
var values = new RouteValueDictionary(new { p = "14", });
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint, values);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("/")]
|
|
[InlineData("/a/b")]
|
|
[InlineData("/a/b/c")]
|
|
[InlineData("//")]
|
|
public virtual async Task NotMatch_SingleParameter(string path)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher("/{p}");
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertNotMatch(feature);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("/subscriptions/{subscriptionId}/providers/Microsoft.Insights/metricAlerts", "/subscriptions/foo/providers/Microsoft.Insights/metricAlerts", new string[] { "subscriptionId", }, new string[] { "foo", })]
|
|
[InlineData("/{a}/b", "/54/b", new string[] { "a", }, new string[] {"54", })]
|
|
[InlineData("/{a}/b", "/54/b/", new string[] { "a", }, new string[] { "54", })]
|
|
[InlineData("/{a}/{b}", "/54/73", new string[] { "a", "b" }, new string[] { "54", "73", })]
|
|
[InlineData("/a/{b}/c", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
|
|
[InlineData("/a/{b}/c/", "/a/b/c", new string[] { "b", }, new string[] { "b", })]
|
|
[InlineData("/{a}/b/{c}", "/54/b/c", new string[] { "a", "c", }, new string[] { "54", "c", })]
|
|
[InlineData("/{a}/{b}/{c}", "/54/b/c", new string[] { "a", "b", "c", }, new string[] { "54", "b", "c", })]
|
|
public virtual async Task Match_MultipleParameters(string template, string path, string[] keys, string[] values)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher(template);
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertMatch(feature, endpoint, keys, values);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("/{a}/b", "/54/bb")]
|
|
[InlineData("/{a}/b", "/54/b/17")]
|
|
[InlineData("/{a}/b", "/54/b//")]
|
|
[InlineData("/{a}/{b}", "//73")]
|
|
[InlineData("/{a}/{b}", "/54//")]
|
|
[InlineData("/{a}/{b}", "/54/73/18")]
|
|
[InlineData("/a/{b}/c", "/aa/b/c")]
|
|
[InlineData("/a/{b}/c", "/a/b/cc")]
|
|
[InlineData("/a/{b}/c", "/a/b/c/d")]
|
|
[InlineData("/{a}/b/{c}", "/54/bb/c")]
|
|
[InlineData("/{a}/{b}/{c}", "/54/b/c/d")]
|
|
[InlineData("/{a}/{b}/{c}", "/54/b/c//")]
|
|
[InlineData("/{a}/{b}/{c}", "//b/c/")]
|
|
[InlineData("/{a}/{b}/{c}", "/54//c/")]
|
|
[InlineData("/{a}/{b}/{c}", "/54/b//")]
|
|
public virtual async Task NotMatch_MultipleParameters(string template, string path)
|
|
{
|
|
// Arrange
|
|
var (matcher, endpoint) = CreateMatcher(template);
|
|
var (httpContext, feature) = CreateContext(path);
|
|
|
|
// Act
|
|
await matcher.MatchAsync(httpContext, feature);
|
|
|
|
// Assert
|
|
DispatcherAssert.AssertNotMatch(feature);
|
|
}
|
|
|
|
internal static (HttpContext httpContext, IEndpointFeature feature) CreateContext(string path)
|
|
{
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Method = "TEST";
|
|
httpContext.Request.Path = path;
|
|
httpContext.RequestServices = CreateServices();
|
|
|
|
var feature = new EndpointFeature();
|
|
httpContext.Features.Set<IEndpointFeature>(feature);
|
|
|
|
return (httpContext, feature);
|
|
}
|
|
|
|
// The older routing implementations retrieve services when they first execute.
|
|
internal static IServiceProvider CreateServices()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddLogging();
|
|
return services.BuildServiceProvider();
|
|
}
|
|
|
|
internal static MatcherEndpoint CreateEndpoint(string template)
|
|
{
|
|
return new MatcherEndpoint(
|
|
MatcherEndpoint.EmptyInvoker,
|
|
template,
|
|
null,
|
|
0,
|
|
EndpointMetadataCollection.Empty,
|
|
"endpoint: " + template,
|
|
address: null);
|
|
}
|
|
|
|
internal (Matcher matcher, MatcherEndpoint endpoint) CreateMatcher(string template)
|
|
{
|
|
var endpoint = CreateEndpoint(template);
|
|
return (CreateMatcher(endpoint), endpoint);
|
|
}
|
|
}
|
|
}
|