test: preserve server SDK 2.0 value security and history contracts

This commit is contained in:
Joseph Doherty
2026-11-10 10:37:52 -05:00
parent ac5e9d84f0
commit 9cddc9306b
45 changed files with 1215 additions and 641 deletions
@@ -43,27 +43,27 @@ public sealed class NodeManagerNativeAlarmRebuildCarryOverTests : IDisposable
nm.RebuildAddressSpace();
Materialise(nm, native: true);
var restored = Condition(nm);
var idBeforeComplete = (byte[]?)restored.EventId.Value?.Clone();
var idBeforeComplete = (byte[]?)restored.EventId!.Value.ToArray();
// State is back as soon as the node exists — a Read or a ConditionRefresh in between already sees it.
restored.ActiveState.Id.Value.ShouldBeTrue();
restored.AckedState.Id.Value.ShouldBeFalse();
restored.Retain.Value.ShouldBeTrue();
restored.Severity.Value.ShouldBe((ushort)800);
restored.Message.Value.Text.ShouldBe("Xtravel FAULT 1001");
restored.Quality.Value.Code.ShouldBe(StatusCodes.Good);
restored.ActiveState!.Id!.Value.ShouldBeTrue();
restored.AckedState!.Id!.Value.ShouldBeFalse();
restored.Retain!.Value.ShouldBeTrue();
restored.Severity!.Value.ShouldBe((ushort)800);
restored.Message!.Value.Text.ShouldBe("Xtravel FAULT 1001");
restored.Quality!.Value.Code.ShouldBe(StatusCodes.Good.Code);
nm.CompleteAddressSpaceApply();
// The report happens at the END of the apply (after the notifier wiring), stamped with the instant the
// state came about, not the deploy time.
restored.EventId.Value.ShouldNotBe(idBeforeComplete);
restored.Time.Value.ShouldBe(raisedAt);
restored.EventId!.Value.ToArray().ShouldNotBe(idBeforeComplete);
restored.Time!.Value.ShouldBe(raisedAt);
// ...and only once: a second completion with nothing pending reports nothing.
var idAfterComplete = (byte[]?)restored.EventId.Value?.Clone();
var idAfterComplete = (byte[]?)restored.EventId!.Value.ToArray();
nm.CompleteAddressSpaceApply();
restored.EventId.Value.ShouldBe(idAfterComplete);
restored.EventId!.Value.ToArray().ShouldBe(idAfterComplete);
await host.DisposeAsync();
}
@@ -87,9 +87,9 @@ public sealed class NodeManagerNativeAlarmRebuildCarryOverTests : IDisposable
nm.CompleteAddressSpaceApply();
var restored = Condition(nm);
restored.ActiveState.Id.Value.ShouldBeTrue();
restored.AckedState.Id.Value.ShouldBeTrue();
restored.Retain.Value.ShouldBeTrue();
restored.ActiveState!.Id!.Value.ShouldBeTrue();
restored.AckedState!.Id!.Value.ShouldBeTrue();
restored.Retain!.Value.ShouldBeTrue();
await host.DisposeAsync();
}
@@ -108,17 +108,17 @@ public sealed class NodeManagerNativeAlarmRebuildCarryOverTests : IDisposable
var raisedAt = new DateTime(2026, 10, 8, 9, 0, 0, DateTimeKind.Utc);
Materialise(nm, native: true);
nm.WriteAlarmCondition(Alarm, Fault(acked: false), raisedAt, AddressSpaceRealm.Raw);
Condition(nm).Comment.Value = new LocalizedText("on it");
Condition(nm).Comment!.Value = new LocalizedText("on it");
nm.RebuildAddressSpace();
Materialise(nm, native: true);
var later = raisedAt.AddMinutes(5);
nm.WriteAlarmQuality(Alarm, StatusCodes.BadNoCommunication, later, AddressSpaceRealm.Raw); // reports at `later`
nm.WriteAlarmQuality(Alarm, StatusCodes.BadNoCommunication.Code, later, AddressSpaceRealm.Raw); // reports at `later`
nm.CompleteAddressSpaceApply();
var restored = Condition(nm);
restored.Comment.Value.Text.ShouldBe("on it");
restored.Time.Value.ShouldBe(later, "the restore report must not stamp the condition back to the raise time");
restored.Comment!.Value.Text.ShouldBe("on it");
restored.Time!.Value.ShouldBe(later, "the restore report must not stamp the condition back to the raise time");
await host.DisposeAsync();
}
@@ -137,15 +137,15 @@ public sealed class NodeManagerNativeAlarmRebuildCarryOverTests : IDisposable
nm.RebuildAddressSpace();
Materialise(nm, native: true);
var fresh = Condition(nm);
var idBefore = (byte[]?)fresh.EventId.Value?.Clone();
var idBefore = (byte[]?)fresh.EventId!.Value.ToArray();
nm.CompleteAddressSpaceApply();
fresh.EventId.Value.ShouldBe(idBefore);
fresh.ActiveState.Id.Value.ShouldBeFalse();
fresh.AckedState.Id.Value.ShouldBeTrue();
fresh.Retain.Value.ShouldBeFalse();
fresh.Message.Value.Text.ShouldBe("Xtravel", "a normal condition keeps the materialise's message");
fresh.Severity.Value.ShouldBe((ushort)700, "and its authored severity");
fresh.EventId!.Value.ToArray().ShouldBe(idBefore);
fresh.ActiveState!.Id!.Value.ShouldBeFalse();
fresh.AckedState!.Id!.Value.ShouldBeTrue();
fresh.Retain!.Value.ShouldBeFalse();
fresh.Message!.Value.Text.ShouldBe("Xtravel", "a normal condition keeps the materialise's message");
fresh.Severity!.Value.ShouldBe((ushort)700, "and its authored severity");
await host.DisposeAsync();
}
@@ -166,11 +166,11 @@ public sealed class NodeManagerNativeAlarmRebuildCarryOverTests : IDisposable
nm.RebuildAddressSpace();
Materialise(nm, native: true);
var fresh = Condition(nm);
var idBefore = (byte[]?)fresh.EventId.Value?.Clone();
var idBefore = (byte[]?)fresh.EventId!.Value.ToArray();
nm.CompleteAddressSpaceApply();
fresh.Quality.Value.Code.ShouldBe(StatusCodes.Good);
fresh.EventId.Value.ShouldBe(idBefore);
fresh.Quality!.Value.Code.ShouldBe(StatusCodes.Good.Code);
fresh.EventId!.Value.ToArray().ShouldBe(idBefore);
await host.DisposeAsync();
}
@@ -192,8 +192,8 @@ public sealed class NodeManagerNativeAlarmRebuildCarryOverTests : IDisposable
nm.CompleteAddressSpaceApply();
var readded = Condition(nm);
readded.ActiveState.Id.Value.ShouldBeFalse();
readded.AckedState.Id.Value.ShouldBeTrue();
readded.ActiveState!.Id!.Value.ShouldBeFalse();
readded.AckedState!.Id!.Value.ShouldBeTrue();
await host.DisposeAsync();
}
@@ -212,7 +212,7 @@ public sealed class NodeManagerNativeAlarmRebuildCarryOverTests : IDisposable
Materialise(nm, native: false);
nm.CompleteAddressSpaceApply();
Condition(nm).ActiveState.Id.Value.ShouldBeFalse();
Condition(nm).ActiveState!.Id!.Value.ShouldBeFalse();
await host.DisposeAsync();
}
@@ -231,7 +231,7 @@ public sealed class NodeManagerNativeAlarmRebuildCarryOverTests : IDisposable
Materialise(nm, native: false);
nm.CompleteAddressSpaceApply();
Condition(nm).ActiveState.Id.Value.ShouldBeFalse();
Condition(nm).ActiveState!.Id!.Value.ShouldBeFalse();
await host.DisposeAsync();
}