using Serilog.Events; using Xunit; using YZWater.Core.Services; namespace YZWater.Core.Tests.Services; /// /// 测点数据质量事件的记录方式回归。 /// 背景:现场报警历史被"测点 X 读取失败 / 已恢复"刷满 —— 一次抖动就是"点位数 × 2"行, /// 真报警被埋掉,而且这些行是系统自动确认的流水,操作员既确认不了也复位不了, /// 误点复位还会让程序往 PLC 的复位地址写一次脉冲。 /// 现在一轮最多两条汇总(明细仍在日志文件里),且带独立类型 Quality。 /// [Collection("Serilog minimum level")] public class QualityJournalTests : IDisposable { private readonly LogEventLevel _originalLevel = LogService.MinimumLevel; private readonly AlarmService _service; public QualityJournalTests() { LogService.ApplyMinimumLevel("Error"); _service = new AlarmService(PlcPollingService.Instance); } public void Dispose() { _service.Dispose(); LogService.ApplyMinimumLevel(_originalLevel.ToString()); } [Fact] public void TwentyBadTags_SummarizeIntoOneMessage_NotTwenty() { var lost = Enumerable.Range(1, 20).Select(i => $"{i}#泵.Run").ToList(); var (lostMessage, restoredMessage) = PlcPollingService.BuildQualityJournalMessages(lost, new List()); Assert.Null(restoredMessage); Assert.NotNull(lostMessage); var message = lostMessage!; // 一条消息讲清规模,而不是一个点一条流水 Assert.Contains("20", message); Assert.Contains("共 20 个", message); // 清单截断到前 8 个,避免整张点位表被塞进一条记录 Assert.DoesNotContain("20#泵.Run", message); Assert.Contains("8#泵.Run", message); // 键名拼错时 LanguageService 会原样返回键,这条断言保证模板真的解析了 Assert.DoesNotContain("TagQualityLostSummary", message); } [Fact] public void LostAndRestored_ProduceTwoSeparateMessages() { var (lost, restored) = PlcPollingService.BuildQualityJournalMessages( new List { "DI.高压报警" }, new List { "1#泵.Fault", "2#泵.Fault" }); Assert.NotNull(lost); Assert.Contains("DI.高压报警", lost); Assert.NotNull(restored); Assert.Contains("1#泵.Fault", restored); Assert.DoesNotContain("DI.高压报警", restored); } [Fact] public void NoTransitions_ProduceNoRows() { var (lost, restored) = PlcPollingService.BuildQualityJournalMessages( new List(), new List()); Assert.Null(lost); Assert.Null(restored); } /// /// 诊断流水不得进入活跃报警集合 —— 否则它会占用报警位、被升级/泛溢逻辑统计, /// 还会让"复位"误以为有东西可复位而去写 PLC。 /// [Fact] public void JournalAlarm_NeverBecomesAnActiveAlarm() { _service.RecordJournalAlarm("1 个测点读取失败:1#泵.Run", level: 1, alarmType: "Quality"); Assert.Equal(0, _service.ActiveAlarmCount); Assert.False(_service.HasActiveAlarm); } }