fix(alarms): historize the real operator for shelve/unshelve/enable/disable transitions
This commit is contained in:
@@ -512,7 +512,13 @@ public sealed class ScriptedAlarmHostActor : ReceiveActor
|
|||||||
{
|
{
|
||||||
EmissionKind.Acknowledged => e.Condition.LastAckUser ?? "system",
|
EmissionKind.Acknowledged => e.Condition.LastAckUser ?? "system",
|
||||||
EmissionKind.Confirmed => e.Condition.LastConfirmUser ?? "system",
|
EmissionKind.Confirmed => e.Condition.LastConfirmUser ?? "system",
|
||||||
EmissionKind.CommentAdded => e.Condition.Comments.Count > 0 ? e.Condition.Comments[^1].User : "system",
|
// Shelve / unshelve / enable / disable / comment ops each append the acting user as the LAST audit
|
||||||
|
// entry on the emitted condition (engine auto-unshelve appends "system"); read it from there.
|
||||||
|
EmissionKind.CommentAdded
|
||||||
|
or EmissionKind.Shelved
|
||||||
|
or EmissionKind.Unshelved
|
||||||
|
or EmissionKind.Enabled
|
||||||
|
or EmissionKind.Disabled => e.Condition.Comments.Count > 0 ? e.Condition.Comments[^1].User : "system",
|
||||||
_ => "system",
|
_ => "system",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+32
@@ -612,6 +612,38 @@ public sealed class ScriptedAlarmHostActorTests : RuntimeActorTestBase
|
|||||||
evtFalse.HistorizeToAveva.ShouldBe(false);
|
evtFalse.HistorizeToAveva.ShouldBe(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>OneShotShelve transition carries the operator's identity: an operator-driven OneShotShelve
|
||||||
|
/// drives <c>OneShotShelveAsync</c> — the resulting <see cref="AlarmTransitionEvent"/>(<c>"Shelved"</c>)
|
||||||
|
/// on the alerts topic must carry <c>User == cmd.User</c> (the acting operator), NOT the generic
|
||||||
|
/// <c>"system"</c> sentinel. This verifies the <see cref="ScriptedAlarmHostActor"/>
|
||||||
|
/// <c>TransitionUser</c> arm for <see cref="EmissionKind.Shelved"/>.</summary>
|
||||||
|
[Fact]
|
||||||
|
public void Shelved_transition_carries_operator_user()
|
||||||
|
{
|
||||||
|
var publish = CreateTestProbe();
|
||||||
|
var mux = CreateTestProbe();
|
||||||
|
var alerts = CreateTestProbe();
|
||||||
|
SubscribeToAlerts(alerts);
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
evt.AlarmId.ShouldBe("alm-1");
|
||||||
|
evt.User.ShouldBe("carol"); // operator — NOT "system"
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Absent-node default-emit (A1): a <see cref="RedundancyStateChanged"/> snapshot that
|
/// <summary>Absent-node default-emit (A1): a <see cref="RedundancyStateChanged"/> snapshot that
|
||||||
/// contains ONLY other nodes (the host's own <see cref="LocalNode"/> is absent) must leave the
|
/// contains ONLY other nodes (the host's own <see cref="LocalNode"/> is absent) must leave the
|
||||||
/// cached local role unchanged (null/unknown) — the host therefore defaults to emit, publishing
|
/// cached local role unchanged (null/unknown) — the host therefore defaults to emit, publishing
|
||||||
|
|||||||
Reference in New Issue
Block a user