using Serilog.Events;
using SqlSugar;
using Xunit;
using YZWater.Core.Models;
using YZWater.Core.Services;
namespace YZWater.Core.Tests.Services;
///
/// 上次运行遗留的"未恢复"报警收尾。
/// conditionId→DbId 的映射只在内存里,进程被杀/正常重启都来不及回写 RecoverTime:
/// 现场库里那条 07:36:19 的 PLC_Connection 记录一直是 NULL,历史页把它显示成"进行中/无限时长",
/// 操作员以为设备还在报警。诊断流水(ConditionId 为空)是瞬时事件,本就没有恢复一说,不该被盖上时间。
///
[Collection("Serilog minimum level")]
public class AlarmOrphanRepairTests : IDisposable
{
private readonly LogEventLevel _originalLevel = LogService.MinimumLevel;
private readonly AlarmService _service;
public AlarmOrphanRepairTests()
{
LogService.ApplyMinimumLevel("Error");
DatabaseService.Initialize();
_service = new AlarmService(PlcPollingService.Instance);
}
public void Dispose()
{
_service.Dispose();
LogService.ApplyMinimumLevel(_originalLevel.ToString());
}
private int Insert(AlarmRecord record) =>
DatabaseService.Db.Insertable(record).ExecuteReturnIdentity();
private DateTime? ReadRecoverTime(int id) =>
DatabaseService.Db.Queryable().Where(r => r.Id == id).Select(r => r.RecoverTime).First();
[Fact]
public async Task Startup_ClosesOrphanedRealAlarm()
{
var marker = Guid.NewGuid().ToString("N");
var orphanId = Insert(new AlarmRecord
{
AlarmType = "PLC",
AlarmMessage = $"遗留活跃报警 {marker}",
AlarmTime = DateTime.Now.AddHours(-2),
ConditionId = $"PLC_{marker}"
});
Assert.Null(ReadRecoverTime(orphanId));
await _service.CloseOrphanedAlarmsAsync();
var closed = ReadRecoverTime(orphanId);
Assert.NotNull(closed);
// 收尾时间取本次启动时刻,不是报警时间:中间这段时间设备确实处于无人监视状态
Assert.True(closed!.Value > DateTime.Now.AddMinutes(-5));
}
[Fact]
public async Task Startup_LeavesJournalEventsAndRecoveredRowsAlone()
{
var marker = Guid.NewGuid().ToString("N");
// 诊断流水:没有 conditionId,也没有恢复概念
var journalId = Insert(new AlarmRecord
{
AlarmType = "Quality",
AlarmMessage = $"测点质量流水 {marker}",
AlarmTime = DateTime.Now.AddHours(-2),
IsConfirmed = true,
ConfirmedBy = "系统"
});
// 正常已恢复的报警,恢复时间不能被覆盖成启动时刻
var recoveredAt = DateTime.Now.AddHours(-3);
var recoveredId = Insert(new AlarmRecord
{
AlarmType = "DI",
AlarmMessage = $"已恢复报警 {marker}",
AlarmTime = recoveredAt.AddHours(-1),
ConditionId = $"DI_{marker}",
RecoverTime = recoveredAt
});
await _service.CloseOrphanedAlarmsAsync();
Assert.Null(ReadRecoverTime(journalId));
// SQLite 只存到毫秒,比较放宽到 1 秒,避免亚毫秒精度造成假红
var kept = ReadRecoverTime(recoveredId);
Assert.NotNull(kept);
Assert.True(Math.Abs((kept!.Value - recoveredAt).TotalSeconds) < 1);
}
}