review(Driver.Historian.Wonderware.Client): async frame-header write + wire-parity test

Re-review at 7286d320. -011: FrameWriter folded the sync WriteByte (could block on SslStream
past the call timeout) into one async 5-byte header write. -012: DefaultTcpConnectFactory
readonly. -013: wire-parity test for PerEventStatus [Key(4)]. No wire change.
This commit is contained in:
Joseph Doherty
2026-06-19 11:58:15 -04:00
parent c95a8c6b19
commit cd072baad8
4 changed files with 110 additions and 12 deletions
@@ -183,7 +183,7 @@ public sealed class ContractsWireParityTests
rt.Events[0].EventTimeUtcTicks.ShouldBe(999L);
}
/// <summary>Verifies that WriteAlarmEventsReply round-trips correctly.</summary>
/// <summary>Verifies that WriteAlarmEventsReply round-trips correctly (legacy PerEventOk path).</summary>
[Fact]
public void WriteAlarmEventsReply_RoundTrips()
{
@@ -202,6 +202,36 @@ public sealed class ContractsWireParityTests
rt.PerEventOk.ShouldBe(new[] { true, false, true });
}
/// <summary>
/// Pins the <c>[Key(4)]</c> index for <see cref="WriteAlarmEventsReply.PerEventStatus"/>,
/// the additive granular status field added in the <c>feddc2b8</c> commit. A silent
/// Key-index drift in either the client or the sidecar mirror copy would swap the legacy
/// <c>PerEventOk</c> bool array and the new status byte array, misclassifying outcomes
/// at runtime. (Finding 013.)
/// </summary>
[Fact]
public void WriteAlarmEventsReply_PerEventStatus_IsAtKey4_AndRoundTrips()
{
var original = new WriteAlarmEventsReply
{
CorrelationId = "s",
Success = true,
PerEventOk = [true],
PerEventStatus = [0, 1, 2], // Ack, Retry, Permanent
};
var bytes = MessagePackSerializer.Serialize(original);
// The array must start with fixarray(5) — five keys at indices 0-4.
bytes[0].ShouldBe((byte)0x95, "WriteAlarmEventsReply must be a 5-field MessagePack array");
var rt = MessagePackSerializer.Deserialize<WriteAlarmEventsReply>(bytes);
rt.CorrelationId.ShouldBe("s");
rt.Success.ShouldBeTrue();
rt.PerEventOk.ShouldBe(new[] { true });
// Key(4): PerEventStatus must round-trip independently of Key(3): PerEventOk.
rt.PerEventStatus.ShouldBe(new byte[] { 0, 1, 2 });
}
// ---- MessageKind enum values are pinned ----
// Changing a MessageKind value is a wire break; pin them explicitly.