using System.CommandLine; using ZB.MOM.WW.ScadaBridge.CLI; using ZB.MOM.WW.ScadaBridge.CLI.Commands; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; namespace ZB.MOM.WW.ScadaBridge.CLI.Tests; /// /// Parse tests for the site-routed data-connection subcommands added for actor parity /// (arch-review C4): browse, search, verify-endpoint, and /// certs list|trust|remove. Each test parses a real command line through /// System.CommandLine and asserts the exact management command record produced, /// including the routed --siteSiteIdentifier mapping. /// public class ConnectionBrowseCommandsTests { /// /// Builds a root command with the browse/search/verify/certs subcommands attached /// under a bare data-connection group. The interceptor captures the built /// command record instead of sending it over HTTP, so the tests never need a live /// management server. /// private static (RootCommand root, List captured) BuildHarness() { var url = new Option("--url") { Recursive = true }; var username = new Option("--username") { Recursive = true }; var password = new Option("--password") { Recursive = true }; var format = CliOptions.CreateFormatOption(); var root = new RootCommand(); root.Add(url); root.Add(username); root.Add(password); root.Add(format); var captured = new List(); var dataConnection = new Command("data-connection"); ConnectionBrowseCommands.AddTo(dataConnection, url, format, username, password, cmd => { captured.Add(cmd); return 0; }); root.Add(dataConnection); return (root, captured); } [Fact] public async Task Browse_BuildsBrowseNodeCommand_WithSiteIdentifier() { var (root, captured) = BuildHarness(); var exit = await root .Parse(new[] { "data-connection", "browse", "--site", "SITE1", "--connection", "conn1", "--node-id", "ns=2;s=X" }) .InvokeAsync(); Assert.Equal(0, exit); var cmd = Assert.IsType(Assert.Single(captured)); Assert.Equal("SITE1", cmd.SiteIdentifier); Assert.Equal("conn1", cmd.ConnectionName); Assert.Equal("ns=2;s=X", cmd.ParentNodeId); } [Fact] public async Task Browse_WithoutNodeId_BrowsesFromRoot() { var (root, captured) = BuildHarness(); var exit = await root .Parse(new[] { "data-connection", "browse", "--site", "SITE1", "--connection", "conn1" }) .InvokeAsync(); Assert.Equal(0, exit); var cmd = Assert.IsType(Assert.Single(captured)); Assert.Equal("SITE1", cmd.SiteIdentifier); Assert.Null(cmd.ParentNodeId); } [Fact] public async Task Search_BuildsSearchAddressSpaceCommand_WithSiteIdentifier() { var (root, captured) = BuildHarness(); var exit = await root .Parse(new[] { "data-connection", "search", "--site", "SITE1", "--connection", "conn1", "--query", "Motor", "--max-depth", "3", "--max-results", "50" }) .InvokeAsync(); Assert.Equal(0, exit); var cmd = Assert.IsType(Assert.Single(captured)); Assert.Equal("SITE1", cmd.SiteIdentifier); Assert.Equal("conn1", cmd.ConnectionName); Assert.Equal("Motor", cmd.Query); Assert.Equal(3, cmd.MaxDepth); Assert.Equal(50, cmd.MaxResults); } [Fact] public async Task VerifyEndpoint_ReadsConfigJsonFromFile_WithSiteIdentifier() { var (root, captured) = BuildHarness(); var configPath = Path.GetTempFileName(); const string configJson = "{\"endpointUrl\":\"opc.tcp://server:4840\"}"; await File.WriteAllTextAsync(configPath, configJson); try { var exit = await root .Parse(new[] { "data-connection", "verify-endpoint", "--site", "SITE1", "--protocol", "OpcUa", "--config-file", configPath }) .InvokeAsync(); Assert.Equal(0, exit); var cmd = Assert.IsType(Assert.Single(captured)); Assert.Equal("SITE1", cmd.SiteIdentifier); Assert.Equal("OpcUa", cmd.Protocol); Assert.Equal(configJson, cmd.ConfigJson); } finally { File.Delete(configPath); } } [Fact] public async Task CertsList_BuildsListServerCertsCommand_WithSiteIdentifier() { var (root, captured) = BuildHarness(); var exit = await root .Parse(new[] { "data-connection", "certs", "list", "--site", "SITE1", "--connection", "conn1" }) .InvokeAsync(); Assert.Equal(0, exit); var cmd = Assert.IsType(Assert.Single(captured)); Assert.Equal("SITE1", cmd.SiteIdentifier); } [Fact] public async Task CertsTrust_Base64EncodesDerFile_WithSiteIdentifier() { var (root, captured) = BuildHarness(); var derPath = Path.GetTempFileName(); var derBytes = new byte[] { 0x30, 0x82, 0x01, 0x0A, 0xDE, 0xAD, 0xBE, 0xEF }; await File.WriteAllBytesAsync(derPath, derBytes); try { var exit = await root .Parse(new[] { "data-connection", "certs", "trust", "--site", "SITE1", "--connection", "conn1", "--der-file", derPath, "--thumbprint", "AABBCC" }) .InvokeAsync(); Assert.Equal(0, exit); var cmd = Assert.IsType(Assert.Single(captured)); Assert.Equal("SITE1", cmd.SiteIdentifier); Assert.Equal("conn1", cmd.ConnectionName); Assert.Equal("AABBCC", cmd.Thumbprint); Assert.Equal(Convert.ToBase64String(derBytes), cmd.DerBase64); } finally { File.Delete(derPath); } } [Fact] public async Task CertsRemove_BuildsRemoveServerCertCommand_WithSiteIdentifier() { var (root, captured) = BuildHarness(); var exit = await root .Parse(new[] { "data-connection", "certs", "remove", "--site", "SITE1", "--connection", "conn1", "--thumbprint", "AABBCC" }) .InvokeAsync(); Assert.Equal(0, exit); var cmd = Assert.IsType(Assert.Single(captured)); Assert.Equal("SITE1", cmd.SiteIdentifier); Assert.Equal("AABBCC", cmd.Thumbprint); } }