test: preserve server SDK 2.0 value security and history contracts
This commit is contained in:
+85
-48
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user