using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests; [Trait("Category", "Unit")] public sealed class OpcUaClientAlarmTests { [Theory] [InlineData((ushort)1, AlarmSeverity.Low)] [InlineData((ushort)200, AlarmSeverity.Low)] [InlineData((ushort)201, AlarmSeverity.Medium)] [InlineData((ushort)500, AlarmSeverity.Medium)] [InlineData((ushort)501, AlarmSeverity.High)] [InlineData((ushort)800, AlarmSeverity.High)] [InlineData((ushort)801, AlarmSeverity.Critical)] [InlineData((ushort)1000, AlarmSeverity.Critical)] public void MapSeverity_buckets_per_OPC_UA_Part_9_guidance(ushort opcSev, AlarmSeverity expected) { OpcUaClientDriver.MapSeverity(opcSev).ShouldBe(expected); } [Fact] public void MapSeverity_zero_maps_to_Low() { // 0 isn't in OPC UA's 1-1000 range but we handle it gracefully as Low. OpcUaClientDriver.MapSeverity(0).ShouldBe(AlarmSeverity.Low); } [Fact] public async Task SubscribeAlarmsAsync_without_initialize_throws_InvalidOperationException() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-alarm-uninit"); await Should.ThrowAsync(async () => await drv.SubscribeAlarmsAsync([], TestContext.Current.CancellationToken)); } [Fact] public async Task UnsubscribeAlarmsAsync_with_unknown_handle_is_noop() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-alarm-unknown"); // Parallels the subscribe handle path — session-drop races shouldn't crash the caller. await drv.UnsubscribeAlarmsAsync(new FakeAlarmHandle(), TestContext.Current.CancellationToken); } [Fact] public async Task AcknowledgeAsync_without_initialize_throws_InvalidOperationException() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-ack-uninit"); await Should.ThrowAsync(async () => await drv.AcknowledgeAsync( [new AlarmAcknowledgeRequest("ns=2;s=Src", "ns=2;s=Cond", "operator ack")], TestContext.Current.CancellationToken)); } [Fact] public async Task AcknowledgeAsync_with_empty_batch_is_noop_even_without_init() { // Empty batch short-circuits before touching the session, so it's safe pre-init. This // keeps batch-ack callers from needing to guard the list size themselves. using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-ack-empty"); await drv.AcknowledgeAsync([], TestContext.Current.CancellationToken); } private sealed class FakeAlarmHandle : IAlarmSubscriptionHandle { public string DiagnosticId => "fake-alarm"; } }