Skip to content

Commit c9cefa1

Browse files
Upgrade s_routeParameterRegex to [GeneratedRegex] and add direct regex unit tests
- Make RouteConfigurationService partial to support [GeneratedRegex] - Replace static readonly Regex field with [GeneratedRegex] source-generated method - Make RouteParameterRegex() internal for testability - Add Properties/AssemblyInfo.cs with [InternalsVisibleTo("EssentialCSharp.Web.Tests")] - Add RouteParameterFilterTests.cs: 13 focused unit tests calling the regex method directly (curly-brace params, constrained params, optional params, catch-all, square brackets, plus static routes that must not match)
1 parent e828ea4 commit c9cefa1

3 files changed

Lines changed: 74 additions & 4 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using EssentialCSharp.Web.Services;
2+
3+
namespace EssentialCSharp.Web.Tests;
4+
5+
/// <summary>
6+
/// Unit tests for the route parameter regex in RouteConfigurationService.
7+
/// Calls RouteParameterRegex() directly to verify which patterns match (and should be
8+
/// excluded from the sitemap) vs. which do not (and should be included).
9+
/// </summary>
10+
public class RouteParameterFilterTests
11+
{
12+
// --- patterns that SHOULD match (route is parameterised → excluded from sitemap) ---
13+
14+
[Test]
15+
public async Task RouteParameterRegex_MatchesSimpleCurlyBraceParameter()
16+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("{chapter}")).IsTrue();
17+
18+
[Test]
19+
public async Task RouteParameterRegex_MatchesConstrainedParameter()
20+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("{id:guid}")).IsTrue();
21+
22+
[Test]
23+
public async Task RouteParameterRegex_MatchesOptionalParameter()
24+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("{id?}")).IsTrue();
25+
26+
[Test]
27+
public async Task RouteParameterRegex_MatchesParameterWithDefault()
28+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("{action=Index}")).IsTrue();
29+
30+
[Test]
31+
public async Task RouteParameterRegex_MatchesCatchAllParameter()
32+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("{*catchall}")).IsTrue();
33+
34+
[Test]
35+
public async Task RouteParameterRegex_MatchesParameterEmbeddedInPath()
36+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("chapter/{chapter}/listing/{listing}")).IsTrue();
37+
38+
[Test]
39+
public async Task RouteParameterRegex_MatchesSquareBracketSegment()
40+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("[optional]")).IsTrue();
41+
42+
[Test]
43+
public async Task RouteParameterRegex_MatchesSquareBracketEmbeddedInPath()
44+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("area/[optional]/page")).IsTrue();
45+
46+
// --- patterns that should NOT match (static route → eligible for sitemap) ---
47+
48+
[Test]
49+
public async Task RouteParameterRegex_DoesNotMatchBareWord()
50+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("about")).IsFalse();
51+
52+
[Test]
53+
public async Task RouteParameterRegex_DoesNotMatchHyphenatedRoute()
54+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("mcp-setup")).IsFalse();
55+
56+
[Test]
57+
public async Task RouteParameterRegex_DoesNotMatchStaticMultiSegmentPath()
58+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("identity/account/login")).IsFalse();
59+
60+
[Test]
61+
public async Task RouteParameterRegex_DoesNotMatchApiPrefixAlone()
62+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch("api/listing")).IsFalse();
63+
64+
[Test]
65+
public async Task RouteParameterRegex_DoesNotMatchEmptyString()
66+
=> await Assert.That(RouteConfigurationService.RouteParameterRegex().IsMatch(string.Empty)).IsFalse();
67+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("EssentialCSharp.Web.Tests")]

EssentialCSharp.Web/Services/RouteConfigurationService.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
namespace EssentialCSharp.Web.Services;
55

6-
public class RouteConfigurationService : IRouteConfigurationService
6+
public partial class RouteConfigurationService : IRouteConfigurationService
77
{
8-
private static readonly Regex s_routeParameterRegex =
9-
new(@"\{[^}]+\}|\[[^\]]+\]", RegexOptions.Compiled);
8+
[GeneratedRegex(@"\{[^}]+\}|\[[^\]]+\]")]
9+
internal static partial Regex RouteParameterRegex();
1010

1111
private readonly IActionDescriptorCollectionProvider _ActionDescriptorCollectionProvider;
1212
private readonly HashSet<string> _StaticRoutes;
@@ -75,7 +75,7 @@ private HashSet<string> ExtractIndexableRoutes()
7575
{
7676
return _StaticRoutes
7777
.Where(route => !route.StartsWith("api/", StringComparison.OrdinalIgnoreCase))
78-
.Where(route => !s_routeParameterRegex.IsMatch(route))
78+
.Where(route => !RouteParameterRegex().IsMatch(route))
7979
.Where(route => !route.Contains("identity", StringComparison.OrdinalIgnoreCase))
8080
.ToHashSet(StringComparer.OrdinalIgnoreCase);
8181
}

0 commit comments

Comments
 (0)