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);
|
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>
|
/// <summary>
|
||||||
/// Rolling-deploy back-compat: an older sidecar that sends an empty PerEventStatus but a
|
/// 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).
|
/// populated PerEventOk must still classify via the legacy bool path (false→RetryPlease).
|
||||||
|
|||||||
Reference in New Issue
Block a user