Expand XML docs across bridge and test code

This commit is contained in:
Joseph Doherty
2026-03-25 11:45:12 -04:00
parent 3f813b3869
commit 4833765606
86 changed files with 2323 additions and 0 deletions

View File

@@ -11,6 +11,9 @@ using ZB.MOM.WW.LmxOpcUa.Tests.Helpers;
namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
{
/// <summary>
/// Verifies MXAccess client connection lifecycle behavior, including transitions, registration, and reconnect handling.
/// </summary>
public class MxAccessClientConnectionTests : IDisposable
{
private readonly StaComThread _staThread;
@@ -19,6 +22,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
private readonly MxAccessClient _client;
private readonly List<(ConnectionState Previous, ConnectionState Current)> _stateChanges = new();
/// <summary>
/// Initializes the connection test fixture with a fake runtime proxy and state-change recorder.
/// </summary>
public MxAccessClientConnectionTests()
{
_staThread = new StaComThread();
@@ -30,6 +36,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_client.ConnectionStateChanged += (_, e) => _stateChanges.Add((e.PreviousState, e.CurrentState));
}
/// <summary>
/// Disposes the connection test fixture and its supporting resources.
/// </summary>
public void Dispose()
{
_client.Dispose();
@@ -37,12 +46,18 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_metrics.Dispose();
}
/// <summary>
/// Confirms that a newly created MXAccess client starts in the disconnected state.
/// </summary>
[Fact]
public void InitialState_IsDisconnected()
{
_client.State.ShouldBe(ConnectionState.Disconnected);
}
/// <summary>
/// Confirms that connecting drives the expected disconnected-to-connecting-to-connected transitions.
/// </summary>
[Fact]
public async Task Connect_TransitionsToConnected()
{
@@ -53,6 +68,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_stateChanges.ShouldContain(s => s.Previous == ConnectionState.Connecting && s.Current == ConnectionState.Connected);
}
/// <summary>
/// Confirms that a successful connect registers exactly once with the runtime proxy.
/// </summary>
[Fact]
public async Task Connect_RegistersCalled()
{
@@ -60,6 +78,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_proxy.RegisterCallCount.ShouldBe(1);
}
/// <summary>
/// Confirms that disconnecting drives the expected shutdown transitions back to disconnected.
/// </summary>
[Fact]
public async Task Disconnect_TransitionsToDisconnected()
{
@@ -71,6 +92,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_stateChanges.ShouldContain(s => s.Current == ConnectionState.Disconnected);
}
/// <summary>
/// Confirms that disconnecting unregisters the runtime proxy session.
/// </summary>
[Fact]
public async Task Disconnect_UnregistersCalled()
{
@@ -79,6 +103,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_proxy.UnregisterCallCount.ShouldBe(1);
}
/// <summary>
/// Confirms that registration failures move the client into the error state.
/// </summary>
[Fact]
public async Task ConnectFails_TransitionsToError()
{
@@ -88,6 +115,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_client.State.ShouldBe(ConnectionState.Error);
}
/// <summary>
/// Confirms that repeated connect calls do not perform duplicate runtime registrations.
/// </summary>
[Fact]
public async Task DoubleConnect_NoOp()
{
@@ -96,6 +126,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_proxy.RegisterCallCount.ShouldBe(1);
}
/// <summary>
/// Confirms that reconnect increments the reconnect counter and restores the connected state.
/// </summary>
[Fact]
public async Task Reconnect_IncrementsCount()
{

View File

@@ -10,12 +10,18 @@ using ZB.MOM.WW.LmxOpcUa.Tests.Helpers;
namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
{
/// <summary>
/// Verifies the background connectivity monitor used to reconnect the MXAccess bridge after faults or stale probes.
/// </summary>
public class MxAccessClientMonitorTests : IDisposable
{
private readonly StaComThread _staThread;
private readonly FakeMxProxy _proxy;
private readonly PerformanceMetrics _metrics;
/// <summary>
/// Initializes the monitor test fixture with a shared STA thread, fake proxy, and metrics collector.
/// </summary>
public MxAccessClientMonitorTests()
{
_staThread = new StaComThread();
@@ -24,12 +30,18 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_metrics = new PerformanceMetrics();
}
/// <summary>
/// Disposes the monitor test fixture resources.
/// </summary>
public void Dispose()
{
_staThread.Dispose();
_metrics.Dispose();
}
/// <summary>
/// Confirms that the monitor reconnects the client after an observed disconnect.
/// </summary>
[Fact]
public async Task Monitor_ReconnectsOnDisconnect()
{
@@ -54,6 +66,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
client.Dispose();
}
/// <summary>
/// Confirms that the monitor can be started and stopped without throwing.
/// </summary>
[Fact]
public async Task Monitor_StopsOnCancel()
{
@@ -69,6 +84,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
client.Dispose();
}
/// <summary>
/// Confirms that a stale probe tag triggers a reconnect when monitoring is enabled.
/// </summary>
[Fact]
public async Task Monitor_ProbeStale_ForcesReconnect()
{
@@ -93,6 +111,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
client.Dispose();
}
/// <summary>
/// Confirms that fresh probe updates prevent unnecessary reconnects.
/// </summary>
[Fact]
public async Task Monitor_ProbeDataChange_PreventsStaleReconnect()
{
@@ -122,6 +143,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
client.Dispose();
}
/// <summary>
/// Confirms that enabling the monitor without a probe tag does not trigger false reconnects.
/// </summary>
[Fact]
public async Task Monitor_NoProbeConfigured_NoFalseReconnect()
{

View File

@@ -11,6 +11,9 @@ using ZB.MOM.WW.LmxOpcUa.Tests.Helpers;
namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
{
/// <summary>
/// Verifies MXAccess client read and write behavior against the fake runtime proxy used by the bridge.
/// </summary>
public class MxAccessClientReadWriteTests : IDisposable
{
private readonly StaComThread _staThread;
@@ -18,6 +21,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
private readonly PerformanceMetrics _metrics;
private readonly MxAccessClient _client;
/// <summary>
/// Initializes the COM-threaded MXAccess test fixture with a fake runtime proxy and metrics collector.
/// </summary>
public MxAccessClientReadWriteTests()
{
_staThread = new StaComThread();
@@ -28,6 +34,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_client = new MxAccessClient(_staThread, _proxy, config, _metrics);
}
/// <summary>
/// Disposes the MXAccess client fixture and its supporting STA thread and metrics collector.
/// </summary>
public void Dispose()
{
_client.Dispose();
@@ -35,6 +44,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_metrics.Dispose();
}
/// <summary>
/// Confirms that reads fail with bad-not-connected quality when the runtime session is offline.
/// </summary>
[Fact]
public async Task Read_NotConnected_ReturnsBad()
{
@@ -42,6 +54,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
result.Quality.ShouldBe(Quality.BadNotConnected);
}
/// <summary>
/// Confirms that a runtime data-change callback completes a pending read with the published value.
/// </summary>
[Fact]
public async Task Read_ReturnsValueOnDataChange()
{
@@ -59,6 +74,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
result.Quality.ShouldBe(Quality.Good);
}
/// <summary>
/// Confirms that reads time out with bad communication-failure quality when the runtime never responds.
/// </summary>
[Fact]
public async Task Read_Timeout_ReturnsBadCommFailure()
{
@@ -69,6 +87,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
result.Quality.ShouldBe(Quality.BadCommFailure);
}
/// <summary>
/// Confirms that timed-out reads are recorded as failed read operations in the metrics collector.
/// </summary>
[Fact]
public async Task Read_Timeout_RecordsFailedMetrics()
{
@@ -83,6 +104,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
stats["Read"].SuccessCount.ShouldBe(0);
}
/// <summary>
/// Confirms that writes are rejected when the runtime session is not connected.
/// </summary>
[Fact]
public async Task Write_NotConnected_ReturnsFalse()
{
@@ -90,6 +114,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
result.ShouldBe(false);
}
/// <summary>
/// Confirms that successful runtime write acknowledgments return success and record the written payload.
/// </summary>
[Fact]
public async Task Write_Success_ReturnsTrue()
{
@@ -101,6 +128,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_proxy.WrittenValues.ShouldContain(w => w.Address == "TestTag.Attr" && (int)w.Value == 42);
}
/// <summary>
/// Confirms that MXAccess error codes on write completion are surfaced as failed writes.
/// </summary>
[Fact]
public async Task Write_ErrorCode_ReturnsFalse()
{
@@ -111,6 +141,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
result.ShouldBe(false);
}
/// <summary>
/// Confirms that write timeouts are recorded as failed write operations in the metrics collector.
/// </summary>
[Fact]
public async Task Write_Timeout_ReturnsFalse_AndRecordsFailedMetrics()
{
@@ -126,6 +159,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
stats["Write"].SuccessCount.ShouldBe(0);
}
/// <summary>
/// Confirms that successful reads contribute a read entry to the metrics collector.
/// </summary>
[Fact]
public async Task Read_RecordsMetrics()
{
@@ -141,6 +177,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
stats["Read"].TotalCount.ShouldBe(1);
}
/// <summary>
/// Confirms that writes contribute a write entry to the metrics collector.
/// </summary>
[Fact]
public async Task Write_RecordsMetrics()
{

View File

@@ -11,6 +11,9 @@ using ZB.MOM.WW.LmxOpcUa.Tests.Helpers;
namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
{
/// <summary>
/// Verifies how the MXAccess client manages persistent subscriptions, reconnect replay, and probe-tag behavior.
/// </summary>
public class MxAccessClientSubscriptionTests : IDisposable
{
private readonly StaComThread _staThread;
@@ -18,6 +21,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
private readonly PerformanceMetrics _metrics;
private readonly MxAccessClient _client;
/// <summary>
/// Initializes the subscription test fixture with a fake runtime proxy and STA thread.
/// </summary>
public MxAccessClientSubscriptionTests()
{
_staThread = new StaComThread();
@@ -27,6 +33,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_client = new MxAccessClient(_staThread, _proxy, new MxAccessConfiguration(), _metrics);
}
/// <summary>
/// Disposes the subscription test fixture and its supporting resources.
/// </summary>
public void Dispose()
{
_client.Dispose();
@@ -34,6 +43,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_metrics.Dispose();
}
/// <summary>
/// Confirms that subscribing creates a runtime item, advises it, and increments the active subscription count.
/// </summary>
[Fact]
public async Task Subscribe_CreatesItemAndAdvises()
{
@@ -45,6 +57,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_client.ActiveSubscriptionCount.ShouldBe(1);
}
/// <summary>
/// Confirms that unsubscribing clears the active subscription count after a tag was previously monitored.
/// </summary>
[Fact]
public async Task Unsubscribe_RemovesItemAndUnadvises()
{
@@ -55,6 +70,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_client.ActiveSubscriptionCount.ShouldBe(0);
}
/// <summary>
/// Confirms that runtime data changes are delivered to the per-subscription callback.
/// </summary>
[Fact]
public async Task OnDataChange_InvokesCallback()
{
@@ -70,6 +88,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
received.Value.Quality.ShouldBe(Quality.Good);
}
/// <summary>
/// Confirms that runtime data changes are also delivered to the client's global tag-change event.
/// </summary>
[Fact]
public async Task OnDataChange_InvokesGlobalHandler()
{
@@ -84,6 +105,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
globalAddr.ShouldBe("TestTag.Attr");
}
/// <summary>
/// Confirms that stored subscriptions are replayed after reconnect so live updates resume automatically.
/// </summary>
[Fact]
public async Task StoredSubscriptions_ReplayedAfterReconnect()
{
@@ -102,6 +126,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
callbackInvoked.ShouldBe(true);
}
/// <summary>
/// Confirms that one-shot reads do not remove persistent subscriptions when the client reconnects.
/// </summary>
[Fact]
public async Task OneShotRead_DoesNotRemovePersistentSubscription_OnReconnect()
{
@@ -122,6 +149,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_client.ActiveSubscriptionCount.ShouldBe(1);
}
/// <summary>
/// Confirms that transient writes do not prevent later removal of a persistent subscription.
/// </summary>
[Fact]
public async Task OneShotWrite_DoesNotBreakPersistentUnsubscribe()
{
@@ -138,6 +168,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_proxy.Items.Values.ShouldNotContain("TestTag.Attr");
}
/// <summary>
/// Confirms that the configured probe tag is subscribed during connect so connectivity monitoring can start immediately.
/// </summary>
[Fact]
public async Task ProbeTag_SubscribedOnConnect()
{
@@ -152,6 +185,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
client.Dispose();
}
/// <summary>
/// Confirms that the probe tag cannot be unsubscribed accidentally because it is reserved for connection monitoring.
/// </summary>
[Fact]
public async Task ProbeTag_ProtectedFromUnsubscribe()
{

View File

@@ -7,18 +7,30 @@ using ZB.MOM.WW.LmxOpcUa.Host.MxAccess;
namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
{
/// <summary>
/// Verifies the single-threaded apartment worker used to marshal COM calls for the MXAccess bridge.
/// </summary>
public class StaComThreadTests : IDisposable
{
private readonly StaComThread _thread;
/// <summary>
/// Starts a fresh STA thread instance for each test.
/// </summary>
public StaComThreadTests()
{
_thread = new StaComThread();
_thread.Start();
}
/// <summary>
/// Disposes the STA thread after each test.
/// </summary>
public void Dispose() => _thread.Dispose();
/// <summary>
/// Confirms that queued work runs on a thread configured for STA apartment state.
/// </summary>
[Fact]
public async Task RunAsync_ExecutesOnStaThread()
{
@@ -26,6 +38,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
apartmentState.ShouldBe(ApartmentState.STA);
}
/// <summary>
/// Confirms that action delegates run to completion on the STA thread.
/// </summary>
[Fact]
public async Task RunAsync_Action_Completes()
{
@@ -34,6 +49,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
executed.ShouldBe(true);
}
/// <summary>
/// Confirms that function delegates can return results from the STA thread.
/// </summary>
[Fact]
public async Task RunAsync_Func_ReturnsResult()
{
@@ -41,6 +59,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
result.ShouldBe(42);
}
/// <summary>
/// Confirms that exceptions thrown on the STA thread propagate back to the caller.
/// </summary>
[Fact]
public async Task RunAsync_PropagatesException()
{
@@ -48,6 +69,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
_thread.RunAsync(() => throw new InvalidOperationException("test error")));
}
/// <summary>
/// Confirms that disposing the STA thread stops it from accepting additional work.
/// </summary>
[Fact]
public void Dispose_Stops_Thread()
{
@@ -59,6 +83,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
Should.Throw<ObjectDisposedException>(() => thread.RunAsync(() => { }).GetAwaiter().GetResult());
}
/// <summary>
/// Confirms that multiple queued work items all execute successfully on the STA thread.
/// </summary>
[Fact]
public async Task MultipleWorkItems_ExecuteInOrder()
{