Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcPlcProfile.cs
Joseph Doherty ebc0511c72 fix(driver-opcuaclient): resolve High code-review findings (Driver.OpcUaClient-001..-005)
Driver.OpcUaClient-001 — ReadAsync/WriteAsync/DiscoverAsync captured the
session before acquiring _gate, so a reconnect that completed while the
operation was blocked on the gate left the wire call bound to a stale,
closed session. All three now re-read Session (and parse NodeIds) inside
the _gate critical section after WaitAsync returns.

Driver.OpcUaClient-002 — OnReconnectComplete ignored the give-up (null
session) case, permanently wedging the driver with no Faulted signal and
no reconnect loop. The give-up branch now transitions HostState to
Faulted, sets a Faulted DriverHealth with an explanatory message, and
re-arms a fresh SessionReconnectHandler (TryRearmReconnect) against the
last-known session so an always-on gateway self-heals.

Driver.OpcUaClient-003 — BrowseRecursiveAsync discarded browse
continuation points, silently truncating large remote folders.
It now loops on BrowseResult.ContinuationPoint calling BrowseNextAsync
and appending each page until the continuation point is empty.

Driver.OpcUaClient-004 — driver-specs.md §8 namespace handling was
absent. Added NamespaceMap (built from session.NamespaceUris at connect,
rebuilt on reconnect) which persists discovered NodeIds in the
server-stable nsu=<uri>;... form; reads/writes re-resolve that form
against the current session so a remote namespace-table reorder no
longer misaddresses nodes. Added the TargetNamespaceKind option +
UnsMappingTable and ValidateNamespaceKind startup enforcement.

Driver.OpcUaClient-005 — OnKeepAlive read/wrote _reconnectHandler
without a lock, racing the SDK keep-alive timer thread and leaking
handlers. The check-and-set in OnKeepAlive, the take-and-clear in
ShutdownAsync, and the dispose/re-arm in OnReconnectComplete now all
run inside the _probeLock critical section.

Adds OpcUaClientNamespaceTests (11 xUnit + Shouldly regression tests)
covering ValidateNamespaceKind and the NamespaceMap stable encoding.
Reconnect/browse wire paths remain fixture-gated per finding -015.

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

42 lines
2.0 KiB
C#

using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests;
/// <summary>
/// Driver-side configuration + well-known opc-plc node identifiers that the smoke
/// tests address. Node IDs are stable across opc-plc releases — the simulator
/// guarantees the same <c>ns=3;s=...</c> names shipped since v1.0. If a release
/// bump breaks these, the fixture's pinned image tag needs a coordinated bump.
/// </summary>
public static class OpcPlcProfile
{
/// <summary>opc-plc monotonically-increasing UInt32; ticks once per second under default opts.</summary>
public const string StepUp = "ns=3;s=StepUp";
/// <summary>opc-plc random Int32 node; new value ~every 100ms.</summary>
public const string RandomSignedInt32 = "ns=3;s=RandomSignedInt32";
/// <summary>opc-plc alternating boolean; flips every second.</summary>
public const string AlternatingBoolean = "ns=3;s=AlternatingBoolean";
/// <summary>opc-plc fast uint node — ticks every 100ms. Used for subscription-cadence tests.</summary>
public const string FastUInt1 = "ns=3;s=FastUInt1";
public static OpcUaClientDriverOptions BuildOptions(string endpointUrl) => new()
{
EndpointUrl = endpointUrl,
SecurityPolicy = OpcUaSecurityPolicy.None,
SecurityMode = OpcUaSecurityMode.None,
AuthType = OpcUaAuthType.Anonymous,
// opc-plc auto-accepts client certs (--aa) but we still present one; trust the
// server's cert back since the simulator regenerates it each container spin-up
// and there's no meaningful chain to validate against.
AutoAcceptCertificates = true,
Timeout = TimeSpan.FromSeconds(10),
SessionTimeout = TimeSpan.FromSeconds(30),
// opc-plc's standard address space mirrors verbatim — treat it as SystemPlatform so
// §8 namespace validation passes without requiring a UNS mapping table.
TargetNamespaceKind = OpcUaTargetNamespaceKind.SystemPlatform,
};
}