using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol; using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters; namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Adapters; /// /// Tests for . /// /// Two shapes here: /// /// 1. A live round-trip against the infra OPC UA server /// (opc.tcp://localhost:50000 — see infra/docker-compose.yml, /// started via cd infra && docker compose up -d opcua). /// Marked [SkippableFact] so it reports Skipped — not failed — on /// machines that don't have the infra stack running. The live test asserts /// that the server root browse returns the standard "Server" node, which /// proves we targeted ObjectsFolder (ns=0;i=85) and that the /// response mapping survived the round trip. /// /// 2. A pure unit test that exercises the not-connected guard — no infra /// needed, runs in every build. /// [Trait("Category", "RequiresOpcUa")] public class RealOpcUaClientBrowseTests { // The infra/docker-compose.yml opcua container maps the OPC PLC simulator // on host port 50000 (not the OPC UA default 4840). Matches what the // existing docker/ and docker-env2/ topologies dial into. private const string EndpointUrl = "opc.tcp://localhost:50000"; [SkippableFact] public async Task BrowseChildren_at_root_returns_ObjectsFolder_with_Server_node() { await using var client = new RealOpcUaClient(); // Probe the endpoint before asserting anything. If the infra OPC UA // server isn't up, ConnectAsync surfaces a socket/timeout error from // deep inside the OPC Foundation SDK — we treat that as "infra not // available" and skip rather than fail, mirroring the SkippableFact // pattern already used in ConfigurationDatabase/AuditLog tests. try { await client.ConnectAsync( EndpointUrl, new OpcUaConnectionOptions(AutoAcceptUntrustedCerts: true)); } catch (Exception ex) { Skip.If(true, $"OPC UA test server not reachable on {EndpointUrl}: {ex.Message}"); return; // Skip.If throws; this is unreachable but keeps the compiler happy. } var result = await client.BrowseChildrenAsync(parentNodeId: null); // Under ObjectsFolder (ns=0;i=85) every OPC UA-compliant server // exposes a 'Server' object at ns=0;i=2253 — its presence confirms // we hit the right root and that DisplayName mapping survives the // round trip. Assert.NotEmpty(result.Children); Assert.Contains(result.Children, n => n.DisplayName == "Server"); } [Fact] public async Task BrowseChildren_throws_ConnectionNotConnected_when_session_is_null() { // No ConnectAsync — _session is still null, so the typed guard at the // top of BrowseChildrenAsync should fire before any SDK call. await using var client = new RealOpcUaClient(); await Assert.ThrowsAsync( () => client.BrowseChildrenAsync(parentNodeId: null)); } }