From a0edac81fb838dd0005d7893d26d4271db76932e Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 25 Mar 2026 06:10:49 -0400 Subject: [PATCH] Add probe/stale tag monitoring tests Cover the full probe staleness detection path that was previously untested: stale probe forces reconnect, data changes prevent false staleness, no-probe config skips staleness check, probe tag subscribed on connect and protected from unsubscribe. 5 new tests, 184 total passing. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../MxAccess/MxAccessClientMonitorTests.cs | 76 +++++++++++++++++++ .../MxAccessClientSubscriptionTests.cs | 32 ++++++++ 2 files changed, 108 insertions(+) diff --git a/tests/ZB.MOM.WW.LmxOpcUa.Tests/MxAccess/MxAccessClientMonitorTests.cs b/tests/ZB.MOM.WW.LmxOpcUa.Tests/MxAccess/MxAccessClientMonitorTests.cs index 071b6c3..8ca1be1 100644 --- a/tests/ZB.MOM.WW.LmxOpcUa.Tests/MxAccess/MxAccessClientMonitorTests.cs +++ b/tests/ZB.MOM.WW.LmxOpcUa.Tests/MxAccess/MxAccessClientMonitorTests.cs @@ -68,5 +68,81 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess await Task.Delay(200); client.Dispose(); } + + [Fact] + public async Task Monitor_ProbeStale_ForcesReconnect() + { + var config = new MxAccessConfiguration + { + ProbeTag = "TestProbe", + ProbeStaleThresholdSeconds = 2, + MonitorIntervalSeconds = 1, + AutoReconnect = true + }; + var client = new MxAccessClient(_staThread, _proxy, config, _metrics); + + await client.ConnectAsync(); + client.StartMonitor(); + + // Wait long enough for probe to go stale (threshold=2s, monitor interval=1s) + // No data changes simulated → probe becomes stale → reconnect triggered + await Task.Delay(4000); + + client.StopMonitor(); + client.ReconnectCount.ShouldBeGreaterThan(0); + client.Dispose(); + } + + [Fact] + public async Task Monitor_ProbeDataChange_PreventsStaleReconnect() + { + var config = new MxAccessConfiguration + { + ProbeTag = "TestProbe", + ProbeStaleThresholdSeconds = 2, + MonitorIntervalSeconds = 1, + AutoReconnect = true + }; + var client = new MxAccessClient(_staThread, _proxy, config, _metrics); + + await client.ConnectAsync(); + client.StartMonitor(); + + // Continuously simulate probe data changes to keep it fresh + for (int i = 0; i < 8; i++) + { + await Task.Delay(500); + _proxy.SimulateDataChangeByAddress("TestProbe", i, 192); + } + + client.StopMonitor(); + // Probe was kept fresh → no reconnect should have happened + client.ReconnectCount.ShouldBe(0); + client.State.ShouldBe(ConnectionState.Connected); + client.Dispose(); + } + + [Fact] + public async Task Monitor_NoProbeConfigured_NoFalseReconnect() + { + var config = new MxAccessConfiguration + { + ProbeTag = null, // No probe + MonitorIntervalSeconds = 1, + AutoReconnect = true + }; + var client = new MxAccessClient(_staThread, _proxy, config, _metrics); + + await client.ConnectAsync(); + client.StartMonitor(); + + // Wait several monitor cycles — should stay connected with no reconnects + await Task.Delay(3000); + + client.StopMonitor(); + client.State.ShouldBe(ConnectionState.Connected); + client.ReconnectCount.ShouldBe(0); + client.Dispose(); + } } } diff --git a/tests/ZB.MOM.WW.LmxOpcUa.Tests/MxAccess/MxAccessClientSubscriptionTests.cs b/tests/ZB.MOM.WW.LmxOpcUa.Tests/MxAccess/MxAccessClientSubscriptionTests.cs index c4e46ec..d5e5730 100644 --- a/tests/ZB.MOM.WW.LmxOpcUa.Tests/MxAccess/MxAccessClientSubscriptionTests.cs +++ b/tests/ZB.MOM.WW.LmxOpcUa.Tests/MxAccess/MxAccessClientSubscriptionTests.cs @@ -101,5 +101,37 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess _proxy.SimulateDataChangeByAddress("TestTag.Attr", "value", 192); callbackInvoked.ShouldBe(true); } + + [Fact] + public async Task ProbeTag_SubscribedOnConnect() + { + var proxy = new FakeMxProxy(); + var config = new MxAccessConfiguration { ProbeTag = "TestProbe" }; + var client = new MxAccessClient(_staThread, proxy, config, _metrics); + + await client.ConnectAsync(); + + // Probe tag should be subscribed (present in proxy items) + proxy.Items.Values.ShouldContain("TestProbe"); + client.Dispose(); + } + + [Fact] + public async Task ProbeTag_ProtectedFromUnsubscribe() + { + var proxy = new FakeMxProxy(); + var config = new MxAccessConfiguration { ProbeTag = "TestProbe" }; + var client = new MxAccessClient(_staThread, proxy, config, _metrics); + + await client.ConnectAsync(); + proxy.Items.Values.ShouldContain("TestProbe"); + + // Attempt to unsubscribe the probe tag — should be protected + await client.UnsubscribeAsync("TestProbe"); + + // Probe should still be in the proxy items (not removed) + proxy.Items.Values.ShouldContain("TestProbe"); + client.Dispose(); + } } }