File: Matching\DataSourceDependentMatcherTest.cs
Web Access
Project: src\src\Http\Routing\test\UnitTests\Microsoft.AspNetCore.Routing.Tests.csproj (Microsoft.AspNetCore.Routing.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects;
 
namespace Microsoft.AspNetCore.Routing.Matching;
 
public class DataSourceDependentMatcherTest
{
    [Fact]
    public void Matcher_Initializes_InConstructor()
    {
        // Arrange
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
 
        // Act
        var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
 
        // Assert
        var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
        Assert.Empty(inner.Endpoints);
 
        Assert.NotNull(lifetime.Cache);
    }
 
    [Fact]
    public void Matcher_Reinitializes_WhenDataSourceChanges()
    {
        // Arrange
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
        var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
 
        var endpoint = new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("a/b/c"),
            0,
            EndpointMetadataCollection.Empty,
            "test");
 
        // Act
        dataSource.AddEndpoint(endpoint);
 
        // Assert
        var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
        Assert.Collection(
            inner.Endpoints,
            e => Assert.Same(endpoint, e));
    }
 
    [Fact]
    public void Matcher_IgnoresUpdate_WhenDisposed()
    {
        // Arrange
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
        var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
 
        var endpoint = new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("a/b/c"),
            0,
            EndpointMetadataCollection.Empty,
            "test");
 
        lifetime.Dispose();
 
        // Act
        dataSource.AddEndpoint(endpoint);
 
        // Assert
        var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
        Assert.Empty(inner.Endpoints);
    }
 
    [Fact]
    public void Matcher_Ignores_NonRouteEndpoint()
    {
        // Arrange
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
        var endpoint = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "test");
        dataSource.AddEndpoint(endpoint);
 
        // Act
        var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
 
        // Assert
        var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
        Assert.Empty(inner.Endpoints);
    }
 
    [Fact]
    public void Matcher_Ignores_SuppressedEndpoint()
    {
        // Arrange
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
        var endpoint = new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("/"),
            0,
            new EndpointMetadataCollection(new SuppressMatchingMetadata()),
            "test");
        dataSource.AddEndpoint(endpoint);
 
        // Act
        var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
 
        // Assert
        var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
        Assert.Empty(inner.Endpoints);
    }
 
    [Fact]
    public void Matcher_UnsuppressedEndpoint_IsUsed()
    {
        // Arrange
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
        var endpoint = new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("/"),
            0,
            new EndpointMetadataCollection(new SuppressMatchingMetadata(), new EncourageMatchingMetadata()),
            "test");
        dataSource.AddEndpoint(endpoint);
 
        // Act
        var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
 
        // Assert
        var inner = Assert.IsType<TestMatcher>(matcher.CurrentMatcher);
        Assert.Same(endpoint, Assert.Single(inner.Endpoints));
    }
 
    [Fact]
    public void Matcher_ThrowsOnDuplicateEndpoints()
    {
        // Arrange
        var expectedError = "Duplicate endpoint name 'Foo' found on '/bar' and '/foo'. Endpoint names must be globally unique.";
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
        dataSource.AddEndpoint(new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("/foo"),
            0,
            new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
            "/foo"
        ));
        dataSource.AddEndpoint(new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("/bar"),
            0,
            new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
            "/bar"
        ));
 
        // Assert
        var exception = Assert.Throws<InvalidOperationException>(
            () => new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create));
        Assert.Equal(expectedError, exception.Message);
    }
 
    [Fact]
    public void Matcher_ThrowsOnDuplicateEndpointsFromMultipleSources()
    {
        // Arrange
        var expectedError = "Duplicate endpoint name 'Foo' found on '/foo2' and '/foo'. Endpoint names must be globally unique.";
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
        dataSource.AddEndpoint(new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("/foo"),
            0,
            new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
            "/foo"
        ));
        dataSource.AddEndpoint(new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("/bar"),
            0,
            new EndpointMetadataCollection(new EndpointNameMetadata("Bar")),
            "/bar"
        ));
        var anotherDataSource = new DynamicEndpointDataSource();
        anotherDataSource.AddEndpoint(new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("/foo2"),
            0,
            new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
            "/foo2"
        ));
 
        var compositeDataSource = new CompositeEndpointDataSource(new[] { dataSource, anotherDataSource });
 
        // Assert
        var exception = Assert.Throws<InvalidOperationException>(
            () => new DataSourceDependentMatcher(compositeDataSource, lifetime, TestMatcherBuilder.Create));
        Assert.Equal(expectedError, exception.Message);
    }
 
    [Fact]
    public void Matcher_ThrowsOnDuplicateEndpointAddedLater()
    {
        // Arrange
        var expectedError = "Duplicate endpoint name 'Foo' found on '/bar' and '/foo'. Endpoint names must be globally unique.";
        var dataSource = new DynamicEndpointDataSource();
        var lifetime = new DataSourceDependentMatcher.Lifetime();
        dataSource.AddEndpoint(new RouteEndpoint(
            TestConstants.EmptyRequestDelegate,
            RoutePatternFactory.Parse("/foo"),
            0,
            new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
            "/foo"
        ));
 
        // Act (should be all good since no duplicate has been added yet)
        var matcher = new DataSourceDependentMatcher(dataSource, lifetime, TestMatcherBuilder.Create);
 
        // Assert that rerunning initializer throws AggregateException
        var exception = Assert.Throws<AggregateException>(
            () => dataSource.AddEndpoint(new RouteEndpoint(
                TestConstants.EmptyRequestDelegate,
                RoutePatternFactory.Parse("/bar"),
                0,
                new EndpointMetadataCollection(new EndpointNameMetadata("Foo")),
                "/bar"
        )));
        Assert.Equal(expectedError, exception.InnerException.Message);
    }
 
    private class TestMatcherBuilder : MatcherBuilder
    {
        public static Func<MatcherBuilder> Create = () => new TestMatcherBuilder();
 
        private List<RouteEndpoint> Endpoints { get; } = new List<RouteEndpoint>();
 
        public override void AddEndpoint(RouteEndpoint endpoint)
        {
            Endpoints.Add(endpoint);
        }
 
        public override Matcher Build()
        {
            return new TestMatcher() { Endpoints = Endpoints, };
        }
    }
 
    private class TestMatcher : Matcher
    {
        public IReadOnlyList<RouteEndpoint> Endpoints { get; set; }
 
        public override Task MatchAsync(HttpContext httpContext)
        {
            throw new NotImplementedException();
        }
    }
 
    private class EncourageMatchingMetadata : ISuppressMatchingMetadata
    {
        public bool SuppressMatching => false;
    }
}