fix(runtime): mux adapter forwards the dependency's real status code (05/STAB-19)
This commit is contained in:
+91
@@ -0,0 +1,91 @@
|
||||
using Akka.Actor;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
||||
using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.VirtualTags;
|
||||
|
||||
/// <summary>
|
||||
/// Archreview <b>05/STAB-19</b> — the adapter that carries dependency values from the per-node
|
||||
/// dependency mux into an <see cref="IDependencyConsumer"/> (today: the Calculation driver).
|
||||
///
|
||||
/// <para><b>The defect these pin.</b> The adapter forwarded a hard-coded <c>statusCode: 0u</c> behind
|
||||
/// a comment claiming "quality is stripped at the mux boundary". That was true once; since #478/#519
|
||||
/// <see cref="VirtualTagActor.DependencyValueChanged"/> carries the source driver's full status code,
|
||||
/// sub-code included. The consequence was that a comms-lost input reached the Calculation driver as
|
||||
/// <em>arrived, Good, null</em> — indistinguishable from a real Good reading of null, which is how a
|
||||
/// calc output kept publishing Good over a dead input.</para>
|
||||
/// </summary>
|
||||
public sealed class DependencyConsumerMuxAdapterTests : RuntimeActorTestBase
|
||||
{
|
||||
private const string TagA = "Plant/Modbus/dev1/a";
|
||||
private const string TagB = "Plant/Modbus/dev1/b";
|
||||
|
||||
/// <summary>Single delivery: the status code reaches the consumer verbatim, sub-code and all.</summary>
|
||||
[Fact]
|
||||
public void A_single_dependency_value_carries_its_status_code_to_the_consumer()
|
||||
{
|
||||
var (consumer, adapter) = Spawn();
|
||||
|
||||
adapter.Tell(new VirtualTagActor.DependencyValueChanged(
|
||||
TagA, Value: null, DateTime.UtcNow, OpcUaStatus.BadNoCommunication));
|
||||
|
||||
AwaitCondition(() => consumer.Received.Count == 1, PresenceBudget);
|
||||
consumer.Received[0].Ref.ShouldBe(TagA);
|
||||
consumer.Received[0].StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication);
|
||||
consumer.Received[0].StatusCode.ShouldNotBe(OpcUaStatus.Good,
|
||||
"a Bad input forwarded as Good is the laundering 05/STAB-19 is about");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batched delivery goes through the same seam, so it must carry status too — the batch path is
|
||||
/// what a real driver burst uses, and a fix applied only to the single-value path would be inert
|
||||
/// in production.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void A_batched_delivery_carries_each_items_status_code()
|
||||
{
|
||||
var (consumer, adapter) = Spawn();
|
||||
var ts = DateTime.UtcNow;
|
||||
|
||||
adapter.Tell(new VirtualTagActor.DependencyValueBatch(new[]
|
||||
{
|
||||
new VirtualTagActor.DependencyValueChanged(TagA, 1.0, ts, OpcUaStatus.Good),
|
||||
new VirtualTagActor.DependencyValueChanged(TagB, null, ts, OpcUaStatus.BadNoCommunication),
|
||||
}));
|
||||
|
||||
AwaitCondition(() => consumer.Received.Count == 2, PresenceBudget);
|
||||
consumer.Received[0].StatusCode.ShouldBe(OpcUaStatus.Good);
|
||||
consumer.Received[1].StatusCode.ShouldBe(OpcUaStatus.BadNoCommunication);
|
||||
}
|
||||
|
||||
private (RecordingConsumer Consumer, IActorRef Adapter) Spawn()
|
||||
{
|
||||
var consumer = new RecordingConsumer();
|
||||
var mux = CreateTestProbe();
|
||||
var adapter = Sys.ActorOf(DependencyConsumerMuxAdapter.Props(consumer, mux.Ref));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(PresenceBudget);
|
||||
return (consumer, adapter);
|
||||
}
|
||||
|
||||
private sealed class RecordingConsumer : IDependencyConsumer
|
||||
{
|
||||
private readonly List<(string Ref, object? Value, uint StatusCode)> _received = new();
|
||||
private readonly object _gate = new();
|
||||
|
||||
public IReadOnlyList<(string Ref, object? Value, uint StatusCode)> Received
|
||||
{
|
||||
get { lock (_gate) return _received.ToArray(); }
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<string> DependencyRefs { get; } = [TagA, TagB];
|
||||
|
||||
public void OnDependencyValue(string fullReference, object? value, uint statusCode, DateTime timestampUtc)
|
||||
{
|
||||
lock (_gate) _received.Add((fullReference, value, statusCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user