@@ -0,0 +1,65 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user