File: Logging\AcceptanceTests.Routing.cs
Web Access
Project: src\test\Libraries\Microsoft.AspNetCore.Diagnostics.Middleware.Tests\Microsoft.AspNetCore.Diagnostics.Middleware.Tests.csproj (Microsoft.AspNetCore.Diagnostics.Middleware.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#if NET8_0_OR_GREATER
 
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.Logging.Test.Controllers;
using Microsoft.Extensions.Compliance.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Http.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Shared.Text;
using Xunit;
using static Microsoft.Extensions.Http.Diagnostics.HttpRouteParameterRedactionMode;
 
namespace Microsoft.AspNetCore.Diagnostics.Logging.Test;
 
public partial class AcceptanceTests
{
    [SuppressMessage("Design", "CA1052:Static holder types should be Static or NotInheritable", Justification = "Needed for reflection")]
    private class TestStartupWithRouting
    {
        [SuppressMessage("Major Code Smell", "S1144:Unused private types or members should be removed", Justification = "Used through reflection")]
        public static void ConfigureServices(IServiceCollection services)
            => services
                .AddFakeRedaction(x => x.RedactionFormat = RedactedFormat)
                .AddRouting()
                .AddControllers();
 
        [SuppressMessage("Major Code Smell", "S1144:Unused private types or members should be removed", Justification = "Used through reflection")]
        public static void Configure(IApplicationBuilder app)
            => app
                .UseRouting()
                .UseHttpLogging()
                .UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
 
                    endpoints.MapControllerRoute(
                        name: "default",
                        pattern: ConventionalRoutingController.Route);
 
                    endpoints.MapControllerRoute(
                        name: "mixed-routing",
                        pattern: "mixed/conventional-routing",
                        defaults: new { controller = "MixedRouting", action = "ConventionalRouting" });
                });
    }
 
    private static Task RunRoutingTestAsync<TStartup>(
        string httpPath,
        Action<IServiceCollection> configureHttpLogging,
        Action<Dictionary<string, string?>> validateRequestState)
        where TStartup : class
    {
        return RunAsync<TStartup>(
            LogLevel.Information,
            configureHttpLogging,
            async (logCollector, client, _) =>
            {
                using var response = await client.GetAsync(httpPath).ConfigureAwait(false);
                Assert.True(response.IsSuccessStatusCode);
 
                await WaitForLogRecordsAsync(logCollector, TimeSpan.FromSeconds(30));
 
                Assert.Equal(1, logCollector.Count);
 
                var logRecord = logCollector.LatestRecord;
                Assert.Null(logRecord.Exception);
                Assert.Equal(LoggingCategory, logRecord.Category);
                Assert.Equal(LogLevel.Information, logRecord.Level);
 
                var responseStatus = ((int)response.StatusCode).ToInvariantString();
                var state = logRecord.StructuredState!;
                validateRequestState(new Dictionary<string, string?>(state));
            });
    }
 
    [Theory]
 
    // Conventional routing.
    [InlineData(Strict, "", "")]
    [InlineData(Loose, "", "")]
    [InlineData(Strict, "ConventionalRouting", "ConventionalRouting")]
    [InlineData(Loose, "ConventionalRouting", "ConventionalRouting")]
    [InlineData(Strict, "ConventionalRouting/GetEntity/12345", "ConventionalRouting/GetEntity/<redacted:12345>")]
    [InlineData(Loose, "ConventionalRouting/GetEntity/12345", "ConventionalRouting/GetEntity/<redacted:12345>")]
    [InlineData(Strict, "ConventionalRouting/GetData/12345", $"ConventionalRouting/GetData/{TelemetryConstants.Redacted}")]
    [InlineData(Loose, "ConventionalRouting/GetData/12345", "ConventionalRouting/GetData/12345")]
 
    // Attribute routing.
    [InlineData(Strict, "AttributeRouting", "AttributeRouting")]
    [InlineData(Loose, "AttributeRouting", "AttributeRouting")]
    [InlineData(Strict, "AttributeRouting/all", "AttributeRouting/all")]
    [InlineData(Loose, "AttributeRouting/all", "AttributeRouting/all")]
    [InlineData(Strict, "AttributeRouting/get-1/12345", "AttributeRouting/get-1/<redacted:12345>")]
    [InlineData(Loose, "AttributeRouting/get-1/12345", "AttributeRouting/get-1/<redacted:12345>")]
    [InlineData(Strict, "AttributeRouting/get-2", "AttributeRouting/get-2")]
    [InlineData(Loose, "AttributeRouting/get-2", "AttributeRouting/get-2")]
    [InlineData(Strict, "AttributeRouting/get-2/12345", "AttributeRouting/get-2/<redacted:12345>")]
    [InlineData(Loose, "AttributeRouting/get-2/12345", "AttributeRouting/get-2/<redacted:12345>")]
    [InlineData(Strict, "AttributeRouting/get-3", "AttributeRouting/get-3")]
    [InlineData(Loose, "AttributeRouting/get-3", "AttributeRouting/get-3")]
    [InlineData(Strict, "AttributeRouting/get-3/top10", "AttributeRouting/get-3/top10")]
    [InlineData(Loose, "AttributeRouting/get-3/top10", "AttributeRouting/get-3/top10")]
    [InlineData(Strict, "AttributeRouting/get-4/top10", $"AttributeRouting/get-4/{TelemetryConstants.Redacted}")]
    [InlineData(Loose, "AttributeRouting/get-4/top10", "AttributeRouting/get-4/top10")]
 
    // Mixed routing.
    [InlineData(Strict, "mixed/conventional-routing", "mixed/conventional-routing")]
    [InlineData(Loose, "mixed/conventional-routing", "mixed/conventional-routing")]
    [InlineData(Strict, "mixed/attribute-routing-1/12345", "mixed/attribute-routing-1/<redacted:12345>")]
    [InlineData(Loose, "mixed/attribute-routing-1/12345", "mixed/attribute-routing-1/<redacted:12345>")]
    [InlineData(Strict, "mixed/attribute-routing-2", "mixed/attribute-routing-2")]
    [InlineData(Loose, "mixed/attribute-routing-2", "mixed/attribute-routing-2")]
    [InlineData(Strict, "mixed/attribute-routing-2/12345", "mixed/attribute-routing-2/<redacted:12345>")]
    [InlineData(Loose, "mixed/attribute-routing-2/12345", "mixed/attribute-routing-2/<redacted:12345>")]
    [InlineData(Strict, "mixed/attribute-routing-3", "mixed/attribute-routing-3")]
    [InlineData(Loose, "mixed/attribute-routing-3", "mixed/attribute-routing-3")]
    [InlineData(Strict, "mixed/attribute-routing-3/top10", "mixed/attribute-routing-3/top10")]
    [InlineData(Loose, "mixed/attribute-routing-3/top10", "mixed/attribute-routing-3/top10")]
    [InlineData(Strict, "mixed/attribute-routing-4/test1234", $"mixed/attribute-routing-4/{TelemetryConstants.Redacted}")]
    [InlineData(Loose, "mixed/attribute-routing-4/test1234", "mixed/attribute-routing-4/test1234")]
    public async Task Routing_WithFormattedPath_RedactPath(
        HttpRouteParameterRedactionMode mode, string httpPath, string expectedHttpPath)
    {
        await RunRoutingTestAsync<TestStartupWithRouting>(
            httpPath,
            configureHttpLogging: services =>
            {
                services.AddHttpLoggingRedaction(o => o.RequestPathParameterRedactionMode = mode);
            },
            validateRequestState: state =>
            {
                Assert.Equal(expectedHttpPath, state[HttpLoggingTagNames.Path]);
            });
    }
 
    [Theory]
 
    // Conventional routing.
    [InlineData("", ConventionalRoutingController.Route, "ConventionalRouting", "Index", "")]
    [InlineData("ConventionalRouting", ConventionalRoutingController.Route, "ConventionalRouting", "Index", "")]
    [InlineData("ConventionalRouting/GetEntity/12345", ConventionalRoutingController.Route, "ConventionalRouting", "GetEntity", "<redacted:12345>")]
 
    // Attribute routing.
    [InlineData("AttributeRouting", "AttributeRouting", null, null, null)]
    [InlineData("AttributeRouting/all", "AttributeRouting/all", null, null, null)]
    [InlineData("AttributeRouting/get-1/12345", "AttributeRouting/get-1/{param:int:min(1)}", null, null, "<redacted:12345>")]
    [InlineData("AttributeRouting/get-2", "AttributeRouting/get-2/{param?}", null, null, "")]
    [InlineData("AttributeRouting/get-2/12345", "AttributeRouting/get-2/{param?}", null, null, "<redacted:12345>")]
    [InlineData("AttributeRouting/get-3", "AttributeRouting/get-3/{param=all}", null, null, "all")]
    [InlineData("AttributeRouting/get-3/top10", "AttributeRouting/get-3/{param=all}", null, null, "top10")]
 
    // Mixed routing.
    [InlineData("mixed/conventional-routing", "mixed/conventional-routing", null, null, null)]
    [InlineData("mixed/attribute-routing-1/12345", "mixed/attribute-routing-1/{param:int:min(1)}", null, null, "<redacted:12345>")]
    [InlineData("mixed/attribute-routing-2", "mixed/attribute-routing-2/{param?}", null, null, "")]
    [InlineData("mixed/attribute-routing-2/12345", "mixed/attribute-routing-2/{param?}", null, null, "<redacted:12345>")]
    [InlineData("mixed/attribute-routing-3", "mixed/attribute-routing-3/{param=all}", null, null, "all")]
    [InlineData("mixed/attribute-routing-3/top10", "mixed/attribute-routing-3/{param=all}", null, null, "top10")]
    public async Task Routing_WithStructuredPath_RedactParameters(
        string httpPath, string httpRoute, string? controller, string? action, string? param)
    {
        await RunRoutingTestAsync<TestStartupWithRouting>(
            httpPath,
            configureHttpLogging: services => services.AddHttpLoggingRedaction(options =>
            {
                options.RequestPathLoggingMode = IncomingPathLoggingMode.Structured;
            }),
            validateRequestState: state =>
            {
                Assert.Equal(httpRoute, state[HttpLoggingTagNames.Path]);
 
                if (controller != null)
                {
                    Assert.Equal(controller, state["controller"]);
                }
 
                if (action != null)
                {
                    Assert.Equal(action, state["action"]);
                }
 
                if (param == null)
                {
                    Assert.DoesNotContain("param", state.Keys);
                }
                else
                {
                    Assert.Equal(param, state["param"]);
                }
            });
    }
}
#endif