File: ExceptionSummarizerTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.Diagnostics.ExceptionSummarization.Tests\Microsoft.Extensions.Diagnostics.ExceptionSummarization.Tests.csproj (Microsoft.Extensions.Diagnostics.ExceptionSummarization.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 System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Moq;
using Xunit;
 
namespace Microsoft.Extensions.Diagnostics.ExceptionSummarization.Test;
 
public class ExceptionSummarizerTests
{
    private const string DefaultDescription = "Unknown";
    private const string AdditionalDetails = "Some additional details";
    private static readonly List<string> _httpDescriptions = new List<string> { "TaskTimeout", "TaskCanceled" }
        .Concat(Enum.GetNames(typeof(WebExceptionStatus)).ToList())
        .Concat(Enum.GetNames(typeof(SocketError)).ToList()).ToList();
    private static readonly List<Type> _httpSupportedExceptionTypes =
    [
        typeof(TaskCanceledException),
        typeof(OperationCanceledException),
        typeof(WebException),
        typeof(SocketException),
    ];
 
    private readonly Mock<IExceptionSummaryProvider> _httpExceptionProviderMock;
    private readonly IExceptionSummarizer _exceptionSummarizer;
 
    public ExceptionSummarizerTests()
    {
        _httpExceptionProviderMock = new Mock<IExceptionSummaryProvider>(MockBehavior.Strict);
        _httpExceptionProviderMock.Setup(mock => mock.SupportedExceptionTypes).Returns(_httpSupportedExceptionTypes);
        _httpExceptionProviderMock.Setup(mock => mock.Descriptions).Returns(_httpDescriptions);
 
        _exceptionSummarizer = new ExceptionSummarizer(new List<IExceptionSummaryProvider> { _httpExceptionProviderMock.Object });
    }
 
    [Fact]
    public void Summarize_WithProviderSummaryAndInvalidIndex_ReturnSummary()
    {
        var exception = new WebException("test", WebExceptionStatus.RequestCanceled);
        var descriptionIndex = 1000;
        var additionalDetails = $"Exception summary provider {_httpExceptionProviderMock.Object.GetType().Name} returned invalid short description index {descriptionIndex}";
        var exceptionSummary = new ExceptionSummary("WebException", DefaultDescription, additionalDetails);
 
        _httpExceptionProviderMock
            .Setup(mock => mock.Describe(exception, out additionalDetails)).Returns(1000);
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionSummary, summary);
        Assert.Equal(exceptionSummary.ToString(), summary.ToString());
        Assert.Equal(exceptionSummary.AdditionalDetails, summary.AdditionalDetails);
    }
 
    [Fact]
    public void Summarize_WithProviderSummaryAndIndexSameAsCount_ReturnSummary()
    {
        var exception = new WebException("test", WebExceptionStatus.RequestCanceled);
        var descriptionIndex = _httpDescriptions.Count;
        var additionalDetails = $"Exception summary provider {_httpExceptionProviderMock.Object.GetType().Name} returned invalid short description index {descriptionIndex}";
        var exceptionSummary = new ExceptionSummary("WebException", DefaultDescription, additionalDetails);
 
        _httpExceptionProviderMock
            .Setup(mock => mock.Describe(exception, out additionalDetails)).Returns(descriptionIndex);
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionSummary, summary);
        Assert.Equal(exceptionSummary.ToString(), summary.ToString());
        Assert.Equal(exceptionSummary.AdditionalDetails, summary.AdditionalDetails);
    }
 
    [Fact]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Intended.")]
    public void Summarize_WithInnerWebExceptionAndInvalidIndex_ReturnSummary()
    {
        var exception = new Exception("test", new WebException("Error", (WebExceptionStatus)30));
        var descriptionIndex = -1;
        var additionalDetails = $"Exception summary provider {_httpExceptionProviderMock.Object.GetType().Name} returned invalid short description index {descriptionIndex}";
        var innerExceptionSummary = new ExceptionSummary("Exception->WebException", DefaultDescription, additionalDetails);
 
        if (exception.InnerException != null)
        {
            _httpExceptionProviderMock
                .Setup(mock => mock.Describe(exception.InnerException, out additionalDetails))
                .Returns(descriptionIndex);
        }
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(innerExceptionSummary, summary);
        Assert.Equal(innerExceptionSummary.ToString(), summary.ToString());
    }
 
    [Fact]
    public void Summarize_WithProviderSummaryAndValidIndex_ReturnSummary()
    {
        var exception = new WebException("test", WebExceptionStatus.RequestCanceled);
        var descriptionIndex = _httpDescriptions.FindIndex(x => x.Equals(WebExceptionStatus.RequestCanceled.ToString()));
        var additionalDetails = DefaultDescription;
        var exceptionSummary = new ExceptionSummary("WebException", "RequestCanceled", additionalDetails);
 
        _httpExceptionProviderMock
            .Setup(mock => mock.Describe(exception, out additionalDetails))
            .Returns(descriptionIndex);
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionSummary, summary);
        Assert.Equal(exceptionSummary.ToString(), summary.ToString());
    }
 
    [Fact]
    public void Summarize_WithProviderSummaryAndAdditionalDetails_ReturnSummary()
    {
        var exception = new WebException("test", WebExceptionStatus.RequestCanceled);
        var descriptionIndex = _httpDescriptions.FindIndex(x => x.Equals(WebExceptionStatus.RequestCanceled.ToString()));
        var additionalDetails = AdditionalDetails;
        var exceptionSummary = new ExceptionSummary("WebException", "RequestCanceled", additionalDetails);
 
        _httpExceptionProviderMock
            .Setup(mock => mock.Describe(exception, out additionalDetails))
            .Returns(descriptionIndex);
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionSummary, summary);
        Assert.Equal(exceptionSummary.ToString(), summary.ToString());
    }
 
    [Theory]
    [InlineData(AdditionalDetails)]
    [InlineData(DefaultDescription)]
    public void Summarize_WithWebException_ReturnSummary(string? additionalDetails)
    {
        var exception = new WebException("test", WebExceptionStatus.ConnectFailure);
        var descriptionIndex = _httpDescriptions.FindIndex(x => x.Equals(WebExceptionStatus.ConnectFailure.ToString()));
        var exceptionSummary = new ExceptionSummary("WebException", "ConnectFailure", additionalDetails ?? DefaultDescription);
 
        _httpExceptionProviderMock
            .Setup(mock => mock.Describe(exception, out additionalDetails))
            .Returns(descriptionIndex);
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionSummary, summary);
    }
 
    [Theory]
    [InlineData(AdditionalDetails)]
    [InlineData(DefaultDescription)]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Intended.")]
    public void Summarize_WithInnerWebException_ReturnSummary(string? additionalDetails)
    {
        var exception = new Exception("test", new WebException("test", WebExceptionStatus.RequestCanceled));
        var descriptionIndex = _httpDescriptions
            .FindIndex(x => x.Equals(WebExceptionStatus.RequestCanceled.ToString()));
        var innerExceptionSummary = new ExceptionSummary("Exception->WebException", "RequestCanceled", additionalDetails ?? DefaultDescription);
 
        if (exception.InnerException != null)
        {
            _httpExceptionProviderMock
                .Setup(mock => mock.Describe(exception.InnerException, out additionalDetails))
                .Returns(descriptionIndex);
        }
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(innerExceptionSummary, summary);
        Assert.Equal(innerExceptionSummary.ToString(), summary.ToString());
    }
 
    [Theory]
    [InlineData(AdditionalDetails)]
    [InlineData(DefaultDescription)]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Intended.")]
    public void Summarize_WithInnerSocketException_ReturnSummary(string? additionalDetails)
    {
        var exception = new Exception("test", new SocketException((int)SocketError.TimedOut));
        var descriptionIndex = _httpDescriptions.FindIndex(x => x.Equals(SocketError.TimedOut.ToString()));
        var innerExceptionSummary = new ExceptionSummary("Exception->SocketException", "TimedOut", additionalDetails ?? DefaultDescription);
 
        if (exception.InnerException != null)
        {
            _httpExceptionProviderMock
                .Setup(mock => mock.Describe(exception.InnerException, out additionalDetails))
                .Returns(descriptionIndex);
        }
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(innerExceptionSummary, summary);
        Assert.Equal(innerExceptionSummary.ToString(), summary.ToString());
    }
 
    [Theory]
    [InlineData(AdditionalDetails)]
    [InlineData(DefaultDescription)]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Intended.")]
    public async Task Summarize_WithInnerTaskCanceledException_ReturnSummary(string? additionalDetails)
    {
        var descriptionIndex = _httpDescriptions.FindIndex(x => x.Equals("TaskCanceled"));
 
        try
        {
            using var tokenSource = new CancellationTokenSource(TimeSpan.Zero);
            tokenSource.Cancel();
            var task = Task.Run(() => { }, tokenSource.Token);
 
            await task;
        }
        catch (TaskCanceledException ex)
        {
            var exception = new Exception("test", ex);
            var innerExceptionSummary = new ExceptionSummary("Exception->TaskCanceledException", "TaskCanceled", additionalDetails ?? DefaultDescription);
 
            if (exception.InnerException != null)
            {
                _httpExceptionProviderMock
                    .Setup(mock => mock.Describe(exception.InnerException, out additionalDetails))
                    .Returns(descriptionIndex);
            }
 
            var summary = _exceptionSummarizer.Summarize(exception);
 
            Assert.Equal(innerExceptionSummary, summary);
            Assert.Equal(innerExceptionSummary.ToString(), summary.ToString());
        }
    }
 
    [Theory]
    [InlineData(AdditionalDetails)]
    [InlineData(DefaultDescription)]
    [InlineData(null)]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "Intended.")]
    public void Summarize_WithInnerTaskTimeout_ReturnSummary(string? additionalDetails)
    {
        var exception = new Exception("test", new TaskCanceledException());
        var descriptionIndex = _httpDescriptions.FindIndex(x => x.Equals("TaskTimeout"));
        var innerExceptionSummary = new ExceptionSummary("Exception->TaskCanceledException", "TaskTimeout", additionalDetails ?? DefaultDescription);
 
        if (exception.InnerException != null)
        {
            _httpExceptionProviderMock
                .Setup(mock => mock.Describe(exception.InnerException, out additionalDetails))
                .Returns(descriptionIndex);
        }
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(innerExceptionSummary, summary);
        Assert.Equal(innerExceptionSummary.ToString(), summary.ToString());
    }
 
    [Fact]
    public void Summarize_WithNotDefaultHResult_ReturnSummary()
    {
        uint resultCode = 0x80131501;
        var exception = new TestException(resultCode);
        var exceptionHResultSummary = new ExceptionSummary("TestException", "Unknown", "-2146233087");
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionHResultSummary, summary);
        Assert.Equal(exceptionHResultSummary.ToString(), summary.ToString());
    }
 
    [Fact]
    public void Summarize_WithDefaultHResultAndWithoutInnerException_ReturnDefaultSummary()
    {
        var exception = new TestException(0);
        var exceptionHResultSummary = new ExceptionSummary("TestException", "Unknown", "Unknown");
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionHResultSummary, summary);
        Assert.Equal(exceptionHResultSummary.ToString(), summary.ToString());
    }
 
    [Fact]
    public void Summarize_WithDefaultHResultAndInnerException_ReturnSummary()
    {
        var exception = new TestException(0, "Test", new TestException(0));
        var exceptionHResultSummary = new ExceptionSummary("TestException", "TestException", "Unknown");
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionHResultSummary, summary);
        Assert.Equal(exceptionHResultSummary.ToString(), summary.ToString());
    }
 
    private class DerivedException : TestException
    {
        public DerivedException(uint hResult, string message, Exception innerException)
            : base(hResult, message, innerException)
        {
        }
    }
 
    [Fact]
    public void Summarize_WithDerivedExceptionType_ReturnSummary()
    {
        var exception = new DerivedException(0, "Test", new TestException(0));
        var exceptionHResultSummary = new ExceptionSummary("DerivedException", "TestException", "Unknown");
 
        var summary = _exceptionSummarizer.Summarize(exception);
 
        Assert.Equal(exceptionHResultSummary, summary);
        Assert.Equal(exceptionHResultSummary.ToString(), summary.ToString());
    }
}