using Opc.Ua; using ZB.MOM.WW.OtOpcUa.Client.Shared.Adapters; namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests.Fakes; /// /// Test double for used to drive monitored-item behavior in shared-client tests. /// internal sealed class FakeSubscriptionAdapter : ISubscriptionAdapter { private readonly Dictionary? DataCallback, Action? EventCallback )> _items = new(); // Guards _items so concurrent-subscription tests exercise the production // locking rather than tripping over the test double's own state. private readonly object _itemsLock = new(); private uint _nextHandle = 100; /// /// Gets a value indicating whether the fake subscription has been deleted. /// public bool Deleted { get; private set; } /// /// Gets a value indicating whether a condition refresh was requested by the client under test. /// public bool ConditionRefreshCalled { get; private set; } /// /// Gets or sets a value indicating whether condition refresh should throw to simulate unsupported servers. /// public bool ThrowOnConditionRefresh { get; set; } public int AddDataChangeCount { get; private set; } public int AddEventCount { get; private set; } public int RemoveCount { get; private set; } /// /// Gets the handles of all active items. /// public IReadOnlyCollection ActiveHandles { get { lock (_itemsLock) return _items.Keys.ToList(); } } /// public uint SubscriptionId { get; set; } = 42; /// public Task AddDataChangeMonitoredItemAsync(NodeId nodeId, int samplingIntervalMs, Action onDataChange, CancellationToken ct) { lock (_itemsLock) { AddDataChangeCount++; var handle = _nextHandle++; _items[handle] = (nodeId, onDataChange, null); return Task.FromResult(handle); } } /// public Task RemoveMonitoredItemAsync(uint clientHandle, CancellationToken ct) { lock (_itemsLock) { RemoveCount++; _items.Remove(clientHandle); } return Task.CompletedTask; } /// public Task AddEventMonitoredItemAsync(NodeId nodeId, int samplingIntervalMs, EventFilter filter, Action onEvent, CancellationToken ct) { lock (_itemsLock) { AddEventCount++; var handle = _nextHandle++; _items[handle] = (nodeId, null, onEvent); return Task.FromResult(handle); } } /// public Task ConditionRefreshAsync(CancellationToken ct) { ConditionRefreshCalled = true; if (ThrowOnConditionRefresh) throw new InvalidOperationException("Condition refresh not supported"); return Task.CompletedTask; } /// public Task DeleteAsync(CancellationToken ct) { Deleted = true; lock (_itemsLock) _items.Clear(); return Task.CompletedTask; } /// /// Clears tracked monitored items when the fake subscription is disposed by the client under test. /// public void Dispose() { lock (_itemsLock) _items.Clear(); } /// /// Simulates a data change notification for testing. /// public void SimulateDataChange(uint handle, DataValue value) { (NodeId NodeId, Action? DataCallback, Action? EventCallback) item; lock (_itemsLock) { if (!_items.TryGetValue(handle, out item)) return; } item.DataCallback?.Invoke(item.NodeId.ToString(), value); } /// /// Simulates an event notification for testing. /// public void SimulateEvent(uint handle, EventFieldList eventFields) { (NodeId NodeId, Action? DataCallback, Action? EventCallback) item; lock (_itemsLock) { if (!_items.TryGetValue(handle, out item)) return; } item.EventCallback?.Invoke(eventFields); } }