File: Http\HttpParserTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Telemetry.Tests\Microsoft.Extensions.Telemetry.Tests.csproj (Microsoft.Extensions.Telemetry.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Compliance.Classification;
using Microsoft.Extensions.Compliance.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Diagnostics;
using Xunit;
 
namespace Microsoft.Extensions.Http.Diagnostics.Test;
 
public class HttpParserTests
{
    [Theory]
    [CombinatorialData]
    public void TryExtractParameters_NullRouteParametersArray_ReturnsFalse(HttpRouteParameterRedactionMode redactionMode)
    {
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new() { { "chatId", FakeTaxonomy.PrivateData } };
 
        string httpPath = "api/routes/routeId123/chats/chatId123";
        string httpRoute = "/api/routes/{routeId}/chats/{chatId}";
 
        var routeSegments = httpParser.ParseRoute(httpRoute);
 
        HttpRouteParameter[] httpRouteParameters = null!;
        var success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.False(success);
    }
 
    [Theory]
    [CombinatorialData]
    public void TryExtractParameters_RouteParametersArraySmallerThanActualParamCount_ReturnsFalse(HttpRouteParameterRedactionMode redactionMode)
    {
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new() { { "chatId", FakeTaxonomy.PrivateData } };
 
        string httpPath = "api/routes/routeId123/chats/chatId123";
        string httpRoute = "/api/routes/{routeId}/chats/{chatId}";
 
        var routeSegments = httpParser.ParseRoute(httpRoute);
 
        HttpRouteParameter[] httpRouteParameters = new HttpRouteParameter[1];
        var success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.False(success);
    }
 
    [Theory]
    [InlineData(-1)]
    [InlineData(3)]
    [InlineData(4)]
    public void TryExtractParameters_InvalidHttpRouteParameterRedactionMode_Throws(int redactionMode)
    {
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new() { { "routeId", FakeTaxonomy.PrivateData } };
 
        string httpPath = "routeId123/chats/chatId123/";
        string httpRoute = "{routeId}/chats/{chatId}/";
        var routeSegments = httpParser.ParseRoute(httpRoute);
 
        HttpRouteParameter[] httpRouteParameters = new HttpRouteParameter[2];
        var ex = Assert.Throws<InvalidOperationException>(
            () => httpParser.TryExtractParameters(httpPath, routeSegments, (HttpRouteParameterRedactionMode)redactionMode, parametersToRedact, ref httpRouteParameters));
        Assert.Equal(TelemetryCommonExtensions.UnsupportedEnumValueExceptionMessage, ex.Message);
    }
 
    [Theory]
    [CombinatorialData]
    public void TryExtractParameters_RouteHasFirstParameterToBeRedacted_ReturnsCorrectParameters(HttpRouteParameterRedactionMode redactionMode)
    {
        bool isRedacted = redactionMode != HttpRouteParameterRedactionMode.None;
        string redactedPrefix = isRedacted ? "Redacted:" : string.Empty;
 
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new() { { "routeId", FakeTaxonomy.PrivateData } };
 
        string httpPath = "routeId123/chats/chatId123/";
        string httpRoute = "{routeId}/chats/{chatId}/";
        var routeSegments = httpParser.ParseRoute(httpRoute);
 
        HttpRouteParameter[] httpRouteParameters = new HttpRouteParameter[2];
        var success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
 
        ValidateRouteParameter(httpRouteParameters[0], "routeId", $"{redactedPrefix}routeId123", isRedacted);
 
        if (redactionMode == HttpRouteParameterRedactionMode.Strict)
        {
            ValidateRouteParameter(httpRouteParameters[1], "chatId", TelemetryConstants.Redacted, true);
        }
 
        if (redactionMode == HttpRouteParameterRedactionMode.Loose)
        {
            ValidateRouteParameter(httpRouteParameters[1], "chatId", "chatId123", false);
        }
 
        // route begins with forward slash
        httpPath = "routeId123/chats/chatId123/";
        httpRoute = "/{routeId}/chats/{chatId}/";
        routeSegments = httpParser.ParseRoute(httpRoute);
 
        httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
 
        ValidateRouteParameter(httpRouteParameters[0], "routeId", $"{redactedPrefix}routeId123", isRedacted);
 
        if (redactionMode == HttpRouteParameterRedactionMode.Strict)
        {
            ValidateRouteParameter(httpRouteParameters[1], "chatId", TelemetryConstants.Redacted, true);
        }
 
        if (redactionMode == HttpRouteParameterRedactionMode.Loose)
        {
            ValidateRouteParameter(httpRouteParameters[1], "chatId", "chatId123", false);
        }
    }
 
    [Theory]
    [CombinatorialData]
    public void TryExtractParameters_NoParameters_ReturnEmptyArray(HttpRouteParameterRedactionMode redactionMode)
    {
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new() { { "routeId", FakeTaxonomy.PrivateData } };
 
        string httpPath = "users/chats/messages";
        string httpRoute = "users/chats/messages";
        var routeSegments = httpParser.ParseRoute(httpRoute);
        HttpRouteParameter[] httpRouteParameters = new HttpRouteParameter[0];
        var success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.Empty(httpRouteParameters);
 
        httpPath = "/";
        httpRoute = "/";
        routeSegments = httpParser.ParseRoute(httpRoute);
        httpRouteParameters = new HttpRouteParameter[0];
        success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.Empty(httpRouteParameters);
 
        httpPath = "";
        httpRoute = "/";
        routeSegments = httpParser.ParseRoute(httpRoute);
        httpRouteParameters = new HttpRouteParameter[0];
        success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.Empty(httpRouteParameters);
    }
 
    [Theory]
    [CombinatorialData]
    public void TryExtractParameters_ReturnsExpectedParameters(HttpRouteParameterRedactionMode redactionMode)
    {
        bool isRedacted = redactionMode != HttpRouteParameterRedactionMode.None;
        string redactedPrefix = isRedacted ? "Redacted:" : string.Empty;
 
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new() { { "chatId", FakeTaxonomy.PrivateData } };
 
        // Route ends with text
        string httpPath = "api/routes/routeId123/chats/chatId123";
        string httpRoute = "/api/routes/{routeId}/chats/{chatId}";
 
        var routeSegments = httpParser.ParseRoute(httpRoute);
 
        HttpRouteParameter[] httpRouteParameters = new HttpRouteParameter[2];
        var success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        if (redactionMode == HttpRouteParameterRedactionMode.Strict)
        {
            ValidateRouteParameter(httpRouteParameters[0], "routeId", TelemetryConstants.Redacted, true);
        }
 
        if (redactionMode == HttpRouteParameterRedactionMode.Loose)
        {
            ValidateRouteParameter(httpRouteParameters[0], "routeId", "routeId123", false);
        }
 
        ValidateRouteParameter(httpRouteParameters[1], "chatId", $"{redactedPrefix}chatId123", isRedacted);
 
        // Route ends with parameter that needs to be redacted
        httpPath = "api/routes/routeId123/chats/chatId123/";
        httpRoute = "/api/routes/{routeId}/chats/{chatId}/";
        success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        if (redactionMode == HttpRouteParameterRedactionMode.Strict)
        {
            ValidateRouteParameter(httpRouteParameters[0], "routeId", TelemetryConstants.Redacted, true);
        }
 
        if (redactionMode == HttpRouteParameterRedactionMode.Loose)
        {
            ValidateRouteParameter(httpRouteParameters[0], "routeId", "routeId123", false);
        }
 
        ValidateRouteParameter(httpRouteParameters[1], "chatId", $"{redactedPrefix}chatId123", isRedacted);
 
        // Route ends with parameter that doesn't need to be redacted
        parametersToRedact.Add("routeId", FakeTaxonomy.PrivateData);
        parametersToRedact.Remove("chatId");
        httpPath = "api/routes/routeId123/chats/chatId123";
        httpRoute = "/api/routes/{routeId}/chats/{chatId}";
        success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "routeId", $"{redactedPrefix}routeId123", isRedacted);
 
        if (redactionMode == HttpRouteParameterRedactionMode.Strict)
        {
            ValidateRouteParameter(httpRouteParameters[1], "chatId", TelemetryConstants.Redacted, true);
        }
 
        if (redactionMode == HttpRouteParameterRedactionMode.Loose)
        {
            ValidateRouteParameter(httpRouteParameters[1], "chatId", "chatId123", false);
        }
    }
 
    [Theory]
    [CombinatorialData]
    public void TryExtractParameters_WithSomeParametersWithPublicNonPersonalData_ReturnsExpectedParameters(HttpRouteParameterRedactionMode redactionMode)
    {
        bool isRedacted = redactionMode != HttpRouteParameterRedactionMode.None;
        string redactedPrefix = isRedacted ? "Redacted:" : string.Empty;
 
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new() { { "chatId", FakeTaxonomy.PrivateData } };
 
        // Route ends with text
        string httpPath = "api/routes/routeId123/chats/chatId123";
        string httpRoute = "/api/routes/{routeId}/chats/{chatId}";
 
        var routeSegments = httpParser.ParseRoute(httpRoute);
 
        HttpRouteParameter[] httpRouteParameters = new HttpRouteParameter[2];
        var success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        if (redactionMode == HttpRouteParameterRedactionMode.Strict)
        {
            ValidateRouteParameter(httpRouteParameters[0], "routeId", TelemetryConstants.Redacted, true);
        }
 
        if (redactionMode == HttpRouteParameterRedactionMode.Loose)
        {
            ValidateRouteParameter(httpRouteParameters[0], "routeId", "routeId123", false);
        }
 
        ValidateRouteParameter(httpRouteParameters[1], "chatId", $"{redactedPrefix}chatId123", isRedacted);
 
        // Route ends with parameter that needs to be redacted
        parametersToRedact.Add("routeId", DataClassification.None);
        httpPath = "api/routes/routeId123/chats/chatId123/";
        httpRoute = "/api/routes/{routeId}/chats/{chatId}/";
        success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "routeId", "routeId123", false);
        ValidateRouteParameter(httpRouteParameters[1], "chatId", $"{redactedPrefix}chatId123", isRedacted);
 
        // Route ends with parameter that doesn't need to be redacted
        parametersToRedact.Remove("chatId");
        parametersToRedact.Add("chatId", DataClassification.None);
        httpPath = "api/routes/routeId123/chats/chatId123";
        httpRoute = "/api/routes/{routeId}/chats/{chatId}";
        success = httpParser.TryExtractParameters(httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "routeId", "routeId123", false);
        ValidateRouteParameter(httpRouteParameters[1], "chatId", "chatId123", false);
    }
 
    [Theory]
    [CombinatorialData]
    public void TryExtractParameters_WhenRouteHasDefaultParameters_ReturnsExpectedParameters(HttpRouteParameterRedactionMode redactionMode)
    {
        bool isRedacted = redactionMode != HttpRouteParameterRedactionMode.None;
        string redactedPrefix = isRedacted ? "Redacted:" : string.Empty;
 
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new() { { "filter", FakeTaxonomy.PrivateData } };
        HttpRouteParameter[] httpRouteParameters = new HttpRouteParameter[3];
 
        const string HttpRoute = "{controller=home}/{action=index}/{filter=all}";
        ParsedRouteSegments routeSegments = httpParser.ParseRoute(HttpRoute);
 
        // An http path includes well known "controller" and "action" parameters, and a parameter "filter".
        // Well known parameters are not redacted, a parameter with default value is redacted.
        string httpPath = "users/list/top10";
 
        bool success = httpParser.TryExtractParameters(
            httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "controller", "users", false);
        ValidateRouteParameter(httpRouteParameters[1], "action", "list", false);
        ValidateRouteParameter(httpRouteParameters[2], "filter", $"{redactedPrefix}top10", isRedacted);
 
        // An http path doesn't include some of the optional parameters.
        // Well known parameters are not redacted, a missing parameter with default value is not redacted.
        httpPath = "users";
 
        success = httpParser.TryExtractParameters(
            httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "controller", "users", false);
        ValidateRouteParameter(httpRouteParameters[1], "action", "index", false);
        ValidateRouteParameter(httpRouteParameters[2], "filter", "all", false);
 
        // An http path doesn't include all optional parameters.
        // Well known parameters are not redacted,
        // a missing parameter with default value is not redacted.
        httpPath = "";
 
        success = httpParser.TryExtractParameters(
            httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "controller", "home", false);
        ValidateRouteParameter(httpRouteParameters[1], "action", "index", false);
        ValidateRouteParameter(httpRouteParameters[2], "filter", "all", false);
 
        // A well known parameter is redacted when it is explicitly specified in an http path,
        // and is not redacted when it is omitted.
        parametersToRedact.Add("controller", FakeTaxonomy.PrivateData);
        parametersToRedact.Add("action", FakeTaxonomy.PrivateData);
 
        httpPath = "users";
 
        success = httpParser.TryExtractParameters(
            httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "controller", $"{redactedPrefix}users", isRedacted);
        ValidateRouteParameter(httpRouteParameters[1], "action", "index", false);
        ValidateRouteParameter(httpRouteParameters[2], "filter", "all", false);
    }
 
    [Theory]
    [CombinatorialData]
    public void TryExtractParameters_WhenRouteHasOptionalsAndConstraints_ReturnsExpectedParameters(HttpRouteParameterRedactionMode redactionMode)
    {
        bool isRedacted = redactionMode != HttpRouteParameterRedactionMode.None;
        string redactedPrefix = isRedacted ? "Redacted:" : string.Empty;
 
        HttpRouteParser httpParser = CreateHttpRouteParser();
        Dictionary<string, DataClassification> parametersToRedact = new()
        {
            { "routeId", FakeTaxonomy.PrivateData },
            { "chatId", FakeTaxonomy.PrivateData },
        };
        HttpRouteParameter[] httpRouteParameters = new HttpRouteParameter[2];
 
        const string HttpRoute = "api/routes/{routeId:int:min(1)}/chats/{chatId?}";
        ParsedRouteSegments routeSegments = httpParser.ParseRoute(HttpRoute);
 
        // An http path includes a parameter with a constraint and an optional parameter.
        // Both parameters are redacted.
        string httpPath = "api/routes/routeId123/chats/chatId123";
 
        bool success = httpParser.TryExtractParameters(
            httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "routeId", $"{redactedPrefix}routeId123", isRedacted);
        ValidateRouteParameter(httpRouteParameters[1], "chatId", $"{redactedPrefix}chatId123", isRedacted);
 
        // An http path includes a parameter with a constraint, an optional parameter is not provided.
        // The parameter with constraint is redacted, the optional parameter isn't.
        httpPath = "api/routes/routeId123/chats";
 
        success = httpParser.TryExtractParameters(
            httpPath, routeSegments, redactionMode, parametersToRedact, ref httpRouteParameters);
        Assert.True(success);
 
        ValidateRouteParameter(httpRouteParameters[0], "routeId", $"{redactedPrefix}routeId123", isRedacted);
        ValidateRouteParameter(httpRouteParameters[1], "chatId", "", false);
    }
 
    [Fact]
    public void ParseRoute_WithRouteParameter_ReturnsRouteSegments()
    {
        HttpRouteParser httpParser = CreateHttpRouteParser();
 
        // An http route has parameters and ends with a parameter.
        string httpRoute = "/api/routes/{routeId}/chats/{chatId}";
        var routeSegments = httpParser.ParseRoute(httpRoute);
 
        Assert.Equal(4, routeSegments.Segments.Length);
        Assert.Equal("api/routes/{routeId}/chats/{chatId}", routeSegments.RouteTemplate);
 
        ValidateRouteSegment(routeSegments.Segments[0], "api/routes/", false, "", "", 0, 11);
        ValidateRouteSegment(routeSegments.Segments[1], "routeId", true, "routeId", "", 11, 20);
        ValidateRouteSegment(routeSegments.Segments[2], "/chats/", false, "", "", 20, 27);
        ValidateRouteSegment(routeSegments.Segments[3], "chatId", true, "chatId", "", 27, 35);
 
        // An http route has parameters and ends with text.
        httpRoute = "/api/routes/{routeId}/chats/{chatId}/messages";
        routeSegments = httpParser.ParseRoute(httpRoute);
 
        Assert.Equal(5, routeSegments.Segments.Length);
        Assert.Equal("api/routes/{routeId}/chats/{chatId}/messages", routeSegments.RouteTemplate);
 
        ValidateRouteSegment(routeSegments.Segments[0], "api/routes/", false, "", "", 0, 11);
        ValidateRouteSegment(routeSegments.Segments[1], "routeId", true, "routeId", "", 11, 20);
        ValidateRouteSegment(routeSegments.Segments[2], "/chats/", false, "", "", 20, 27);
        ValidateRouteSegment(routeSegments.Segments[3], "chatId", true, "chatId", "", 27, 35);
        ValidateRouteSegment(routeSegments.Segments[4], "/messages", false, "", "", 35, 44);
    }
 
    [Fact]
    public void ParseRoute_WithQueryParameter_ReturnRouteSegmentExcludingQueryParams()
    {
        HttpRouteParser httpParser = CreateHttpRouteParser();
 
        string httpRoute = "/api/routes/{routeId}/chats/{chatId}/messages?from=7";
        var routeSegments = httpParser.ParseRoute(httpRoute);
 
        Assert.Equal(5, routeSegments.Segments.Length);
        Assert.Equal("api/routes/{routeId}/chats/{chatId}/messages", routeSegments.RouteTemplate);
 
        ValidateRouteSegment(routeSegments.Segments[0], "api/routes/", false, "", "", 0, 11);
        ValidateRouteSegment(routeSegments.Segments[1], "routeId", true, "routeId", "", 11, 20);
        ValidateRouteSegment(routeSegments.Segments[2], "/chats/", false, "", "", 20, 27);
        ValidateRouteSegment(routeSegments.Segments[3], "chatId", true, "chatId", "", 27, 35);
        ValidateRouteSegment(routeSegments.Segments[4], "/messages", false, "", "", 35, 44);
 
        // Route doesn't start with forward slash, the final result should begin with forward slash.
        httpRoute = "api/routes/{routeId}/chats/{chatId}/messages?from=7";
        routeSegments = httpParser.ParseRoute(httpRoute);
 
        Assert.Equal(5, routeSegments.Segments.Length);
        Assert.Equal("api/routes/{routeId}/chats/{chatId}/messages", routeSegments.RouteTemplate);
 
        ValidateRouteSegment(routeSegments.Segments[0], "api/routes/", false, "", "", 0, 11);
        ValidateRouteSegment(routeSegments.Segments[1], "routeId", true, "routeId", "", 11, 20);
        ValidateRouteSegment(routeSegments.Segments[2], "/chats/", false, "", "", 20, 27);
        ValidateRouteSegment(routeSegments.Segments[3], "chatId", true, "chatId", "", 27, 35);
        ValidateRouteSegment(routeSegments.Segments[4], "/messages", false, "", "", 35, 44);
    }
 
    [Fact]
    public void ParseRoute_WhenRouteHasDefaultsOptionalsConstraints_ReturnsRouteSegments()
    {
        HttpRouteParser httpParser = CreateHttpRouteParser();
 
        string httpRoute = "api/{controller=home}/{action=index}/{routeId:int:min(1)}/{chatId?}";
        ParsedRouteSegments routeSegments = httpParser.ParseRoute(httpRoute);
 
        Assert.Equal(8, routeSegments.Segments.Length);
        Assert.Equal(httpRoute, routeSegments.RouteTemplate);
 
        ValidateRouteSegment(routeSegments.Segments[0], "api/", false, "", "", 0, 4);
        ValidateRouteSegment(routeSegments.Segments[1], "controller=home", true, "controller", "home", 4, 21);
        ValidateRouteSegment(routeSegments.Segments[2], "/", false, "", "", 21, 22);
        ValidateRouteSegment(routeSegments.Segments[3], "action=index", true, "action", "index", 22, 36);
        ValidateRouteSegment(routeSegments.Segments[4], "/", false, "", "", 36, 37);
        ValidateRouteSegment(routeSegments.Segments[5], "routeId:int:min(1)", true, "routeId", "", 37, 57);
        ValidateRouteSegment(routeSegments.Segments[6], "/", false, "", "", 57, 58);
        ValidateRouteSegment(routeSegments.Segments[7], "chatId?", true, "chatId", "", 58, 67);
    }
 
    [Fact]
    public void AddHttpRouteProcessor_ParserAndFormatterInstanceAdded()
    {
        var sp = new ServiceCollection().AddHttpRouteProcessor().AddFakeRedaction().BuildServiceProvider();
 
        var httpRouteParser = sp.GetRequiredService<IHttpRouteParser>();
        var httpRouteFormatter = sp.GetRequiredService<IHttpRouteFormatter>();
 
        Assert.NotNull(httpRouteParser);
        Assert.NotNull(httpRouteFormatter);
    }
 
    private static HttpRouteParser CreateHttpRouteParser()
    {
        var redactorProvider = new FakeRedactorProvider(
            new FakeRedactorOptions { RedactionFormat = "Redacted:{0}" });
        return new HttpRouteParser(redactorProvider);
    }
 
    private static void ValidateRouteParameter(
        HttpRouteParameter parameter, string name, string value, bool isRedacted)
    {
        Assert.Equal(name, parameter.Name);
        Assert.Equal(value, parameter.Value);
        Assert.Equal(isRedacted, parameter.IsRedacted);
    }
 
    private static void ValidateRouteSegment(
        Segment segment, string content, bool isParam, string paramName, string defaultValue, int start, int end)
    {
        Assert.Equal(content, segment.Content);
        Assert.Equal(isParam, segment.IsParam);
        Assert.Equal(paramName, segment.ParamName);
        Assert.Equal(defaultValue, segment.DefaultValue);
        Assert.Equal(start, segment.Start);
        Assert.Equal(end, segment.End);
    }
}