public async Task ApplyAsync_CanReplaceFoundEndpoints()
    {
        // Arrange
        var policy = new DynamicControllerEndpointMatcherPolicy(SelectorCache, Comparer);

        var endpoints = new[] { DynamicEndpoint, };
        var values    = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
        var scores    = new[] { 0, };

        var candidates = new CandidateSet(endpoints, values, scores);

        Transform = (c, values, state) =>
        {
            return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary(new
            {
                controller = "Home",
                action = "Index",
                state
            })));
        };

        Filter = (c, values, state, endpoints) => new ValueTask <IReadOnlyList <Endpoint> >(new[]
        {
            new Endpoint((ctx) => Task.CompletedTask, new EndpointMetadataCollection(Array.Empty <object>()), "ReplacedEndpoint")
        });

        var httpContext = new DefaultHttpContext()
        {
            RequestServices = Services,
        };

        // Act
        await policy.ApplyAsync(httpContext, candidates);

        // Assert
        Assert.Collection(
            candidates[0].Values.OrderBy(kvp => kvp.Key),
            kvp =>
        {
            Assert.Equal("action", kvp.Key);
            Assert.Equal("Index", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("controller", kvp.Key);
            Assert.Equal("Home", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("slug", kvp.Key);
            Assert.Equal("test", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("state", kvp.Key);
            Assert.Same(State, kvp.Value);
        });
        Assert.Equal("ReplacedEndpoint", candidates[0].Endpoint.DisplayName);
        Assert.True(candidates.IsValidCandidate(0));
    }
    public async Task ApplyAsync_ThrowsForTransformerWithInvalidLifetime()
    {
        // Arrange
        var policy = new DynamicControllerEndpointMatcherPolicy(SelectorCache, Comparer);

        var endpoints = new[] { DynamicEndpoint, };
        var values    = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
        var scores    = new[] { 0, };

        var candidates = new CandidateSet(endpoints, values, scores);

        Transform = (c, values, state) =>
        {
            return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary(new
            {
                controller = "Home",
                action = "Index",
                state
            })));
        };

        var httpContext = new DefaultHttpContext()
        {
            RequestServices = new ServiceCollection().AddScoped(sp => new CustomTransformer {
                State = "Invalid"
            }).BuildServiceProvider(),
        };

        // Act & Assert
        await Assert.ThrowsAsync <InvalidOperationException>(() => policy.ApplyAsync(httpContext, candidates));
    }
    public async Task ApplyAsync_HasMatchNoEndpointFound()
    {
        // Arrange
        var policy = new DynamicControllerEndpointMatcherPolicy(SelectorCache, Comparer);

        var endpoints = new[] { DynamicEndpoint, };
        var values    = new RouteValueDictionary[] { null, };
        var scores    = new[] { 0, };

        var candidates = new CandidateSet(endpoints, values, scores);

        Transform = (c, values, state) =>
        {
            return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary()));
        };

        var httpContext = new DefaultHttpContext()
        {
            RequestServices = Services,
        };

        // Act
        await policy.ApplyAsync(httpContext, candidates);

        // Assert
        Assert.Null(candidates[0].Endpoint);
        Assert.Null(candidates[0].Values);
        Assert.False(candidates.IsValidCandidate(0));
    }
    public async Task ApplyAsync_NoMatch()
    {
        // Arrange
        var policy = new DynamicControllerEndpointMatcherPolicy(SelectorCache, Comparer);

        var endpoints = new[] { DynamicEndpoint, };
        var values    = new RouteValueDictionary[] { null, };
        var scores    = new[] { 0, };

        var candidates = new CandidateSet(endpoints, values, scores);

        candidates.SetValidity(0, false);

        Transform = (c, values, state) =>
        {
            throw new InvalidOperationException();
        };

        var httpContext = new DefaultHttpContext()
        {
            RequestServices = Services,
        };

        // Act
        await policy.ApplyAsync(httpContext, candidates);

        // Assert
        Assert.False(candidates.IsValidCandidate(0));
    }
    public async Task ApplyAsync_HasMatchFindsEndpoint_WithRouteValues()
    {
        // Arrange
        var policy = new DynamicControllerEndpointMatcherPolicy(SelectorCache, Comparer);

        var endpoints = new[] { DynamicEndpoint, };
        var values    = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
        var scores    = new[] { 0, };

        var candidates = new CandidateSet(endpoints, values, scores);

        Transform = (c, values, state) =>
        {
            return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary(new
            {
                controller = "Home",
                action = "Index",
                state
            })));
        };

        var httpContext = new DefaultHttpContext()
        {
            RequestServices = Services,
        };

        // Act
        await policy.ApplyAsync(httpContext, candidates);

        // Assert
        Assert.Same(ControllerEndpoints[0], candidates[0].Endpoint);
        Assert.Collection(
            candidates[0].Values.OrderBy(kvp => kvp.Key),
            kvp =>
        {
            Assert.Equal("action", kvp.Key);
            Assert.Equal("Index", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("controller", kvp.Key);
            Assert.Equal("Home", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("slug", kvp.Key);
            Assert.Equal("test", kvp.Value);
        },
            kvp =>
        {
            Assert.Equal("state", kvp.Key);
            Assert.Same(State, kvp.Value);
        });
        Assert.True(candidates.IsValidCandidate(0));
    }
    public async Task ApplyAsync_CanExpandTheListOfFoundEndpoints()
    {
        // Arrange
        var policy = new DynamicControllerEndpointMatcherPolicy(SelectorCache, Comparer);

        var endpoints = new[] { DynamicEndpoint, };
        var values    = new RouteValueDictionary[] { new RouteValueDictionary(new { slug = "test", }), };
        var scores    = new[] { 0, };

        var candidates = new CandidateSet(endpoints, values, scores);

        Transform = (c, values, state) =>
        {
            return(new ValueTask <RouteValueDictionary>(new RouteValueDictionary(new
            {
                controller = "Home",
                action = "Index",
                state
            })));
        };

        Filter = (c, values, state, endpoints) => new ValueTask <IReadOnlyList <Endpoint> >(new[]
        {
            ControllerEndpoints[1], ControllerEndpoints[2]
        });

        var httpContext = new DefaultHttpContext()
        {
            RequestServices = Services,
        };

        // Act
        await policy.ApplyAsync(httpContext, candidates);

        // Assert
        Assert.Equal(2, candidates.Count);
        Assert.True(candidates.IsValidCandidate(0));
        Assert.True(candidates.IsValidCandidate(1));
        Assert.Same(ControllerEndpoints[1], candidates[0].Endpoint);
        Assert.Same(ControllerEndpoints[2], candidates[1].Endpoint);
    }