b61f3e0560
v2-ci / unit-tests (push) Has been cancelled
GalaxyAlarmPriority, the one table live Galaxy alarms (MxAccessSeverityMapper) and AVEVA A&E history (EventMapper, #886) map the AVEVA priority through, ranked a higher number as more severe. AVEVA ranks priority 1 as the most urgent, so a priority-1 alarm read 250 and a priority-999 system event 1000. It now uses AVEVA's default priority ranges, most urgent first: AVEVA priority AVEVA severity bucket OPC UA Severity 1-250 1 Critical Critical 1000 251-500 2 High High 750 501-750 3 Medium Medium 500 751-999 4 Low Low 250 (history only) 0 not an alarm - 1 Out-of-range priorities clamp (<= 0 Critical, > 999 Low). Evidence, vd03 Runtime.dbo.Events read-only: priority 1/100 alarms carry AVEVA severity 1, 400/500 severity 2, 600/700 severity 3, and every priority-999 System.* / User.* row severity 0 with IsAlarm 0. - Live and history now use the same number. GalaxyDriver passes the table's exact value as AlarmEventArgs.OpcUaSeverity. Before, it passed only the bucket, which NativeAlarmProjector re-mapped through its generic 200/500/700/900 ladder, so live and history disagreed even before the direction fix. - History: a row whose AVEVA severity is 0 (System.Start/Stop/Deploy, User.Write...) is not an alarm and reads Severity 1, below every alarm band. For alarms AVEVA's 1-4 severity is still never the value: the live feed carries only the priority, so it cannot use it, and history must agree. - Docs: docs/Historian.md gains "Galaxy alarm priority -> Severity (lmxopcua#887)"; docs/AlarmTracking.md updated. - Live re-run of the #801/#886 harness against vd03 HistorianGateway ea9f21c (read-only, network-only): 224 of 233 cases identical to the #886 run, the 9 others differ only in Severity (priority-700 alarm rows 750 -> 500, 19 system/user events 1000 -> 1). Record: aveva-history-801.md "#887 re-run", report aveva-gate-sdk2-887.json.gz. Client-visible: every OPC UA client that ranks or filters Galaxy alarms by Severity sees the order reversed (live and HistoryReadEvents). ScadaBridge has no code that depends on the old values; its own mixed severity directions are ScadaBridge#129. Tests updated because they pinned the inverted table: - Driver.Galaxy.Tests MxAccessSeverityMapperTests (rewritten: edges 1/250/251/ 500/501/750/751/999, 0/-100/int.MinValue/1000/int.MaxValue clamps, monotonic-decreasing sweep, equals GalaxyAlarmPriority for -2..1001) - Driver.Galaxy.Tests GatewayGalaxyAlarmFeedTests (750 and 600 -> Medium) - Driver.Historian.Gateway.Tests EventMapperTests (band table, priority 999 -> 250, 1 -> 1000; system events -> 1) - OpcUaServer.IntegrationTests GalaxyEquipmentEventHistoryWireTests (700 -> 500, 100 -> 1000, System.Stop -> 1; the fake now stores AVEVA's real 0-4 severity) New tests: - Runtime.Tests NativeAlarmRestatementEndToEndTests .Galaxy_live_condition_severity_is_the_AVEVA_priority_band (8 rows: the real feed -> real GalaxyDriver -> host -> projector; the same rows EventMapperTests pins on the history path) - EventMapperTests.Aveva_severity_zero_marks_an_event_that_is_not_an_alarm (5 rows, incl. the priority-999 alarm positive control) - GalaxyDriverAlarmSourceTests: OpcUaSeverity passes through Mutation check, 8 mutants, all killed: the pre-#887 inverted table; the driver dropping OpcUaSeverity; no not-an-alarm rule; each of the three band edges off by one; history ignoring priority; a missing AVEVA severity read as 0. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
67 lines
3.2 KiB
C#
67 lines
3.2 KiB
C#
using Shouldly;
|
||
using Xunit;
|
||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime;
|
||
|
||
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime;
|
||
|
||
/// <summary>
|
||
/// Pins the AVEVA priority → (AlarmSeverity, OPC UA numeric) table the live Galaxy alarm feed uses (lmxopcua#887).
|
||
/// mxaccessgw sends the AVEVA priority (1 = most urgent … 999 = least) as the alarm's raw severity; the table is
|
||
/// AVEVA's default four ranges, most urgent first.
|
||
/// </summary>
|
||
public sealed class MxAccessSeverityMapperTests
|
||
{
|
||
/// <summary>Verifies the band table at every edge, plus the out-of-range clamps.</summary>
|
||
/// <param name="rawPriority">The raw value the gateway sends: the AVEVA priority.</param>
|
||
/// <param name="expectedBucket">The expected AlarmSeverity bucket.</param>
|
||
/// <param name="expectedOpcUaSeverity">The expected OPC UA numeric severity value.</param>
|
||
[Theory]
|
||
[InlineData(1, AlarmSeverity.Critical, 1000)] // AVEVA Critical 1–250
|
||
[InlineData(250, AlarmSeverity.Critical, 1000)]
|
||
[InlineData(251, AlarmSeverity.High, 750)] // AVEVA High 251–500
|
||
[InlineData(500, AlarmSeverity.High, 750)]
|
||
[InlineData(501, AlarmSeverity.Medium, 500)] // AVEVA Medium 501–750
|
||
[InlineData(750, AlarmSeverity.Medium, 500)]
|
||
[InlineData(751, AlarmSeverity.Low, 250)] // AVEVA Low 751–999
|
||
[InlineData(999, AlarmSeverity.Low, 250)]
|
||
[InlineData(0, AlarmSeverity.Critical, 1000)] // below range: clamped to priority 1
|
||
[InlineData(-100, AlarmSeverity.Critical, 1000)]
|
||
[InlineData(int.MinValue, AlarmSeverity.Critical, 1000)]
|
||
[InlineData(1000, AlarmSeverity.Low, 250)] // above range: clamped to priority 999
|
||
[InlineData(int.MaxValue, AlarmSeverity.Low, 250)]
|
||
public void Map_assigns_the_AVEVA_priority_band(int rawPriority, AlarmSeverity expectedBucket, int expectedOpcUaSeverity)
|
||
{
|
||
var (bucket, opcUa) = MxAccessSeverityMapper.Map(rawPriority);
|
||
|
||
bucket.ShouldBe(expectedBucket);
|
||
opcUa.ShouldBe(expectedOpcUaSeverity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifies the mapping is monotonic decreasing over the whole AVEVA range: a more urgent (lower) priority
|
||
/// never yields a lower OPC UA severity, and the two ends differ (so a constant mapping fails too).
|
||
/// </summary>
|
||
[Fact]
|
||
public void Map_never_ranks_a_less_urgent_priority_above_a_more_urgent_one()
|
||
{
|
||
var previous = MxAccessSeverityMapper.Map(1).OpcUaSeverity;
|
||
for (var priority = 2; priority <= 999; priority++)
|
||
{
|
||
var current = MxAccessSeverityMapper.Map(priority).OpcUaSeverity;
|
||
current.ShouldBeLessThanOrEqualTo(previous, $"priority {priority} must not be more severe than {priority - 1}");
|
||
previous = current;
|
||
}
|
||
|
||
MxAccessSeverityMapper.Map(1).OpcUaSeverity.ShouldBeGreaterThan(MxAccessSeverityMapper.Map(999).OpcUaSeverity);
|
||
}
|
||
|
||
/// <summary>Verifies the live mapper is the shared table history uses (Gitea #886), value for value.</summary>
|
||
[Fact]
|
||
public void Map_is_the_shared_GalaxyAlarmPriority_table()
|
||
{
|
||
for (var priority = -2; priority <= 1001; priority++)
|
||
MxAccessSeverityMapper.Map(priority).ShouldBe(GalaxyAlarmPriority.Map(priority));
|
||
}
|
||
}
|