feat(scripted-alarms): spawn + apply ScriptedAlarmHostActor in DriverHostActor (T10)

This commit is contained in:
Joseph Doherty
2026-06-10 15:17:29 -04:00
parent dafaf2faec
commit 5256761368
3 changed files with 187 additions and 4 deletions
@@ -1,3 +1,4 @@
using System.Text.Json;
using Akka.Actor;
using Microsoft.EntityFrameworkCore;
using Shouldly;
@@ -7,6 +8,7 @@ using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.Configuration;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
using ZB.MOM.WW.OtOpcUa.Runtime.Drivers;
using ZB.MOM.WW.OtOpcUa.Runtime.ScriptedAlarms;
using ZB.MOM.WW.OtOpcUa.Runtime.Tests.Harness;
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Drivers;
@@ -126,6 +128,84 @@ public sealed class DriverHostActorTests : RuntimeActorTestBase
.Status.ShouldBe(NodeDeploymentStatus.Applied);
}
/// <summary>Fresh apply: dispatching a deployment whose artifact carries one Equipment
/// ScriptedAlarm forwards a <see cref="ScriptedAlarmHostActor.ApplyScriptedAlarms"/> carrying that
/// plan to the injected ScriptedAlarm host (via the <c>scriptedAlarmHostOverride</c> seam, mirroring
/// the VirtualTag-host wiring).</summary>
[Fact]
public void Apply_forwards_EquipmentScriptedAlarms_to_scripted_alarm_host()
{
var db = NewInMemoryDbFactory();
var deploymentId = SeedDeploymentWithScriptedAlarm(db, RevA);
var coordinator = CreateTestProbe();
var alarmHost = CreateTestProbe();
var actor = Sys.ActorOf(DriverHostActor.Props(
db, TestNode, coordinator.Ref,
localRoles: new HashSet<string> { "driver" },
scriptedAlarmHostOverride: alarmHost.Ref));
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
var apply = alarmHost.ExpectMsg<ScriptedAlarmHostActor.ApplyScriptedAlarms>(TimeSpan.FromSeconds(5));
var plan = apply.Plans.ShouldHaveSingleItem();
plan.ScriptedAlarmId.ShouldBe("al-1");
plan.EquipmentId.ShouldBe("eq-1");
plan.Name.ShouldBe("Overheat");
plan.PredicateSource.ShouldContain("ctx.GetTag");
}
private static DeploymentId SeedDeploymentWithScriptedAlarm(
IDbContextFactory<OtOpcUaConfigDbContext> db, RevisionHash rev)
{
// Artifact carries a ScriptedAlarm joined (by PredicateScriptId) to its predicate Script —
// the same shape ConfigComposer emits and DeploymentArtifact.ParseComposition decodes into
// composition.EquipmentScriptedAlarms.
var artifact = JsonSerializer.SerializeToUtf8Bytes(new
{
Scripts = new[]
{
new
{
ScriptId = "scr-1",
SourceCode = "return System.Convert.ToDouble(ctx.GetTag(\"Mach1.Temp\").Value) > 80;",
},
},
ScriptedAlarms = new[]
{
new
{
ScriptedAlarmId = "al-1",
EquipmentId = "eq-1",
Name = "Overheat",
AlarmType = "LimitAlarm",
Severity = 700,
MessageTemplate = "Machine 1 hot",
PredicateScriptId = "scr-1",
HistorizeToAveva = true,
Retain = true,
Enabled = true,
},
},
});
var id = DeploymentId.NewId();
using var ctx = db.CreateDbContext();
ctx.Deployments.Add(new Configuration.Entities.Deployment
{
DeploymentId = id.Value,
RevisionHash = rev.Value,
Status = DeploymentStatus.Sealed,
CreatedBy = "test",
SealedAtUtc = DateTime.UtcNow,
ArtifactBlob = artifact,
});
ctx.SaveChanges();
return id;
}
private static DeploymentId SeedDeployment(
IDbContextFactory<OtOpcUaConfigDbContext> db,
RevisionHash rev,