File: Patterns\RoutePatternDebugStringFormatterTest.cs
Web Access
Project: src\src\Http\Routing\test\UnitTests\Microsoft.AspNetCore.Routing.Tests.csproj (Microsoft.AspNetCore.Routing.Tests)
// 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.Routing.Constraints;
 
namespace Microsoft.AspNetCore.Routing.Patterns;
 
public class RoutePatternDebugStringFormatterTest
{
    [Fact]
    public void DebuggerToString_WithRequiredValues_ReplacesMatchingParameters()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "{controller=Home}/{action=Index}/{id?}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new { controller = "Store", action = "Index" });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("Store/Index/{id?}", result);
    }
 
    [Fact]
    public void DebuggerToString_WithPartialRequiredValues_ReplacesOnlyMatchingParameters()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "{controller}/{action}/{id?}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new { controller = "Products" });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("Products/{action}/{id?}", result);
    }
 
    [Fact]
    public void DebuggerToString_WithRequiredValueAny_DoesNotReplaceParameter()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "{controller}/{action}/{id?}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new RouteValueDictionary
            {
                { "controller", RoutePattern.RequiredValueAny },
                { "action", "Index" }
            });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("{controller}/Index/{id?}", result);
    }
 
    [Fact]
    public void DebuggerToString_WithNullRequiredValue_DoesNotReplaceParameter()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "{controller}/{action}/{id?}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new RouteValueDictionary
            {
                { "controller", null },
                { "action", "Index" }
            });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("{controller}/Index/{id?}", result);
    }
 
    [Fact]
    public void DebuggerToString_WithEmptyStringRequiredValue_DoesNotReplaceParameter()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "{controller}/{action}/{id?}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new RouteValueDictionary
            {
                { "controller", "" },
                { "action", "Index" }
            });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("{controller}/Index/{id?}", result);
    }
 
    [Fact]
    public void DebuggerToString_WithCatchAllParameter_ReplacesWithRequiredValue()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "{controller}/{*path}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new { controller = "Files", path = "docs/readme.md" });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("Files/docs/readme.md", result);
    }
 
    [Fact]
    public void DebuggerToString_WithComplexSegment_ReplacesMatchingParameters()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "{controller}-{action}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new { controller = "Home", action = "Index" });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("Home-Index", result);
    }
 
    [Fact]
    public void DebuggerToString_WithLiteralSegments_PreservesLiterals()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "api/{controller}/{id}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new { controller = "Products" });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("api/Products/{id}", result);
    }
 
    [Fact]
    public void DebuggerToString_WithConstraints_PreservesConstraintsForUnmatchedParameters()
    {
        // Arrange
        var pattern = RoutePatternFactory.Parse(
            "{controller}/{action}/{id:int}",
            defaults: null,
            parameterPolicies: null,
            requiredValues: new { controller = "Store" });
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal("Store/{action}/{id:int}", result);
    }
 
    [Fact]
    public void DebuggerToString_WithMultipleConstraints_PreservesAllConstraints()
    {
        // Arrange
        var template = "{a:int}/{b:regex(^\\d+$)}/{c:int}";
        var defaults = new { a = 0 }; // Required value needs a corresponding parameter or default
        var constraints = new { b = "fizz", c = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
        var requiredValues = new { a = "test" };
 
        var pattern = RoutePatternFactory.Parse(template, defaults, constraints, requiredValues);
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        // Constraints added via parameterPolicies come first, then inline constraints
        // RegexRouteConstraint shows as regex(pattern) format
        // String constraints like "fizz" are converted to RegexRouteConstraint with pattern "^(fizz)$"
        // RegexRouteConstraint("foo") uses raw pattern "foo"
        Assert.Equal("test/{b:regex(^(fizz)$):regex(^\\d+$)}/{c:regex(foo):regex(bar):regex(^(baz)$):int}", result);
    }
 
    [Theory]
    [InlineData("{controller=Home}/{action=Index}/{id?}", "", "{controller=Home}/{action=Index}/{id?}")]
    [InlineData("{controller}/{action}/{id?}", "controller=Home,action=Index", "Home/Index/{id?}")]
    [InlineData("{controller}/{action}", "controller=Products", "Products/{action}")]
    [InlineData("{controller}/{id:int}", "controller=Orders", "Orders/{id:int}")]
    [InlineData("{controller:alpha}/{action:alpha}", "controller=Home,action=Index", "Home/Index")]
    [InlineData("{controller=Home}/{action=Index}", "controller=Blog", "Blog/{action=Index}")]
    [InlineData("api-{version}-{controller}", "version=v1,controller=Users", "api-v1-Users")]
    [InlineData("{controller}/{*path}", "controller=Files", "Files/{*path}")]
    [InlineData("{controller}/{**path}", "controller=Files", "Files/{**path}")]
    [InlineData("api/v1/{controller}", "controller=Users", "api/v1/Users")]
    [InlineData("{id:int:range(1,100)}", "id=50", "50")]
    [InlineData("{a}/{b}/{c}", "a=1,c=3", "1/{b}/3")]
    [InlineData("prefix-{param}-suffix", "param=value", "prefix-value-suffix")]
    [InlineData("{a:int}/{b:int}/{c:int}", "b=2", "{a:int}/2/{c:int}")]
    [InlineData("{a:int}/{b:int}/{c:int}", "b=", "{a:int}/{b:int}/{c:int}")] // Empty string should not replace
    [InlineData("/{controller}/{action}", "controller=Home,action=Index", "/Home/Index")]
    [InlineData("/{controller}/{action}/{id?}", "controller=Store", "/Store/{action}/{id?}")]
    [InlineData("/{controller}/{action}", "", "/{controller}/{action}")]
    [InlineData("", "", "/")]
    [InlineData("/", "", "/")]
    public void DebuggerToString_ProducesExpectedOutput(string template, string requiredValuesText, string expectedOutput)
    {
        // Arrange
        var requiredValues = string.IsNullOrEmpty(requiredValuesText) ? null : ParseRequiredValues(requiredValuesText);
        var pattern = RoutePatternFactory.Parse(template, defaults: null, parameterPolicies: null, requiredValues: requiredValues);
 
        // Act
        var result = pattern.DebuggerToString();
 
        // Assert
        Assert.Equal(expectedOutput, result);
    }
 
    private static RouteValueDictionary ParseRequiredValues(string requiredValuesText)
    {
        var requiredValues = new RouteValueDictionary();
        foreach (var pair in requiredValuesText.Split(','))
        {
            var eqIndex = pair.IndexOf('=');
            var key = pair.Substring(0, eqIndex);
            var value = pair.Substring(eqIndex + 1);
            requiredValues[key] = value;
        }
        return requiredValues;
    }
}