fix(opcua): raw open-ended/end-only reads per Part 11; extreme-bound conformance (#669)
This commit is contained in:
@@ -73,6 +73,65 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>Part 11: only a StartTime (EndTime unset = MinValue) with a cap pages FORWARD to an open upper bound —
|
||||
/// it must not be mistaken for a reverse read, and the stored cursor must not carry MinValue as its end.</summary>
|
||||
[Fact]
|
||||
public async Task Raw_open_ended_forward_read_pages_ascending_exactly_once()
|
||||
{
|
||||
var (host, server) = await BootAsync();
|
||||
var nm = server.NodeManager!;
|
||||
nm.HistoryContinuationStore = new InMemoryHistoryContinuationStore();
|
||||
nm.HistorianDataSource = new SeriesHistorianDataSource(MakeSeries(count: 250, stepSeconds: 1));
|
||||
nm.EnsureVariable("eq-1/open", parentFolderNodeId: null, displayName: "Open", dataType: "Double",
|
||||
writable: false, historianTagname: "WW.Open", realm: AddressSpaceRealm.Uns);
|
||||
var nodeId = nm.TryGetVariable("eq-1/open")!.NodeId;
|
||||
|
||||
var collected = new List<double>();
|
||||
byte[]? cp = null;
|
||||
var pages = 0;
|
||||
do
|
||||
{
|
||||
var (values, error, next) = ReadRaw(nm, nodeId, Epoch, DateTime.MinValue, max: 100, inboundCp: cp);
|
||||
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
|
||||
collected.AddRange(values);
|
||||
cp = next;
|
||||
(++pages).ShouldBeLessThan(20);
|
||||
}
|
||||
while (cp is not null);
|
||||
|
||||
collected.ShouldBe(Enumerable.Range(0, 250).Select(i => (double)i));
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>Part 11: only an EndTime (StartTime unset = MinValue) with a cap reads BACKWARD from the EndTime.</summary>
|
||||
[Fact]
|
||||
public async Task Raw_end_only_read_pages_descending_from_the_end_time()
|
||||
{
|
||||
var (host, server) = await BootAsync();
|
||||
var nm = server.NodeManager!;
|
||||
nm.HistoryContinuationStore = new InMemoryHistoryContinuationStore();
|
||||
nm.HistorianDataSource = new SeriesHistorianDataSource(MakeSeries(count: 250, stepSeconds: 1));
|
||||
nm.EnsureVariable("eq-1/endonly", parentFolderNodeId: null, displayName: "EndOnly", dataType: "Double",
|
||||
writable: false, historianTagname: "WW.EndOnly", realm: AddressSpaceRealm.Uns);
|
||||
var nodeId = nm.TryGetVariable("eq-1/endonly")!.NodeId;
|
||||
|
||||
var collected = new List<double>();
|
||||
byte[]? cp = null;
|
||||
var pages = 0;
|
||||
do
|
||||
{
|
||||
var (values, error, next) = ReadRaw(nm, nodeId, DateTime.MinValue, Epoch.AddHours(1), max: 100, inboundCp: cp);
|
||||
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
|
||||
collected.AddRange(values);
|
||||
cp = next;
|
||||
(++pages).ShouldBeLessThan(20);
|
||||
}
|
||||
while (cp is not null);
|
||||
|
||||
collected.ShouldBe(Enumerable.Range(0, 250).Reverse().Select(i => (double)i));
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>An exactly-full series whose total is a multiple of the cap pages to a trailing
|
||||
/// EMPTY page (GoodNoData, no CP) — the server can't know the prior page was the last until it reads
|
||||
/// past the end.</summary>
|
||||
|
||||
Reference in New Issue
Block a user