test: preserve server SDK 2.0 value security and history contracts

This commit is contained in:
Joseph Doherty
2026-11-10 10:37:52 -05:00
parent ac5e9d84f0
commit 9cddc9306b
45 changed files with 1215 additions and 641 deletions
@@ -54,7 +54,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
{
var (values, error, nextCp) = ReadRaw(nm, nodeId, start: Epoch, end: Epoch.AddHours(1),
max: 100, inboundCp: cp);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
collected.AddRange(values);
cp = nextCp;
if (cp is not null) sawCp = true;
@@ -92,7 +92,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
do
{
var (values, error, next) = ReadRaw(nm, nodeId, Epoch, DateTime.MinValue, max: 100, inboundCp: cp);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
collected.AddRange(values);
cp = next;
(++pages).ShouldBeLessThan(20);
@@ -121,7 +121,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
do
{
var (values, error, next) = ReadRaw(nm, nodeId, DateTime.MinValue, Epoch.AddHours(1), max: 100, inboundCp: cp);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
collected.AddRange(values);
cp = next;
(++pages).ShouldBeLessThan(20);
@@ -155,7 +155,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
nm.HistorianDataSource = new SeriesHistorianDataSource(MakeSeries(count: 101, stepSeconds: 1));
var (r2, e2, cp2) = ReadRaw(nm, nodeId, Epoch, Epoch.AddHours(1), max: 100, inboundCp: null);
r2.Count.ShouldBe(100);
e2.StatusCode.Code.ShouldBe(StatusCodes.Good);
e2.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
cp2.ShouldNotBeNull();
var (r3, _, cp3) = ReadRaw(nm, nodeId, Epoch, Epoch.AddHours(1), max: 100, inboundCp: cp2);
r3.ShouldBe([100d]);
@@ -184,7 +184,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
Epoch.AddSeconds(2), // 3 │ tied at Epoch+2s
Epoch.AddSeconds(2), // 4 ┘
};
var series = ts.Select((t, i) => new DataValueSnapshot((double)i, StatusCodes.Good, t, t)).ToArray();
var series = ts.Select((t, i) => new DataValueSnapshot((double)i, StatusCodes.Good.Code, t, t)).ToArray();
nm.HistorianDataSource = new SeriesHistorianDataSource(series);
nm.EnsureVariable("eq-1/tie", parentFolderNodeId: null, displayName: "Tie", dataType: "Double",
@@ -242,7 +242,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
{
var (values, error, nextCp) = ReadRaw(nm, nodeId, Epoch, Epoch.AddHours(1),
max: 2, inboundCp: cp);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
collected.AddRange(values);
cp = nextCp;
pageCount++;
@@ -273,7 +273,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
// 5 samples ALL sharing one timestamp (Epoch+2s) — a tie cluster of 5 with a page cap of 2.
var t = Epoch.AddSeconds(2);
var series = Enumerable.Range(0, 5)
.Select(i => new DataValueSnapshot((double)i, StatusCodes.Good, t, t)).ToArray();
.Select(i => new DataValueSnapshot((double)i, StatusCodes.Good.Code, t, t)).ToArray();
nm.HistorianDataSource = new SeriesHistorianDataSource(series);
nm.EnsureVariable("eq-1/absurd", parentFolderNodeId: null, displayName: "Absurd", dataType: "Double",
@@ -282,14 +282,14 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
// Page 1: a full page of the first 2 ties, with a continuation point.
var (r1, e1, cp1) = ReadRaw(nm, nodeId, Epoch, Epoch.AddHours(1), max: 2, inboundCp: null);
e1.StatusCode.Code.ShouldBe(StatusCodes.Good);
e1.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
r1.ShouldBe(new[] { 0.0, 1.0 });
cp1.ShouldNotBeNull();
// Page 2: the resume read hits the cluster; over-fetch finds 5 > bound 3 ⇒ a clear error, NOT a
// silent GoodNoData that would drop the un-emitted ties.
var (r2, e2, cp2) = ReadRaw(nm, nodeId, Epoch, Epoch.AddHours(1), max: 2, inboundCp: cp1);
e2.StatusCode.Code.ShouldBe(StatusCodes.BadTooManyOperations);
e2.StatusCode.Code.ShouldBe(StatusCodes.BadTooManyOperations.Code);
r2.ShouldBeEmpty();
cp2.ShouldBeNull();
@@ -312,7 +312,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (r, e, cp) = ReadRaw(nm, nodeId, Epoch, Epoch.AddHours(1), max: 0, inboundCp: null);
e.StatusCode.Code.ShouldBe(StatusCodes.Good);
e.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
r.Count.ShouldBe(250);
cp.ShouldBeNull("an unlimited (max==0) request must not page");
@@ -338,7 +338,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var bogus = Guid.NewGuid().ToByteArray();
var (_, e, cp) = ReadRaw(nm, nodeId, Epoch, Epoch.AddHours(1), max: 100, inboundCp: bogus);
e.StatusCode.Code.ShouldBe(StatusCodes.BadContinuationPointInvalid);
e.StatusCode.Code.ShouldBe(StatusCodes.BadContinuationPointInvalid.Code);
cp.ShouldBeNull();
fake.ReadCount.ShouldBe(0); // never reached the backend.
@@ -372,7 +372,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
// Resuming the released point now misses.
var (_, e, _) = ReadRaw(nm, nodeId, Epoch, Epoch.AddHours(1), max: 100, inboundCp: cp1);
e.StatusCode.Code.ShouldBe(StatusCodes.BadContinuationPointInvalid);
e.StatusCode.Code.ShouldBe(StatusCodes.BadContinuationPointInvalid.Code);
await host.DisposeAsync();
}
@@ -436,7 +436,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
nm.HistoryContinuationStore = new InMemoryHistoryContinuationStore();
// Microsecond-spaced samples, so a one-tick step would land strictly between two of them.
var series = Enumerable.Range(0, 6)
.Select(i => { var t = Epoch.AddTicks(10L * i); return new DataValueSnapshot((double)i, StatusCodes.Good, t, t); })
.Select(i => { var t = Epoch.AddTicks(10L * i); return new DataValueSnapshot((double)i, StatusCodes.Good.Code, t, t); })
.ToArray();
nm.HistorianDataSource = new MicrosecondHistorianDataSource(series);
@@ -458,7 +458,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var nm = server.NodeManager!;
nm.HistoryContinuationStore = new InMemoryHistoryContinuationStore();
var series = Enumerable.Range(0, 6)
.Select(i => { var t = Epoch.AddTicks(10L * (i + 1)); return new DataValueSnapshot((double)i, StatusCodes.Good, t, t); })
.Select(i => { var t = Epoch.AddTicks(10L * (i + 1)); return new DataValueSnapshot((double)i, StatusCodes.Good.Code, t, t); })
.ToArray();
nm.HistorianDataSource = new MicrosecondHistorianDataSource(series);
@@ -516,7 +516,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (values, error, cp) = ReadRaw(nm, nodeId, DateTime.MinValue, Epoch.AddSeconds(100), max: 3, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe([100d, 99d, 98d]);
cp.ShouldNotBeNull();
source.ReverseWindowCount.ShouldBe(0);
@@ -533,7 +533,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
// (end, start] = (Epoch+9s, Epoch+200s] — both edges exercised.
var (values, error, cp) = ReadRaw(nm, nodeId, Epoch.AddSeconds(200), Epoch.AddSeconds(9), max: 0, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe(Enumerable.Range(10, 191).Reverse().Select(i => (double)i));
cp.ShouldBeNull();
source.ReverseWindowCount.ShouldBe(0);
@@ -583,7 +583,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
static DataValueSnapshot At(int year, double v)
{
var t = new DateTime(year, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return new DataValueSnapshot(v, StatusCodes.Good, t, t);
return new DataValueSnapshot(v, StatusCodes.Good.Code, t, t);
}
var source = new ForwardOnlyHistorianDataSource([At(1990, 1), At(2000, 2)]);
@@ -591,7 +591,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (values, error, cp) = ReadRaw(nm, nodeId, DateTime.MinValue, Epoch, max: 5, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe([2d, 1d]);
cp.ShouldBeNull();
source.ReadCount.ShouldBeLessThanOrEqualTo(20);
@@ -611,7 +611,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (_, error, cp) = ReadRaw(nm, nodeId, Epoch.AddHours(1), Epoch.AddSeconds(-1), max: 2, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.BadTooManyOperations);
error.StatusCode.Code.ShouldBe(StatusCodes.BadTooManyOperations.Code);
cp.ShouldBeNull();
source.ReverseWindowCount.ShouldBe(0);
await host.DisposeAsync();
@@ -624,7 +624,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
{
// 2000 samples 10 ms apart: a 1-minute first segment holds all of them, far over a budget of 64.
var series = Enumerable.Range(0, 2000)
.Select(i => { var t = Epoch.AddMilliseconds(10L * i); return new DataValueSnapshot((double)i, StatusCodes.Good, t, t); })
.Select(i => { var t = Epoch.AddMilliseconds(10L * i); return new DataValueSnapshot((double)i, StatusCodes.Good.Code, t, t); })
.ToArray();
var source = new ForwardOnlyHistorianDataSource(series);
var (host, nm, nodeId) = await BootForwardOnlyAsync(source, "FwdDensity");
@@ -640,7 +640,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
while (cp is not null)
{
var (values, error, next) = ReadRaw(nm, nodeId, DateTime.MinValue, Epoch.AddSeconds(20), max: 20, inboundCp: cp);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
collected.AddRange(values);
cp = next;
(++pages).ShouldBeLessThan(200);
@@ -665,7 +665,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
{
// 100,000 samples 100 ms apart (~2.8 h): far more than the default tie budget would ever let a page read.
var series = Enumerable.Range(0, 100_000)
.Select(i => { var t = Epoch.AddMilliseconds(100L * i); return new DataValueSnapshot((double)i, StatusCodes.Good, t, t); })
.Select(i => { var t = Epoch.AddMilliseconds(100L * i); return new DataValueSnapshot((double)i, StatusCodes.Good.Code, t, t); })
.ToArray();
var source = new ForwardOnlyHistorianDataSource(series);
var (host, nm, nodeId) = await BootForwardOnlyAsync(source, "FwdDense689");
@@ -678,7 +678,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
{
source.ResetCounts();
var (values, error, next) = ReadRaw(nm, nodeId, DateTime.MinValue, end, max: 3, inboundCp: cp);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldNotBeEmpty();
// #692: every page is full (N = 3) — a resumed page used to net N − 1 after trimming its boundary sample.
values.ShouldBe([expected, expected - 1d, expected - 2d], $"page {page} continues exactly");
@@ -726,7 +726,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
for (var k = 0; k < ties; k++)
{
var ts = new DateTime(t, DateTimeKind.Utc);
series.Add(new DataValueSnapshot((double)v++, StatusCodes.Good, ts, ts));
series.Add(new DataValueSnapshot((double)v++, StatusCodes.Good.Code, ts, ts));
}
}
@@ -759,7 +759,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
break;
}
error.StatusCode.Code.ShouldBe(StatusCodes.Good, $"seed {seed}, N = {max}, ms max {msMax}");
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code, $"seed {seed}, N = {max}, ms max {msMax}");
collected.AddRange(values);
cp = next;
(++pages).ShouldBeLessThan(2000, "paging must terminate, not loop");
@@ -793,7 +793,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
{
source.ResetCounts();
var (values, error, next) = ReadRaw(nm, nodeId, DateTime.MinValue, Epoch.AddHours(1), max: 10, inboundCp: cp);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
collected.AddRange(values);
cp = next;
page++;
@@ -818,7 +818,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (values, error, _) = ReadRaw(nm, nodeId, DateTime.MaxValue, Epoch.AddSeconds(-1), max: 3, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe([249d, 248d, 247d]);
source.MaxRequestedEnd.ShouldBeLessThanOrEqualTo(now.AddDays(1).AddMilliseconds(1));
source.ReadCount.ShouldBeLessThanOrEqualTo(10);
@@ -836,7 +836,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (values, error, _) = ReadRaw(nm, nodeId, Epoch.AddSeconds(10), Epoch.AddTicks(5_000), max: 100, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe(Enumerable.Range(1, 10).Reverse().Select(i => (double)i)); // (Epoch+0.5ms, Epoch+10s]: no 0
await host.DisposeAsync();
}
@@ -849,7 +849,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
DataValueSnapshot At(long ticks, double v)
{
var t = Epoch.AddTicks(ticks);
return new DataValueSnapshot(v, StatusCodes.Good, t, t);
return new DataValueSnapshot(v, StatusCodes.Good.Code, t, t);
}
var t0 = TimeSpan.TicksPerSecond;
@@ -858,7 +858,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (values, error, _) = ReadRaw(nm, nodeId, Epoch.AddTicks(t0), Epoch.AddSeconds(-1), max: 10, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe([1d, 0d]);
await host.DisposeAsync();
}
@@ -874,7 +874,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (values, error, _) = ReadRaw(nm, nodeId, Epoch.AddHours(1), Epoch.AddSeconds(-1), max: 100, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe(Enumerable.Range(0, 8).Reverse().Select(i => (double)i));
await host.DisposeAsync();
}
@@ -889,7 +889,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
for (var c = 0; c < 3; c++)
{
var t = Epoch.AddSeconds(5).AddMilliseconds(c);
for (var i = 0; i < 4; i++) series.Add(new DataValueSnapshot((double)v++, StatusCodes.Good, t, t));
for (var i = 0; i < 4; i++) series.Add(new DataValueSnapshot((double)v++, StatusCodes.Good.Code, t, t));
}
var source = new ForwardOnlyHistorianDataSource(series);
@@ -898,7 +898,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (values, error, _) = ReadRaw(nm, nodeId, Epoch.AddHours(1), Epoch.AddSeconds(-1), max: 100, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe(Enumerable.Range(0, 12).Reverse().Select(i => (double)i));
await host.DisposeAsync();
}
@@ -916,7 +916,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var (values, error, _) = ReadRaw(nm, nodeId, DateTime.MinValue, Epoch.AddSeconds(100), max: 3, inboundCp: null);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
values.ShouldBe([100d, 99d, 98d]);
await host.DisposeAsync();
}
@@ -951,7 +951,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
do
{
var (values, error, nextCp) = ReadRaw(nm, nodeId, start, end, max, inboundCp: cp);
error.StatusCode.Code.ShouldBe(StatusCodes.Good);
error.StatusCode.Code.ShouldBe(StatusCodes.Good.Code);
collected.AddRange(values);
cp = nextCp;
pageCount++;
@@ -965,11 +965,48 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
/// <summary>Issue a single-node Raw HistoryRead and return that node's decoded sample values, error,
/// and outbound continuation point.</summary>
[Fact]
public async Task A_cursor_cannot_cross_aliases_that_share_a_historian_tagname()
{
var (host, server) = await BootAsync();
await using var ownedHost = host;
var nm = server.NodeManager!;
nm.HistoryContinuationStore = new InMemoryHistoryContinuationStore();
nm.HistorianDataSource = new SeriesHistorianDataSource(MakeSeries(10, 1));
foreach (var realm in new[] { AddressSpaceRealm.Raw, AddressSpaceRealm.Uns })
nm.EnsureVariable("same", null, "Same", "Double", false, realm, "WW.PV");
var raw = nm.TryGetVariable("same", AddressSpaceRealm.Raw)!.NodeId;
var uns = nm.TryGetVariable("same", AddressSpaceRealm.Uns)!.NodeId;
var first = ReadRaw(nm, raw, Epoch, Epoch.AddHours(1), 2, null);
first.Cp.ShouldNotBeNull();
ReadRaw(nm, uns, Epoch, Epoch.AddHours(1), 2, first.Cp).Error.StatusCode.ShouldBe(StatusCodes.BadContinuationPointInvalid);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Topology_replacement_invalidates_an_existing_cursor(bool rebuild)
{
var (host, server) = await BootAsync();
await using var ownedHost = host;
var nm = server.NodeManager!;
nm.HistoryContinuationStore = new InMemoryHistoryContinuationStore();
nm.HistorianDataSource = new SeriesHistorianDataSource(MakeSeries(10, 1));
nm.EnsureVariable("v", null, "Value", "Double", false, AddressSpaceRealm.Raw, "WW.PV");
var node = nm.TryGetVariable("v", AddressSpaceRealm.Raw)!.NodeId;
var first = ReadRaw(nm, node, Epoch, Epoch.AddHours(1), 2, null);
first.Cp.ShouldNotBeNull();
if (rebuild) nm.RebuildAddressSpace();
else nm.RemoveVariableNode("v", AddressSpaceRealm.Raw).ShouldBeTrue();
nm.EnsureVariable("v", null, "Value", "Double", false, AddressSpaceRealm.Raw, "WW.PV");
ReadRaw(nm, node, Epoch, Epoch.AddHours(1), 2, first.Cp).Error.StatusCode.ShouldBe(StatusCodes.BadContinuationPointInvalid);
}
private static (IReadOnlyList<double> Values, ServiceResult Error, byte[]? Cp) ReadRaw(
OtOpcUaNodeManager nm, NodeId nodeId, DateTime start, DateTime end, uint max, byte[]? inboundCp)
{
var context = new OperationContext(
new RequestHeader(), secureChannelContext: null, RequestType.HistoryRead, identity: null);
new RequestHeader(), secureChannelContext: null, RequestType.HistoryRead, requestLifetime: RequestLifetime.None, identity: null);
var details = new ReadRawModifiedDetails
{
@@ -982,7 +1019,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var nodesToRead = new List<HistoryReadValueId>
{
new() { NodeId = nodeId, ContinuationPoint = inboundCp },
new() { NodeId = nodeId, ContinuationPoint = ByteString.From(inboundCp) },
};
var results = new List<SdkHistoryReadResult> { null! };
var errors = new List<ServiceResult> { null! };
@@ -991,26 +1028,26 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
releaseContinuationPoints: false, nodesToRead, results, errors);
var values = new List<double>();
if (results[0]?.HistoryData is { } ho && !ExtensionObject.IsNull(ho))
if (results[0]?.HistoryData is { } ho && !ho.IsNull)
{
var data = (HistoryData)ExtensionObject.ToEncodeable(ho);
foreach (var dv in data.DataValues) values.Add((double)dv.Value);
var data = (HistoryData)ExtensionObject.ToEncodeable(ho)!;
foreach (var dv in data.DataValues) values.Add((double)SdkValueBoundary.ToClr(dv.WrappedValue)!);
}
return (values, errors[0], results[0]?.ContinuationPoint);
return (values, errors[0], results[0]?.ContinuationPoint.ToNullableArray());
}
/// <summary>Issue a release-only HistoryRead (releaseContinuationPoints=true) for the node + point.</summary>
private static void InvokeRelease(OtOpcUaNodeManager nm, NodeId nodeId, byte[] cp)
{
var context = new OperationContext(
new RequestHeader(), secureChannelContext: null, RequestType.HistoryRead, identity: null);
new RequestHeader(), secureChannelContext: null, RequestType.HistoryRead, requestLifetime: RequestLifetime.None, identity: null);
var details = new ReadRawModifiedDetails
{
ReturnBounds = false,
StartTime = Epoch, EndTime = Epoch.AddHours(1), NumValuesPerNode = 100, IsReadModified = false,
};
var nodesToRead = new List<HistoryReadValueId> { new() { NodeId = nodeId, ContinuationPoint = cp } };
var nodesToRead = new List<HistoryReadValueId> { new() { NodeId = nodeId, ContinuationPoint = ByteString.From(cp) } };
var results = new List<SdkHistoryReadResult> { null! };
var errors = new List<ServiceResult> { null! };
@@ -1023,7 +1060,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
.Select(i =>
{
var t = Epoch.AddSeconds((long)i * stepSeconds);
return new DataValueSnapshot((double)i, StatusCodes.Good, t, t);
return new DataValueSnapshot((double)i, StatusCodes.Good.Code, t, t);
})
.ToArray();
@@ -1040,17 +1077,17 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
for (var i = 0; i < distinctBefore; i++, second++)
{
var t = Epoch.AddSeconds(second);
samples.Add(new DataValueSnapshot((double)value++, StatusCodes.Good, t, t));
samples.Add(new DataValueSnapshot((double)value++, StatusCodes.Good.Code, t, t));
}
var tieT = Epoch.AddSeconds(second++); // the single shared timestamp for the whole cluster
for (var i = 0; i < tieCount; i++)
samples.Add(new DataValueSnapshot((double)value++, StatusCodes.Good, tieT, tieT));
samples.Add(new DataValueSnapshot((double)value++, StatusCodes.Good.Code, tieT, tieT));
for (var i = 0; i < distinctAfter; i++, second++)
{
var t = Epoch.AddSeconds(second);
samples.Add(new DataValueSnapshot((double)value++, StatusCodes.Good, t, t));
samples.Add(new DataValueSnapshot((double)value++, StatusCodes.Good.Code, t, t));
}
return samples.ToArray();
@@ -1176,7 +1213,7 @@ public sealed class NodeManagerHistoryReadPagingTests : IDisposable
var rows = new List<DataValueSnapshot>();
if (emitBoundaryRow)
rows.Add(prior is null
? new DataValueSnapshot(0d, StatusCodes.BadNoData, stamp, stamp)
? new DataValueSnapshot(0d, StatusCodes.BadNoData.Code, stamp, stamp)
: new DataValueSnapshot(prior.Value, prior.StatusCode, stamp, stamp));
rows.AddRange(series.Where(s => Ts(s) >= startUtc && Ts(s) <= endUtc));
var page = maxValuesPerNode == 0 ? rows : rows.Take((int)maxValuesPerNode).ToList();