Files
jobtrackingapp/JobTrackerApi.Tests/RouteUniquenessTests.cs
T

43 lines
1.9 KiB
C#

using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Xunit;
namespace JobTrackerApi.Tests;
public sealed class RouteUniquenessTests
{
[Fact]
public void Controller_actions_have_one_handler_per_http_method_and_route()
{
var actions = typeof(JobTrackerApi.Controllers.JobApplicationsController).Assembly.GetTypes()
.Where(type => !type.IsAbstract && typeof(ControllerBase).IsAssignableFrom(type))
.SelectMany(type => type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)
.SelectMany(method => method.GetCustomAttributes<HttpMethodAttribute>()
.Select(attribute => new
{
Action = $"{type.Name}.{method.Name}",
Methods = string.Join(",", attribute.HttpMethods.OrderBy(x => x)),
Route = Normalize(type, attribute.Template),
})))
.ToList();
var duplicates = actions
.GroupBy(x => $"{x.Methods} {x.Route}", StringComparer.OrdinalIgnoreCase)
.Where(group => group.Count() > 1)
.Select(group => $"{group.Key}: {string.Join(", ", group.Select(x => x.Action))}")
.ToList();
Assert.Empty(duplicates);
}
private static string Normalize(Type controller, string? actionTemplate)
{
var prefix = controller.GetCustomAttribute<RouteAttribute>()?.Template ?? string.Empty;
prefix = prefix.Replace("[controller]", controller.Name[..^"Controller".Length], StringComparison.OrdinalIgnoreCase);
var route = $"{prefix.TrimEnd('/')}/{(actionTemplate ?? string.Empty).TrimStart('/')}".ToLowerInvariant();
return Regex.Replace(route, @"\{[^}:]+(?<constraint>:[^}]+)?\}", match => $"{{parameter{match.Groups["constraint"].Value}}}");
}
}