using System.Collections;
using System.Reflection;
using Stardust.Data.Monitors;
using Stardust.Server.Services;
using Xunit;
namespace ServerTest.Services;
/// <summary>追踪统计服务单元测试。验证热门窗口门控、内存查询与分层周期的关键行为</summary>
public class TraceStatServiceTests
{
/// <summary>创建关闭定时器的统计服务,避免测试期间触发后台任务与数据库访问</summary>
private static TraceStatService CreateService() => new(null)
{
FlowPeriod = 0,
BatchPeriod = 0,
};
private static TraceData CreateTrace(Int32 appId, String name)
{
var now = DateTime.Now;
return new TraceData
{
AppId = appId,
Name = name,
StatDate = now.Date,
StatHour = now.Date.AddHours(now.Hour),
StatMinute = now.Date.AddHours(now.Hour).AddMinutes(now.Minute / 5 * 5),
};
}
private static Object GetField(Object target, String name)
{
var field = typeof(TraceStatService).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(field);
return field.GetValue(target);
}
/// <summary>非热门应用不登记小时/日包,只保留分钟包与当天活跃登记,交由巡检按周期重算</summary>
[Fact]
[Trait("Category", "Monitor")]
public void Add_NonHotApp_OnlyMinuteBag()
{
var svc = CreateService();
svc.Add([CreateTrace(101, "api/test")]);
Assert.Empty((IEnumerable)GetField(svc, "_bagDay"));
Assert.Empty((IEnumerable)GetField(svc, "_bagHour"));
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagMinute"));
Assert.NotEmpty((IEnumerable)GetField(svc, "_dayActive"));
}
/// <summary>热门应用保持事件驱动重算,上报数据时登记小时与日包</summary>
[Fact]
[Trait("Category", "Monitor")]
public void Add_HotApp_RegistersAllBags()
{
var svc = CreateService();
// 直接注入热窗口标记,避免 SetHotApp 内部的手动重算登记影响断言
((IDictionary)GetField(svc, "_hotApps"))[102] = DateTime.Now;
svc.Add([CreateTrace(102, "api/test")]);
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagDay"));
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagHour"));
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagMinute"));
}
/// <summary>设置热门应用时立即登记重算,保证打开监控页面后分钟槽、小时与当天统计即时刷新</summary>
[Fact]
[Trait("Category", "Monitor")]
public void SetHotApp_RegistersRecompute()
{
var svc = CreateService();
svc.SetHotApp(104);
Assert.True(((IDictionary)GetField(svc, "_hotApps")).Contains(104));
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagDay"));
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagHour"));
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagMinute"));
}
/// <summary>手动补算(如网页重新统计)不受热门门控影响,始终登记小时/日包</summary>
[Fact]
[Trait("Category", "Monitor")]
public void AddManually_NonHotApp_RegistersAllBags()
{
var svc = CreateService();
svc.Add(103, DateTime.Now);
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagDay"));
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagHour"));
Assert.NotEmpty((IEnumerable)GetField(svc, "_bagMinute"));
}
/// <summary>内存查询与紧急落盘在队列未初始化时安全返回,不抛异常</summary>
[Fact]
[Trait("Category", "Monitor")]
public void LiveQuery_And_Flush_NullSafe()
{
var svc = CreateService();
Assert.Null(svc.GetLiveMinute(1, 2, DateTime.Now));
Assert.Null(svc.GetLiveAppMinute(1, DateTime.Now));
svc.TriggerFlush();
svc.Flush();
}
/// <summary>周期默认值。落盘放宽到300秒,巡检600秒(活跃窗口为巡检周期3倍)</summary>
[Fact]
[Trait("Category", "Monitor")]
public void Defaults_MonitorPeriods()
{
var svc = new TraceStatService(null);
Assert.Equal(300, svc.SavePeriod);
Assert.Equal(600, svc.PatrolPeriod);
}
/// <summary>告警辅助函数:错误率现场计算;内存值优先于数据库值,数据库值更新时取数据库值</summary>
[Fact]
[Trait("Category", "Monitor")]
public void AlarmHelpers_CalcErrorRate_And_SelectLatest()
{
var alarmType = typeof(AlarmService);
var calc = alarmType.GetMethod("CalcErrorRate", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(calc);
Assert.Equal(0D, (Double)calc.Invoke(null, [0L, 5L]));
Assert.Equal(0.1D, (Double)calc.Invoke(null, [100L, 10L]));
var select = alarmType.GetMethod("SelectLatest", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(select);
var live = new TraceMinuteStat { Total = 100, Errors = 10 };
var db = new TraceMinuteStat { Total = 50, Errors = 5 };
Assert.Same(live, select.Invoke(null, [live, db]));
Assert.Same(live, select.Invoke(null, [live, null]));
Assert.Same(db, select.Invoke(null, [null, db]));
// 数据库值更新(错误数更大)时取数据库值
var db2 = new TraceMinuteStat { Total = 200, Errors = 20 };
Assert.Same(db2, select.Invoke(null, [live, db2]));
}
}
|