|
// 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);
}
[Theory]
[CombinatorialData]
public void TryExtractParameters_WhenRouteHasCatchAllParameter_ReturnsCorrectParameters(
bool routeHasMessageSegment,
bool roundTripSyntax,
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 },
{ "catchAll", FakeTaxonomy.PrivateData },
};
string httpPath = "api/routes/routeId123/chats/chatId123/messages/1/2/3/";
var paramName = "*catchAll";
if (roundTripSyntax)
{
paramName = "**catchAll";
}
var expectedValue = "messages/1/2/3/";
var segment = string.Empty;
if (routeHasMessageSegment)
{
segment = "/messages";
expectedValue = "1/2/3/";
}
string httpRoute = $"api/routes/{{routeId}}/chats/{{chatId}}{segment}/{{{paramName}}}/";
var routeSegments = httpParser.ParseRoute(httpRoute);
var httpRouteParameters = new HttpRouteParameter[3];
var 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);
ValidateRouteParameter(httpRouteParameters[2], "catchAll", $"{redactedPrefix}{expectedValue}", isRedacted);
}
[Theory]
[CombinatorialData]
public void TryExtractParameters_WhenRouteHasCatchAllParameter_Optional_ReturnsCorrectParameters(
bool routeHasDefaultValue,
bool useRoundTripSyntax,
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 },
{ "catchAll", FakeTaxonomy.PrivateData },
};
var httpPath = "api/routes/routeId123/chats/chatId123";
var paramName = "*catchAll";
if (useRoundTripSyntax)
{
paramName = "**catchAll";
}
var expectedValue = string.Empty;
if (routeHasDefaultValue)
{
expectedValue = nameof(routeHasDefaultValue);
paramName += $"={expectedValue}";
}
var httpRoute = $"api/routes/{{routeId}}/chats/{{chatId}}/{{{paramName}}}";
var routeSegments = httpParser.ParseRoute(httpRoute);
var httpRouteParameters = new HttpRouteParameter[3];
var 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);
ValidateRouteParameter(httpRouteParameters[2], "catchAll", expectedValue, 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, false));
ValidateRouteSegment(routeSegments.Segments[1], ("routeId", true, "routeId", "", 11, 20, false));
ValidateRouteSegment(routeSegments.Segments[2], ("/chats/", false, "", "", 20, 27, false));
ValidateRouteSegment(routeSegments.Segments[3], ("chatId", true, "chatId", "", 27, 35, false));
// 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, false));
ValidateRouteSegment(routeSegments.Segments[1], ("routeId", true, "routeId", "", 11, 20, false));
ValidateRouteSegment(routeSegments.Segments[2], ("/chats/", false, "", "", 20, 27, false));
ValidateRouteSegment(routeSegments.Segments[3], ("chatId", true, "chatId", "", 27, 35, false));
ValidateRouteSegment(routeSegments.Segments[4], ("/messages", false, "", "", 35, 44, false));
}
[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, false));
ValidateRouteSegment(routeSegments.Segments[1], ("routeId", true, "routeId", "", 11, 20, false));
ValidateRouteSegment(routeSegments.Segments[2], ("/chats/", false, "", "", 20, 27, false));
ValidateRouteSegment(routeSegments.Segments[3], ("chatId", true, "chatId", "", 27, 35, false));
ValidateRouteSegment(routeSegments.Segments[4], ("/messages", false, "", "", 35, 44, false));
// 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, false));
ValidateRouteSegment(routeSegments.Segments[1], ("routeId", true, "routeId", "", 11, 20, false));
ValidateRouteSegment(routeSegments.Segments[2], ("/chats/", false, "", "", 20, 27, false));
ValidateRouteSegment(routeSegments.Segments[3], ("chatId", true, "chatId", "", 27, 35, false));
ValidateRouteSegment(routeSegments.Segments[4], ("/messages", false, "", "", 35, 44, false));
}
[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, false));
ValidateRouteSegment(routeSegments.Segments[1], ("controller=home", true, "controller", "home", 4, 21, false));
ValidateRouteSegment(routeSegments.Segments[2], ("/", false, "", "", 21, 22, false));
ValidateRouteSegment(routeSegments.Segments[3], ("action=index", true, "action", "index", 22, 36, false));
ValidateRouteSegment(routeSegments.Segments[4], ("/", false, "", "", 36, 37, false));
ValidateRouteSegment(routeSegments.Segments[5], ("routeId:int:min(1)", true, "routeId", "", 37, 57, false));
ValidateRouteSegment(routeSegments.Segments[6], ("/", false, "", "", 57, 58, false));
ValidateRouteSegment(routeSegments.Segments[7], ("chatId?", true, "chatId", "", 58, 67, false));
}
[Theory]
[InlineData("api/{controller=home}/{action=index}/{*url}/{invalid}")]
[InlineData("api/{controller=home}/{action=index}/{**url}/{invalid}")]
public void ParseRoute_WhenRouteHasCatchAllParameter_OutOfOrder(string httpRoute)
{
HttpRouteParser httpParser = CreateHttpRouteParser();
var exception = Assert.Throws<ArgumentException>(() => httpParser.ParseRoute(httpRoute));
Assert.StartsWith("A catch-all parameter must be the last segment in the route.", exception.Message);
}
[Theory]
[InlineData("api/{controller=home}/{action=index}/{*url}")]
[InlineData("api/{controller=home}/{action=index}/{*url}/")]
[InlineData("api/{controller=home}/{action=index}/{**url}")]
[InlineData("api/{controller=home}/{action=index}/{**url}/")]
public void ParseRoute_WhenRouteHasCatchAllParameter_InCorrectPosition(string httpRoute)
{
HttpRouteParser httpParser = CreateHttpRouteParser();
ParsedRouteSegments routeSegments = httpParser.ParseRoute(httpRoute);
Assert.Equal(3, routeSegments.ParameterCount);
Assert.Equal(httpRoute, routeSegments.RouteTemplate);
}
[Theory]
[InlineData("api/{controller=home}/{action=index}/{*url}", 37, 43)]
[InlineData("api/{controller=home}/{action=index}/{**url}", 37, 44)]
public void ParseRoute_WhenRouteHasCatchAllParameter_ReturnsRouteSegments(string httpRoute, int start, int end)
{
HttpRouteParser httpParser = CreateHttpRouteParser();
ParsedRouteSegments routeSegments = httpParser.ParseRoute(httpRoute);
Assert.Equal(6, routeSegments.Segments.Length);
Assert.Equal(httpRoute, routeSegments.RouteTemplate);
ValidateRouteSegment(routeSegments.Segments[0], ("api/", false, "", "", 0, 4, false));
ValidateRouteSegment(routeSegments.Segments[1], ("controller=home", true, "controller", "home", 4, 21, false));
ValidateRouteSegment(routeSegments.Segments[2], ("/", false, "", "", 21, 22, false));
ValidateRouteSegment(routeSegments.Segments[3], ("action=index", true, "action", "index", 22, 36, false));
ValidateRouteSegment(routeSegments.Segments[4], ("/", false, "", "", 36, 37, false));
ValidateRouteSegment(routeSegments.Segments[5], ("url", true, "url", "", start, end, true));
}
[Theory]
[InlineData("api/{controller=home}/{action=index}/{*url:int:min(1)}", 37, 54)]
[InlineData("api/{controller=home}/{action=index}/{**url:int:min(1)}", 37, 55)]
public void ParseRoute_WhenRouteHasCatchAllParameterWithRouteConstraint_ReturnsRouteSegments(string httpRoute, int start, int end)
{
HttpRouteParser httpParser = CreateHttpRouteParser();
ParsedRouteSegments routeSegments = httpParser.ParseRoute(httpRoute);
Assert.Equal(6, routeSegments.Segments.Length);
Assert.Equal(httpRoute, routeSegments.RouteTemplate);
ValidateRouteSegment(routeSegments.Segments[0], ("api/", false, "", "", 0, 4, false));
ValidateRouteSegment(routeSegments.Segments[1], ("controller=home", true, "controller", "home", 4, 21, false));
ValidateRouteSegment(routeSegments.Segments[2], ("/", false, "", "", 21, 22, false));
ValidateRouteSegment(routeSegments.Segments[3], ("action=index", true, "action", "index", 22, 36, false));
ValidateRouteSegment(routeSegments.Segments[4], ("/", false, "", "", 36, 37, false));
ValidateRouteSegment(routeSegments.Segments[5], ("url:int:min(1)", true, "url", "", start, end, true));
}
[Theory]
[InlineData("api/{controller=home}/{action=index}/{*url:regex(^(web|shared*)$)}", 37, 66)]
[InlineData("api/{controller=home}/{action=index}/{**url:regex(^(web|shared*)$)}", 37, 67)]
public void ParseRoute_WhenRouteHasCatchAllParameterWithRouteConstraintContainingRegexWithStar_ReturnsRouteSegments(string httpRoute, int start, int end)
{
HttpRouteParser httpParser = CreateHttpRouteParser();
ParsedRouteSegments routeSegments = httpParser.ParseRoute(httpRoute);
Assert.Equal(6, routeSegments.Segments.Length);
Assert.Equal(httpRoute, routeSegments.RouteTemplate);
ValidateRouteSegment(routeSegments.Segments[0], ("api/", false, "", "", 0, 4, false));
ValidateRouteSegment(routeSegments.Segments[1], ("controller=home", true, "controller", "home", 4, 21, false));
ValidateRouteSegment(routeSegments.Segments[2], ("/", false, "", "", 21, 22, false));
ValidateRouteSegment(routeSegments.Segments[3], ("action=index", true, "action", "index", 22, 36, false));
ValidateRouteSegment(routeSegments.Segments[4], ("/", false, "", "", 36, 37, false));
ValidateRouteSegment(routeSegments.Segments[5], ("url:regex(^(web|shared*)$)", true, "url", "", start, end, true));
}
[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, bool isCatchAll) values)
{
var (content, isParam, paramName, defaultValue, start, end, isCatchAll) = values;
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);
Assert.Equal(isCatchAll, segment.IsCatchAll);
}
}
|