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
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.
177 lines
7.6 KiB
C#
177 lines
7.6 KiB
C#
using System.Collections.Concurrent;
|
|
using Akka.Actor;
|
|
using Akka.TestKit;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Engines;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Alerts;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Logging;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.ScriptedAlarms;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.VirtualTags;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.ScriptedAlarms;
|
|
|
|
public sealed class ScriptedAlarmActorTests : RuntimeActorTestBase
|
|
{
|
|
/// <summary>Verifies that full state cycle publishes StateChanged messages to parent at each transition.</summary>
|
|
[Fact]
|
|
public void Full_state_cycle_publishes_StateChanged_to_parent_at_each_transition()
|
|
{
|
|
var parent = CreateTestProbe();
|
|
var actor = parent.ChildActorOf(ScriptedAlarmActor.Props("alarm-1"));
|
|
|
|
actor.Tell(new ScriptedAlarmActor.ConditionMet("threshold"));
|
|
var t1 = parent.ExpectMsg<ScriptedAlarmActor.StateChanged>();
|
|
t1.State.ShouldBe(ScriptedAlarmActorState.Active);
|
|
|
|
actor.Tell(new ScriptedAlarmActor.AcknowledgeAlarm("joe"));
|
|
var t2 = parent.ExpectMsg<ScriptedAlarmActor.StateChanged>();
|
|
t2.State.ShouldBe(ScriptedAlarmActorState.Acknowledged);
|
|
|
|
actor.Tell(new ScriptedAlarmActor.ConditionCleared());
|
|
var t3 = parent.ExpectMsg<ScriptedAlarmActor.StateChanged>();
|
|
t3.State.ShouldBe(ScriptedAlarmActorState.Inactive);
|
|
}
|
|
|
|
/// <summary>Verifies that duplicate ConditionMet messages in Active state are ignored.</summary>
|
|
[Fact]
|
|
public void Duplicate_ConditionMet_in_Active_is_ignored()
|
|
{
|
|
var parent = CreateTestProbe();
|
|
var actor = parent.ChildActorOf(ScriptedAlarmActor.Props("alarm-1"));
|
|
|
|
actor.Tell(new ScriptedAlarmActor.ConditionMet("first"));
|
|
parent.ExpectMsg<ScriptedAlarmActor.StateChanged>();
|
|
|
|
actor.Tell(new ScriptedAlarmActor.ConditionMet("second"));
|
|
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
|
}
|
|
|
|
/// <summary>Verifies that active transition publishes AlarmTransitionEvent to the alerts topic.</summary>
|
|
[Fact]
|
|
public void Engine_active_transition_publishes_AlarmTransitionEvent_to_alerts_topic()
|
|
{
|
|
var capture = new CapturingPublisher();
|
|
var parent = CreateTestProbe();
|
|
var config = new ScriptedAlarmActor.AlarmConfig(
|
|
AlarmId: "alarm-7",
|
|
AlarmName: "High Temp",
|
|
EquipmentPath: "/site-1/line-A/oven",
|
|
Severity: 800,
|
|
Predicate: "temp > 80");
|
|
var actor = parent.ChildActorOf(ScriptedAlarmActor.Props(
|
|
config,
|
|
evaluator: new ThresholdEvaluator(80),
|
|
publisherFactory: () => new DPSPublisher(capture.Publish)));
|
|
|
|
actor.Tell(new ScriptedAlarmActor.DependencyValueChanged("temp", 92, DateTime.UtcNow));
|
|
parent.ExpectMsg<ScriptedAlarmActor.StateChanged>().State.ShouldBe(ScriptedAlarmActorState.Active);
|
|
|
|
AwaitAssert(() =>
|
|
{
|
|
var transitionEvt = capture.Payloads.OfType<AlarmTransitionEvent>().SingleOrDefault();
|
|
transitionEvt.ShouldNotBeNull();
|
|
transitionEvt.AlarmId.ShouldBe("alarm-7");
|
|
transitionEvt.AlarmName.ShouldBe("High Temp");
|
|
transitionEvt.EquipmentPath.ShouldBe("/site-1/line-A/oven");
|
|
transitionEvt.Severity.ShouldBe(800);
|
|
transitionEvt.TransitionKind.ShouldBe("Activated");
|
|
transitionEvt.User.ShouldBe("system");
|
|
|
|
var log = capture.Payloads.OfType<ScriptLogEntry>().SingleOrDefault();
|
|
log.ShouldNotBeNull();
|
|
log.AlarmId.ShouldBe("alarm-7");
|
|
}, duration: TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
/// <summary>Verifies that clear transition publishes Cleared event.</summary>
|
|
[Fact]
|
|
public void Engine_clear_transition_publishes_Cleared_event()
|
|
{
|
|
var capture = new CapturingPublisher();
|
|
var parent = CreateTestProbe();
|
|
var config = new ScriptedAlarmActor.AlarmConfig("alarm-7", "High Temp", "/p", 500, "temp > 80");
|
|
var evaluator = new ThresholdEvaluator(80);
|
|
var actor = parent.ChildActorOf(ScriptedAlarmActor.Props(
|
|
config, evaluator,
|
|
publisherFactory: () => new DPSPublisher(capture.Publish)));
|
|
|
|
actor.Tell(new ScriptedAlarmActor.DependencyValueChanged("temp", 92, DateTime.UtcNow));
|
|
parent.ExpectMsg<ScriptedAlarmActor.StateChanged>();
|
|
|
|
actor.Tell(new ScriptedAlarmActor.DependencyValueChanged("temp", 70, DateTime.UtcNow));
|
|
parent.ExpectMsg<ScriptedAlarmActor.StateChanged>().State.ShouldBe(ScriptedAlarmActorState.Inactive);
|
|
|
|
AwaitAssert(() =>
|
|
{
|
|
var kinds = capture.Payloads.OfType<AlarmTransitionEvent>().Select(e => e.TransitionKind).ToList();
|
|
kinds.ShouldContain("Activated");
|
|
kinds.ShouldContain("Cleared");
|
|
}, duration: TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
/// <summary>Verifies that manual acknowledge emits Acknowledged transition with the user.</summary>
|
|
[Fact]
|
|
public void Manual_acknowledge_emits_Acknowledged_transition_with_user()
|
|
{
|
|
var capture = new CapturingPublisher();
|
|
var parent = CreateTestProbe();
|
|
var config = new ScriptedAlarmActor.AlarmConfig("a-1", "Pump Fail", "/eq", 700, Predicate: null);
|
|
var actor = parent.ChildActorOf(ScriptedAlarmActor.Props(
|
|
config, evaluator: null,
|
|
publisherFactory: () => new DPSPublisher(capture.Publish)));
|
|
|
|
actor.Tell(new ScriptedAlarmActor.ConditionMet("driver-fault"));
|
|
parent.ExpectMsg<ScriptedAlarmActor.StateChanged>();
|
|
|
|
actor.Tell(new ScriptedAlarmActor.AcknowledgeAlarm("operator-jane"));
|
|
parent.ExpectMsg<ScriptedAlarmActor.StateChanged>().State.ShouldBe(ScriptedAlarmActorState.Acknowledged);
|
|
|
|
AwaitAssert(() =>
|
|
{
|
|
var ackEvt = capture.Payloads.OfType<AlarmTransitionEvent>()
|
|
.SingleOrDefault(e => e.TransitionKind == "Acknowledged");
|
|
ackEvt.ShouldNotBeNull();
|
|
ackEvt.User.ShouldBe("operator-jane");
|
|
}, duration: TimeSpan.FromSeconds(1));
|
|
}
|
|
|
|
/// <summary>A threshold-based alarm evaluator for testing.</summary>
|
|
private sealed class ThresholdEvaluator : IScriptedAlarmEvaluator
|
|
{
|
|
private readonly double _threshold;
|
|
|
|
/// <summary>Initializes a new instance of the ThresholdEvaluator class.</summary>
|
|
/// <param name="threshold">The threshold value to compare against.</param>
|
|
public ThresholdEvaluator(double threshold) { _threshold = threshold; }
|
|
|
|
/// <inheritdoc />
|
|
public ScriptedAlarmEvalResult Evaluate(string id, string predicate, IReadOnlyDictionary<string, object?> deps)
|
|
{
|
|
if (!deps.TryGetValue("temp", out var raw) || raw is null)
|
|
return ScriptedAlarmEvalResult.Failure("missing temp");
|
|
return ScriptedAlarmEvalResult.Ok(Convert.ToDouble(raw) > _threshold);
|
|
}
|
|
}
|
|
|
|
/// <summary>A test publisher that captures published messages.</summary>
|
|
private sealed class CapturingPublisher
|
|
{
|
|
/// <summary>Gets the topics that messages were published to.</summary>
|
|
public ConcurrentBag<string> Topics { get; } = new();
|
|
|
|
/// <summary>Gets the payloads that were published.</summary>
|
|
public ConcurrentBag<object> Payloads { get; } = new();
|
|
|
|
/// <summary>Publishes a message to the specified topic.</summary>
|
|
/// <param name="topic">The topic name.</param>
|
|
/// <param name="payload">The message payload.</param>
|
|
public void Publish(string topic, object payload)
|
|
{
|
|
Topics.Add(topic);
|
|
Payloads.Add(payload);
|
|
}
|
|
}
|
|
}
|