diff options
Diffstat (limited to 'src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs')
-rw-r--r-- | src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs | 853 |
1 files changed, 426 insertions, 427 deletions
diff --git a/src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs b/src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs index cf1f1ad259..1584eff8ef 100644 --- a/src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs +++ b/src/Http/Routing/test/UnitTests/RouteValuesAddressSchemeTest.cs @@ -5,464 +5,463 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.TestObjects; -namespace Microsoft.AspNetCore.Routing +namespace Microsoft.AspNetCore.Routing; + +public class RouteValuesAddressSchemeTest { - public class RouteValuesAddressSchemeTest + [Fact] + public void GetOutboundMatches_GetsNamedMatchesFor_EndpointsHaving_IRouteNameMetadata() { - [Fact] - public void GetOutboundMatches_GetsNamedMatchesFor_EndpointsHaving_IRouteNameMetadata() - { - // Arrange - var endpoint1 = CreateEndpoint("/a", routeName: "other"); - var endpoint2 = CreateEndpoint("/a", routeName: "named"); - - // Act - var addressScheme = CreateAddressScheme(endpoint1, endpoint2); - - // Assert - var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - Assert.Equal(2, allMatches.Count); - Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches)); - var namedMatch = Assert.Single(namedMatches); - var actual = Assert.IsType<RouteEndpoint>(namedMatch.Match.Entry.Data); - Assert.Same(endpoint2, actual); - } + // Arrange + var endpoint1 = CreateEndpoint("/a", routeName: "other"); + var endpoint2 = CreateEndpoint("/a", routeName: "named"); + + // Act + var addressScheme = CreateAddressScheme(endpoint1, endpoint2); + + // Assert + var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); + Assert.Equal(2, allMatches.Count); + Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches)); + var namedMatch = Assert.Single(namedMatches); + var actual = Assert.IsType<RouteEndpoint>(namedMatch.Match.Entry.Data); + Assert.Same(endpoint2, actual); + } - [Fact] - public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName() - { - // Arrange - var endpoint1 = CreateEndpoint("/a", routeName: "other"); - var endpoint2 = CreateEndpoint("/a", routeName: "named"); - var endpoint3 = CreateEndpoint("/b", routeName: "named"); - - // Act - var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3); - - // Assert - var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - Assert.Equal(3, allMatches.Count); - Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches)); - Assert.Equal(2, namedMatches.Count); - Assert.Same(endpoint2, Assert.IsType<RouteEndpoint>(namedMatches[0].Match.Entry.Data)); - Assert.Same(endpoint3, Assert.IsType<RouteEndpoint>(namedMatches[1].Match.Entry.Data)); - } + [Fact] + public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName() + { + // Arrange + var endpoint1 = CreateEndpoint("/a", routeName: "other"); + var endpoint2 = CreateEndpoint("/a", routeName: "named"); + var endpoint3 = CreateEndpoint("/b", routeName: "named"); + + // Act + var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3); + + // Assert + var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); + Assert.Equal(3, allMatches.Count); + Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches)); + Assert.Equal(2, namedMatches.Count); + Assert.Same(endpoint2, Assert.IsType<RouteEndpoint>(namedMatches[0].Match.Entry.Data)); + Assert.Same(endpoint3, Assert.IsType<RouteEndpoint>(namedMatches[1].Match.Entry.Data)); + } - [Fact] - public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName_IgnoringCase() - { - // Arrange - var endpoint1 = CreateEndpoint("/a", routeName: "other"); - var endpoint2 = CreateEndpoint("/a", routeName: "named"); - var endpoint3 = CreateEndpoint("/b", routeName: "NaMed"); - - // Act - var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3); - - // Assert - var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - Assert.Equal(3, allMatches.Count); - Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches)); - Assert.Equal(2, namedMatches.Count); - Assert.Same(endpoint2, Assert.IsType<RouteEndpoint>(namedMatches[0].Match.Entry.Data)); - Assert.Same(endpoint3, Assert.IsType<RouteEndpoint>(namedMatches[1].Match.Entry.Data)); - } + [Fact] + public void GetOutboundMatches_GroupsMultipleEndpoints_WithSameName_IgnoringCase() + { + // Arrange + var endpoint1 = CreateEndpoint("/a", routeName: "other"); + var endpoint2 = CreateEndpoint("/a", routeName: "named"); + var endpoint3 = CreateEndpoint("/b", routeName: "NaMed"); + + // Act + var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3); + + // Assert + var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); + Assert.Equal(3, allMatches.Count); + Assert.True(addressScheme.State.NamedMatches.TryGetValue("named", out var namedMatches)); + Assert.Equal(2, namedMatches.Count); + Assert.Same(endpoint2, Assert.IsType<RouteEndpoint>(namedMatches[0].Match.Entry.Data)); + Assert.Same(endpoint3, Assert.IsType<RouteEndpoint>(namedMatches[1].Match.Entry.Data)); + } - [Fact] - public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches() - { - // Arrange 1 - var endpoint1 = CreateEndpoint("/a", routeName: "a"); - var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 }); - - // Act 1 - var addressScheme = new RouteValuesAddressScheme(new CompositeEndpointDataSource(new[] { dynamicDataSource })); - - // Assert 1 - var state = addressScheme.State; - var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - - Assert.NotEmpty(allMatches); - - var match = Assert.Single(allMatches); - var actual = Assert.IsType<RouteEndpoint>(match.Entry.Data); - Assert.Same(endpoint1, actual); - - // Arrange 2 - var endpoint2 = CreateEndpoint("/b", routeName: "b"); - - // Act 2 - // Trigger change - dynamicDataSource.AddEndpoint(endpoint2); - - // Assert 2 - Assert.NotSame(state, addressScheme.State); - state = addressScheme.State; - - // Arrange 3 - var endpoint3 = CreateEndpoint("/c", routeName: "c"); - - // Act 3 - // Trigger change - dynamicDataSource.AddEndpoint(endpoint3); - - // Assert 3 - Assert.NotSame(state, addressScheme.State); - state = addressScheme.State; - - // Arrange 4 - var endpoint4 = CreateEndpoint("/d", routeName: "d"); - - // Act 4 - // Trigger change - dynamicDataSource.AddEndpoint(endpoint4); - - // Assert 4 - Assert.NotSame(state, addressScheme.State); - state = addressScheme.State; - - allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - - Assert.NotEmpty(allMatches); - Assert.Collection( - allMatches, - (m) => - { - actual = Assert.IsType<RouteEndpoint>(m.Entry.Data); - Assert.Same(endpoint1, actual); - }, - (m) => - { - actual = Assert.IsType<RouteEndpoint>(m.Entry.Data); - Assert.Same(endpoint2, actual); - }, - (m) => - { - actual = Assert.IsType<RouteEndpoint>(m.Entry.Data); - Assert.Same(endpoint3, actual); - }, - (m) => - { - actual = Assert.IsType<RouteEndpoint>(m.Entry.Data); - Assert.Same(endpoint4, actual); - }); - } + [Fact] + public void EndpointDataSource_ChangeCallback_Refreshes_OutboundMatches() + { + // Arrange 1 + var endpoint1 = CreateEndpoint("/a", routeName: "a"); + var dynamicDataSource = new DynamicEndpointDataSource(new[] { endpoint1 }); - [Fact] - public void FindEndpoints_LookedUpByCriteria_NoMatch() - { - // Arrange - var endpoint1 = CreateEndpoint( - "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", - defaults: new { zipCode = 3510 }, - metadataRequiredValues: new { id = 7 }); - var endpoint2 = CreateEndpoint( - "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", - defaults: new { id = 12 }, - metadataRequiredValues: new { zipCode = 3510 }); - var addressScheme = CreateAddressScheme(endpoint1, endpoint2); - - // Act - var foundEndpoints = addressScheme.FindEndpoints( - new RouteValuesAddress - { - ExplicitValues = new RouteValueDictionary(new { id = 8 }), - AmbientValues = new RouteValueDictionary(new { urgent = false }), - }); - - // Assert - Assert.Empty(foundEndpoints); - } + // Act 1 + var addressScheme = new RouteValuesAddressScheme(new CompositeEndpointDataSource(new[] { dynamicDataSource })); - [Fact] - public void FindEndpoints_LookedUpByCriteria_OneMatch() - { - // Arrange - var endpoint1 = CreateEndpoint( - "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", - defaults: new { zipCode = 3510 }, - metadataRequiredValues: new { id = 7 }); - var endpoint2 = CreateEndpoint( - "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", - defaults: new { id = 12 }); - var addressScheme = CreateAddressScheme(endpoint1, endpoint2); - - // Act - var foundEndpoints = addressScheme.FindEndpoints( - new RouteValuesAddress - { - ExplicitValues = new RouteValueDictionary(new { id = 7 }), - AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }), - }); - - // Assert - var actual = Assert.Single(foundEndpoints); - Assert.Same(endpoint1, actual); - } + // Assert 1 + var state = addressScheme.State; + var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - [Fact] - public void FindEndpoints_LookedUpByCriteria_MultipleMatches() - { - // Arrange - var endpoint1 = CreateEndpoint( - "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", - defaults: new { zipCode = 3510 }, - metadataRequiredValues: new { id = 7 }); - var endpoint2 = CreateEndpoint( - "api/orders/{id}/{name?}/{urgent}/{zipCode}", - defaults: new { id = 12 }, - metadataRequiredValues: new { id = 12 }); - var endpoint3 = CreateEndpoint( - "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", - defaults: new { id = 12 }, - metadataRequiredValues: new { id = 12 }); - var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3); - - // Act - var foundEndpoints = addressScheme.FindEndpoints( - new RouteValuesAddress - { - ExplicitValues = new RouteValueDictionary(new { id = 12 }), - AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }), - }); - - // Assert - Assert.Collection(foundEndpoints, - e => Assert.Equal(endpoint3, e), - e => Assert.Equal(endpoint2, e)); - } + Assert.NotEmpty(allMatches); - [Fact] - public void FindEndpoints_LookedUpByCriteria_ExcludeEndpointWithoutRouteValuesAddressMetadata() - { - // Arrange - var endpoint1 = CreateEndpoint( - "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", - defaults: new { zipCode = 3510 }, - metadataRequiredValues: new { id = 7 }); - var endpoint2 = CreateEndpoint("test"); - - var addressScheme = CreateAddressScheme(endpoint1, endpoint2); - - // Act - var foundEndpoints = addressScheme.FindEndpoints( - new RouteValuesAddress - { - ExplicitValues = new RouteValueDictionary(new { id = 7 }), - AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }), - }).ToList(); - - // Assert - Assert.DoesNotContain(endpoint2, foundEndpoints); - Assert.Contains(endpoint1, foundEndpoints); - } + var match = Assert.Single(allMatches); + var actual = Assert.IsType<RouteEndpoint>(match.Entry.Data); + Assert.Same(endpoint1, actual); - [Fact] - public void FindEndpoints_ReturnsEndpoint_WhenLookedUpByRouteName() - { - // Arrange - var expected = CreateEndpoint( - "api/orders/{id}", - defaults: new { controller = "Orders", action = "GetById" }, - metadataRequiredValues: new { controller = "Orders", action = "GetById" }, - routeName: "OrdersApi"); - var addressScheme = CreateAddressScheme(expected); - - // Act - var foundEndpoints = addressScheme.FindEndpoints( - new RouteValuesAddress - { - ExplicitValues = new RouteValueDictionary(new { id = 10 }), - AmbientValues = new RouteValueDictionary(new { controller = "Home", action = "Index" }), - RouteName = "OrdersApi" - }); - - // Assert - var actual = Assert.Single(foundEndpoints); - Assert.Same(expected, actual); - } + // Arrange 2 + var endpoint2 = CreateEndpoint("/b", routeName: "b"); - [Fact] - public void FindEndpoints_ReturnsEndpoint_UsingRoutePatternRequiredValues() - { - // Arrange - var expected = CreateEndpoint( - "api/orders/{id}", - defaults: new { controller = "Orders", action = "GetById" }, - metadataRequiredValues: new { controller = "Orders", action = "GetById" }); - var addressScheme = CreateAddressScheme(expected); - - // Act - var foundEndpoints = addressScheme.FindEndpoints( - new RouteValuesAddress - { - ExplicitValues = new RouteValueDictionary(new { id = 10 }), - AmbientValues = new RouteValueDictionary(new { controller = "Orders", action = "GetById" }), - }); - - // Assert - var actual = Assert.Single(foundEndpoints); - Assert.Same(expected, actual); - } + // Act 2 + // Trigger change + dynamicDataSource.AddEndpoint(endpoint2); - [Fact] - public void FindEndpoints_AlwaysReturnsEndpointsByRouteName_IgnoringMissingRequiredParameterValues() - { - // Here 'id' is the required value. The endpoint addressScheme would always return an endpoint by looking up - // name only. Its the link generator which uses these endpoints finally to generate a link or not - // based on the required parameter values being present or not. - - // Arrange - var expected = CreateEndpoint( - "api/orders/{id}", - defaults: new { controller = "Orders", action = "GetById" }, - metadataRequiredValues: new { controller = "Orders", action = "GetById" }, - routeName: "OrdersApi"); - var addressScheme = CreateAddressScheme(expected); - - // Act - var foundEndpoints = addressScheme.FindEndpoints( - new RouteValuesAddress - { - ExplicitValues = new RouteValueDictionary(), - AmbientValues = new RouteValueDictionary(), - RouteName = "OrdersApi" - }); - - // Assert - var actual = Assert.Single(foundEndpoints); - Assert.Same(expected, actual); - } + // Assert 2 + Assert.NotSame(state, addressScheme.State); + state = addressScheme.State; - [Fact] - public void GetOutboundMatches_Includes_SameEndpointInNamedMatchesAndMatchesWithRequiredValues() - { - // Arrange - var endpoint = CreateEndpoint( - "api/orders/{id}", - defaults: new { controller = "Orders", action = "GetById" }, - metadataRequiredValues: new { controller = "Orders", action = "GetById" }, - routeName: "a"); - - // Act - var addressScheme = CreateAddressScheme(endpoint); - - // Assert - var matchWithRequiredValue = Assert.Single(addressScheme.State.MatchesWithRequiredValues); - var namedMatches = Assert.Single(addressScheme.State.NamedMatches).Value; - var namedMatch = Assert.Single(namedMatches).Match; - - Assert.Same(endpoint, matchWithRequiredValue.Entry.Data); - Assert.Same(endpoint, namedMatch.Entry.Data); - } + // Arrange 3 + var endpoint3 = CreateEndpoint("/c", routeName: "c"); - // Regression test for https://github.com/dotnet/aspnetcore/issues/35592 - [Fact] - public void GetOutboundMatches_DoesNotInclude_EndpointsWithoutRequiredValuesInMatchesWithRequiredValues() - { - // Arrange - var endpoint = CreateEndpoint( - "api/orders/{id}", - defaults: new { controller = "Orders", action = "GetById" }, - routeName: "a"); + // Act 3 + // Trigger change + dynamicDataSource.AddEndpoint(endpoint3); - // Act - var addressScheme = CreateAddressScheme(endpoint); + // Assert 3 + Assert.NotSame(state, addressScheme.State); + state = addressScheme.State; - // Assert - Assert.Empty(addressScheme.State.MatchesWithRequiredValues); + // Arrange 4 + var endpoint4 = CreateEndpoint("/d", routeName: "d"); - var namedMatches = Assert.Single(addressScheme.State.NamedMatches).Value; - var namedMatch = Assert.Single(namedMatches).Match; - Assert.Same(endpoint, namedMatch.Entry.Data); - } + // Act 4 + // Trigger change + dynamicDataSource.AddEndpoint(endpoint4); - [Fact] - public void GetOutboundMatches_DoesNotInclude_EndpointsWithSuppressLinkGenerationMetadata() - { - // Arrange - var endpoint = CreateEndpoint( - "api/orders/{id}", - defaults: new { controller = "Orders", action = "GetById" }, - metadataRequiredValues: new { controller = "Orders", action = "GetById" }, - routeName: "a", - metadataCollection: new EndpointMetadataCollection(new[] { new SuppressLinkGenerationMetadata() })); - - // Act - var addressScheme = CreateAddressScheme(endpoint); - - // Assert - var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - Assert.Empty(allMatches); - } + // Assert 4 + Assert.NotSame(state, addressScheme.State); + state = addressScheme.State; - [Fact] - public void AddressScheme_UnsuppressedEndpoint_IsUsed() - { - // Arrange - var endpoint = EndpointFactory.CreateRouteEndpoint( - "/a", - metadata: new object[] { new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), new RouteNameMetadata("a"), }); + allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - // Act - var addressScheme = CreateAddressScheme(endpoint); + Assert.NotEmpty(allMatches); + Assert.Collection( + allMatches, + (m) => + { + actual = Assert.IsType<RouteEndpoint>(m.Entry.Data); + Assert.Same(endpoint1, actual); + }, + (m) => + { + actual = Assert.IsType<RouteEndpoint>(m.Entry.Data); + Assert.Same(endpoint2, actual); + }, + (m) => + { + actual = Assert.IsType<RouteEndpoint>(m.Entry.Data); + Assert.Same(endpoint3, actual); + }, + (m) => + { + actual = Assert.IsType<RouteEndpoint>(m.Entry.Data); + Assert.Same(endpoint4, actual); + }); + } - // Assert - var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); - Assert.Same(endpoint, Assert.Single(allMatches).Entry.Data); - } + [Fact] + public void FindEndpoints_LookedUpByCriteria_NoMatch() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", + defaults: new { zipCode = 3510 }, + metadataRequiredValues: new { id = 7 }); + var endpoint2 = CreateEndpoint( + "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", + defaults: new { id = 12 }, + metadataRequiredValues: new { zipCode = 3510 }); + var addressScheme = CreateAddressScheme(endpoint1, endpoint2); + + // Act + var foundEndpoints = addressScheme.FindEndpoints( + new RouteValuesAddress + { + ExplicitValues = new RouteValueDictionary(new { id = 8 }), + AmbientValues = new RouteValueDictionary(new { urgent = false }), + }); - private RouteValuesAddressScheme CreateAddressScheme(params Endpoint[] endpoints) - { - return CreateAddressScheme(new DefaultEndpointDataSource(endpoints)); - } + // Assert + Assert.Empty(foundEndpoints); + } - private RouteValuesAddressScheme CreateAddressScheme(params EndpointDataSource[] dataSources) - { - return new RouteValuesAddressScheme(new CompositeEndpointDataSource(dataSources)); - } + [Fact] + public void FindEndpoints_LookedUpByCriteria_OneMatch() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", + defaults: new { zipCode = 3510 }, + metadataRequiredValues: new { id = 7 }); + var endpoint2 = CreateEndpoint( + "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", + defaults: new { id = 12 }); + var addressScheme = CreateAddressScheme(endpoint1, endpoint2); + + // Act + var foundEndpoints = addressScheme.FindEndpoints( + new RouteValuesAddress + { + ExplicitValues = new RouteValueDictionary(new { id = 7 }), + AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }), + }); - private RouteEndpoint CreateEndpoint( - string template, - object defaults = null, - object metadataRequiredValues = null, - int order = 0, - string routeName = null, - EndpointMetadataCollection metadataCollection = null) - { - if (metadataCollection == null) + // Assert + var actual = Assert.Single(foundEndpoints); + Assert.Same(endpoint1, actual); + } + + [Fact] + public void FindEndpoints_LookedUpByCriteria_MultipleMatches() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", + defaults: new { zipCode = 3510 }, + metadataRequiredValues: new { id = 7 }); + var endpoint2 = CreateEndpoint( + "api/orders/{id}/{name?}/{urgent}/{zipCode}", + defaults: new { id = 12 }, + metadataRequiredValues: new { id = 12 }); + var endpoint3 = CreateEndpoint( + "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", + defaults: new { id = 12 }, + metadataRequiredValues: new { id = 12 }); + var addressScheme = CreateAddressScheme(endpoint1, endpoint2, endpoint3); + + // Act + var foundEndpoints = addressScheme.FindEndpoints( + new RouteValuesAddress { - var metadata = new List<object>(); - if (!string.IsNullOrEmpty(routeName)) - { - metadata.Add(new RouteNameMetadata(routeName)); - } - metadataCollection = new EndpointMetadataCollection(metadata); - } + ExplicitValues = new RouteValueDictionary(new { id = 12 }), + AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }), + }); + + // Assert + Assert.Collection(foundEndpoints, + e => Assert.Equal(endpoint3, e), + e => Assert.Equal(endpoint2, e)); + } - return new RouteEndpoint( - TestConstants.EmptyRequestDelegate, - RoutePatternFactory.Parse(template, defaults, parameterPolicies: null, requiredValues: metadataRequiredValues), - order, - metadataCollection, - null); - } + [Fact] + public void FindEndpoints_LookedUpByCriteria_ExcludeEndpointWithoutRouteValuesAddressMetadata() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}/{name?}/{urgent=true}/{zipCode}", + defaults: new { zipCode = 3510 }, + metadataRequiredValues: new { id = 7 }); + var endpoint2 = CreateEndpoint("test"); + + var addressScheme = CreateAddressScheme(endpoint1, endpoint2); + + // Act + var foundEndpoints = addressScheme.FindEndpoints( + new RouteValuesAddress + { + ExplicitValues = new RouteValueDictionary(new { id = 7 }), + AmbientValues = new RouteValueDictionary(new { zipCode = 3500 }), + }).ToList(); - private static List<Tree.OutboundMatch> GetMatchesWithRequiredValuesPlusNamedMatches(RouteValuesAddressScheme routeValuesAddressScheme) - { - var state = routeValuesAddressScheme.State; + // Assert + Assert.DoesNotContain(endpoint2, foundEndpoints); + Assert.Contains(endpoint1, foundEndpoints); + } - Assert.NotNull(state.MatchesWithRequiredValues); - Assert.NotNull(state.NamedMatches); + [Fact] + public void FindEndpoints_ReturnsEndpoint_WhenLookedUpByRouteName() + { + // Arrange + var expected = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + metadataRequiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "OrdersApi"); + var addressScheme = CreateAddressScheme(expected); + + // Act + var foundEndpoints = addressScheme.FindEndpoints( + new RouteValuesAddress + { + ExplicitValues = new RouteValueDictionary(new { id = 10 }), + AmbientValues = new RouteValueDictionary(new { controller = "Home", action = "Index" }), + RouteName = "OrdersApi" + }); + + // Assert + var actual = Assert.Single(foundEndpoints); + Assert.Same(expected, actual); + } - var namedMatches = state.NamedMatches.Aggregate(Enumerable.Empty<Tree.OutboundMatch>(), - (acc, kvp) => acc.Concat(kvp.Value.Select(matchResult => matchResult.Match))); - return state.MatchesWithRequiredValues.Concat(namedMatches).ToList(); - } + [Fact] + public void FindEndpoints_ReturnsEndpoint_UsingRoutePatternRequiredValues() + { + // Arrange + var expected = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + metadataRequiredValues: new { controller = "Orders", action = "GetById" }); + var addressScheme = CreateAddressScheme(expected); + + // Act + var foundEndpoints = addressScheme.FindEndpoints( + new RouteValuesAddress + { + ExplicitValues = new RouteValueDictionary(new { id = 10 }), + AmbientValues = new RouteValueDictionary(new { controller = "Orders", action = "GetById" }), + }); + + // Assert + var actual = Assert.Single(foundEndpoints); + Assert.Same(expected, actual); + } + + [Fact] + public void FindEndpoints_AlwaysReturnsEndpointsByRouteName_IgnoringMissingRequiredParameterValues() + { + // Here 'id' is the required value. The endpoint addressScheme would always return an endpoint by looking up + // name only. Its the link generator which uses these endpoints finally to generate a link or not + // based on the required parameter values being present or not. + + // Arrange + var expected = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + metadataRequiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "OrdersApi"); + var addressScheme = CreateAddressScheme(expected); + + // Act + var foundEndpoints = addressScheme.FindEndpoints( + new RouteValuesAddress + { + ExplicitValues = new RouteValueDictionary(), + AmbientValues = new RouteValueDictionary(), + RouteName = "OrdersApi" + }); + + // Assert + var actual = Assert.Single(foundEndpoints); + Assert.Same(expected, actual); + } + + [Fact] + public void GetOutboundMatches_Includes_SameEndpointInNamedMatchesAndMatchesWithRequiredValues() + { + // Arrange + var endpoint = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + metadataRequiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "a"); + + // Act + var addressScheme = CreateAddressScheme(endpoint); + + // Assert + var matchWithRequiredValue = Assert.Single(addressScheme.State.MatchesWithRequiredValues); + var namedMatches = Assert.Single(addressScheme.State.NamedMatches).Value; + var namedMatch = Assert.Single(namedMatches).Match; + + Assert.Same(endpoint, matchWithRequiredValue.Entry.Data); + Assert.Same(endpoint, namedMatch.Entry.Data); + } + + // Regression test for https://github.com/dotnet/aspnetcore/issues/35592 + [Fact] + public void GetOutboundMatches_DoesNotInclude_EndpointsWithoutRequiredValuesInMatchesWithRequiredValues() + { + // Arrange + var endpoint = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + routeName: "a"); + + // Act + var addressScheme = CreateAddressScheme(endpoint); + + // Assert + Assert.Empty(addressScheme.State.MatchesWithRequiredValues); + + var namedMatches = Assert.Single(addressScheme.State.NamedMatches).Value; + var namedMatch = Assert.Single(namedMatches).Match; + Assert.Same(endpoint, namedMatch.Entry.Data); + } + + [Fact] + public void GetOutboundMatches_DoesNotInclude_EndpointsWithSuppressLinkGenerationMetadata() + { + // Arrange + var endpoint = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + metadataRequiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "a", + metadataCollection: new EndpointMetadataCollection(new[] { new SuppressLinkGenerationMetadata() })); + + // Act + var addressScheme = CreateAddressScheme(endpoint); + + // Assert + var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); + Assert.Empty(allMatches); + } + + [Fact] + public void AddressScheme_UnsuppressedEndpoint_IsUsed() + { + // Arrange + var endpoint = EndpointFactory.CreateRouteEndpoint( + "/a", + metadata: new object[] { new SuppressLinkGenerationMetadata(), new EncourageLinkGenerationMetadata(), new RouteNameMetadata("a"), }); + + // Act + var addressScheme = CreateAddressScheme(endpoint); - private class EncourageLinkGenerationMetadata : ISuppressLinkGenerationMetadata + // Assert + var allMatches = GetMatchesWithRequiredValuesPlusNamedMatches(addressScheme); + Assert.Same(endpoint, Assert.Single(allMatches).Entry.Data); + } + + private RouteValuesAddressScheme CreateAddressScheme(params Endpoint[] endpoints) + { + return CreateAddressScheme(new DefaultEndpointDataSource(endpoints)); + } + + private RouteValuesAddressScheme CreateAddressScheme(params EndpointDataSource[] dataSources) + { + return new RouteValuesAddressScheme(new CompositeEndpointDataSource(dataSources)); + } + + private RouteEndpoint CreateEndpoint( + string template, + object defaults = null, + object metadataRequiredValues = null, + int order = 0, + string routeName = null, + EndpointMetadataCollection metadataCollection = null) + { + if (metadataCollection == null) { - public bool SuppressLinkGeneration => false; + var metadata = new List<object>(); + if (!string.IsNullOrEmpty(routeName)) + { + metadata.Add(new RouteNameMetadata(routeName)); + } + metadataCollection = new EndpointMetadataCollection(metadata); } + + return new RouteEndpoint( + TestConstants.EmptyRequestDelegate, + RoutePatternFactory.Parse(template, defaults, parameterPolicies: null, requiredValues: metadataRequiredValues), + order, + metadataCollection, + null); + } + + private static List<Tree.OutboundMatch> GetMatchesWithRequiredValuesPlusNamedMatches(RouteValuesAddressScheme routeValuesAddressScheme) + { + var state = routeValuesAddressScheme.State; + + Assert.NotNull(state.MatchesWithRequiredValues); + Assert.NotNull(state.NamedMatches); + + var namedMatches = state.NamedMatches.Aggregate(Enumerable.Empty<Tree.OutboundMatch>(), + (acc, kvp) => acc.Concat(kvp.Value.Select(matchResult => matchResult.Match))); + return state.MatchesWithRequiredValues.Concat(namedMatches).ToList(); + } + + private class EncourageLinkGenerationMetadata : ISuppressLinkGenerationMetadata + { + public bool SuppressLinkGeneration => false; } } |