using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests; /// /// Reverse-connect smoke (PR-11). Asserts the driver binds a listener at the /// configured URL and accepts an inbound dial from opc-plc-rc (the /// reverse-connect variant of Microsoft Industrial IoT's OPC UA simulator). /// The session that comes up should be functionally identical to a dialled /// session — same Read / Subscribe surface — but the transport direction is /// server → client instead of client → server. /// /// /// /// Build-only by default: the test is gated on OPCUA_RC_SIM /// + the docker-compose opc-plc-rc service. CI runs that don't /// spin up the dialer skip with a clear message; the build still has to /// compile so wire-level regressions in the reverse-connect code path are /// caught even when the dialer isn't around. /// /// [Collection(OpcPlcReverseConnectCollection.Name)] [Trait("Category", "Integration")] [Trait("Simulator", "opc-plc-rc")] public sealed class OpcUaClientReverseConnectSmokeTests(OpcPlcReverseConnectFixture rc) { [Fact] public async Task Driver_accepts_reverse_connect_from_opc_plc_rc_simulator() { if (rc.SkipReason is not null) Assert.Skip(rc.SkipReason); var options = new OpcUaClientDriverOptions { // Conventional EndpointUrl still required — the driver derives the // EndpointDescription from it for the session-create call. The actual // dial direction is flipped by ReverseConnect.Enabled below. EndpointUrl = "opc.tcp://opc-plc-rc:50001", SecurityPolicy = OpcUaSecurityPolicy.None, SecurityMode = OpcUaSecurityMode.None, AuthType = OpcUaAuthType.Anonymous, AutoAcceptCertificates = true, Timeout = TimeSpan.FromSeconds(15), SessionTimeout = TimeSpan.FromSeconds(60), ReverseConnect = new ReverseConnectOptions( Enabled: true, ListenerUrl: rc.ListenerUrl, // null = accept any upstream — only one is dialling this listener in // the smoke test, so there's no demux to worry about. ExpectedServerUri: null), }; await using var drv = new OpcUaClientDriver(options, "opcua-rc-smoke"); await drv.InitializeAsync("{}", TestContext.Current.CancellationToken); // The session is up via reverse path — assert a steady-state read works. var snapshots = await drv.ReadAsync( [OpcPlcProfile.StepUp], TestContext.Current.CancellationToken); snapshots.Count.ShouldBe(1); snapshots[0].StatusCode.ShouldBe(0u, "reverse-connect session must round-trip a Read identically to a dialled session"); } }