Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcUaClientReverseConnectSmokeTests.cs
2026-04-26 06:08:30 -04:00

66 lines
2.9 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests;
/// <summary>
/// Reverse-connect smoke (PR-11). Asserts the driver binds a listener at the
/// configured URL and accepts an inbound dial from <c>opc-plc-rc</c> (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.
/// </summary>
/// <remarks>
/// <para>
/// <b>Build-only by default</b>: the test is gated on <c>OPCUA_RC_SIM</c>
/// + the docker-compose <c>opc-plc-rc</c> 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.
/// </para>
/// </remarks>
[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");
}
}