节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
4.86 KiB
Stardust
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Stardust.Server.Services;
using Xunit;

namespace ServerTest.Services;

/// <summary>异常采样过滤器测试</summary>
public class ErrorSampleFilterTests
{
    [Fact]
    [DisplayName("客户端提前断开请求体属于传输层中断,不计入应用错误")]
    public void IsTransportAbort_MatchesClientAbort()
    {
        Assert.True(ErrorSampleFilter.IsTransportAbort("Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Unexpected end of request content."));
        Assert.True(ErrorSampleFilter.IsTransportAbort("The client disconnected."));
        Assert.True(ErrorSampleFilter.IsTransportAbort("Connection reset by peer"));
        Assert.True(ErrorSampleFilter.IsTransportAbort("System.Net.WebSockets.WebSocketException: The remote party closed the WebSocket connection without completing the close handshake."));
        Assert.False(ErrorSampleFilter.IsTransportAbort("SqlException: Timeout expired"));
        Assert.False(ErrorSampleFilter.IsTransportAbort(""));
        Assert.False(ErrorSampleFilter.IsTransportAbort(null));
    }

    [Fact]
    [DisplayName("全部采样均为传输层中断时才清零错误数,且采样不能少于错误数")]
    public void FixErrors_OnlyWhenAllSamplesAreAborts()
    {
        var keywords = ErrorSampleFilter.DefaultKeywords;
        List<String> aborts = ["Unexpected end of request content", "The client disconnected"];

        Assert.Equal(0, ErrorSampleFilter.FixErrors(2, aborts, keywords));

        // 混入真实业务异常,保持原错误数
        List<String> mixed = ["Unexpected end of request content", "NullReferenceException"];
        Assert.Equal(2, ErrorSampleFilter.FixErrors(2, mixed, keywords));

        // 错误数多于采样数,说明存在未采样的错误,不能整体忽略
        Assert.Equal(9, ErrorSampleFilter.FixErrors(9, aborts, keywords));

        // 没有采样时不做判定
        Assert.Equal(1, ErrorSampleFilter.FixErrors(1, [], keywords));

        // 本来就没有错误
        Assert.Equal(0, ErrorSampleFilter.FixErrors(0, aborts, keywords));
    }

    [Fact]
    [DisplayName("传输层中断的异常采样只保留首行和首个业务帧,避免长堆栈刷屏")]
    public void Summarize_CompressesTransportAbort()
    {
        var error = "System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake." +
            Environment.NewLine + "   at System.Net.WebSockets.ManagedWebSocket.ThrowEOFUnexpected()" +
            Environment.NewLine + "   at NewLife.Remoting.Extensions.Services.WsCommandSession.ReceiveLoopAsync(IPEndPoint remote, CancellationTokenSource source)" +
            Environment.NewLine + "   at Stardust.Server.Controllers.AppController.HandleNotify(DeviceContext context, WebSocket socket, CancellationToken cancellationToken) in C:\\X\\Stardust\\Stardust.Server\\Controllers\\AppController.cs:line 210" +
            Environment.NewLine + "   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()";

        var summary = ErrorSampleFilter.Summarize(error);

        Assert.Contains("WebSocketException", summary);
        Assert.Contains("AppController.HandleNotify", summary);
        Assert.DoesNotContain("ThrowEOFUnexpected", summary);
        Assert.DoesNotContain("ControllerActionInvoker", summary);
        Assert.True(summary.Length < error.Length);
    }

    [Fact]
    [DisplayName("单行异常同样能压缩堆栈")]
    public void Summarize_CompressesSingleLineStack()
    {
        var error = "Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException: Unexpected end of request content." +
            " at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Http1ContentLengthMessageBody.ReadAsyncInternal(CancellationToken cancellationToken)" +
            " at Stardust.Server.Controllers.TraceController.ReportRaw(String token) in C:\\X\\Stardust\\Stardust.Server\\Controllers\\TraceController.cs:line 90" +
            " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()";

        var summary = ErrorSampleFilter.Summarize(error);

        Assert.Contains("BadHttpRequestException", summary);
        Assert.Contains("TraceController.ReportRaw", summary);
        Assert.DoesNotContain("Http1ContentLengthMessageBody", summary);
        Assert.DoesNotContain("ControllerActionInvoker", summary);
    }

    [Fact]
    [DisplayName("真实业务异常保留完整堆栈")]
    public void Summarize_KeepsRealError()
    {
        var error = "System.NullReferenceException: Object reference not set to an instance of an object." +
            Environment.NewLine + "   at Stardust.Server.Controllers.OrderController.Pay(Int32 id)";

        Assert.Equal(error, ErrorSampleFilter.Summarize(error));
    }
}