Implements Client.Shared (IOpcUaClientService with connection lifecycle, failover, browse, read/write, subscriptions, alarms, history, redundancy), Client.CLI (8 CliFx commands mirroring tools/opcuacli-dotnet), and Client.UI (Avalonia desktop app with tree browser, read/write, subscriptions, alarms, and history tabs). All three target .NET 10 and are covered by 249 unit tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
167 lines
6.7 KiB
C#
167 lines
6.7 KiB
C#
using Opc.Ua;
|
|
using ZB.MOM.WW.LmxOpcUa.Client.Shared;
|
|
using ZB.MOM.WW.LmxOpcUa.Client.Shared.Models;
|
|
using BrowseResult = ZB.MOM.WW.LmxOpcUa.Client.Shared.Models.BrowseResult;
|
|
using ConnectionState = ZB.MOM.WW.LmxOpcUa.Client.Shared.Models.ConnectionState;
|
|
|
|
namespace ZB.MOM.WW.LmxOpcUa.Client.UI.Tests.Fakes;
|
|
|
|
/// <summary>
|
|
/// Fake IOpcUaClientService for unit testing.
|
|
/// </summary>
|
|
public sealed class FakeOpcUaClientService : IOpcUaClientService
|
|
{
|
|
// Configurable responses
|
|
public ConnectionInfo? ConnectResult { get; set; }
|
|
public Exception? ConnectException { get; set; }
|
|
public IReadOnlyList<BrowseResult> BrowseResults { get; set; } = Array.Empty<BrowseResult>();
|
|
public Exception? BrowseException { get; set; }
|
|
public DataValue ReadResult { get; set; } = new DataValue(new Variant(42), StatusCodes.Good, DateTime.UtcNow, DateTime.UtcNow);
|
|
public Exception? ReadException { get; set; }
|
|
public StatusCode WriteResult { get; set; } = StatusCodes.Good;
|
|
public Exception? WriteException { get; set; }
|
|
public RedundancyInfo? RedundancyResult { get; set; }
|
|
public Exception? RedundancyException { get; set; }
|
|
public IReadOnlyList<DataValue> HistoryRawResult { get; set; } = Array.Empty<DataValue>();
|
|
public IReadOnlyList<DataValue> HistoryAggregateResult { get; set; } = Array.Empty<DataValue>();
|
|
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 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; }
|
|
|
|
public bool IsConnected { get; set; }
|
|
public ConnectionInfo? CurrentConnectionInfo { get; set; }
|
|
|
|
public event EventHandler<DataChangedEventArgs>? DataChanged;
|
|
public event EventHandler<AlarmEventArgs>? AlarmEvent;
|
|
public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionStateChanged;
|
|
|
|
public Task<ConnectionInfo> ConnectAsync(ConnectionSettings settings, CancellationToken ct = default)
|
|
{
|
|
ConnectCallCount++;
|
|
if (ConnectException != null) throw ConnectException;
|
|
IsConnected = true;
|
|
CurrentConnectionInfo = ConnectResult;
|
|
return Task.FromResult(ConnectResult!);
|
|
}
|
|
|
|
public Task DisconnectAsync(CancellationToken ct = default)
|
|
{
|
|
DisconnectCallCount++;
|
|
IsConnected = false;
|
|
CurrentConnectionInfo = null;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task<DataValue> ReadValueAsync(NodeId nodeId, CancellationToken ct = default)
|
|
{
|
|
ReadCallCount++;
|
|
LastReadNodeId = nodeId;
|
|
if (ReadException != null) throw ReadException;
|
|
return Task.FromResult(ReadResult);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public Task<IReadOnlyList<BrowseResult>> BrowseAsync(NodeId? parentNodeId = null, CancellationToken ct = default)
|
|
{
|
|
BrowseCallCount++;
|
|
LastBrowseParentNodeId = parentNodeId;
|
|
if (BrowseException != null) throw BrowseException;
|
|
return Task.FromResult(BrowseResults);
|
|
}
|
|
|
|
public Task SubscribeAsync(NodeId nodeId, int intervalMs = 1000, CancellationToken ct = default)
|
|
{
|
|
SubscribeCallCount++;
|
|
LastSubscribeNodeId = nodeId;
|
|
LastSubscribeIntervalMs = intervalMs;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task UnsubscribeAsync(NodeId nodeId, CancellationToken ct = default)
|
|
{
|
|
UnsubscribeCallCount++;
|
|
LastUnsubscribeNodeId = nodeId;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task SubscribeAlarmsAsync(NodeId? sourceNodeId = null, int intervalMs = 1000, CancellationToken ct = default)
|
|
{
|
|
SubscribeAlarmsCallCount++;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task UnsubscribeAlarmsAsync(CancellationToken ct = default)
|
|
{
|
|
UnsubscribeAlarmsCallCount++;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task RequestConditionRefreshAsync(CancellationToken ct = default)
|
|
{
|
|
RequestConditionRefreshCallCount++;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
public Task<RedundancyInfo> GetRedundancyInfoAsync(CancellationToken ct = default)
|
|
{
|
|
GetRedundancyInfoCallCount++;
|
|
if (RedundancyException != null) throw RedundancyException;
|
|
return Task.FromResult(RedundancyResult!);
|
|
}
|
|
|
|
// Methods to raise events from tests
|
|
public void RaiseDataChanged(DataChangedEventArgs args) => DataChanged?.Invoke(this, args);
|
|
public void RaiseAlarmEvent(AlarmEventArgs args) => AlarmEvent?.Invoke(this, args);
|
|
public void RaiseConnectionStateChanged(ConnectionStateChangedEventArgs args) => ConnectionStateChanged?.Invoke(this, args);
|
|
|
|
public void Dispose()
|
|
{
|
|
// No-op for testing
|
|
}
|
|
}
|