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.IntegrationTests; /// /// End-to-end test against opc-plc Docker fixture. Validates that NodeId strings returned /// by RootAsync/ExpandAsync round-trip cleanly through ExpandAsync — the regression test /// for namespace-stable address encoding. /// [Trait("Category", "Integration"), Trait("Fixture", "opc-plc")] public class BrowseRoundTripTests { [Fact] public async Task Three_level_expand_round_trips_resolve_back() { var endpoint = Environment.GetEnvironmentVariable("OPCUA_SIM_ENDPOINT") ?? "opc.tcp://10.100.0.35:50000"; // Numeric ordinals because the production browser uses default System.Text.Json // without a JsonStringEnumConverter (SecurityPolicy.None=0, SecurityMode.None=0, // AuthType.Anonymous=0). var json = $$""" { "EndpointUrl":"{{endpoint}}", "SecurityPolicy":0,"SecurityMode":0,"AuthType":0, "SessionTimeout":"00:01:00","Timeout":"00:00:10","PerEndpointConnectTimeout":"00:00:10" } """; var browser = new OpcUaClientDriverBrowser(); await using var session = await browser.OpenAsync(json, TestContext.Current.CancellationToken); var roots = await session.RootAsync(TestContext.Current.CancellationToken); var current = roots.FirstOrDefault(n => n.HasChildrenHint); current.ShouldNotBeNull("expected at least one Folder under ObjectsFolder on opc-plc"); // Drill down up to 2 more levels by alternately expanding the first Folder for (var depth = 0; depth < 2; depth++) { var next = await session.ExpandAsync(current!.NodeId, TestContext.Current.CancellationToken); var step = next.FirstOrDefault(n => n.HasChildrenHint); if (step is null) break; current = step; } // Round-trip the leaf-most reachable folder string — must resolve back through the // namespace map, prove the nsu= encoding survives. var children = await session.ExpandAsync(current!.NodeId, TestContext.Current.CancellationToken); children.ShouldNotBeNull(); } }