Renames all 11 projects (5 src + 6 tests), the .slnx solution file, all source-file namespaces, all axaml namespace references, and all v1 documentation references in CLAUDE.md and docs/*.md (excluding docs/v2/ which is already in OtOpcUa form). Also updates the TopShelf service registration name from "LmxOpcUa" to "OtOpcUa" per Phase 0 Task 0.6.
Preserves runtime identifiers per Phase 0 Out-of-Scope rules to avoid breaking v1/v2 client trust during coexistence: OPC UA `ApplicationUri` defaults (`urn:{GalaxyName}:LmxOpcUa`), server `EndpointPath` (`/LmxOpcUa`), `ServerName` default (feeds cert subject CN), `MxAccessConfiguration.ClientName` default (defensive — stays "LmxOpcUa" for MxAccess audit-trail consistency), client OPC UA identifiers (`ApplicationName = "LmxOpcUaClient"`, `ApplicationUri = "urn:localhost:LmxOpcUaClient"`, cert directory `%LocalAppData%\LmxOpcUaClient\pki\`), and the `LmxOpcUaServer` class name (class rename out of Phase 0 scope per Task 0.5 sed pattern; happens in Phase 1 alongside `LmxNodeManager → GenericDriverNodeManager` Core extraction). 23 LmxOpcUa references retained, all enumerated and justified in `docs/v2/implementation/exit-gate-phase-0.md`.
Build clean: 0 errors, 30 warnings (lower than baseline 167). Tests at strict improvement over baseline: 821 passing / 1 failing vs baseline 820 / 2 (one flaky pre-existing failure passed this run; the other still fails — both pre-existing and unrelated to the rename). `Client.UI.Tests`, `Historian.Aveva.Tests`, `Client.Shared.Tests`, `IntegrationTests` all match baseline exactly. Exit gate compliance results recorded in `docs/v2/implementation/exit-gate-phase-0.md` with all 7 checks PASS or DEFERRED-to-PR-review (#7 service install verification needs Windows service permissions on the reviewer's box).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
173 lines
5.8 KiB
C#
173 lines
5.8 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Host.Configuration;
|
|
using ZB.MOM.WW.OtOpcUa.Host.Domain;
|
|
using ZB.MOM.WW.OtOpcUa.Host.Metrics;
|
|
using ZB.MOM.WW.OtOpcUa.Host.MxAccess;
|
|
using ZB.MOM.WW.OtOpcUa.Tests.Helpers;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.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 PerformanceMetrics _metrics;
|
|
private readonly FakeMxProxy _proxy;
|
|
private readonly StaComThread _staThread;
|
|
|
|
/// <summary>
|
|
/// Initializes the monitor test fixture with a shared STA thread, fake proxy, and metrics collector.
|
|
/// </summary>
|
|
public MxAccessClientMonitorTests()
|
|
{
|
|
_staThread = new StaComThread();
|
|
_staThread.Start();
|
|
_proxy = new FakeMxProxy();
|
|
_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()
|
|
{
|
|
var config = new MxAccessConfiguration
|
|
{
|
|
MonitorIntervalSeconds = 1,
|
|
AutoReconnect = true
|
|
};
|
|
var client = new MxAccessClient(_staThread, _proxy, config, _metrics);
|
|
|
|
await client.ConnectAsync();
|
|
await client.DisconnectAsync();
|
|
|
|
client.StartMonitor();
|
|
|
|
// Wait for monitor to detect disconnect and reconnect
|
|
await Task.Delay(2500);
|
|
|
|
client.StopMonitor();
|
|
client.State.ShouldBe(ConnectionState.Connected);
|
|
client.ReconnectCount.ShouldBeGreaterThan(0);
|
|
client.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Confirms that the monitor can be started and stopped without throwing.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Monitor_StopsOnCancel()
|
|
{
|
|
var config = new MxAccessConfiguration { MonitorIntervalSeconds = 1 };
|
|
var client = new MxAccessClient(_staThread, _proxy, config, _metrics);
|
|
|
|
await client.ConnectAsync();
|
|
client.StartMonitor();
|
|
client.StopMonitor();
|
|
|
|
// Should not throw
|
|
await Task.Delay(200);
|
|
client.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Confirms that a stale probe tag triggers a reconnect when monitoring is enabled.
|
|
/// </summary>
|
|
[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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Confirms that fresh probe updates prevent unnecessary reconnects.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Monitor_ProbeDataChange_PreventsStaleReconnect()
|
|
{
|
|
var config = new MxAccessConfiguration
|
|
{
|
|
ProbeTag = "TestProbe",
|
|
ProbeStaleThresholdSeconds = 5,
|
|
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
|
|
// Stale threshold (5s) is well above the delay (500ms) to avoid timing flakes
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
await Task.Delay(500);
|
|
_proxy.SimulateDataChangeByAddress("TestProbe", i);
|
|
}
|
|
|
|
client.StopMonitor();
|
|
// Probe was kept fresh → no reconnect should have happened
|
|
client.ReconnectCount.ShouldBe(0);
|
|
client.State.ShouldBe(ConnectionState.Connected);
|
|
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()
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
} |