fix(alarms): native-alarm re-statements no longer duplicate history or hide acked alarms (#656)
- AlarmTransitionKind.ActiveAcknowledged (appended): active AND acked applied as ONE projection. Galaxy ActiveAcked snapshot entries and OpcUaClient inactive->(active,acked) use it; the projector writes 'Activated' only if not already active, 'Acknowledged' only if not acked. - AlarmEventArgs.IsRestatement (optional, default false): set only by Galaxy snapshot entries and OpcUaClient first sight. A flagged Retrigger on an Active+unacked condition with the same message and severity, or a flagged Clear on an inactive condition, writes no /alerts row. - NativeAlarmProjector reports the history rows per transition; ForwardNativeAlarm writes one row per entry. Unflagged sources (FOCAS/MTConnect/AbCip, live Galaxy) are unchanged. - Tests: projector rules each with a must-write control; OpcUaClient transition table; Galaxy feed/driver mapping; Galaxy + OpcUaClient end-to-end through DriverHostActor. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+45
-2
@@ -64,14 +64,57 @@ public sealed class GatewayGalaxyAlarmFeedTests
|
||||
observed[0].SeverityBucket.ShouldBe(AlarmSeverity.Critical);
|
||||
observed[0].RawMxAccessSeverity.ShouldBe(750);
|
||||
|
||||
// Acknowledged snapshot entry → Acknowledge, operator fields preserved.
|
||||
observed[1].TransitionKind.ShouldBe(GalaxyAlarmTransitionKind.Acknowledge);
|
||||
// Acknowledged snapshot entry → ActiveAcknowledged (#656 — an Acknowledge kept the host's prior Active, so a
|
||||
// cold host projected it INACTIVE), operator fields preserved.
|
||||
observed[1].TransitionKind.ShouldBe(GalaxyAlarmTransitionKind.ActiveAcknowledged);
|
||||
observed[1].OperatorUser.ShouldBe("alice");
|
||||
observed[1].OperatorComment.ShouldBe("investigating");
|
||||
|
||||
// #656: snapshot entries re-state upstream state; the live transition is unflagged.
|
||||
observed[0].IsRestatement.ShouldBeTrue();
|
||||
observed[1].IsRestatement.ShouldBeTrue();
|
||||
|
||||
// Live transition after snapshot_complete.
|
||||
observed[2].TransitionKind.ShouldBe(GalaxyAlarmTransitionKind.Clear);
|
||||
observed[2].OriginalRaiseTimestampUtc.ShouldBe(raise);
|
||||
observed[2].IsRestatement.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gitea #656 — an Inactive snapshot entry is a re-stated Clear; a LIVE Retrigger (a genuine MXAccess
|
||||
/// re-occurrence) is never flagged, so the host keeps its history row.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Inactive_snapshot_entry_is_a_restated_clear_and_a_live_retrigger_is_unflagged()
|
||||
{
|
||||
var at = new DateTime(2026, 5, 1, 12, 0, 0, DateTimeKind.Utc);
|
||||
var messages = new[]
|
||||
{
|
||||
SnapshotMessage("Tank01.Level.HiHi", AlarmConditionState.Inactive, severity: 750, lastTransition: at),
|
||||
new AlarmFeedMessage { SnapshotComplete = true },
|
||||
TransitionMessage("Tank01.Level.HiHi", AlarmTransitionKind.Retrigger, severity: 750,
|
||||
transitionTime: at.AddMinutes(1)),
|
||||
};
|
||||
|
||||
var observed = new List<GalaxyAlarmTransition>();
|
||||
var got2 = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
await using var feed = new GatewayGalaxyAlarmFeed((_, ct) => OpenStream(messages, ct), clientName: "FeedTest656");
|
||||
feed.OnAlarmTransition += (_, t) =>
|
||||
{
|
||||
lock (observed)
|
||||
{
|
||||
observed.Add(t);
|
||||
if (observed.Count == 2) got2.TrySetResult(true);
|
||||
}
|
||||
};
|
||||
feed.Start();
|
||||
|
||||
(await Task.WhenAny(got2.Task, Task.Delay(TimeSpan.FromSeconds(2)))).ShouldBe(got2.Task);
|
||||
|
||||
observed[0].TransitionKind.ShouldBe(GalaxyAlarmTransitionKind.Clear);
|
||||
observed[0].IsRestatement.ShouldBeTrue();
|
||||
observed[1].TransitionKind.ShouldBe(GalaxyAlarmTransitionKind.Retrigger);
|
||||
observed[1].IsRestatement.ShouldBeFalse();
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the feed drops transitions with unspecified kind and empty messages.</summary>
|
||||
|
||||
Reference in New Issue
Block a user