using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using ArchestrA.MxAccess; using ZB.MOM.WW.LmxOpcUa.Host.Domain; namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers { /// /// Fake IMxProxy for testing without the MxAccess COM runtime. /// Simulates connections, subscriptions, data changes, and writes. /// public class FakeMxProxy : IMxProxy { private int _nextHandle = 1; private int _connectionHandle; private bool _registered; public event MxDataChangeHandler? OnDataChange; public event MxWriteCompleteHandler? OnWriteComplete; public ConcurrentDictionary Items { get; } = new ConcurrentDictionary(); public ConcurrentDictionary AdvisedItems { get; } = new ConcurrentDictionary(); public List<(string Address, object Value)> WrittenValues { get; } = new List<(string, object)>(); public bool IsRegistered => _registered; public int RegisterCallCount { get; private set; } public int UnregisterCallCount { get; private set; } public bool ShouldFailRegister { get; set; } public bool ShouldFailWrite { get; set; } public int WriteCompleteStatus { get; set; } = 0; // 0 = success public int Register(string clientName) { RegisterCallCount++; if (ShouldFailRegister) throw new InvalidOperationException("Register failed (simulated)"); _registered = true; _connectionHandle = Interlocked.Increment(ref _nextHandle); return _connectionHandle; } public void Unregister(int handle) { UnregisterCallCount++; _registered = false; _connectionHandle = 0; } public int AddItem(int handle, string address) { var itemHandle = Interlocked.Increment(ref _nextHandle); Items[itemHandle] = address; return itemHandle; } public void RemoveItem(int handle, int itemHandle) { Items.TryRemove(itemHandle, out _); } public void AdviseSupervisory(int handle, int itemHandle) { AdvisedItems[itemHandle] = true; } public void UnAdviseSupervisory(int handle, int itemHandle) { AdvisedItems.TryRemove(itemHandle, out _); } public void Write(int handle, int itemHandle, object value, int securityClassification) { if (ShouldFailWrite) throw new InvalidOperationException("Write failed (simulated)"); if (Items.TryGetValue(itemHandle, out var address)) WrittenValues.Add((address, value)); // Simulate async write complete callback var status = new MXSTATUS_PROXY[1]; if (WriteCompleteStatus == 0) { status[0].success = 1; } else { status[0].success = 0; status[0].detail = (short)WriteCompleteStatus; } OnWriteComplete?.Invoke(_connectionHandle, itemHandle, ref status); } /// /// Simulates an MXAccess data change event for a specific item handle. /// public void SimulateDataChange(int itemHandle, object value, int quality = 192, DateTime? timestamp = null) { var status = new MXSTATUS_PROXY[1]; status[0].success = 1; OnDataChange?.Invoke(_connectionHandle, itemHandle, value, quality, (object)(timestamp ?? DateTime.UtcNow), ref status); } /// /// Simulates data change for a specific address (finds handle by address). /// public void SimulateDataChangeByAddress(string address, object value, int quality = 192, DateTime? timestamp = null) { foreach (var kvp in Items) { if (string.Equals(kvp.Value, address, StringComparison.OrdinalIgnoreCase)) { SimulateDataChange(kvp.Key, value, quality, timestamp); return; } } } } }