using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.DataConnectionLayer; namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests; /// /// M2.4 (#8): the alarm conditionFilter is a comma-separated, case-insensitive /// list of condition type names. Blank = allow all. These tests pin the /// parse-once / IsAllowed predicate that the DataConnectionActor uses as the /// authoritative client-side gate. /// public class AlarmConditionFilterTests { private static NativeAlarmTransition Tx(string typeName, AlarmTransitionKind kind = AlarmTransitionKind.Raise) => new("ref", "obj", typeName, kind, new AlarmConditionState(true, false, null, AlarmShelveState.Unshelved, false, 500), "cat", "desc", "msg", "", "", null, DateTimeOffset.UtcNow, "1", "0"); [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] [InlineData(",")] [InlineData(" , , ")] public void NullOrBlankFilter_IsEmpty_AllowsEverything(string? filter) { var f = AlarmConditionFilter.Parse(filter); Assert.True(f.IsEmpty); Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi"))); Assert.True(f.IsAllowed(Tx("anything-at-all"))); } [Fact] public void Parse_SplitsCommaSeparatedList() { var f = AlarmConditionFilter.Parse("AnalogLimit.Hi,DiscreteAlarm,AnalogLimit.Lo"); Assert.False(f.IsEmpty); Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi"))); Assert.True(f.IsAllowed(Tx("DiscreteAlarm"))); Assert.True(f.IsAllowed(Tx("AnalogLimit.Lo"))); Assert.False(f.IsAllowed(Tx("AnalogLimit.HiHi"))); } [Fact] public void IsAllowed_IsCaseInsensitive() { var f = AlarmConditionFilter.Parse("AnalogLimit.Hi"); Assert.True(f.IsAllowed(Tx("analoglimit.hi"))); Assert.True(f.IsAllowed(Tx("ANALOGLIMIT.HI"))); Assert.False(f.IsAllowed(Tx("DiscreteAlarm"))); } [Fact] public void Parse_TrimsWhitespaceAroundEachName() { var f = AlarmConditionFilter.Parse(" AnalogLimit.Hi ,\tDiscreteAlarm "); Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi"))); Assert.True(f.IsAllowed(Tx("DiscreteAlarm"))); } [Fact] public void Parse_DropsEmptyEntries_KeepsNonEmpty() { var f = AlarmConditionFilter.Parse("AnalogLimit.Hi,, ,DiscreteAlarm"); Assert.False(f.IsEmpty); Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi"))); Assert.True(f.IsAllowed(Tx("DiscreteAlarm"))); Assert.False(f.IsAllowed(Tx(""))); } [Fact] public void IsAllowed_NeverDropsSnapshotCompleteFramingSentinel() { // SnapshotComplete is a pure framing sentinel (empty AlarmTypeName) that // drives the NativeAlarmActor's atomic snapshot swap. A type filter must // never swallow it or the snapshot replay never completes. var f = AlarmConditionFilter.Parse("AnalogLimit.Hi"); Assert.True(f.IsAllowed(Tx("", AlarmTransitionKind.SnapshotComplete))); } [Fact] public void IsAllowed_FiltersReplayedSnapshotConditionsByType() { // Snapshot-kind transitions carry real conditions and ARE filtered. var f = AlarmConditionFilter.Parse("AnalogLimit.Hi"); Assert.True(f.IsAllowed(Tx("AnalogLimit.Hi", AlarmTransitionKind.Snapshot))); Assert.False(f.IsAllowed(Tx("DiscreteAlarm", AlarmTransitionKind.Snapshot))); } [Fact] public void Names_ExposesNormalizedSet_ForServerSideOptimization() { var f = AlarmConditionFilter.Parse(" AnalogLimit.Hi , DiscreteAlarm "); Assert.Equal(new[] { "AnalogLimit.Hi", "DiscreteAlarm" }, f.Names.OrderBy(n => n).ToArray()); Assert.Empty(AlarmConditionFilter.Parse(null).Names); } }