using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters; namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests; /// Task-11: pure OPC UA A&C field → AlarmConditionState/transition mapping. public class OpcUaAlarmMapperTests { [Fact] public void NormalizeSeverity_ClampsTo0_1000() { Assert.Equal(1000, OpcUaAlarmMapper.NormalizeSeverity(5000)); Assert.Equal(0, OpcUaAlarmMapper.NormalizeSeverity(-1)); Assert.Equal(500, OpcUaAlarmMapper.NormalizeSeverity(500)); } [Fact] public void BuildCondition_ActiveUnacked() { var c = OpcUaAlarmMapper.BuildCondition(active: true, acked: false, confirmed: null, shelve: AlarmShelveState.Unshelved, suppressed: false, severity: 700); Assert.True(c.Active); Assert.False(c.Acknowledged); Assert.Equal(700, c.Severity); Assert.Equal(AlarmShelveState.Unshelved, c.Shelve); } [Fact] public void DeriveKind_AckEdge_YieldsAcknowledge() { Assert.Equal(AlarmTransitionKind.Acknowledge, OpcUaAlarmMapper.DeriveKind(prevAcked: false, nowAcked: true, prevActive: true, nowActive: true)); } [Fact] public void DeriveKind_ActiveEdge_YieldsRaise() { Assert.Equal(AlarmTransitionKind.Raise, OpcUaAlarmMapper.DeriveKind(prevAcked: true, nowAcked: true, prevActive: false, nowActive: true)); } [Fact] public void DeriveKind_InactiveEdge_YieldsClear() { Assert.Equal(AlarmTransitionKind.Clear, OpcUaAlarmMapper.DeriveKind(prevAcked: true, nowAcked: true, prevActive: true, nowActive: false)); } [Theory] [InlineData("OneShotShelved", AlarmShelveState.OneShotShelved)] [InlineData("TimedShelved", AlarmShelveState.TimedShelved)] [InlineData("Unshelved", AlarmShelveState.Unshelved)] [InlineData(null, AlarmShelveState.Unshelved)] public void MapShelve_MapsCurrentStateName(string? name, AlarmShelveState expected) { Assert.Equal(expected, OpcUaAlarmMapper.MapShelve(name)); } // ── PickLimitValue (M2.13 / #27) ───────────────────────────────────────── [Fact] public void PickLimitValue_AllNull_ReturnsEmpty() { // All four limit fields absent (non-limit alarm type) → empty string. Assert.Equal("", OpcUaAlarmMapper.PickLimitValue(null, null, null, null)); } [Fact] public void PickLimitValue_HighHighLimitPresent_ReturnsIt() { // HighHighLimit takes top priority; other fields are null (absent). var result = OpcUaAlarmMapper.PickLimitValue(100.5, null, null, null); Assert.Equal("100.5", result); } [Fact] public void PickLimitValue_OnlyHighLimit_ReturnsHighLimit() { // Only HighLimit present (HighHighLimit absent on this alarm type). var result = OpcUaAlarmMapper.PickLimitValue(null, 80.0, null, null); Assert.Equal("80", result); } [Fact] public void PickLimitValue_PriorityOrder_HighHighWinsOverHigh() { // When multiple limits are present, HighHighLimit takes precedence. var result = OpcUaAlarmMapper.PickLimitValue(95.0, 80.0, 20.0, 5.0); Assert.Equal("95", result); } [Fact] public void PickLimitValue_OnlyLowLow_ReturnsLowLow() { // LowLowLimit only — last in priority, but should still be returned. var result = OpcUaAlarmMapper.PickLimitValue(null, null, null, -10.5); Assert.Equal("-10.5", result); } [Fact] public void PickLimitValue_UsesInvariantCulture() { // Decimal separator must always be '.' regardless of thread culture. var result = OpcUaAlarmMapper.PickLimitValue(1.5, null, null, null); Assert.Contains('.', result); // invariant culture: '.' not ',' Assert.Equal("1.5", result); } }