using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config; using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests; /// /// Pins that the GalaxyDriver populates the extended AlarmEventArgs fields /// (OperatorComment, OriginalRaiseTimestampUtc, AlarmCategory) when the gateway /// alarm feed delivers a transition with the rich payload, and leaves them null on /// transitions that don't carry them. /// public sealed class GalaxyDriverAlarmEventArgsExtensionTests { [Fact] public async Task Acknowledge_transition_with_full_payload_populates_extended_fields() { var feed = new FakeAlarmFeed(); using var driver = NewDriver(feed); await driver.SubscribeAlarmsAsync(["Tank01"], CancellationToken.None); var observed = new List(); driver.OnAlarmEvent += (_, args) => observed.Add(args); var raise = new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc); var ack = raise.AddSeconds(45); feed.Emit(new GalaxyAlarmTransition( AlarmFullReference: "Tank01.Level.HiHi", SourceObjectReference: "Tank01", AlarmTypeName: "AnalogLimitAlarm.HiHi", TransitionKind: GalaxyAlarmTransitionKind.Acknowledge, SeverityBucket: AlarmSeverity.Critical, OpcUaSeverity: 800, RawMxAccessSeverity: 750, OriginalRaiseTimestampUtc: raise, TransitionTimestampUtc: ack, OperatorUser: "alice", OperatorComment: "investigating", Category: "Process", Description: "Tank 01 high-high level")); observed.ShouldHaveSingleItem(); observed[0].OperatorComment.ShouldBe("investigating"); observed[0].OriginalRaiseTimestampUtc.ShouldBe(raise); observed[0].AlarmCategory.ShouldBe("Process"); } [Fact] public async Task Raise_transition_without_optional_fields_leaves_them_null() { var feed = new FakeAlarmFeed(); using var driver = NewDriver(feed); await driver.SubscribeAlarmsAsync(["Tank01"], CancellationToken.None); var observed = new List(); driver.OnAlarmEvent += (_, args) => observed.Add(args); feed.Emit(new GalaxyAlarmTransition( AlarmFullReference: "Tank01.Level.HiHi", SourceObjectReference: string.Empty, AlarmTypeName: "AnalogLimitAlarm.HiHi", TransitionKind: GalaxyAlarmTransitionKind.Raise, SeverityBucket: AlarmSeverity.Critical, OpcUaSeverity: 800, RawMxAccessSeverity: 750, OriginalRaiseTimestampUtc: null, TransitionTimestampUtc: DateTime.UtcNow, OperatorUser: string.Empty, OperatorComment: string.Empty, Category: string.Empty, Description: string.Empty)); observed.ShouldHaveSingleItem(); observed[0].OperatorComment.ShouldBeNull(); observed[0].OriginalRaiseTimestampUtc.ShouldBeNull(); observed[0].AlarmCategory.ShouldBeNull(); } private static GalaxyDriver NewDriver(IGalaxyAlarmFeed feed) { var options = new GalaxyDriverOptions( new GalaxyGatewayOptions("http://localhost:5000", "literal-api-key"), new GalaxyMxAccessOptions("AlarmExtensionTest"), new GalaxyRepositoryOptions(), new GalaxyReconnectOptions()); return new GalaxyDriver( driverInstanceId: "drv-1", options: options, hierarchySource: null, alarmFeed: feed); } /// In-memory the test drives directly. private sealed class FakeAlarmFeed : IGalaxyAlarmFeed { public event EventHandler? OnAlarmTransition; public void Start() { } public void Emit(GalaxyAlarmTransition transition) => OnAlarmTransition?.Invoke(this, transition); public ValueTask DisposeAsync() => ValueTask.CompletedTask; } }