using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Commons.Browsing; using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.Tests; /// /// Live-server tests against the opc-plc Docker fixture. Bring up with /// `lmxopcua-fix up opcuaclient` from PowerShell before running. These tests /// drive the internal OpcUaClientBrowseSession through its public factory /// so no InternalsVisibleTo is needed. /// Filter out with --filter "Category!=RequiresOpcPlc" when the fixture /// is unreachable. /// [Trait("Category", "RequiresOpcPlc")] public sealed class OpcUaClientBrowseSessionTests { private static string Endpoint => Environment.GetEnvironmentVariable("OPCUA_SIM_ENDPOINT") ?? "opc.tcp://10.100.0.35:50000"; // Enum values use their numeric ordinal because the browser uses default // System.Text.Json with no JsonStringEnumConverter. SecurityPolicy.None, // SecurityMode.None, OpcUaAuthType.Anonymous are all index 0, so omitting them // also works — keeping them explicit for documentation value. private static string ConfigJson => $$""" { "EndpointUrl":"{{Endpoint}}", "SecurityPolicy":0, "SecurityMode":0, "AuthType":0, "SessionTimeout":"00:01:00", "Timeout":"00:00:10", "PerEndpointConnectTimeout":"00:00:10" } """; /// RootAsync should surface at least one node under ObjectsFolder /// (opc-plc exposes a non-empty top level). [Fact] public async Task RootAsync_returns_at_least_one_node() { var ct = TestContext.Current.CancellationToken; var browser = new OpcUaClientDriverBrowser(); await using var session = await browser.OpenAsync(ConfigJson, ct); var roots = await session.RootAsync(ct); roots.Count.ShouldBeGreaterThan(0); } /// ExpandAsync should accept a stable NodeId returned by RootAsync /// and round-trip through the live namespace table. [Fact] public async Task ExpandAsync_round_trips_stable_NodeId() { var ct = TestContext.Current.CancellationToken; var browser = new OpcUaClientDriverBrowser(); await using var session = await browser.OpenAsync(ConfigJson, ct); var roots = await session.RootAsync(ct); var folder = roots.FirstOrDefault(n => n.Kind == BrowseNodeKind.Folder); folder.ShouldNotBeNull("expected at least one Folder under ObjectsFolder"); var children = await session.ExpandAsync(folder!.NodeId, ct); children.ShouldNotBeNull(); } /// The OPC UA picker treats variables as terminal leaves, so /// AttributesAsync must always be empty for this driver. [Fact] public async Task AttributesAsync_is_empty_for_opcuaclient() { var ct = TestContext.Current.CancellationToken; var browser = new OpcUaClientDriverBrowser(); await using var session = await browser.OpenAsync(ConfigJson, ct); var attrs = await session.AttributesAsync("nsu=http://opcfoundation.org/UA/;i=85", ct); attrs.ShouldBeEmpty(); } }