File: Logging\HttpHeadersReaderTest.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Http.Diagnostics.Tests\Microsoft.Extensions.Http.Diagnostics.Tests.csproj (Microsoft.Extensions.Http.Diagnostics.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.Collections.Generic;
using System.Net.Http;
using System.Net.Mime;
using FluentAssertions;
using Microsoft.Extensions.Compliance.Classification;
using Microsoft.Extensions.Compliance.Testing;
using Microsoft.Extensions.Http.Logging.Internal;
using Microsoft.Extensions.Http.Logging.Test.Internal;
using Microsoft.Extensions.Telemetry.Internal;
using Moq;
using Xunit;
 
namespace Microsoft.Extensions.Http.Logging.Test;
 
public class HttpHeadersReaderTest
{
    [Fact]
    public void HttpHeadersReader_WhenEmptyHeaders_DoesNothing()
    {
        using var httpRequest = new HttpRequestMessage();
        using var httpResponse = new HttpResponseMessage();
        var options = new LoggingOptions();
 
        var headersReader = new HttpHeadersReader(options.ToOptionsMonitor(), Mock.Of<IHttpHeadersRedactor>());
        var buffer = new List<KeyValuePair<string, string>>();
 
        headersReader.ReadRequestHeaders(httpRequest, buffer);
        buffer.Should().BeEmpty();
 
        headersReader.ReadResponseHeaders(httpResponse, buffer);
        buffer.Should().BeEmpty();
    }
 
    [Fact]
    public void HttpHeadersReader_WhenHeadersProvided_ReadsThem()
    {
        const string Redacted = "REDACTED";
        using var httpRequest = new HttpRequestMessage();
        using var httpResponse = new HttpResponseMessage();
 
        var mockHeadersRedactor = new Mock<IHttpHeadersRedactor>();
        mockHeadersRedactor.Setup(r => r.Redact(It.IsAny<IEnumerable<string>>(), FakeTaxonomy.PrivateData))
            .Returns(Redacted);
        mockHeadersRedactor.Setup(r => r.Redact(It.IsAny<IEnumerable<string>>(), FakeTaxonomy.PublicData))
            .Returns<IEnumerable<string>, DataClassification>((x, _) => string.Join(",", x));
 
        var options = new LoggingOptions
        {
            RequestHeadersDataClasses = new Dictionary<string, DataClassification>
            {
                { "Header1", FakeTaxonomy.PrivateData },
                { "Header2", FakeTaxonomy.PrivateData }
            },
            ResponseHeadersDataClasses = new Dictionary<string, DataClassification>
            {
                { "Header3", FakeTaxonomy.PublicData },
                { "Header4", FakeTaxonomy.PublicData },
                { "hEaDeR7", FakeTaxonomy.PrivateData } // This one is to test case-insensitivity
            }
        };
 
        var headersReader = new HttpHeadersReader(options.ToOptionsMonitor(), mockHeadersRedactor.Object);
        var buffer = new List<KeyValuePair<string, string>>();
 
        headersReader.ReadRequestHeaders(httpRequest, buffer);
        buffer.Should().BeEmpty();
 
        headersReader.ReadResponseHeaders(httpResponse, buffer);
        buffer.Should().BeEmpty();
 
        httpRequest.Headers.Add("Header1", "Value.1");
        httpRequest.Headers.Add("Header2", "Value.2");
        httpResponse.Headers.Add("Header3", "Value.3");
        httpResponse.Headers.Add("Header4", "Value.4");
        httpRequest.Headers.Add("Header5", string.Empty);
        httpResponse.Headers.Add("Header6", string.Empty);
        httpResponse.Headers.Add("Header7", "Value.7");
 
        var requestBuffer = new List<KeyValuePair<string, string>>();
        var responseBuffer = new List<KeyValuePair<string, string>>();
        var expectedRequest = new[]
        {
            new KeyValuePair<string, string>("Header1", Redacted),
            new KeyValuePair<string, string>("Header2", Redacted)
        };
 
        var expectedResponse = new[]
        {
            new KeyValuePair<string, string>("Header3", "Value.3"),
            new KeyValuePair<string, string>("Header4", "Value.4"),
            new KeyValuePair<string, string>("hEaDeR7", Redacted)
        };
 
        headersReader.ReadRequestHeaders(httpRequest, requestBuffer);
        headersReader.ReadResponseHeaders(httpResponse, responseBuffer);
 
        requestBuffer.Should().BeEquivalentTo(expectedRequest);
        responseBuffer.Should().BeEquivalentTo(expectedResponse);
    }
 
    [Theory]
    [CombinatorialData]
    public void HttpHeadersReader_WhenProvided_ReadsContentHeaders(bool logContentHeaders)
    {
        var mockHeadersRedactor = new Mock<IHttpHeadersRedactor>();
        mockHeadersRedactor.Setup(r => r.Redact(It.IsAny<IEnumerable<string>>(), FakeTaxonomy.PublicData))
            .Returns<IEnumerable<string>, DataClassification>((x, _) => string.Join(",", x));
 
        var options = new LoggingOptions
        {
            RequestHeadersDataClasses = new Dictionary<string, DataClassification>
            {
                { "Header1", FakeTaxonomy.PublicData },
                { "Content-Header1", FakeTaxonomy.PublicData },
                { "Content-Type", FakeTaxonomy.PublicData }
            },
            ResponseHeadersDataClasses = new Dictionary<string, DataClassification>
            {
                { "Header3", FakeTaxonomy.PublicData },
                { "Content-Header2", FakeTaxonomy.PublicData },
                { "Content-Length", FakeTaxonomy.PublicData }
            },
            LogContentHeaders = logContentHeaders
        };
 
        var headersReader = new HttpHeadersReader(options.ToOptionsMonitor(), mockHeadersRedactor.Object);
 
        using var requestContent = new StringContent(string.Empty);
        requestContent.Headers.ContentType = new(MediaTypeNames.Application.Soap);
        requestContent.Headers.ContentLength = 42;
        requestContent.Headers.Add("Content-Header1", "Content.1");
 
        using var httpRequest = new HttpRequestMessage { Content = requestContent };
        httpRequest.Headers.Add("Header1", "Value.1");
        httpRequest.Headers.Add("Header2", "Value.2");
 
        using var responseContent = new StringContent(string.Empty);
        responseContent.Headers.ContentType = new(MediaTypeNames.Text.Html);
        responseContent.Headers.ContentLength = 24;
        responseContent.Headers.Add("Content-Header2", "Content.2");
 
        using var httpResponse = new HttpResponseMessage { Content = responseContent };
        httpResponse.Headers.Add("Header3", "Value.3");
        httpResponse.Headers.Add("Header4", "Value.4");
 
        List<KeyValuePair<string, string>> expectedRequest = [new KeyValuePair<string, string>("Header1", "Value.1")];
        List<KeyValuePair<string, string>> expectedResponse = [new KeyValuePair<string, string>("Header3", "Value.3")];
        if (logContentHeaders)
        {
            expectedRequest.Add(new KeyValuePair<string, string>("Content-Header1", "Content.1"));
            expectedRequest.Add(new KeyValuePair<string, string>("Content-Type", MediaTypeNames.Application.Soap));
 
            expectedResponse.Add(new KeyValuePair<string, string>("Content-Header2", "Content.2"));
            expectedResponse.Add(new KeyValuePair<string, string>("Content-Length", "24"));
        }
 
        List<KeyValuePair<string, string>> requestBuffer = [];
        headersReader.ReadRequestHeaders(httpRequest, requestBuffer);
        requestBuffer.Should().BeEquivalentTo(expectedRequest);
 
        List<KeyValuePair<string, string>> responseBuffer = [];
        headersReader.ReadResponseHeaders(httpResponse, responseBuffer);
        responseBuffer.Should().BeEquivalentTo(expectedResponse);
    }
 
    [Fact]
    public void HttpHeadersReader_WhenBufferIsNull_DoesNothing()
    {
        var options = new LoggingOptions();
 
        var headersReader = new HttpHeadersReader(options.ToOptionsMonitor(), Mock.Of<IHttpHeadersRedactor>());
        List<KeyValuePair<string, string>>? responseBuffer = null;
 
        using var httpRequest = new HttpRequestMessage();
        using var httpResponse = new HttpResponseMessage();
 
        headersReader.ReadResponseHeaders(httpResponse, responseBuffer);
        responseBuffer.Should().BeNull();
 
        headersReader.ReadRequestHeaders(httpRequest, responseBuffer);
        responseBuffer.Should().BeNull();
    }
}