97 lines
4.9 KiB
C#
97 lines
4.9 KiB
C#
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian; // AlarmHistorianEvent
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions; // AlarmSeverity
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests.Mapping;
|
|
|
|
public sealed class AlarmEventMapperTests
|
|
{
|
|
[Fact]
|
|
public void A_direct_event_has_a_nonzero_wire_id_that_survives_repeated_mapping()
|
|
{
|
|
var alarm = new AlarmHistorianEvent("A", "S", "N", "LimitAlarm", AlarmSeverity.High,
|
|
"Activated", "m", "system", null, new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc));
|
|
var first = AlarmEventMapper.ToHistorianEvent(alarm).Id;
|
|
Assert.True(Guid.TryParse(first, out var id), $"Expected a delivery GUID; received '{first}'.");
|
|
Assert.NotEqual(Guid.Empty, id);
|
|
Assert.Equal(first, AlarmEventMapper.ToHistorianEvent(alarm).Id);
|
|
}
|
|
|
|
[Fact]
|
|
public void Maps_source_time_type_and_rich_properties()
|
|
{
|
|
var a = new AlarmHistorianEvent("A1", "Area/Line/Pump1", "HiHi", "LimitAlarm",
|
|
AlarmSeverity.High, "Activated", "Temp high", "operator1", "ack note",
|
|
new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc));
|
|
var e = AlarmEventMapper.ToHistorianEvent(a);
|
|
Assert.Equal("Area/Line/Pump1", e.SourceName);
|
|
Assert.Equal("LimitAlarm", e.Type);
|
|
Assert.Equal(new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc), e.EventTime.ToDateTime());
|
|
Assert.Equal(a.DeliveryId.ToString("D"), e.Id);
|
|
Assert.Equal("A1", e.Properties["AlarmId"]);
|
|
Assert.Equal("HiHi", e.Properties["AlarmName"]);
|
|
Assert.Equal("Activated", e.Properties["EventKind"]);
|
|
// Severity is the AlarmHistorianEvent.Severity XML doc contract: "Mapped to Historian's
|
|
// numeric priority on the sink side" — NOT the .NET enum name. EventMapper.ParseSeverity on
|
|
// the read side int.TryParse()s this same property, so a non-numeric string (the old bug:
|
|
// alarm.Severity.ToString() == "High") silently always read back as the minimum severity (1).
|
|
Assert.Equal("700", e.Properties["Severity"]);
|
|
Assert.Equal("operator1", e.Properties["User"]);
|
|
Assert.Equal("ack note", e.Properties["Comment"]);
|
|
Assert.Equal("Temp high", e.Properties["Message"]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Driver.Historian.Gateway-001 regression: the write side (<see cref="AlarmEventMapper"/>) and
|
|
/// the read side (<see cref="EventMapper"/>) must agree on the wire representation of severity —
|
|
/// a numeric OPC UA Part 9 priority string, matching the convention already used elsewhere in the
|
|
/// codebase for the same enum (<c>NativeAlarmProjector.MapSeverity</c>: Low=200/Medium=500/
|
|
/// High=700/Critical=900). Round-tripping through both mappers must reproduce the numeric severity,
|
|
/// not collapse to the minimum every time.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData(AlarmSeverity.Low, (ushort)200)]
|
|
[InlineData(AlarmSeverity.Medium, (ushort)500)]
|
|
[InlineData(AlarmSeverity.High, (ushort)700)]
|
|
[InlineData(AlarmSeverity.Critical, (ushort)900)]
|
|
public void Severity_round_trips_through_EventMapper_as_numeric_priority(AlarmSeverity severity, ushort expected)
|
|
{
|
|
var a = new AlarmHistorianEvent("A1", "Area/Line/Pump1", "HiHi", "LimitAlarm",
|
|
severity, "Activated", "m", "operator1", null,
|
|
new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc));
|
|
|
|
var wire = AlarmEventMapper.ToHistorianEvent(a);
|
|
var readBack = EventMapper.ToHistoricalEvent(wire);
|
|
|
|
Assert.Equal(expected, readBack.Severity);
|
|
}
|
|
|
|
[Fact]
|
|
public void Null_comment_is_omitted_not_null()
|
|
{
|
|
var a = new AlarmHistorianEvent("A", "S", "N", "DiscreteAlarm", AlarmSeverity.Low, "Cleared", "m", "system", null,
|
|
new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc));
|
|
Assert.False(AlarmEventMapper.ToHistorianEvent(a).Properties.ContainsKey("Comment"));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_blank_condition_id_still_has_delivery_identity_and_adds_no_condition_property()
|
|
{
|
|
// Condition identity and event delivery identity serve different purposes.
|
|
var a = new AlarmHistorianEvent("", "S", "N", "DiscreteAlarm", AlarmSeverity.Low, "Cleared", "m", "system", null,
|
|
new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc));
|
|
var e = AlarmEventMapper.ToHistorianEvent(a);
|
|
Assert.Equal(a.DeliveryId.ToString("D"), e.Id);
|
|
Assert.False(e.Properties.ContainsKey("AlarmId"));
|
|
}
|
|
|
|
[Fact]
|
|
public void An_explicitly_empty_delivery_id_is_rejected_instead_of_reminted()
|
|
{
|
|
var alarm = new AlarmHistorianEvent("A", "S", "N", "LimitAlarm", AlarmSeverity.High,
|
|
"Activated", "m", "system", null, DateTime.UtcNow) { DeliveryId = Guid.Empty };
|
|
Assert.Throws<ArgumentException>(() => AlarmEventMapper.ToHistorianEvent(alarm));
|
|
}
|
|
}
|