using System.Collections.Concurrent; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms; namespace ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests; public sealed class FakeUpstream : ITagUpstreamSource { private readonly ConcurrentDictionary _values = new(StringComparer.Ordinal); private readonly ConcurrentDictionary>> _subs = new(StringComparer.Ordinal); public int ActiveSubscriptionCount { get; private set; } public void Set(string path, object? value, uint statusCode = 0u) { var now = DateTime.UtcNow; _values[path] = new DataValueSnapshot(value, statusCode, now, now); } public void Push(string path, object? value, uint statusCode = 0u) { Set(path, value, statusCode); if (_subs.TryGetValue(path, out var list)) { Action[] snap; lock (list) { snap = list.ToArray(); } foreach (var obs in snap) obs(path, _values[path]); } } public DataValueSnapshot ReadTag(string path) => _values.TryGetValue(path, out var v) ? v : new DataValueSnapshot(null, 0x80340000u, null, DateTime.UtcNow); public IDisposable SubscribeTag(string path, Action observer) { var list = _subs.GetOrAdd(path, _ => []); lock (list) { list.Add(observer); } ActiveSubscriptionCount++; return new Unsub(this, path, observer); } private sealed class Unsub : IDisposable { private readonly FakeUpstream _up; private readonly string _path; private readonly Action _observer; public Unsub(FakeUpstream up, string path, Action observer) { _up = up; _path = path; _observer = observer; } public void Dispose() { if (_up._subs.TryGetValue(_path, out var list)) { lock (list) { if (list.Remove(_observer)) _up.ActiveSubscriptionCount--; } } } } }