aspnetcore/test/Microsoft.AspNetCore.Routin.../Matching/DfaMatcherBuilderTest.cs

1105 lines
38 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.Http;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Routing.Matching
{
public class DfaMatcherBuilderTest
{
[Fact]
public void BuildDfaTree_SingleEndpoint_Empty()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint = CreateEndpoint("/");
builder.AddEndpoint(endpoint);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Same(endpoint, Assert.Single(root.Matches));
Assert.Null(root.Parameters);
Assert.Null(root.Literals);
}
[Fact]
public void BuildDfaTree_SingleEndpoint_Literals()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint = CreateEndpoint("a/b/c");
builder.AddEndpoint(endpoint);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Null(a.Matches);
Assert.Null(a.Parameters);
next = Assert.Single(a.Literals);
Assert.Equal("b", next.Key);
var b = next.Value;
Assert.Null(b.Matches);
Assert.Null(b.Parameters);
next = Assert.Single(b.Literals);
Assert.Equal("c", next.Key);
var c = next.Value;
Assert.Same(endpoint, Assert.Single(c.Matches));
Assert.Null(c.Parameters);
Assert.Null(c.Literals);
}
[Fact]
public void BuildDfaTree_SingleEndpoint_Parameters()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint = CreateEndpoint("{a}/{b}/{c}");
builder.AddEndpoint(endpoint);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Literals);
var a = root.Parameters;
Assert.Null(a.Matches);
Assert.Null(a.Literals);
var b = a.Parameters;
Assert.Null(b.Matches);
Assert.Null(b.Literals);
var c = b.Parameters;
Assert.Same(endpoint, Assert.Single(c.Matches));
Assert.Null(c.Parameters);
Assert.Null(c.Literals);
}
[Fact]
public void BuildDfaTree_SingleEndpoint_CatchAll()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint = CreateEndpoint("{a}/{*b}");
builder.AddEndpoint(endpoint);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Literals);
var a = root.Parameters;
// The catch all can match a path like '/a'
Assert.Same(endpoint, Assert.Single(a.Matches));
Assert.Null(a.Literals);
Assert.Null(a.Parameters);
// Catch-all nodes include an extra transition that loops to process
// extra segments.
var catchAll = a.CatchAll;
Assert.Same(endpoint, Assert.Single(catchAll.Matches));
Assert.Null(catchAll.Literals);
Assert.Same(catchAll, catchAll.Parameters);
Assert.Same(catchAll, catchAll.CatchAll);
}
[Fact]
public void BuildDfaTree_SingleEndpoint_CatchAllAtRoot()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint = CreateEndpoint("{*a}");
builder.AddEndpoint(endpoint);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Same(endpoint, Assert.Single(root.Matches));
Assert.Null(root.Literals);
// Catch-all nodes include an extra transition that loops to process
// extra segments.
var catchAll = root.CatchAll;
Assert.Same(endpoint, Assert.Single(catchAll.Matches));
Assert.Null(catchAll.Literals);
Assert.Same(catchAll, catchAll.Parameters);
}
[Fact]
public void BuildDfaTree_MultipleEndpoint_LiteralAndLiteral()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint1 = CreateEndpoint("a/b1/c");
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("a/b2/c");
builder.AddEndpoint(endpoint2);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Null(a.Matches);
Assert.Equal(2, a.Literals.Count);
var b1 = a.Literals["b1"];
Assert.Null(b1.Matches);
Assert.Null(b1.Parameters);
next = Assert.Single(b1.Literals);
Assert.Equal("c", next.Key);
var c1 = next.Value;
Assert.Same(endpoint1, Assert.Single(c1.Matches));
Assert.Null(c1.Parameters);
Assert.Null(c1.Literals);
var b2 = a.Literals["b2"];
Assert.Null(b2.Matches);
Assert.Null(b2.Parameters);
next = Assert.Single(b2.Literals);
Assert.Equal("c", next.Key);
var c2 = next.Value;
Assert.Same(endpoint2, Assert.Single(c2.Matches));
Assert.Null(c2.Parameters);
Assert.Null(c2.Literals);
}
[Fact]
public void BuildDfaTree_MultipleEndpoint_LiteralDifferentCase()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint1 = CreateEndpoint("a/b1/c");
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("A/b2/c");
builder.AddEndpoint(endpoint2);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Null(a.Matches);
Assert.Equal(2, a.Literals.Count);
var b1 = a.Literals["b1"];
Assert.Null(b1.Matches);
Assert.Null(b1.Parameters);
next = Assert.Single(b1.Literals);
Assert.Equal("c", next.Key);
var c1 = next.Value;
Assert.Same(endpoint1, Assert.Single(c1.Matches));
Assert.Null(c1.Parameters);
Assert.Null(c1.Literals);
var b2 = a.Literals["b2"];
Assert.Null(b2.Matches);
Assert.Null(b2.Parameters);
next = Assert.Single(b2.Literals);
Assert.Equal("c", next.Key);
var c2 = next.Value;
Assert.Same(endpoint2, Assert.Single(c2.Matches));
Assert.Null(c2.Parameters);
Assert.Null(c2.Literals);
}
[Fact]
public void BuildDfaTree_MultipleEndpoint_LiteralAndParameter()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint1 = CreateEndpoint("a/b/c");
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("a/{b}/c");
builder.AddEndpoint(endpoint2);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Null(a.Matches);
next = Assert.Single(a.Literals);
Assert.Equal("b", next.Key);
var b = next.Value;
Assert.Null(b.Matches);
Assert.Null(b.Parameters);
next = Assert.Single(b.Literals);
Assert.Equal("c", next.Key);
var c1 = next.Value;
Assert.Collection(
c1.Matches,
e => Assert.Same(endpoint1, e),
e => Assert.Same(endpoint2, e));
Assert.Null(c1.Parameters);
Assert.Null(c1.Literals);
var b2 = a.Parameters;
Assert.Null(b2.Matches);
Assert.Null(b2.Parameters);
next = Assert.Single(b2.Literals);
Assert.Equal("c", next.Key);
var c2 = next.Value;
Assert.Same(endpoint2, Assert.Single(c2.Matches));
Assert.Null(c2.Parameters);
Assert.Null(c2.Literals);
}
[Fact]
public void BuildDfaTree_MultipleEndpoint_ParameterAndParameter()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint1 = CreateEndpoint("a/{b1}/c");
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("a/{b2}/c");
builder.AddEndpoint(endpoint2);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Null(a.Matches);
Assert.Null(a.Literals);
var b = a.Parameters;
Assert.Null(b.Matches);
Assert.Null(b.Parameters);
next = Assert.Single(b.Literals);
Assert.Equal("c", next.Key);
var c = next.Value;
Assert.Collection(
c.Matches,
e => Assert.Same(endpoint1, e),
e => Assert.Same(endpoint2, e));
Assert.Null(c.Parameters);
Assert.Null(c.Literals);
}
[Fact]
public void BuildDfaTree_MultipleEndpoint_LiteralAndCatchAll()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint1 = CreateEndpoint("a/b/c");
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("a/{*b}");
builder.AddEndpoint(endpoint2);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Same(endpoint2, Assert.Single(a.Matches));
next = Assert.Single(a.Literals);
Assert.Equal("b", next.Key);
var b1 = next.Value;
Assert.Same(endpoint2, Assert.Single(a.Matches));
Assert.Null(b1.Parameters);
next = Assert.Single(b1.Literals);
Assert.Equal("c", next.Key);
var c1 = next.Value;
Assert.Collection(
c1.Matches,
e => Assert.Same(endpoint1, e),
e => Assert.Same(endpoint2, e));
Assert.Null(c1.Parameters);
Assert.Null(c1.Literals);
var catchAll = a.CatchAll;
Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
Assert.Same(catchAll, catchAll.Parameters);
Assert.Same(catchAll, catchAll.CatchAll);
}
[Fact]
public void BuildDfaTree_MultipleEndpoint_ParameterAndCatchAll()
{
// Arrange
var builder = CreateDfaMatcherBuilder();
var endpoint1 = CreateEndpoint("a/{b}/c");
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("a/{*b}");
builder.AddEndpoint(endpoint2);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Same(endpoint2, Assert.Single(a.Matches));
Assert.Null(a.Literals);
var b1 = a.Parameters;
Assert.Same(endpoint2, Assert.Single(a.Matches));
Assert.Null(b1.Parameters);
next = Assert.Single(b1.Literals);
Assert.Equal("c", next.Key);
var c1 = next.Value;
Assert.Collection(
c1.Matches,
e => Assert.Same(endpoint1, e),
e => Assert.Same(endpoint2, e));
Assert.Null(c1.Parameters);
Assert.Null(c1.Literals);
var catchAll = a.CatchAll;
Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
Assert.Same(catchAll, catchAll.Parameters);
Assert.Same(catchAll, catchAll.CatchAll);
}
[Fact]
public void BuildDfaTree_WithPolicies()
{
// Arrange
var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), new TestMetadata2(true), });
builder.AddEndpoint(endpoint1);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Empty(a.Matches);
Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
Assert.Collection(
a.PolicyEdges.OrderBy(e => e.Key),
e => Assert.Equal(0, e.Key));
var test1_0 = a.PolicyEdges[0];
Assert.Empty(a.Matches);
Assert.IsType<TestMetadata2MatcherPolicy>(test1_0.NodeBuilder);
Assert.Collection(
test1_0.PolicyEdges.OrderBy(e => e.Key),
e => Assert.Equal(true, e.Key));
var test2_true = test1_0.PolicyEdges[true];
Assert.Same(endpoint1, Assert.Single(test2_true.Matches));
Assert.Null(test2_true.NodeBuilder);
Assert.Null(test2_true.PolicyEdges);
}
[Fact]
public void BuildDfaTree_WithPolicies_AndBranches()
{
// Arrange
var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), new TestMetadata2(true), });
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), new TestMetadata2(true), });
builder.AddEndpoint(endpoint2);
var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), new TestMetadata2(false), });
builder.AddEndpoint(endpoint3);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Empty(a.Matches);
Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
Assert.Collection(
a.PolicyEdges.OrderBy(e => e.Key),
e => Assert.Equal(0, e.Key),
e => Assert.Equal(1, e.Key));
var test1_0 = a.PolicyEdges[0];
Assert.Empty(test1_0.Matches);
Assert.IsType<TestMetadata2MatcherPolicy>(test1_0.NodeBuilder);
Assert.Collection(
test1_0.PolicyEdges.OrderBy(e => e.Key),
e => Assert.Equal(true, e.Key));
var test2_true = test1_0.PolicyEdges[true];
Assert.Same(endpoint1, Assert.Single(test2_true.Matches));
Assert.Null(test2_true.NodeBuilder);
Assert.Null(test2_true.PolicyEdges);
var test1_1 = a.PolicyEdges[1];
Assert.Empty(test1_1.Matches);
Assert.IsType<TestMetadata2MatcherPolicy>(test1_1.NodeBuilder);
Assert.Collection(
test1_1.PolicyEdges.OrderBy(e => e.Key),
e => Assert.Equal(false, e.Key),
e => Assert.Equal(true, e.Key));
test2_true = test1_1.PolicyEdges[true];
Assert.Same(endpoint2, Assert.Single(test2_true.Matches));
Assert.Null(test2_true.NodeBuilder);
Assert.Null(test2_true.PolicyEdges);
var test2_false = test1_1.PolicyEdges[false];
Assert.Same(endpoint3, Assert.Single(test2_false.Matches));
Assert.Null(test2_false.NodeBuilder);
Assert.Null(test2_false.PolicyEdges);
}
[Fact]
public void BuildDfaTree_WithPolicies_AndBranches_FirstPolicySkipped()
{
// Arrange
var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(true), });
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(true), });
builder.AddEndpoint(endpoint2);
var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata2(false), });
builder.AddEndpoint(endpoint3);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Empty(a.Matches);
Assert.IsType<TestMetadata2MatcherPolicy>(a.NodeBuilder);
Assert.Collection(
a.PolicyEdges.OrderBy(e => e.Key),
e => Assert.Equal(false, e.Key),
e => Assert.Equal(true, e.Key));
var test2_true = a.PolicyEdges[true];
Assert.Equal(new[] { endpoint1, endpoint2, }, test2_true.Matches);
Assert.Null(test2_true.NodeBuilder);
Assert.Null(test2_true.PolicyEdges);
var test2_false = a.PolicyEdges[false];
Assert.Equal(new[] { endpoint3, }, test2_false.Matches);
Assert.Null(test2_false.NodeBuilder);
Assert.Null(test2_false.PolicyEdges);
}
[Fact]
public void BuildDfaTree_WithPolicies_AndBranches_SecondSkipped()
{
// Arrange
var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), });
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
builder.AddEndpoint(endpoint2);
var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
builder.AddEndpoint(endpoint3);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Empty(a.Matches);
Assert.IsType<TestMetadata1MatcherPolicy>(a.NodeBuilder);
Assert.Collection(
a.PolicyEdges.OrderBy(e => e.Key),
e => Assert.Equal(0, e.Key),
e => Assert.Equal(1, e.Key));
var test1_0 = a.PolicyEdges[0];
Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
Assert.Null(test1_0.NodeBuilder);
Assert.Null(test1_0.PolicyEdges);
var test1_1 = a.PolicyEdges[1];
Assert.Equal(new[] { endpoint2, endpoint3, }, test1_1.Matches);
Assert.Null(test1_1.NodeBuilder);
Assert.Null(test1_1.PolicyEdges);
}
[Fact]
public void BuildDfaTree_WithPolicies_AndBranches_NonRouteEndpoint()
{
// Arrange
var builder = CreateDfaMatcherBuilder(new TestNonRoutePatternMatcherPolicy());
var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), });
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
builder.AddEndpoint(endpoint2);
var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
builder.AddEndpoint(endpoint3);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Empty(a.Matches);
Assert.IsType<TestNonRoutePatternMatcherPolicy>(a.NodeBuilder);
Assert.Collection(
a.PolicyEdges.OrderBy(e => e.Key),
e => Assert.Equal(0, e.Key),
e => Assert.Equal(1, e.Key),
e => Assert.Equal(int.MaxValue, e.Key));
var test1_0 = a.PolicyEdges[0];
Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
Assert.Null(test1_0.NodeBuilder);
Assert.Null(test1_0.PolicyEdges);
var test1_1 = a.PolicyEdges[1];
Assert.Equal(new[] { endpoint2, endpoint3, }, test1_1.Matches);
Assert.Null(test1_1.NodeBuilder);
Assert.Null(test1_1.PolicyEdges);
var nonRouteEndpoint = a.PolicyEdges[int.MaxValue];
Assert.Equal("MaxValueEndpoint", Assert.Single(nonRouteEndpoint.Matches).DisplayName);
}
[Fact]
public void BuildDfaTree_WithPolicies_AndBranches_BothPoliciesSkipped()
{
// Arrange
var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
var endpoint1 = CreateEndpoint("/a", metadata: new object[] { });
builder.AddEndpoint(endpoint1);
var endpoint2 = CreateEndpoint("/a", metadata: new object[] { });
builder.AddEndpoint(endpoint2);
var endpoint3 = CreateEndpoint("/a", metadata: new object[] { });
builder.AddEndpoint(endpoint3);
// Act
var root = builder.BuildDfaTree();
// Assert
Assert.Null(root.Matches);
Assert.Null(root.Parameters);
var next = Assert.Single(root.Literals);
Assert.Equal("a", next.Key);
var a = next.Value;
Assert.Equal(new[] { endpoint1, endpoint2, endpoint3, }, a.Matches);
Assert.Null(a.NodeBuilder);
Assert.Null(a.PolicyEdges);
}
[Fact]
public void CreateCandidate_JustLiterals()
{
// Arrange
var endpoint = CreateEndpoint("/a/b/c");
var builder = CreateDfaMatcherBuilder();
// Act
var candidate = builder.CreateCandidate(endpoint, score: 0);
// Assert
Assert.Equal(Candidate.CandidateFlags.None, candidate.Flags);
Assert.Empty(candidate.Slots);
Assert.Empty(candidate.Captures);
Assert.Equal(default, candidate.CatchAll);
Assert.Empty(candidate.ComplexSegments);
Assert.Empty(candidate.Constraints);
}
[Fact]
public void CreateCandidate_Parameters()
{
// Arrange
var endpoint = CreateEndpoint("/{a}/{b}/{c}");
var builder = CreateDfaMatcherBuilder();
// Act
var candidate = builder.CreateCandidate(endpoint, score: 0);
// Assert
Assert.Equal(Candidate.CandidateFlags.HasCaptures, candidate.Flags);
Assert.Equal(3, candidate.Slots.Length);
Assert.Collection(
candidate.Captures,
c => Assert.Equal(("a", 0, 0), c),
c => Assert.Equal(("b", 1, 1), c),
c => Assert.Equal(("c", 2, 2), c));
Assert.Equal(default, candidate.CatchAll);
Assert.Empty(candidate.ComplexSegments);
Assert.Empty(candidate.Constraints);
}
[Fact]
public void CreateCandidate_Parameters_WithDefaults()
{
// Arrange
var endpoint = CreateEndpoint("/{a=aa}/{b=bb}/{c=cc}");
var builder = CreateDfaMatcherBuilder();
// Act
var candidate = builder.CreateCandidate(endpoint, score: 0);
// Assert
Assert.Equal(
Candidate.CandidateFlags.HasDefaults | Candidate.CandidateFlags.HasCaptures,
candidate.Flags);
Assert.Collection(
candidate.Slots,
s => Assert.Equal(new KeyValuePair<string, object>("a", "aa"), s),
s => Assert.Equal(new KeyValuePair<string, object>("b", "bb"), s),
s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s));
Assert.Collection(
candidate.Captures,
c => Assert.Equal(("a", 0, 0), c),
c => Assert.Equal(("b", 1, 1), c),
c => Assert.Equal(("c", 2, 2), c));
Assert.Equal(default, candidate.CatchAll);
Assert.Empty(candidate.ComplexSegments);
Assert.Empty(candidate.Constraints);
}
[Fact]
public void CreateCandidate_Parameters_CatchAll()
{
// Arrange
var endpoint = CreateEndpoint("/{a}/{b}/{*c=cc}");
var builder = CreateDfaMatcherBuilder();
// Act
var candidate = builder.CreateCandidate(endpoint, score: 0);
// Assert
Assert.Equal(
Candidate.CandidateFlags.HasDefaults |
Candidate.CandidateFlags.HasCaptures |
Candidate.CandidateFlags.HasCatchAll,
candidate.Flags);
Assert.Collection(
candidate.Slots,
s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s),
s => Assert.Equal(new KeyValuePair<string, object>(null, null), s),
s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
Assert.Collection(
candidate.Captures,
c => Assert.Equal(("a", 0, 1), c),
c => Assert.Equal(("b", 1, 2), c));
Assert.Equal(("c", 2, 0), candidate.CatchAll);
Assert.Empty(candidate.ComplexSegments);
Assert.Empty(candidate.Constraints);
}
// Defaults are processed first, which affects the slot ordering.
[Fact]
public void CreateCandidate_Parameters_OutOfLineDefaults()
{
// Arrange
var endpoint = CreateEndpoint("/{a}/{b}/{c=cc}", new { a = "aa", d = "dd", });
var builder = CreateDfaMatcherBuilder();
// Act
var candidate = builder.CreateCandidate(endpoint, score: 0);
// Assert
Assert.Equal(
Candidate.CandidateFlags.HasDefaults | Candidate.CandidateFlags.HasCaptures,
candidate.Flags);
Assert.Collection(
candidate.Slots,
s => Assert.Equal(new KeyValuePair<string, object>("a", "aa"), s),
s => Assert.Equal(new KeyValuePair<string, object>("d", "dd"), s),
s => Assert.Equal(new KeyValuePair<string, object>("c", "cc"), s),
s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
Assert.Collection(
candidate.Captures,
c => Assert.Equal(("a", 0, 0), c),
c => Assert.Equal(("b", 1, 3), c),
c => Assert.Equal(("c", 2, 2), c));
Assert.Equal(default, candidate.CatchAll);
Assert.Empty(candidate.ComplexSegments);
Assert.Empty(candidate.Constraints);
}
[Fact]
public void CreateCandidate_Parameters_ComplexSegments()
{
// Arrange
var endpoint = CreateEndpoint("/{a}-{b=bb}/{c}");
var builder = CreateDfaMatcherBuilder();
// Act
var candidate = builder.CreateCandidate(endpoint, score: 0);
// Assert
Assert.Equal(
Candidate.CandidateFlags.HasDefaults |
Candidate.CandidateFlags.HasCaptures |
Candidate.CandidateFlags.HasComplexSegments,
candidate.Flags);
Assert.Collection(
candidate.Slots,
s => Assert.Equal(new KeyValuePair<string, object>("b", "bb"), s),
s => Assert.Equal(new KeyValuePair<string, object>(null, null), s));
Assert.Collection(
candidate.Captures,
c => Assert.Equal(("c", 1, 1), c));
Assert.Equal(default, candidate.CatchAll);
Assert.Collection(
candidate.ComplexSegments,
s => Assert.Equal(0, s.segmentIndex));
Assert.Empty(candidate.Constraints);
}
[Fact]
public void CreateCandidate_RouteConstraints()
{
// Arrange
var endpoint = CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), });
var builder = CreateDfaMatcherBuilder();
// Act
var candidate = builder.CreateCandidate(endpoint, score: 0);
// Assert
Assert.Equal(Candidate.CandidateFlags.HasConstraints, candidate.Flags);
Assert.Empty(candidate.Slots);
Assert.Empty(candidate.Captures);
Assert.Equal(default, candidate.CatchAll);
Assert.Empty(candidate.ComplexSegments);
Assert.Single(candidate.Constraints);
}
[Fact]
public void CreateCandidate_CustomParameterPolicy()
{
// Arrange
var endpoint = CreateEndpoint("/a/b/c", constraints: new { a = new CustomParameterPolicy(), });
var builder = CreateDfaMatcherBuilder();
// Act
var candidate = builder.CreateCandidate(endpoint, score: 0);
// Assert
Assert.Equal(Candidate.CandidateFlags.None, candidate.Flags);
Assert.Empty(candidate.Slots);
Assert.Empty(candidate.Captures);
Assert.Equal(default, candidate.CatchAll);
Assert.Empty(candidate.ComplexSegments);
Assert.Empty(candidate.Constraints);
}
private class CustomParameterPolicy : IParameterPolicy
{
}
[Fact]
public void CreateCandidates_CreatesScoresCorrectly()
{
// Arrange
var endpoints = new[]
{
CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), }, metadata: new object[] { new TestMetadata1(), new TestMetadata2(), }),
CreateEndpoint("/a/b/c", constraints: new { a = new AlphaRouteConstraint(), }, metadata: new object[] { new TestMetadata1(), new TestMetadata2(), }),
CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), }, metadata: new object[] { new TestMetadata1(), }),
CreateEndpoint("/a/b/c", constraints: new { a = new IntRouteConstraint(), }, metadata: new object[] { new TestMetadata2(), }),
CreateEndpoint("/a/b/c", constraints: new { }, metadata: new object[] { }),
CreateEndpoint("/a/b/c", constraints: new { }, metadata: new object[] { }),
};
var builder = CreateDfaMatcherBuilder(new TestMetadata1MatcherPolicy(), new TestMetadata2MatcherPolicy());
// Act
var candidates = builder.CreateCandidates(endpoints);
// Assert
Assert.Collection(
candidates,
c => Assert.Equal(0, c.Score),
c => Assert.Equal(0, c.Score),
c => Assert.Equal(1, c.Score),
c => Assert.Equal(2, c.Score),
c => Assert.Equal(3, c.Score),
c => Assert.Equal(3, c.Score));
}
private static DfaMatcherBuilder CreateDfaMatcherBuilder(params MatcherPolicy[] policies)
{
var dataSource = new CompositeEndpointDataSource(Array.Empty<EndpointDataSource>());
return new DfaMatcherBuilder(
NullLoggerFactory.Instance,
new DefaultParameterPolicyFactory(Options.Create(new RouteOptions()), Mock.Of<IServiceProvider>()),
Mock.Of<EndpointSelector>(),
policies);
}
private RouteEndpoint CreateEndpoint(
string template,
object defaults = null,
object constraints = null,
params object[] metadata)
{
return new RouteEndpoint(
TestConstants.EmptyRequestDelegate,
RoutePatternFactory.Parse(template, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints)),
0,
new EndpointMetadataCollection(metadata),
"test");
}
private class TestMetadata1
{
public TestMetadata1()
{
}
public TestMetadata1(int state)
{
State = state;
}
public int State { get; set; }
}
private class TestMetadata1MatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
{
public override int Order => 100;
public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata1>() != null);
}
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
{
throw new NotImplementedException();
}
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
{
return endpoints
.GroupBy(e => e.Metadata.GetMetadata<TestMetadata1>().State)
.Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
.ToArray();
}
}
private class TestMetadata2
{
public TestMetadata2()
{
}
public TestMetadata2(bool state)
{
State = state;
}
public bool State { get; set; }
}
private class TestMetadata2MatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
{
public override int Order => 101;
public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata2>.Default;
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata2>() != null);
}
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
{
throw new NotImplementedException();
}
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
{
return endpoints
.GroupBy(e => e.Metadata.GetMetadata<TestMetadata2>().State)
.Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
.ToArray();
}
}
private class TestNonRoutePatternMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
{
public override int Order => 100;
public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata1>() != null);
}
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
{
throw new NotImplementedException();
}
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
{
var edges = endpoints
.GroupBy(e => e.Metadata.GetMetadata<TestMetadata1>().State)
.Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
.ToList();
var maxValueEndpoint = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "MaxValueEndpoint");
edges.Add(new PolicyNodeEdge(int.MaxValue, new[] { maxValueEndpoint }));
return edges;
}
}
}
}