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; /// /// 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. /// public sealed class FakeOpcUaClientService : IOpcUaClientService { // Configurable responses /// /// Gets or sets the connection metadata returned when a UI test performs a successful connect. /// public ConnectionInfo? ConnectResult { get; set; } /// /// Gets or sets the exception thrown to simulate a failed connect workflow in the UI. /// public Exception? ConnectException { get; set; } /// /// Gets or sets the default browse results returned when no parent-specific branch is configured. /// public IReadOnlyList BrowseResults { get; set; } = []; /// /// Gets or sets browse results keyed by parent node so tree-navigation tests can model multiple address-space branches. /// public Dictionary> BrowseResultsByParent { get; set; } = new(); /// /// Gets or sets the exception thrown to simulate browse failures in the UI tree. /// public Exception? BrowseException { get; set; } /// /// Gets or sets the current value returned by point-read tests. /// public DataValue ReadResult { get; set; } = new(new Variant(42), StatusCodes.Good, DateTime.UtcNow, DateTime.UtcNow); /// /// Gets or sets the exception thrown to simulate point-read failures in the UI. /// public Exception? ReadException { get; set; } /// /// Gets or sets the status code returned by write operations in UI tests. /// public StatusCode WriteResult { get; set; } = StatusCodes.Good; /// /// Gets or sets the exception thrown to simulate failed write workflows in the UI. /// public Exception? WriteException { get; set; } /// /// Gets or sets the redundancy snapshot returned when the UI inspects server redundancy state. /// public RedundancyInfo? RedundancyResult { get; set; } /// /// Gets or sets the exception thrown to simulate redundancy lookup failures. /// public Exception? RedundancyException { get; set; } /// /// Gets or sets the raw historical values returned to history-view tests. /// public IReadOnlyList HistoryRawResult { get; set; } = []; /// /// Gets or sets the aggregate historical values returned to history-view tests. /// public IReadOnlyList HistoryAggregateResult { get; set; } = []; /// /// Gets or sets the exception thrown to simulate historical-data failures in the UI. /// public Exception? HistoryException { get; set; } // Call tracking /// Gets the number of times ConnectAsync has been called. public int ConnectCallCount { get; private set; } /// Gets the number of times DisconnectAsync has been called. public int DisconnectCallCount { get; private set; } /// Gets the number of times ReadValueAsync has been called. public int ReadCallCount { get; private set; } /// Gets the number of times WriteValueAsync has been called. public int WriteCallCount { get; private set; } /// Gets the number of times BrowseAsync has been called. public int BrowseCallCount { get; private set; } /// Gets the number of times SubscribeAsync has been called. public int SubscribeCallCount { get; private set; } /// Gets the number of times UnsubscribeAsync has been called. public int UnsubscribeCallCount { get; private set; } /// Gets the number of times SubscribeAlarmsAsync has been called. public int SubscribeAlarmsCallCount { get; private set; } /// Gets the number of times UnsubscribeAlarmsAsync has been called. public int UnsubscribeAlarmsCallCount { get; private set; } /// Gets the number of times RequestConditionRefreshAsync has been called. public int RequestConditionRefreshCallCount { get; private set; } /// Gets the number of times HistoryReadRawAsync has been called. public int HistoryReadRawCallCount { get; private set; } /// Gets the number of times HistoryReadAggregateAsync has been called. public int HistoryReadAggregateCallCount { get; private set; } /// Gets the number of times GetRedundancyInfoAsync has been called. public int GetRedundancyInfoCallCount { get; private set; } /// Gets the connection settings from the last ConnectAsync call. public ConnectionSettings? LastConnectionSettings { get; private set; } /// Gets the node ID from the last ReadValueAsync call. public NodeId? LastReadNodeId { get; private set; } /// Gets the node ID from the last WriteValueAsync call. public NodeId? LastWriteNodeId { get; private set; } /// Gets the value from the last WriteValueAsync call. public object? LastWriteValue { get; private set; } /// Gets the parent node ID from the last BrowseAsync call. public NodeId? LastBrowseParentNodeId { get; private set; } /// Gets the node ID from the last SubscribeAsync call. public NodeId? LastSubscribeNodeId { get; private set; } /// Gets the interval in milliseconds from the last SubscribeAsync call. public int LastSubscribeIntervalMs { get; private set; } /// Gets the node ID from the last UnsubscribeAsync call. public NodeId? LastUnsubscribeNodeId { get; private set; } /// Gets the aggregate type from the last HistoryReadAggregateAsync call. public AggregateType? LastAggregateType { get; private set; } /// public bool IsConnected { get; set; } /// public ConnectionInfo? CurrentConnectionInfo { get; set; } /// public event EventHandler? DataChanged; /// public event EventHandler? AlarmEvent; /// public event EventHandler? ConnectionStateChanged; /// public Task ConnectAsync(ConnectionSettings settings, CancellationToken ct = default) { ConnectCallCount++; LastConnectionSettings = settings; 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 ReadValueAsync(NodeId nodeId, CancellationToken ct = default) { ReadCallCount++; LastReadNodeId = nodeId; if (ReadException != null) throw ReadException; return Task.FromResult(ReadResult); } /// public Task WriteValueAsync(NodeId nodeId, object value, CancellationToken ct = default) { WriteCallCount++; LastWriteNodeId = nodeId; LastWriteValue = value; if (WriteException != null) throw WriteException; return Task.FromResult(WriteResult); } /// public Task> 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); } /// /// Gets or sets the exception thrown to simulate subscribe failures in the UI. /// public Exception? SubscribeException { get; set; } /// /// Gets or sets the exception thrown to simulate alarm-subscribe failures in the UI. /// public Exception? SubscribeAlarmsException { get; set; } /// public Task SubscribeAsync(NodeId nodeId, int intervalMs = 1000, CancellationToken ct = default) { SubscribeCallCount++; LastSubscribeNodeId = nodeId; LastSubscribeIntervalMs = intervalMs; if (SubscribeException != null) throw SubscribeException; 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++; if (SubscribeAlarmsException != null) throw SubscribeAlarmsException; return Task.CompletedTask; } /// public Task UnsubscribeAlarmsAsync(CancellationToken ct = default) { UnsubscribeAlarmsCallCount++; return Task.CompletedTask; } /// public Task RequestConditionRefreshAsync(CancellationToken ct = default) { RequestConditionRefreshCallCount++; return Task.CompletedTask; } /// Gets or sets the status code returned by acknowledgment operations in UI tests. public StatusCode AcknowledgeResult { get; set; } = StatusCodes.Good; /// Gets or sets the exception thrown to simulate alarm acknowledgment failures in the UI. public Exception? AcknowledgeException { get; set; } /// Gets the number of times AcknowledgeAlarmAsync has been called. public int AcknowledgeCallCount { get; private set; } /// public Task AcknowledgeAlarmAsync(string conditionNodeId, byte[] eventId, string comment, CancellationToken ct = default) { AcknowledgeCallCount++; if (AcknowledgeException != null) throw AcknowledgeException; return Task.FromResult(AcknowledgeResult); } /// public Task> 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> 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 GetRedundancyInfoAsync(CancellationToken ct = default) { GetRedundancyInfoCallCount++; if (RedundancyException != null) throw RedundancyException; return Task.FromResult(RedundancyResult!); } /// /// Releases fake service resources at the end of a UI test run. /// public void Dispose() { // No-op for testing } /// /// Raises a simulated data-change notification so UI tests can validate live update handling. /// /// The data change event arguments to raise. public void RaiseDataChanged(DataChangedEventArgs args) { DataChanged?.Invoke(this, args); } /// /// Raises a simulated alarm event so UI tests can validate alarm-list behavior. /// /// The alarm event arguments to raise. public void RaiseAlarmEvent(AlarmEventArgs args) { AlarmEvent?.Invoke(this, args); } /// /// Raises a simulated connection-state transition so UI tests can validate status presentation and failover behavior. /// /// The connection state changed event arguments to raise. public void RaiseConnectionStateChanged(ConnectionStateChangedEventArgs args) { ConnectionStateChanged?.Invoke(this, args); } }