test(historian): cover PerEventStatus length-mismatch fallback + Retry status mapping
This commit is contained in:
+67
@@ -361,6 +361,73 @@ public sealed class WonderwareHistorianClientTests
|
||||
outcomes[0].ShouldBe(HistorianWriteOutcome.Ack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When <c>PerEventStatus</c> is present but its length does not equal the batch size,
|
||||
/// the client must ignore it and fall back to the legacy <c>PerEventOk</c> path to
|
||||
/// avoid mis-indexing into the status array. Here a 2-event batch receives
|
||||
/// <c>PerEventStatus=[1]</c> (length 1) but <c>PerEventOk=[true, false]</c>; the
|
||||
/// outcomes must reflect the PerEventOk values ([Ack, RetryPlease]), not the status
|
||||
/// byte (which would have produced [RetryPlease] had it been used).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task WriteBatchAsync_PerEventStatusLengthMismatch_FallsBackToPerEventOk()
|
||||
{
|
||||
await using var server = new FakeSidecarServer(Secret)
|
||||
{
|
||||
OnWriteAlarmEvents = _ => new WriteAlarmEventsReply
|
||||
{
|
||||
Success = true,
|
||||
PerEventStatus = [1], // length 1 ≠ batch count 2 → must be ignored
|
||||
PerEventOk = [true, false], // legacy fallback: true→Ack, false→RetryPlease
|
||||
},
|
||||
};
|
||||
await server.StartAsync();
|
||||
|
||||
await using var client = TcpClientFor(server);
|
||||
var batch = new[]
|
||||
{
|
||||
new AlarmHistorianEvent("ev-1", "Tank/HiHi", "HiHi", "LimitAlarm", AlarmSeverity.High, "Activated", "msg", "u", null, DateTime.UtcNow),
|
||||
new AlarmHistorianEvent("ev-2", "Tank/HiHi", "HiHi", "LimitAlarm", AlarmSeverity.High, "Acknowledged", "msg", "u", null, DateTime.UtcNow),
|
||||
};
|
||||
|
||||
var outcomes = await client.WriteBatchAsync(batch, CancellationToken.None);
|
||||
|
||||
outcomes.Count.ShouldBe(2);
|
||||
outcomes[0].ShouldBe(HistorianWriteOutcome.Ack); // PerEventOk[0] = true
|
||||
outcomes[1].ShouldBe(HistorianWriteOutcome.RetryPlease); // PerEventOk[1] = false
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status byte 1 (the only value that is neither 0 nor 2) must map to
|
||||
/// <see cref="HistorianWriteOutcome.RetryPlease"/> via the default arm of the
|
||||
/// <c>PerEventStatus</c> switch. A single-event batch with <c>PerEventStatus=[1]</c>
|
||||
/// (length matches batch) must yield <c>[RetryPlease]</c>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task WriteBatchAsync_PerEventStatusRetry_MapsToRetryPlease()
|
||||
{
|
||||
await using var server = new FakeSidecarServer(Secret)
|
||||
{
|
||||
OnWriteAlarmEvents = _ => new WriteAlarmEventsReply
|
||||
{
|
||||
Success = true,
|
||||
PerEventStatus = [1], // status 1 → RetryPlease
|
||||
},
|
||||
};
|
||||
await server.StartAsync();
|
||||
|
||||
await using var client = TcpClientFor(server);
|
||||
var batch = new[]
|
||||
{
|
||||
new AlarmHistorianEvent("ev-retry", "Tank/HiHi", "HiHi", "LimitAlarm", AlarmSeverity.High, "Activated", "msg", "u", null, DateTime.UtcNow),
|
||||
};
|
||||
|
||||
var outcomes = await client.WriteBatchAsync(batch, CancellationToken.None);
|
||||
|
||||
outcomes.Count.ShouldBe(1);
|
||||
outcomes[0].ShouldBe(HistorianWriteOutcome.RetryPlease);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rolling-deploy back-compat: an older sidecar that sends an empty PerEventStatus but a
|
||||
/// populated PerEventOk must still classify via the legacy bool path (false→RetryPlease).
|
||||
|
||||
Reference in New Issue
Block a user