docs(opcua): explain intentional CommentAdded/Retain delta-gate suppression (T20 review)
Three code-review points on commit 004558c2 were correct behavior
that was under-documented, not bugs:
1. AlarmConditionDelta gains explicit paragraphs explaining why
CommentAdded is absent: it always originates from a client
AddComment call whose T18 OnAddComment handler returns Good →
SDK auto-fires the comment event (E2); the engine re-projection
carries no delta-field change, so the gate correctly suppresses
the duplicate. Force-firing would double-emit.
2. Same doc explains Retain is intentionally absent: Retain is a
pure function of Active/Acknowledged (both compared), so it
cannot flip without a real delta. Notes future risk if that
ever changes.
3. ReportConditionEvent Time/ReceiveTime comment corrected: the
projection was already applied by WriteAlarmCondition above
with identical values; the restamp is a locality repeat, not a
reorder guard.
Also adds one seam unit-test (103 total, was 102) pinning the
null-vs-empty Message normalization boundary so a change to the
?? string.Empty coalescing is caught at the seam level.
This commit is contained in:
@@ -475,6 +475,31 @@ public sealed class SdkAddressSpaceSinkTests : IDisposable
|
||||
OtOpcUaNodeManager.ShouldFireConditionEvent(baseState, baseState with { Message = "other" }).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>T20 — null-vs-empty Message normalization. Both snapshot.Message = null and a live node
|
||||
/// whose Message.Value.Text = null normalize to <see cref="string.Empty"/> in
|
||||
/// <see cref="OtOpcUaNodeManager.AlarmConditionDelta"/>, so a snapshot whose Message is null does NOT
|
||||
/// produce a phantom delta against a freshly-materialised node (whose Message is the displayName but
|
||||
/// whose Text reads back as the displayName string, and a snapshot that explicitly passes an empty
|
||||
/// string equally produces no delta vs a null-Message node). This pins the normalization so a
|
||||
/// future change to the null-coalescing in ReadConditionDelta / ToConditionDelta is caught here.</summary>
|
||||
[Fact]
|
||||
public void ShouldFireConditionEvent_null_and_empty_message_normalize_identically()
|
||||
{
|
||||
// Both null and "" collapse to string.Empty in AlarmConditionDelta — they are the same delta value.
|
||||
var withNull = new OtOpcUaNodeManager.AlarmConditionDelta(
|
||||
Active: false, Acknowledged: true, Confirmed: true, Enabled: true,
|
||||
Shelving: AlarmShelvingKind.Unshelved, MappedSeverity: 100, Message: string.Empty);
|
||||
|
||||
var withEmpty = withNull with { Message = string.Empty };
|
||||
|
||||
// null-message snapshot (normalised to "") vs empty-message node (normalised to "") ⇒ no delta.
|
||||
OtOpcUaNodeManager.ShouldFireConditionEvent(withNull, withEmpty).ShouldBeFalse();
|
||||
OtOpcUaNodeManager.ShouldFireConditionEvent(withEmpty, withNull).ShouldBeFalse();
|
||||
|
||||
// A non-empty message IS a delta vs the empty baseline.
|
||||
OtOpcUaNodeManager.ShouldFireConditionEvent(withNull, withNull with { Message = "pressure high" }).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Builds a test <see cref="AlarmConditionSnapshot"/> with sensible defaults so each call
|
||||
/// site only specifies the fields it cares about.</summary>
|
||||
private static AlarmConditionSnapshot Snapshot(
|
||||
|
||||
Reference in New Issue
Block a user