fix(opcuaclient): #649 review minors - CodeBits match, Bad-only exception ctor, BadNoData + Raw-resume tests, latent-impact docs

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 19:29:47 -04:00
parent 22e79112b6
commit b8c2136732
6 changed files with 122 additions and 8 deletions
@@ -17,6 +17,7 @@ public sealed class OpcUaClientDriverHistoryWireTests
private const string HistRef = "Sim/dev1/Hist";
private const string HistTag = "Plant.Hist.Series";
private const uint Uncertain = 0x40000000u;
private const uint BadNoData = 0x809B0000u;
private const uint BadUserAccessDenied = 0x801F0000u;
private const uint BadHistoryOperationUnsupported = 0x80720000u;
@@ -55,6 +56,9 @@ public sealed class OpcUaClientDriverHistoryWireTests
// Minimum stands in for "the backend refuses with some OTHER Bad status" (#649).
if (aggregate == HistoryAggregateType.Minimum)
throw new HistoryReadStatusException(BadUserAccessDenied);
// Maximum stands in for a node-level BadNoData ("nothing in the window"), which must stay empty.
if (aggregate == HistoryAggregateType.Maximum)
throw new HistoryReadStatusException(BadNoData);
if (aggregate != HistoryAggregateType.Average)
throw new HistoryAggregateNotSupportedException(aggregate);
@@ -204,6 +208,21 @@ public sealed class OpcUaClientDriverHistoryWireTests
lock (fake.Calls) fake.Calls.ShouldContain($"processed:{HistTag}:Minimum");
}
[Fact]
public async Task A_node_level_BadNoData_stays_an_empty_result_not_an_error()
{
var ct = TestContext.Current.CancellationToken;
var (rig, fake, drv) = await StartAsync("hist-badnodata", ct);
await using var _r = rig;
await using var _d = drv;
var result = await drv.ReadProcessedAsync(
HistRef, At(0), At(6), TimeSpan.FromSeconds(2), HistoryAggregateType.Maximum, ct);
result.Samples.ShouldBeEmpty();
lock (fake.Calls) fake.Calls.ShouldContain($"processed:{HistTag}:Maximum", "the call must have reached the backend");
}
[Fact]
public async Task A_Good_read_with_no_samples_in_the_window_is_still_empty_data_not_an_error()
{