Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcUaClientSmokeTests.cs
Joseph Doherty a25593a9c6 chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the
Rider Solution Explorer mirrors the module structure. Folders: Core, Server,
Drivers (with a nested Driver CLIs subfolder), Client, Tooling.

- Move every project folder on disk with git mv (history preserved as renames).
- Recompute relative paths in 57 .csproj files: cross-category ProjectReferences,
  the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external
  mxaccessgw refs in Driver.Galaxy and its test project.
- Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders.
- Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL,
  integration, install).

Build green (0 errors); unit tests pass. Docs left for a separate pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 01:55:28 -04:00

93 lines
3.8 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests;
/// <summary>
/// End-to-end smoke against a live <c>opc-plc</c> (task #215). Drives the real
/// OPC UA Secure Channel + Session + MonitoredItem exchange — no mocks. Every
/// test here proves a capability surface that loopback against our own server
/// couldn't exercise cleanly: real cert negotiation, real endpoint descriptions,
/// real simulated nodes that change without a write.
/// </summary>
[Collection(OpcPlcCollection.Name)]
[Trait("Category", "Integration")]
[Trait("Simulator", "opc-plc")]
public sealed class OpcUaClientSmokeTests(OpcPlcFixture sim)
{
[Fact]
public async Task Client_connects_and_reads_StepUp_node_through_real_OPC_UA_stack()
{
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
var options = OpcPlcProfile.BuildOptions(sim.EndpointUrl);
await using var drv = new OpcUaClientDriver(options, driverInstanceId: "opcua-smoke-read");
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
var snapshots = await drv.ReadAsync(
[OpcPlcProfile.StepUp], TestContext.Current.CancellationToken);
snapshots.Count.ShouldBe(1);
snapshots[0].StatusCode.ShouldBe(0u, "opc-plc StepUp read must succeed end-to-end");
snapshots[0].Value.ShouldNotBeNull("StepUp always has a current value");
}
[Fact]
public async Task Client_reads_batch_of_varied_types_from_live_simulator()
{
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
var options = OpcPlcProfile.BuildOptions(sim.EndpointUrl);
await using var drv = new OpcUaClientDriver(options, driverInstanceId: "opcua-smoke-batch");
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
var snapshots = await drv.ReadAsync(
[OpcPlcProfile.StepUp, OpcPlcProfile.RandomSignedInt32, OpcPlcProfile.AlternatingBoolean],
TestContext.Current.CancellationToken);
snapshots.Count.ShouldBe(3);
foreach (var s in snapshots)
{
s.StatusCode.ShouldBe(0u);
s.Value.ShouldNotBeNull();
}
// AlternatingBoolean should decode as a bool specifically — catches a common
// attribute-mapping regression where the driver stringifies variant values.
snapshots[2].Value.ShouldBeOfType<bool>();
}
[Fact]
public async Task Client_subscribe_receives_StepUp_data_changes_from_live_server()
{
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
var options = OpcPlcProfile.BuildOptions(sim.EndpointUrl);
await using var drv = new OpcUaClientDriver(options, driverInstanceId: "opcua-smoke-sub");
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
var observed = new List<DataChangeEventArgs>();
var gate = new SemaphoreSlim(0);
drv.OnDataChange += (_, e) =>
{
lock (observed) observed.Add(e);
gate.Release();
};
var handle = await drv.SubscribeAsync(
[OpcPlcProfile.FastUInt1], TimeSpan.FromMilliseconds(250),
TestContext.Current.CancellationToken);
// FastUInt1 ticks every 100 ms — one publishing interval (250 ms) should deliver.
// Wait up to 3 s to tolerate container warm-up + first-publish delay.
var got = await gate.WaitAsync(TimeSpan.FromSeconds(3), TestContext.Current.CancellationToken);
got.ShouldBeTrue("opc-plc FastUInt1 must publish at least one data change within 3s");
int observedCount;
lock (observed) observedCount = observed.Count;
observedCount.ShouldBeGreaterThan(0);
await drv.UnsubscribeAsync(handle, TestContext.Current.CancellationToken);
}
}