Files
lmxopcua/tests/Core/ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests/FakeUpstream.cs
T
Joseph Doherty 64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
docs: backfill XML documentation across 756 files
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public
members surfaced by commentchecker — resolves 5,847 of 5,869 issues
(99.6%) across three /fixdocs passes.
2026-05-28 08:10:17 -04:00

83 lines
3.8 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]);
}
}
/// <summary>Reads the current value of a tag, or returns a bad-status snapshot if not set.</summary>
/// <param name="path">The tag path to read.</param>
public DataValueSnapshot ReadTag(string path)
=> _values.TryGetValue(path, out var v) ? v
: new DataValueSnapshot(null, 0x80340000u, null, DateTime.UtcNow);
/// <summary>Subscribes an observer to tag changes for the given path.</summary>
/// <param name="path">The tag path to subscribe to.</param>
/// <param name="observer">The observer callback to invoke on tag changes.</param>
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--;
}
}
}
}
}