722b8663c1
OPC UA (RealOpcUaClient): - Append 5 new SelectClauses at indices 13–17 (never renumber 0–12): - 13: AlarmConditionType/ActiveState/TransitionTime → OriginalRaiseTime - 14–17: LimitAlarmType HighHighLimit/HighLimit/LowLimit/LowLowLimit → LimitValue - New OpcUaAlarmMapper.PickLimitValue helper: first non-null in HiHi→Hi→Lo→LoLo priority order, InvariantCulture-formatted; empty string for non-limit alarm types. - HandleAlarmEvent reads new indices with fields.Count > N guards; hard minimum (6) unchanged so base ConditionType events still process without the limit fields. - Document unavailable-by-protocol fields (Category, Description, OperatorUser, CurrentValue) inline in BuildAlarmEventFilter and HandleAlarmEvent. MxGateway (MxGatewayAlarmMapper): - MapTransition: CurrentValue and LimitValue now populated via MxValueToString (uses MxValueExtensions.ToClrValue + InvariantCulture) from OnAlarmTransitionEvent proto fields current_value/limit_value. - MapSnapshot: same — populated from ActiveAlarmSnapshot.current_value/limit_value. - MxValueToString helper (internal): null-safe MxValue → string conversion. Tests (17 new, 40 total pass): - OpcUaAlarmMapperTests: PickLimitValue priority, InvariantCulture, all-null case. - MxGatewayAlarmMapperTests: CurrentValue/LimitValue populate from double/string MxValue; absent fields yield empty strings. - RealOpcUaClientAlarmFilterTests: index alignment assertions (count=18, per-index TypeDefinitionId+BrowsePath), regression guard on existing indices 0–12.
109 lines
3.9 KiB
C#
109 lines
3.9 KiB
C#
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests;
|
|
|
|
/// <summary>Task-11: pure OPC UA A&C field → AlarmConditionState/transition mapping.</summary>
|
|
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);
|
|
}
|
|
}
|