using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Client.CLI.Commands; using ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes; using BrowseResult = ZB.MOM.WW.OtOpcUa.Client.Shared.Models.BrowseResult; namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests; public class BrowseCommandTests { [Fact] public async Task Execute_PrintsBrowseResults() { var fakeService = new FakeOpcUaClientService { BrowseResults = new List { new("ns=2;s=Obj1", "Object1", "Object", true), new("ns=2;s=Var1", "Variable1", "Variable", false), new("ns=2;s=Meth1", "Method1", "Method", false) } }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new BrowseCommand(factory) { Url = "opc.tcp://localhost:4840" }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); var output = TestConsoleHelper.GetOutput(console); output.ShouldContain("[Object] Object1 (NodeId: ns=2;s=Obj1)"); output.ShouldContain("[Variable] Variable1 (NodeId: ns=2;s=Var1)"); output.ShouldContain("[Method] Method1 (NodeId: ns=2;s=Meth1)"); } [Fact] public async Task Execute_BrowsesFromSpecifiedNode() { var fakeService = new FakeOpcUaClientService { BrowseResults = new List() }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new BrowseCommand(factory) { Url = "opc.tcp://localhost:4840", NodeId = "ns=2;s=StartNode" }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); fakeService.BrowseNodeIds.Count.ShouldBe(1); fakeService.BrowseNodeIds[0].ShouldNotBeNull(); fakeService.BrowseNodeIds[0]!.Identifier.ShouldBe("StartNode"); } [Fact] public async Task Execute_DefaultBrowsesFromNull() { var fakeService = new FakeOpcUaClientService { BrowseResults = new List() }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new BrowseCommand(factory) { Url = "opc.tcp://localhost:4840" }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); fakeService.BrowseNodeIds.Count.ShouldBe(1); fakeService.BrowseNodeIds[0].ShouldBeNull(); } [Fact] public async Task Execute_NonRecursive_BrowsesSingleLevel() { var fakeService = new FakeOpcUaClientService { BrowseResults = new List { new("ns=2;s=Child", "Child", "Object", true) } }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new BrowseCommand(factory) { Url = "opc.tcp://localhost:4840", Depth = 5 // Should be ignored without recursive flag }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); // Only the root level browse should happen, not child fakeService.BrowseNodeIds.Count.ShouldBe(1); } [Fact] public async Task Execute_Recursive_BrowsesChildren() { var fakeService = new FakeOpcUaClientService(); // Override browse to return children only on first call // We can't easily do this with the simple fake, but the default returns results with HasChildren=true // which will trigger child browse with recursive=true, depth=2 var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new BrowseCommand(factory) { Url = "opc.tcp://localhost:4840", Recursive = true, Depth = 2 }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); // Root browse + child browse (for Node1 which HasChildren=true) fakeService.BrowseNodeIds.Count.ShouldBeGreaterThan(1); } [Fact] public async Task Execute_DisconnectsInFinally() { var fakeService = new FakeOpcUaClientService { BrowseResults = new List() }; var factory = new FakeOpcUaClientServiceFactory(fakeService); var command = new BrowseCommand(factory) { Url = "opc.tcp://localhost:4840" }; using var console = TestConsoleHelper.CreateConsole(); await command.ExecuteAsync(console); fakeService.DisconnectCalled.ShouldBeTrue(); fakeService.DisposeCalled.ShouldBeTrue(); } }