fix(DV-2): clarify placeholder comment, stable MinValue timestamp, drain DCL probe in new tests

- Replace placeholder-loop comment with the double-render guard explanation
- Use _alarmTimestamps.GetValueOrDefault(binding, DateTimeOffset.MinValue) so the
  placeholder timestamp is stable/idempotent across snapshot calls (was UtcNow)
- Add dcl.ExpectMsg<SubscribeAlarmsRequest>() drain in Snapshot_QuietNativeBinding_EmitsPlaceholder
  and Snapshot_NativeBindingWithLiveCondition_NoPlaceholder to consume the DCL message
  the NativeAlarmActor sends at startup
This commit is contained in:
Joseph Doherty
2026-06-17 15:08:37 -04:00
parent cc017aabfc
commit b5347faf44
2 changed files with 9 additions and 4 deletions
@@ -1117,9 +1117,11 @@ public class InstanceActor : ReceiveActor
_alarmTimestamps.GetValueOrDefault(name, DateTimeOffset.UtcNow))); _alarmTimestamps.GetValueOrDefault(name, DateTimeOffset.UtcNow)));
} }
// Native source bindings with no live condition: emit a placeholder so the // A native binding is "already represented" if any retained event carries its
// Debug View tree shows the configured binding node even when quiet. A binding // canonical name — it renders in the Debug View via those condition rows. The
// is "quiet" when no retained event carries its canonical name. // placeholder below covers ONLY bindings with zero retained events (truly quiet
// since startup). Do NOT narrow this to State==Active: a binding holding a
// retained Normal/resolved condition would then be rendered twice.
var liveBindings = _latestAlarmEvents.Values var liveBindings = _latestAlarmEvents.Values
.Where(e => !string.IsNullOrEmpty(e.NativeSourceCanonicalName)) .Where(e => !string.IsNullOrEmpty(e.NativeSourceCanonicalName))
.Select(e => e.NativeSourceCanonicalName) .Select(e => e.NativeSourceCanonicalName)
@@ -1130,7 +1132,8 @@ public class InstanceActor : ReceiveActor
if (liveBindings.Contains(binding)) continue; if (liveBindings.Contains(binding)) continue;
states.Add(new AlarmStateChanged( states.Add(new AlarmStateChanged(
_instanceUniqueName, binding, AlarmState.Normal, 0, DateTimeOffset.UtcNow) _instanceUniqueName, binding, AlarmState.Normal, 0,
_alarmTimestamps.GetValueOrDefault(binding, DateTimeOffset.MinValue))
{ {
Kind = _nativeAlarmKinds.GetValueOrDefault(binding, AlarmKind.NativeOpcUa), Kind = _nativeAlarmKinds.GetValueOrDefault(binding, AlarmKind.NativeOpcUa),
NativeSourceCanonicalName = binding, NativeSourceCanonicalName = binding,
@@ -100,6 +100,7 @@ public class InstanceActorNativeAlarmTests : TestKit, IDisposable
{ {
var dcl = CreateTestProbe(); var dcl = CreateTestProbe();
var actor = CreateInstanceActorWithDcl("inst", ConfigWithNativeSource("inst"), dcl.Ref); var actor = CreateInstanceActorWithDcl("inst", ConfigWithNativeSource("inst"), dcl.Ref);
dcl.ExpectMsg<SubscribeAlarmsRequest>();
// No live condition is emitted: the configured "Pressure" binding is quiet. // No live condition is emitted: the configured "Pressure" binding is quiet.
actor.Tell(new SubscribeDebugViewRequest("inst", "c")); actor.Tell(new SubscribeDebugViewRequest("inst", "c"));
@@ -121,6 +122,7 @@ public class InstanceActorNativeAlarmTests : TestKit, IDisposable
{ {
var dcl = CreateTestProbe(); var dcl = CreateTestProbe();
var actor = CreateInstanceActorWithDcl("inst", ConfigWithNativeSource("inst"), dcl.Ref); var actor = CreateInstanceActorWithDcl("inst", ConfigWithNativeSource("inst"), dcl.Ref);
dcl.ExpectMsg<SubscribeAlarmsRequest>();
// The NativeAlarmActor emits a live condition stamped with the binding's // The NativeAlarmActor emits a live condition stamped with the binding's
// canonical name (DV-1: NativeSourceCanonicalName), so the binding is "active". // canonical name (DV-1: NativeSourceCanonicalName), so the binding is "active".