using System.Collections.Generic;
using System.Globalization;
using Microsoft.Extensions.Logging.Abstractions;
using Newtonsoft.Json.Linq;

namespace Dpz.Core.ServiceTest;

[TestFixture]
public class AppLogEntryServiceTest
{
    private const int Top = 10;
    private const int TooManyRequestsStatusCode = 429;
    private static readonly string[] RequestLogFilters =
    [
        "Program = 'Dpz.Core.Web'",
        "SourceContext = 'Serilog.AspNetCore.RequestLoggingMiddleware'",
    ];

    [Test]
    public async Task GetPageAsync_ShouldReturnRequestLogs()
    {
        var service = CreateService();
        var pageSize = 10;
        var part = await service.GetPageAsync(BuildRequestLogFilter(), pageSize: pageSize);

        Assert.That(part, Is.Not.Null);
        Assert.That(part?.Events, Is.Not.Null);
        Assert.That(part?.Events, Is.Not.Empty);
        Assert.That(part?.Events.Count, Is.LessThanOrEqualTo(pageSize));
    }

    [Test]
    public async Task GetLatestAccessNumberAsync_ShouldReturnRecentDailyAccessSummary()
    {
        var service = CreateService();
        var accessNumbers = await service.GetLatestAccessNumberAsync(-7);

        Assert.That(accessNumbers, Is.Not.Null);
        Assert.That(accessNumbers, Is.Not.Empty);

        foreach (var item in accessNumbers)
        {
            Assert.That(
                DateTime.TryParseExact(
                    item.Date,
                    "yyyy/MM/dd",
                    CultureInfo.InvariantCulture,
                    DateTimeStyles.None,
                    out _
                ),
                Is.True
            );
            Assert.That(item.Count, Is.GreaterThanOrEqualTo(0));
        }
    }

    [Test]
    public async Task GetAccessStatisticsAsync_ShouldReturnSummaryDimensions()
    {
        var service = CreateService();
        var statistics = await service.GetAccessStatisticsAsync(days: 7, top: Top);

        Assert.That(statistics, Is.Not.Null);
        Assert.That(statistics.PopularPages, Is.Not.Empty);
        Assert.That(statistics.HourlyTraffic, Is.Not.Empty);
        Assert.That(statistics.BrowserUsage, Is.Not.Empty);

        Assert.That(statistics.SlowRequests.Count, Is.LessThanOrEqualTo(Top));
        Assert.That(statistics.PopularPages.Count, Is.LessThanOrEqualTo(Top));
        Assert.That(statistics.BrowserUsage.Count, Is.LessThanOrEqualTo(Top));
        Assert.That(statistics.Referers.Count, Is.LessThanOrEqualTo(Top));

        foreach (var item in statistics.SlowRequests)
        {
            Assert.That(item.StatusCode, Is.Not.EqualTo(TooManyRequestsStatusCode));
            Assert.That(
                DateTime.TryParseExact(
                    item.Time,
                    "yyyy/MM/dd HH:mm:ss",
                    CultureInfo.InvariantCulture,
                    DateTimeStyles.None,
                    out var recordTime
                ),
                Is.True
            );
            Assert.That(recordTime.Hour == 6 && recordTime.Minute < 30, Is.False);
        }

        foreach (var item in statistics.PopularPages)
        {
            Assert.That(item.RequestPath, Is.Not.Empty);
            Assert.That(item.Count, Is.GreaterThanOrEqualTo(0));
        }

        foreach (var item in statistics.HourlyTraffic)
        {
            Assert.That(
                DateTime.TryParseExact(
                    item.Date,
                    "yyyy/MM/dd",
                    CultureInfo.InvariantCulture,
                    DateTimeStyles.None,
                    out _
                ),
                Is.True
            );
            Assert.That(item.Hour, Is.InRange(0, 23));
            Assert.That(item.Count, Is.GreaterThanOrEqualTo(0));
        }

        foreach (var item in statistics.BrowserUsage)
        {
            Assert.That(item.Browser, Is.Not.Empty);
            Assert.That(item.Count, Is.GreaterThanOrEqualTo(0));
            Assert.That(item.Percentage, Is.InRange(0m, 100m));
        }

        foreach (var item in statistics.Referers)
        {
            Assert.That(item.Referer, Is.Not.Empty);
            Assert.That(item.Count, Is.GreaterThanOrEqualTo(0));
        }
    }

    [Test]
    public void NormalizeString_ShouldReadNewtonsoftJsonValue()
    {
        var method = typeof(AppLogEntryService).GetMethod(
            "NormalizeString",
            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static
        );

        Assert.That(method, Is.Not.Null);
        Assert.That(
            method!.Invoke(null, [new JValue("https://example.com")]),
            Is.EqualTo("https://example.com")
        );
        Assert.That(method.Invoke(null, [JValue.CreateNull()]), Is.Null);
    }

    private static IAppLogEntryService CreateService()
    {
        var configuration = CreateConfiguration();
        return new AppLogEntryService(configuration, NullLogger<AppLogEntryService>.Instance);
    }

    private static IConfiguration CreateConfiguration() =>
        new ConfigurationBuilder().AddJsonFile("appsettings.Test.json").Build();

    private static string BuildRequestLogFilter() => string.Join(" and ", RequestLogFilters);
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码说明

这是一个针对 AppLogEntryService 的单元测试类,用于测试应用程序日志条目服务的功能。该测试使用 NUnit 测试框架。

主要组成部分

常量定义

  • Top: 设置为 10,用于限制查询返回的记录数
  • TooManyRequestsStatusCode: HTTP 429 状态码(请求过多)
  • RequestLogFilters: 请求日志过滤条件数组,筛选来自特定程序和上下文的日志

测试方法

1. GetPageAsync_ShouldReturnRequestLogs

测试分页获取请求日志的功能:

  • 创建服务实例
  • 调用 GetPageAsync 方法获取指定页面大小的日志
  • 验证返回结果不为空,有数据,且数量不超过指定的页面大小

2. GetLatestAccessNumberAsync_ShouldReturnRecentDailyAccessSummary

测试获取最近访问次数统计的功能:

  • 获取最近 7 天的访问统计数据
  • 验证返回的数据:
    • 日期格式正确(yyyy/MM/dd
    • 访问计数大于等于 0

3. GetAccessStatisticsAsync_ShouldReturnSummaryDimensions

这是最复杂的测试,验证访问统计的多维度数据:

  • 获取 7 天内的统计信息
  • 验证多个维度的数据:
    • 慢请求:验证状态码不是 429,时间格式正确,排除特定时间段(6:00-6:30)
    • 热门页面:验证请求路径和访问次数
    • 每小时流量:验证日期格式、小时范围(0-23)和计数
    • 浏览器使用情况:验证浏览器名称、计数和百分比(0-100%)
    • 来源引用:验证引用地址和计数
  • 所有列表的长度都不超过 Top 常量(10)

4. NormalizeString_ShouldReadNewtonsoftJsonValue

测试 AppLogEntryService 类中的私有静态方法 NormalizeString

  • 使用反射获取私有方法
  • 测试该方法能正确处理 Newtonsoft.Json 的 JValue 对象
  • 验证字符串值的正常化和 null 值处理

辅助方法

  • CreateService(): 创建 AppLogEntryService 实例,使用配置和空日志记录器
  • CreateConfiguration(): 从 appsettings.Test.json 文件加载测试配置
  • BuildRequestLogFilter(): 将过滤条件数组连接成查询字符串(用 "and" 连接)

测试目的

该测试套件全面验证了日志服务的核心功能,包括:

  • 数据检索和分页
  • 统计分析(访问量、热门页面、流量分布等)
  • 数据格式和范围验证
  • 边界条件检查
评论加载中...