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() { }
}
}