|
// 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.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.InternalTesting;
namespace Microsoft.AspNetCore.Mvc.Infrastructure;
// The ActionSelectionTable has different code paths for ActionDescriptor and
// RouteEndpoint for creating a table. We're trying to test both code paths
// for creation, but selection works the same for both cases.
public class ActionSelectionTableTest
{
[Fact]
public void Select_SingleMatch()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
},
new ActionDescriptor()
{
DisplayName = "A2",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "About" }
},
},
};
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
Assert.Collection(matches, (a) => Assert.Same(actions[0], a));
}
[Fact]
[ReplaceCulture("de-CH", "de-CH")]
public void Select_ActionDescriptor_SingleMatch_UsesInvariantCulture()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" },
{ "date", "10/31/2018 07:37:38 -07:00" },
},
},
new ActionDescriptor()
{
DisplayName = "A2",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "About" }
},
},
};
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { controller = "Home", action = "Index", });
values.Add(
"date",
new DateTimeOffset(2018, 10, 31, 7, 37, 38, TimeSpan.FromHours(-7)));
// Act
var matches = table.Select(values);
// Assert
Assert.Collection(matches, (a) => Assert.Same(actions[0], a));
}
[Fact]
[ReplaceCulture("de-CH", "de-CH")]
public void Select_Endpoint_SingleMatch_UsesInvariantCulture()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" },
{ "date", "10/31/2018 07:37:38 -07:00" },
},
},
new ActionDescriptor()
{
DisplayName = "A2",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "About" }
},
},
};
var table = CreateTableWithEndpoints(actions);
var values = new RouteValueDictionary(new { controller = "Home", action = "Index", });
values.Add(
"date",
new DateTimeOffset(2018, 10, 31, 7, 37, 38, TimeSpan.FromHours(-7)));
// Act
var matches = table.Select(values);
// Assert
Assert.Collection(matches, (e) => Assert.Same(actions[0], e.Metadata.GetMetadata<ActionDescriptor>()));
}
[Fact]
public void Select_ActionDescriptor_MultipleMatches()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
},
new ActionDescriptor()
{
DisplayName = "A2",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
},
};
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
Assert.Equal(actions.ToArray(), matches.ToArray());
}
[Fact]
public void Select_Endpoint_MultipleMatches()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
},
new ActionDescriptor()
{
DisplayName = "A2",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
},
};
var table = CreateTableWithEndpoints(actions);
var values = new RouteValueDictionary(new { controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
Assert.Equal(actions.ToArray(), matches.Select(e => e.Metadata.GetMetadata<ActionDescriptor>()).ToArray());
}
[Fact]
public void Select_NoMatch()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
},
new ActionDescriptor()
{
DisplayName = "A2",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "About" }
},
},
};
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { controller = "Foo", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
Assert.Empty(matches);
}
[Fact]
public void Select_ActionDescriptors_NoMatch_ExcludesAttributeRoutedActions()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
AttributeRouteInfo = new AttributeRouteInfo()
{
Template = "/Home",
}
},
};
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
Assert.Empty(matches);
}
[Fact]
public void Select_Endpoint_Match_IncludesAttributeRoutedActions()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
AttributeRouteInfo = new AttributeRouteInfo()
{
Template = "/Home",
}
},
};
var table = CreateTableWithEndpoints(actions);
var values = new RouteValueDictionary(new { controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
Assert.Single(matches);
}
// In this context `CaseSensitiveMatch` means that the input route values exactly match one of the action
// descriptor's route values in terms of casing. This is important because we optimize for this case
// in the implementation.
[Fact]
public void Select_Match_CaseSensitiveMatch_IncludesAllCaseInsensitiveMatches()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
},
new ActionDescriptor()
{
DisplayName = "A2",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "home" },
{ "action", "Index" }
},
},
new ActionDescriptor() // This won't match the request
{
DisplayName = "A3",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "About" }
},
},
};
var expected = actions.Take(2).ToArray();
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
Assert.Equal(expected, matches);
}
// In this context `CaseInsensitiveMatch` means that the input route values do not match any action
// descriptor's route values in terms of casing. This is important because we optimize for the case
// where the casing matches - the non-matching-casing path is handled a bit differently.
[Fact]
public void Select_Match_CaseInsensitiveMatch_IncludesAllCaseInsensitiveMatches()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "Index" }
},
},
new ActionDescriptor()
{
DisplayName = "A2",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "home" },
{ "action", "Index" }
},
},
new ActionDescriptor() // This won't match the request
{
DisplayName = "A3",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "controller", "Home" },
{ "action", "About" }
},
},
};
var expected = actions.Take(2).ToArray();
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { controller = "HOME", action = "iNDex", });
// Act
var matches = table.Select(values);
// Assert
Assert.Equal(expected, matches);
}
[Fact]
public void Select_Match_CaseSensitiveMatch_MatchesOnEmptyString()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "area", null },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};
var table = CreateTableWithActionDescriptors(actions);
// Example: In conventional route, one could set non-inline defaults
// new { area = "", controller = "Home", action = "Index" }
var values = new RouteValueDictionary(new { area = "", controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
var action = Assert.Single(matches);
Assert.Same(actions[0], action);
}
[Fact]
public void Select_Match_CaseInsensitiveMatch_MatchesOnEmptyString()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "area", null },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};
var table = CreateTableWithActionDescriptors(actions);
// Example: In conventional route, one could set non-inline defaults
// new { area = "", controller = "Home", action = "Index" }
var values = new RouteValueDictionary(new { area = "", controller = "HoMe", action = "InDeX", });
// Act
var matches = table.Select(values);
// Assert
var action = Assert.Single(matches);
Assert.Same(actions[0], action);
}
[Fact]
public void Select_Match_MatchesOnNull()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "area", null },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};
var table = CreateTableWithActionDescriptors(actions);
// Example: In conventional route, one could set non-inline defaults
// new { area = (string)null, controller = "Foo", action = "Index" }
var values = new RouteValueDictionary(new { area = (string)null, controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
var action = Assert.Single(matches);
Assert.Same(actions[0], action);
}
[Fact]
public void Select_Match_ActionDescriptorWithEmptyRouteValues_MatchesOnEmptyString()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "foo", "" },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { foo = "", controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
var action = Assert.Single(matches);
Assert.Same(actions[0], action);
}
[Fact]
public void Select_Match_ActionDescriptorWithEmptyRouteValues_MatchesOnNull()
{
var actions = new ActionDescriptor[]
{
new ActionDescriptor()
{
DisplayName = "A1",
RouteValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "foo", "" },
{ "controller", "Home" },
{ "action", "Index" }
},
}
};
var table = CreateTableWithActionDescriptors(actions);
var values = new RouteValueDictionary(new { foo = (string)null, controller = "Home", action = "Index", });
// Act
var matches = table.Select(values);
// Assert
var action = Assert.Single(matches);
Assert.Same(actions[0], action);
}
private static ActionSelectionTable<ActionDescriptor> CreateTableWithActionDescriptors(IReadOnlyList<ActionDescriptor> actions)
{
return ActionSelectionTable<ActionDescriptor>.Create(new ActionDescriptorCollection(actions, 0));
}
private static ActionSelectionTable<Endpoint> CreateTableWithEndpoints(IReadOnlyList<ActionDescriptor> actions)
{
var endpoints = actions.Select(a =>
{
var metadata = new List<object>(a.EndpointMetadata ?? Array.Empty<object>());
metadata.Add(a);
return new Endpoint(
requestDelegate: context => Task.CompletedTask,
metadata: new EndpointMetadataCollection(metadata),
displayName: a.DisplayName);
});
return ActionSelectionTable<ActionDescriptor>.Create(endpoints);
}
}
|