Phase 0 — mechanical rename ZB.MOM.WW.LmxOpcUa.* → ZB.MOM.WW.OtOpcUa.*
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>
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
using Opc.Ua;
|
||||
using ZB.MOM.WW.OtOpcUa.Client.Shared;
|
||||
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;
|
||||
using BrowseResult = ZB.MOM.WW.OtOpcUa.Client.Shared.Models.BrowseResult;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Client.UI.Tests.Fakes;
|
||||
|
||||
/// <summary>
|
||||
/// Test double for the shared OPC UA client service used by UI view-model tests.
|
||||
/// It lets tests script connection, browse, history, redundancy, and alarm behavior without a live server.
|
||||
/// </summary>
|
||||
public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
{
|
||||
// Configurable responses
|
||||
/// <summary>
|
||||
/// Gets or sets the connection metadata returned when a UI test performs a successful connect.
|
||||
/// </summary>
|
||||
public ConnectionInfo? ConnectResult { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception thrown to simulate a failed connect workflow in the UI.
|
||||
/// </summary>
|
||||
public Exception? ConnectException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default browse results returned when no parent-specific branch is configured.
|
||||
/// </summary>
|
||||
public IReadOnlyList<BrowseResult> BrowseResults { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets browse results keyed by parent node so tree-navigation tests can model multiple address-space branches.
|
||||
/// </summary>
|
||||
public Dictionary<string, IReadOnlyList<BrowseResult>> BrowseResultsByParent { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception thrown to simulate browse failures in the UI tree.
|
||||
/// </summary>
|
||||
public Exception? BrowseException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current value returned by point-read tests.
|
||||
/// </summary>
|
||||
public DataValue ReadResult { get; set; } =
|
||||
new(new Variant(42), StatusCodes.Good, DateTime.UtcNow, DateTime.UtcNow);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception thrown to simulate point-read failures in the UI.
|
||||
/// </summary>
|
||||
public Exception? ReadException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status code returned by write operations in UI tests.
|
||||
/// </summary>
|
||||
public StatusCode WriteResult { get; set; } = StatusCodes.Good;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception thrown to simulate failed write workflows in the UI.
|
||||
/// </summary>
|
||||
public Exception? WriteException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the redundancy snapshot returned when the UI inspects server redundancy state.
|
||||
/// </summary>
|
||||
public RedundancyInfo? RedundancyResult { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception thrown to simulate redundancy lookup failures.
|
||||
/// </summary>
|
||||
public Exception? RedundancyException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the raw historical values returned to history-view tests.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DataValue> HistoryRawResult { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the aggregate historical values returned to history-view tests.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DataValue> HistoryAggregateResult { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception thrown to simulate historical-data failures in the UI.
|
||||
/// </summary>
|
||||
public Exception? HistoryException { get; set; }
|
||||
|
||||
// Call tracking
|
||||
public int ConnectCallCount { get; private set; }
|
||||
public int DisconnectCallCount { get; private set; }
|
||||
public int ReadCallCount { get; private set; }
|
||||
public int WriteCallCount { get; private set; }
|
||||
public int BrowseCallCount { get; private set; }
|
||||
public int SubscribeCallCount { get; private set; }
|
||||
public int UnsubscribeCallCount { get; private set; }
|
||||
public int SubscribeAlarmsCallCount { get; private set; }
|
||||
public int UnsubscribeAlarmsCallCount { get; private set; }
|
||||
public int RequestConditionRefreshCallCount { get; private set; }
|
||||
public int HistoryReadRawCallCount { get; private set; }
|
||||
public int HistoryReadAggregateCallCount { get; private set; }
|
||||
public int GetRedundancyInfoCallCount { get; private set; }
|
||||
|
||||
public ConnectionSettings? LastConnectionSettings { get; private set; }
|
||||
public NodeId? LastReadNodeId { get; private set; }
|
||||
public NodeId? LastWriteNodeId { get; private set; }
|
||||
public object? LastWriteValue { get; private set; }
|
||||
public NodeId? LastBrowseParentNodeId { get; private set; }
|
||||
public NodeId? LastSubscribeNodeId { get; private set; }
|
||||
public int LastSubscribeIntervalMs { get; private set; }
|
||||
public NodeId? LastUnsubscribeNodeId { get; private set; }
|
||||
public AggregateType? LastAggregateType { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsConnected { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ConnectionInfo? CurrentConnectionInfo { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<DataChangedEventArgs>? DataChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<AlarmEventArgs>? AlarmEvent;
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionStateChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ConnectionInfo> ConnectAsync(ConnectionSettings settings, CancellationToken ct = default)
|
||||
{
|
||||
ConnectCallCount++;
|
||||
LastConnectionSettings = settings;
|
||||
if (ConnectException != null) throw ConnectException;
|
||||
IsConnected = true;
|
||||
CurrentConnectionInfo = ConnectResult;
|
||||
return Task.FromResult(ConnectResult!);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DisconnectAsync(CancellationToken ct = default)
|
||||
{
|
||||
DisconnectCallCount++;
|
||||
IsConnected = false;
|
||||
CurrentConnectionInfo = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DataValue> ReadValueAsync(NodeId nodeId, CancellationToken ct = default)
|
||||
{
|
||||
ReadCallCount++;
|
||||
LastReadNodeId = nodeId;
|
||||
if (ReadException != null) throw ReadException;
|
||||
return Task.FromResult(ReadResult);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<StatusCode> WriteValueAsync(NodeId nodeId, object value, CancellationToken ct = default)
|
||||
{
|
||||
WriteCallCount++;
|
||||
LastWriteNodeId = nodeId;
|
||||
LastWriteValue = value;
|
||||
if (WriteException != null) throw WriteException;
|
||||
return Task.FromResult(WriteResult);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<BrowseResult>> BrowseAsync(NodeId? parentNodeId = null, CancellationToken ct = default)
|
||||
{
|
||||
BrowseCallCount++;
|
||||
LastBrowseParentNodeId = parentNodeId;
|
||||
if (BrowseException != null) throw BrowseException;
|
||||
|
||||
if (parentNodeId != null && BrowseResultsByParent.TryGetValue(parentNodeId.ToString(), out var perParent))
|
||||
return Task.FromResult(perParent);
|
||||
|
||||
return Task.FromResult(BrowseResults);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SubscribeAsync(NodeId nodeId, int intervalMs = 1000, CancellationToken ct = default)
|
||||
{
|
||||
SubscribeCallCount++;
|
||||
LastSubscribeNodeId = nodeId;
|
||||
LastSubscribeIntervalMs = intervalMs;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeAsync(NodeId nodeId, CancellationToken ct = default)
|
||||
{
|
||||
UnsubscribeCallCount++;
|
||||
LastUnsubscribeNodeId = nodeId;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SubscribeAlarmsAsync(NodeId? sourceNodeId = null, int intervalMs = 1000, CancellationToken ct = default)
|
||||
{
|
||||
SubscribeAlarmsCallCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeAlarmsAsync(CancellationToken ct = default)
|
||||
{
|
||||
UnsubscribeAlarmsCallCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RequestConditionRefreshAsync(CancellationToken ct = default)
|
||||
{
|
||||
RequestConditionRefreshCallCount++;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public StatusCode AcknowledgeResult { get; set; } = StatusCodes.Good;
|
||||
public Exception? AcknowledgeException { get; set; }
|
||||
public int AcknowledgeCallCount { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<StatusCode> AcknowledgeAlarmAsync(string conditionNodeId, byte[] eventId, string comment,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
AcknowledgeCallCount++;
|
||||
if (AcknowledgeException != null) throw AcknowledgeException;
|
||||
return Task.FromResult(AcknowledgeResult);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<DataValue>> HistoryReadRawAsync(NodeId nodeId, DateTime startTime, DateTime endTime,
|
||||
int maxValues = 1000, CancellationToken ct = default)
|
||||
{
|
||||
HistoryReadRawCallCount++;
|
||||
if (HistoryException != null) throw HistoryException;
|
||||
return Task.FromResult(HistoryRawResult);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<DataValue>> HistoryReadAggregateAsync(NodeId nodeId, DateTime startTime, DateTime endTime,
|
||||
AggregateType aggregate, double intervalMs = 3600000, CancellationToken ct = default)
|
||||
{
|
||||
HistoryReadAggregateCallCount++;
|
||||
LastAggregateType = aggregate;
|
||||
if (HistoryException != null) throw HistoryException;
|
||||
return Task.FromResult(HistoryAggregateResult);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RedundancyInfo> GetRedundancyInfoAsync(CancellationToken ct = default)
|
||||
{
|
||||
GetRedundancyInfoCallCount++;
|
||||
if (RedundancyException != null) throw RedundancyException;
|
||||
return Task.FromResult(RedundancyResult!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases fake service resources at the end of a UI test run.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// No-op for testing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises a simulated data-change notification so UI tests can validate live update handling.
|
||||
/// </summary>
|
||||
public void RaiseDataChanged(DataChangedEventArgs args)
|
||||
{
|
||||
DataChanged?.Invoke(this, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises a simulated alarm event so UI tests can validate alarm-list behavior.
|
||||
/// </summary>
|
||||
public void RaiseAlarmEvent(AlarmEventArgs args)
|
||||
{
|
||||
AlarmEvent?.Invoke(this, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises a simulated connection-state transition so UI tests can validate status presentation and failover behavior.
|
||||
/// </summary>
|
||||
public void RaiseConnectionStateChanged(ConnectionStateChangedEventArgs args)
|
||||
{
|
||||
ConnectionStateChanged?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user