test: migrate AdminUI/ControlPlane/Runtime/Host tests to xunit.v3 (#756)
v2-ci / unit-tests (push) Failing after 13m22s
v2-ci / unit-tests (push) Failing after 13m22s
Akka.TestKit.Xunit2 -> Akka.TestKit.Xunit 1.5.62 (xunit.v3 TestKit; needs xunit.v3 3.2.2, set via VersionOverride on the four projects). Removes the xunit 2.9.3 pin. IAsyncLifetime returns ValueTask, SqlServerFactAttribute gains the source-info ctor, and xUnit1051 call sites pass TestContext.Current.CancellationToken. Test counts unchanged. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
+78
-78
@@ -150,7 +150,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
Plan(id: "alm-2", depRef: "M.X", enabled: false), // disabled — not loaded, dep absent
|
||||
}));
|
||||
|
||||
var reg = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
|
||||
var reg = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
reg.TagRefs.ShouldContain("M.T");
|
||||
reg.TagRefs.ShouldNotContain("M.X");
|
||||
}
|
||||
@@ -168,12 +168,12 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, now));
|
||||
|
||||
var state = publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout);
|
||||
var state = publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.AlarmNodeId.ShouldBe("alm-1");
|
||||
// The full Part 9 snapshot bridges through (T15) — every Core condition field maps:
|
||||
// on activation the engine sets Active, clears Ack AND Confirm (a new active occurrence needs a
|
||||
@@ -186,7 +186,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
state.State.Severity.ShouldBe((ushort)1000); // 800 → Critical bucket → 1000
|
||||
state.State.Message.ShouldBe("condition"); // e.Message
|
||||
|
||||
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout);
|
||||
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
evt.TransitionKind.ShouldBe("Activated");
|
||||
evt.Severity.ShouldBe(1000); // 800 → Critical bucket → 1000
|
||||
@@ -209,20 +209,20 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan() }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Activate first.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Activated", Timeout);
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Now clear.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 10, DateTime.UtcNow));
|
||||
|
||||
var cleared = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => !m.State.Active, Timeout);
|
||||
var cleared = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => !m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
cleared.AlarmNodeId.ShouldBe("alm-1");
|
||||
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Cleared", Timeout);
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Cleared", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
}
|
||||
|
||||
@@ -236,11 +236,11 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
var first = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
|
||||
var first = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
first.TagRefs.ShouldContain("M.T");
|
||||
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-9", depRef: "M.Y") }));
|
||||
var second = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
|
||||
var second = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
second.TagRefs.ShouldContain("M.Y");
|
||||
second.TagRefs.ShouldNotContain("M.T");
|
||||
}
|
||||
@@ -258,11 +258,11 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
// Apply a plan that fails to compile — LoadAsync faults, the host swallows it as a Warning and
|
||||
// does NOT register interest (no dep refs to register, and the load never completed).
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { BadPlan() }));
|
||||
mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
|
||||
// Prove the actor is still responsive: a later valid apply loads + registers interest as normal.
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-ok", depRef: "M.T") }));
|
||||
var reg = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
|
||||
var reg = mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
reg.TagRefs.ShouldContain("M.T");
|
||||
}
|
||||
|
||||
@@ -289,10 +289,10 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-b", depRef: "M.B") }));
|
||||
|
||||
// The latest generation must win: fish for the RegisterInterest reflecting the second apply.
|
||||
mux.FishForMessage<DependencyMuxActor.RegisterInterest>(r => r.TagRefs.Contains("M.B"), Timeout);
|
||||
mux.FishForMessage<DependencyMuxActor.RegisterInterest>(r => r.TagRefs.Contains("M.B"), Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// No LATER RegisterInterest may re-introduce the first (superseded) apply's "M.A" ref.
|
||||
mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
mux.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Command path: an AlarmCommand("Acknowledge") for an alarm this host owns (and that is
|
||||
@@ -309,23 +309,23 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Activate so there is something to acknowledge (Acknowledge no-ops on an already-acked alarm).
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active && !m.State.Acknowledged, Timeout);
|
||||
alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Activated", Timeout);
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active && !m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Acknowledge via the command topic — the host owns alm-1, so AcknowledgeAsync runs.
|
||||
host.Tell(new AlarmCommand(
|
||||
AlarmId: "alm-1", Operation: "Acknowledge", User: "alice", Comment: "ack-note", UnshelveAtUtc: null));
|
||||
|
||||
var acked = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Acknowledged, Timeout);
|
||||
var acked = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
acked.AlarmNodeId.ShouldBe("alm-1");
|
||||
acked.State.Acknowledged.ShouldBeTrue();
|
||||
|
||||
// The transition carries the acting user mapped from cmd.User (proves AcknowledgeAsync got it).
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Acknowledged", Timeout);
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Acknowledged", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
evt.User.ShouldBe("alice");
|
||||
// Historian feed prep: the operator's ack comment threads through to the transition event.
|
||||
@@ -345,14 +345,14 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed; host owns only alm-1
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed; host owns only alm-1
|
||||
|
||||
// Command targets an alarm this engine never loaded — it must be a no-op.
|
||||
host.Tell(new AlarmCommand(
|
||||
AlarmId: "not-mine", Operation: "Acknowledge", User: "alice", Comment: null, UnshelveAtUtc: null));
|
||||
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // no engine op → no projection
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // no engine op → no transition
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // no engine op → no projection
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // no engine op → no transition
|
||||
}
|
||||
|
||||
/// <summary>Validation: an AddComment command with empty or whitespace comment text is rejected (logged),
|
||||
@@ -370,17 +370,17 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// AddComment with empty/whitespace text is rejected before reaching the engine.
|
||||
host.Tell(new AlarmCommand(
|
||||
AlarmId: "alm-1", Operation: "AddComment", User: "alice", Comment: emptyComment, UnshelveAtUtc: null));
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // rejected → no engine op → no OPC UA projection
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(200)); // rejected → no alerts event
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // rejected → no engine op → no OPC UA projection
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken); // rejected → no alerts event
|
||||
|
||||
// Prove the actor survived: activate the alarm and observe the normal projection flow.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.AlarmNodeId.ShouldBe("alm-1");
|
||||
}
|
||||
|
||||
@@ -397,13 +397,13 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// AddComment with a non-empty comment drives the engine — CommentAdded transition emitted.
|
||||
host.Tell(new AlarmCommand(
|
||||
AlarmId: "alm-1", Operation: "AddComment", User: "bob", Comment: "note from operator", UnshelveAtUtc: null));
|
||||
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "CommentAdded", Timeout);
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "CommentAdded", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
}
|
||||
|
||||
@@ -422,14 +422,14 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Send AddComment — the alarm does NOT need to be active; AddCommentAsync is unconditional.
|
||||
host.Tell(new AlarmCommand(
|
||||
AlarmId: "alm-1", Operation: "AddComment", User: "carol", Comment: "operator note", UnshelveAtUtc: null));
|
||||
|
||||
// The CommentAdded AlarmTransitionEvent must carry the operator's identity, not "system".
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "CommentAdded", Timeout);
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "CommentAdded", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
evt.User.ShouldBe("carol"); // operator — NOT "system"
|
||||
evt.Comment.ShouldBe("operator note");
|
||||
@@ -447,16 +447,16 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// TimedShelve with a null UnshelveAtUtc is malformed — the host rejects + logs, does not throw.
|
||||
host.Tell(new AlarmCommand(
|
||||
AlarmId: "alm-1", Operation: "TimedShelve", User: "alice", Comment: null, UnshelveAtUtc: null));
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500)); // rejected → no engine op → no projection
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken); // rejected → no engine op → no projection
|
||||
|
||||
// Prove the actor survived: activate the alarm and observe the normal projection flow.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.AlarmNodeId.ShouldBe("alm-1");
|
||||
}
|
||||
|
||||
@@ -474,14 +474,14 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux, LocalNode);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// No RedundancyStateChanged sent — local role unknown ⇒ default-emit.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
|
||||
// The OPC UA node write happens AND the alerts transition is published.
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout);
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
evt.TransitionKind.ShouldBe("Activated");
|
||||
}
|
||||
@@ -499,18 +499,18 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux, LocalNode);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Mark this node Secondary, then activate.
|
||||
TellRedundancyRole(host, RedundancyRole.Secondary);
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
|
||||
// The local OPC UA node write is UNGATED — it must still arrive.
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.AlarmNodeId.ShouldBe("alm-1");
|
||||
|
||||
// The cluster-wide alerts publish is gated off on the secondary.
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Primary publishes (T1): when the cached local role is Primary, the host publishes the
|
||||
@@ -525,14 +525,14 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux, LocalNode);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Mark this node Primary, then activate.
|
||||
TellRedundancyRole(host, RedundancyRole.Primary);
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout);
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
evt.TransitionKind.ShouldBe("Activated");
|
||||
}
|
||||
@@ -550,13 +550,13 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = SpawnWithMemberCount(publish, mux, LocalNode, driverMemberCount: 2);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// No snapshot sent ⇒ role unknown; count 2 ⇒ default-DENY.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Boot-window single-driver emit (archreview 03/S4): role unknown + no driver peer (member
|
||||
@@ -571,12 +571,12 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = SpawnWithMemberCount(publish, mux, LocalNode, driverMemberCount: 1);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
alerts.ExpectMsg<AlarmTransitionEvent>(Timeout).AlarmId.ShouldBe("alm-1");
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
alerts.ExpectMsg<AlarmTransitionEvent>(Timeout, cancellationToken: TestContext.Current.CancellationToken).AlarmId.ShouldBe("alm-1");
|
||||
}
|
||||
|
||||
private (IActorRef Host, DependencyMuxTagUpstreamSource Upstream) SpawnWithMemberCount(
|
||||
@@ -601,20 +601,20 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
var (host, _) = Spawn(publish, mux, LocalNode);
|
||||
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Mark this node Secondary — the alerts publish is gated, but command processing is NOT.
|
||||
TellRedundancyRole(host, RedundancyRole.Secondary);
|
||||
|
||||
// Activate so there is something to acknowledge.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active && !m.State.Acknowledged, Timeout);
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active && !m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Acknowledge via the command topic — the engine must process it even on the secondary.
|
||||
host.Tell(new AlarmCommand(
|
||||
AlarmId: "alm-1", Operation: "Acknowledge", User: "alice", Comment: "ack-note", UnshelveAtUtc: null));
|
||||
|
||||
var acked = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Acknowledged, Timeout);
|
||||
var acked = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Acknowledged, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
acked.AlarmNodeId.ShouldBe("alm-1");
|
||||
acked.State.Acknowledged.ShouldBeTrue();
|
||||
}
|
||||
@@ -632,18 +632,18 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux, LocalNode);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Mark this node Detached, then activate.
|
||||
TellRedundancyRole(host, RedundancyRole.Detached);
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
|
||||
// The local OPC UA node write is UNGATED — it must still arrive.
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.AlarmNodeId.ShouldBe("alm-1");
|
||||
|
||||
// The cluster-wide alerts publish is gated off on a detached node.
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>HistorizeToAveva flag threading (middle-link): the host MUST carry the plan's
|
||||
@@ -667,18 +667,18 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { planTrue, planFalse }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Activate the true-flag alarm.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("H.T", 99, DateTime.UtcNow));
|
||||
var evtTrue = alerts.FishForMessage<AlarmTransitionEvent>(
|
||||
e => e.AlarmId == "alm-hist-true" && e.TransitionKind == "Activated", Timeout);
|
||||
e => e.AlarmId == "alm-hist-true" && e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evtTrue.HistorizeToAveva.ShouldBe(true);
|
||||
|
||||
// Activate the false-flag alarm.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("H.F", 99, DateTime.UtcNow));
|
||||
var evtFalse = alerts.FishForMessage<AlarmTransitionEvent>(
|
||||
e => e.AlarmId == "alm-hist-false" && e.TransitionKind == "Activated", Timeout);
|
||||
e => e.AlarmId == "alm-hist-false" && e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evtFalse.HistorizeToAveva.ShouldBe(false);
|
||||
}
|
||||
|
||||
@@ -695,19 +695,19 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T", threshold: 90) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Baseline is Good (unread inputs are not a quality signal — no load-time annotation). Drive the
|
||||
// input Bad with a value below threshold: the predicate freezes (no transition), but the worst-input
|
||||
// quality bucket moves Good→Bad → a QualityChanged annotation flows out of band.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 10, DateTime.UtcNow, OpcUaStatus.Bad));
|
||||
|
||||
var q = publish.FishForMessage<OpcUaPublishActor.AlarmQualityUpdate>(m => m.StatusCode == OpcUaStatus.Bad, Timeout);
|
||||
var q = publish.FishForMessage<OpcUaPublishActor.AlarmQualityUpdate>(m => m.StatusCode == OpcUaStatus.Bad, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
q.AlarmNodeId.ShouldBe("alm-1");
|
||||
q.Realm.ShouldBe(AddressSpaceRealm.Uns);
|
||||
|
||||
// A pure quality change is NOT a state transition: no /alerts row.
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(400));
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>#478 — when a transition fires while an input is Uncertain, the projected full snapshot
|
||||
@@ -723,12 +723,12 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T", threshold: 90) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Above threshold (activates) but Uncertain quality — Uncertain is "ready", so the predicate runs.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow, OpcUaStatus.Uncertain));
|
||||
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.AlarmNodeId.ShouldBe("alm-1");
|
||||
state.State.StatusCode.ShouldBe(OpcUaStatus.Uncertain);
|
||||
}
|
||||
@@ -748,19 +748,19 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(id: "alm-1", depRef: "M.T") }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Activate so there is something to shelve.
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Activated", Timeout);
|
||||
publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Activated", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Shelve via a one-shot shelve command — the host owns alm-1, so OneShotShelveAsync runs.
|
||||
host.Tell(new AlarmCommand(
|
||||
AlarmId: "alm-1", Operation: "OneShotShelve", User: "carol", Comment: null, UnshelveAtUtc: null));
|
||||
|
||||
// The Shelved AlarmTransitionEvent must carry the operator's identity, not "system".
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Shelved", Timeout);
|
||||
var evt = alerts.FishForMessage<AlarmTransitionEvent>(e => e.TransitionKind == "Shelved", Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
evt.User.ShouldBe("carol"); // operator — NOT "system"
|
||||
}
|
||||
@@ -779,7 +779,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
var (host, _) = Spawn(publish, mux, LocalNode);
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout); // load completed
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken); // load completed
|
||||
|
||||
// Send a snapshot that mentions only a DIFFERENT node — LocalNode is absent.
|
||||
// The host cannot determine its own role from this snapshot, so the cached role
|
||||
@@ -789,10 +789,10 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
host.Tell(new VirtualTagActor.DependencyValueChanged("M.T", 99, DateTime.UtcNow));
|
||||
|
||||
// Both the OPC UA write AND the alerts publish must arrive (default-emit, role unknown).
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout);
|
||||
var state = publish.FishForMessage<OpcUaPublishActor.AlarmStateUpdate>(m => m.State.Active, Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.AlarmNodeId.ShouldBe("alm-1");
|
||||
|
||||
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout);
|
||||
var evt = alerts.ExpectMsg<AlarmTransitionEvent>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
evt.AlarmId.ShouldBe("alm-1");
|
||||
evt.TransitionKind.ShouldBe("Activated");
|
||||
}
|
||||
@@ -852,7 +852,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(severity: 800) }));
|
||||
|
||||
var state = publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout);
|
||||
var state = publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.AlarmNodeId.ShouldBe("alm-1");
|
||||
state.Realm.ShouldBe(AddressSpaceRealm.Uns);
|
||||
state.State.Active.ShouldBeTrue("the persisted state was Active — the rebuilt node must say so");
|
||||
@@ -880,10 +880,10 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
// Wait for the re-assert to actually happen FIRST — otherwise the absence window below could
|
||||
// elapse before the load even completed and would prove nothing.
|
||||
publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout);
|
||||
publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Absence assertion: the elapsed time IS the assertion, so this stays deliberately short.
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
alerts.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>An alarm sitting in the Part 9 no-event position already matches what the materialise
|
||||
@@ -900,8 +900,8 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
// Sequence the absence: the re-assert (if any) happens right after the mux registration, so
|
||||
// waiting for that first makes the window below cover the moment the write would have landed.
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout);
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
mux.ExpectMsg<DependencyMuxActor.RegisterInterest>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Not just Active: every field a rebuild would silently lose is restored. A shelved (but
|
||||
@@ -919,7 +919,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan() }));
|
||||
|
||||
var state = publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout);
|
||||
var state = publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
state.State.Active.ShouldBeFalse();
|
||||
state.State.Shelving.ShouldBe(AlarmShelvingKind.OneShot);
|
||||
}
|
||||
@@ -936,7 +936,7 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan() }));
|
||||
|
||||
publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout)
|
||||
publish.ExpectMsg<OpcUaPublishActor.AlarmStateUpdate>(Timeout, cancellationToken: TestContext.Current.CancellationToken)
|
||||
.TimestampUtc.ShouldBe(wentActiveAt);
|
||||
}
|
||||
|
||||
@@ -952,6 +952,6 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
||||
|
||||
host.Tell(new ScriptedAlarmHostActor.ApplyScriptedAlarms(new[] { Plan(enabled: false) }));
|
||||
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user