using CliFx.Exceptions; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Client.CLI.Commands; using ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes; namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests; /// /// Regression tests for Client.CLI-003: numeric command options must validate their ranges /// and report a clean operator-facing error instead of silently passing bad values to the SDK. /// public class CommandRangeValidationTests { [Fact] public async Task BrowseCommand_NegativeDepth_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new BrowseCommand(factory) { Url = "opc.tcp://localhost:4840", Depth = -1 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("--depth"); } [Fact] public async Task BrowseCommand_ZeroDepth_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new BrowseCommand(factory) { Url = "opc.tcp://localhost:4840", Depth = 0 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("--depth"); } [Fact] public async Task SubscribeCommand_ZeroInterval_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new SubscribeCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=N", Interval = 0 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("--interval"); } [Fact] public async Task SubscribeCommand_NegativeInterval_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new SubscribeCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=N", Interval = -50 }; using var console = TestConsoleHelper.CreateConsole(); await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); } [Fact] public async Task SubscribeCommand_RecursiveZeroMaxDepth_ThrowsCommandException() { // --max-depth only matters when --recursive is set; validation is scoped to that combination. var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new SubscribeCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=N", Recursive = true, MaxDepth = 0 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("--max-depth"); } [Fact] public async Task SubscribeCommand_NegativeDuration_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new SubscribeCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=N", DurationSeconds = -5 }; using var console = TestConsoleHelper.CreateConsole(); await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); } [Fact] public async Task AlarmsCommand_ZeroInterval_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new AlarmsCommand(factory) { Url = "opc.tcp://localhost:4840", Interval = 0 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("--interval"); } [Fact] public async Task HistoryReadCommand_NegativeMax_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new HistoryReadCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=N", MaxValues = -10 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("--max"); } [Fact] public async Task HistoryReadCommand_ZeroInterval_ThrowsCommandException() { var fakeService = new FakeOpcUaClientService(); var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new HistoryReadCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=N", Aggregate = "Average", IntervalMs = 0 }; using var console = TestConsoleHelper.CreateConsole(); var ex = await Should.ThrowAsync(async () => await command.ExecuteAsync(console)); ex.Message.ShouldContain("--interval"); } }