using CliFx.Exceptions; using Opc.Ua; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Client.CLI.Commands; using ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes; using ZB.MOM.WW.OtOpcUa.Client.Shared.Models; namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests; public class ShelveCommandTests { /// Verifies that OneShot shelving produces the correct kind and zero duration. [Fact] public async Task Execute_OneShotKind_PassesOneShotToService() { var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.Good }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new ShelveCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=MyAlarm", Kind = "OneShot", DurationSeconds = 0 }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); fakeService.ShelveAlarmCalls.Count.ShouldBe(1); fakeService.ShelveAlarmCalls[0].Kind.ShouldBe(ShelveKind.OneShot); fakeService.ShelveAlarmCalls[0].DurationSeconds.ShouldBe(0); fakeService.ShelveAlarmCalls[0].ConditionNodeId.ShouldBe("ns=2;s=MyAlarm"); var output = TestConsoleHelper.GetOutput(console); output.ShouldContain("successful"); } /// Verifies that Timed shelving passes the correct duration to the service. [Fact] public async Task Execute_TimedKind_PassesDurationToService() { var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.Good }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new ShelveCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=MyAlarm", Kind = "Timed", DurationSeconds = 300 }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); fakeService.ShelveAlarmCalls.Count.ShouldBe(1); fakeService.ShelveAlarmCalls[0].Kind.ShouldBe(ShelveKind.Timed); fakeService.ShelveAlarmCalls[0].DurationSeconds.ShouldBe(300); } /// Verifies that Unshelve produces the Unshelve kind. [Fact] public async Task Execute_UnshelveKind_PassesUnshelveToService() { var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.Good }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new ShelveCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=MyAlarm", Kind = "Unshelve", DurationSeconds = 0 }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); fakeService.ShelveAlarmCalls.Count.ShouldBe(1); fakeService.ShelveAlarmCalls[0].Kind.ShouldBe(ShelveKind.Unshelve); } /// Verifies that kind parsing is case-insensitive (e.g. "oneshot" works). [Fact] public async Task Execute_KindCaseInsensitive_ParsesCorrectly() { var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.Good }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new ShelveCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=MyAlarm", Kind = "oneshot", DurationSeconds = 0 }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); fakeService.ShelveAlarmCalls.Count.ShouldBe(1); fakeService.ShelveAlarmCalls[0].Kind.ShouldBe(ShelveKind.OneShot); } /// Verifies that an invalid Kind value throws CommandException. [Fact] public async Task Execute_InvalidKind_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new ShelveCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=MyAlarm", Kind = "SuperShelve", DurationSeconds = 0 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("kind", Case.Insensitive); } /// Verifies that Timed with zero duration throws CommandException (missing --duration). [Fact] public async Task Execute_TimedWithZeroDuration_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new ShelveCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=MyAlarm", Kind = "Timed", DurationSeconds = 0 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("duration", Case.Insensitive); } /// Verifies that a bad status code from the service prints a failure message. [Fact] public async Task Execute_BadStatus_PrintsFailureMessage() { var fakeService = new FakeOpcUaClientService { ShelveAlarmResult = StatusCodes.BadNotSupported }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new ShelveCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=MyAlarm", Kind = "OneShot", DurationSeconds = 0 }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); var output = TestConsoleHelper.GetOutput(console); output.ShouldContain("failed"); } /// Verifies that the command disconnects in the finally block. [Fact] public async Task Execute_DisconnectsInFinally() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new ShelveCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=MyAlarm", Kind = "OneShot", DurationSeconds = 0 }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); fakeService.DisconnectCalled.ShouldBeTrue(); fakeService.DisposeCalled.ShouldBeTrue(); } }