Files
lmxopcua/tests/Core/ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests/FakeUpstream.cs
T
Joseph Doherty bd6c0b4d3d docs: complete XML doc comments via fixdocs (2757 to 131 findings)
Add missing <returns>/<param>/<summary>/<typeparam> tags and clean up
misused inheritdoc across 481 files so the documented API surface is
complete. Documentation-only (zero code lines changed). The 131 remaining
findings are inheritdoc-style warnings deliberately left to preserve
hand-written implementation rationale (plan-decision notes, race-condition
explanations).
2026-06-03 12:34:34 -04:00

80 lines
3.5 KiB
C#

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;
/// <summary>Test fake implementation of <see cref="ITagUpstreamSource"/> for verifying subscription behavior.</summary>
public sealed class FakeUpstream : ITagUpstreamSource
{
private readonly ConcurrentDictionary<string, DataValueSnapshot> _values = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, List<Action<string, DataValueSnapshot>>> _subs
= new(StringComparer.Ordinal);
/// <summary>Gets the current count of active subscriptions.</summary>
public int ActiveSubscriptionCount { get; private set; }
/// <summary>Sets a tag value without notifying subscribers.</summary>
/// <param name="path">The tag path to set.</param>
/// <param name="value">The value to set for the tag.</param>
/// <param name="statusCode">The OPC UA status code for the value.</param>
public void Set(string path, object? value, uint statusCode = 0u)
{
var now = DateTime.UtcNow;
_values[path] = new DataValueSnapshot(value, statusCode, now, now);
}
/// <summary>Sets a tag value and notifies all current subscribers.</summary>
/// <param name="path">The tag path to set.</param>
/// <param name="value">The value to set for the tag.</param>
/// <param name="statusCode">The OPC UA status code for the value.</param>
public void Push(string path, object? value, uint statusCode = 0u)
{
Set(path, value, statusCode);
if (_subs.TryGetValue(path, out var list))
{
Action<string, DataValueSnapshot>[] snap;
lock (list) { snap = list.ToArray(); }
foreach (var obs in snap) obs(path, _values[path]);
}
}
/// <inheritdoc />
public DataValueSnapshot ReadTag(string path)
=> _values.TryGetValue(path, out var v) ? v
: new DataValueSnapshot(null, 0x80340000u, null, DateTime.UtcNow);
/// <inheritdoc />
public IDisposable SubscribeTag(string path, Action<string, DataValueSnapshot> observer)
{
var list = _subs.GetOrAdd(path, _ => []);
lock (list) { list.Add(observer); }
ActiveSubscriptionCount++;
return new Unsub(this, path, observer);
}
/// <summary>Disposable subscription handle that unsubscribes the observer when disposed.</summary>
private sealed class Unsub : IDisposable
{
private readonly FakeUpstream _up;
private readonly string _path;
private readonly Action<string, DataValueSnapshot> _observer;
/// <summary>Initializes the unsubscription handle with references needed to clean up the subscription.</summary>
/// <param name="up">The upstream source containing the subscription list.</param>
/// <param name="path">The tag path to unsubscribe from.</param>
/// <param name="observer">The observer to remove from the subscription list.</param>
public Unsub(FakeUpstream up, string path, Action<string, DataValueSnapshot> observer)
{ _up = up; _path = path; _observer = observer; }
/// <summary>Removes the observer from the subscription list.</summary>
public void Dispose()
{
if (_up._subs.TryGetValue(_path, out var list))
{
lock (list)
{
if (list.Remove(_observer)) _up.ActiveSubscriptionCount--;
}
}
}
}
}