using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using ZB.MOM.WW.LmxOpcUa.Host.Domain; namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers { /// /// In-memory IMxAccessClient used by tests to drive connection, read, write, and subscription scenarios without COM runtime dependencies. /// public class FakeMxAccessClient : IMxAccessClient { /// /// Gets or sets the connection state returned to the system under test. /// public ConnectionState State { get; set; } = ConnectionState.Connected; /// /// Gets the number of active subscriptions currently stored by the fake client. /// public int ActiveSubscriptionCount => _subscriptions.Count; /// /// Gets or sets the reconnect count exposed to health and dashboard tests. /// public int ReconnectCount { get; set; } /// /// Occurs when tests explicitly simulate a connection-state transition. /// public event EventHandler? ConnectionStateChanged; /// /// Occurs when tests publish a simulated runtime value change. /// public event Action? OnTagValueChanged; private readonly ConcurrentDictionary> _subscriptions = new(StringComparer.OrdinalIgnoreCase); /// /// Gets the in-memory tag-value table returned by fake reads. /// public ConcurrentDictionary TagValues { get; } = new(StringComparer.OrdinalIgnoreCase); /// /// Gets the values written through the fake client so tests can assert write behavior. /// public List<(string Tag, object Value)> WrittenValues { get; } = new(); /// /// Gets or sets the result returned by fake writes to simulate success or failure. /// public bool WriteResult { get; set; } = true; /// /// Simulates establishing a healthy runtime connection. /// /// A cancellation token that is ignored by the in-memory fake. public Task ConnectAsync(CancellationToken ct = default) { State = ConnectionState.Connected; return Task.CompletedTask; } /// /// Simulates disconnecting from the runtime. /// public Task DisconnectAsync() { State = ConnectionState.Disconnected; return Task.CompletedTask; } /// /// Stores a subscription callback so later simulated data changes can target it. /// /// The Galaxy attribute reference to monitor. /// The callback that should receive simulated value changes. public Task SubscribeAsync(string fullTagReference, Action callback) { _subscriptions[fullTagReference] = callback; return Task.CompletedTask; } /// /// Removes a stored subscription callback for the specified tag reference. /// /// The Galaxy attribute reference to stop monitoring. public Task UnsubscribeAsync(string fullTagReference) { _subscriptions.TryRemove(fullTagReference, out _); return Task.CompletedTask; } /// /// Returns the current in-memory VTQ for a tag reference or a bad-quality placeholder when none has been seeded. /// /// The Galaxy attribute reference to read. /// A cancellation token that is ignored by the in-memory fake. /// The seeded VTQ value or a bad not-connected VTQ when the tag was not populated. public Task ReadAsync(string fullTagReference, CancellationToken ct = default) { if (TagValues.TryGetValue(fullTagReference, out var vtq)) return Task.FromResult(vtq); return Task.FromResult(Vtq.Bad(Quality.BadNotConnected)); } /// /// Records a write request, optionally updates the in-memory tag table, and returns the configured write result. /// /// The Galaxy attribute reference being written. /// The value supplied by the code under test. /// A cancellation token that is ignored by the in-memory fake. /// A completed task returning the configured write outcome. public Task WriteAsync(string fullTagReference, object value, CancellationToken ct = default) { WrittenValues.Add((fullTagReference, value)); if (WriteResult) TagValues[fullTagReference] = Vtq.Good(value); return Task.FromResult(WriteResult); } /// /// Publishes a simulated tag-value change to both the event stream and any stored subscription callback. /// /// The Galaxy attribute reference whose value changed. /// The value, timestamp, and quality payload to publish. public void SimulateDataChange(string address, Vtq vtq) { OnTagValueChanged?.Invoke(address, vtq); if (_subscriptions.TryGetValue(address, out var callback)) callback(address, vtq); } /// /// Raises a simulated connection-state transition for health and reconnect tests. /// /// The previous connection state. /// The new connection state. public void RaiseConnectionStateChanged(ConnectionState prev, ConnectionState curr) { State = curr; ConnectionStateChanged?.Invoke(this, new ConnectionStateChangedEventArgs(prev, curr)); } /// /// Releases the fake client. No unmanaged resources are held. /// public void Dispose() { } } }