test: preserve server SDK 2.0 value security and history contracts
This commit is contained in:
+16
-16
@@ -102,7 +102,7 @@ public sealed class NodeManagerHistoryReadRawCapFuzzTests(BoundsServerFixture fx
|
||||
Count("oracle refused");
|
||||
continue;
|
||||
}
|
||||
truthError.StatusCode.Code.ShouldBe(StatusCodes.Good, $"oracle: {tag}");
|
||||
truthError.StatusCode.Code.ShouldBe(StatusCodes.Good.Code, $"oracle: {tag}");
|
||||
truthCp.ShouldBeNull($"oracle (no cap, N = 0) is one page: {tag}");
|
||||
if (!bounds)
|
||||
Render(truth).ShouldBe(Render(Oracle(series, start, end, direction, resolution)), $"oracle vs series: {tag}");
|
||||
@@ -124,8 +124,8 @@ public sealed class NodeManagerHistoryReadRawCapFuzzTests(BoundsServerFixture fx
|
||||
// A boundary tie cluster as large as the cap leaves a resumed page no room: the server steps past it with one
|
||||
// empty page carrying a continuation point (the documented #694 ceiling behaviour) — the one empty page allowed.
|
||||
var oversizedClusters = cap > 0
|
||||
? truth.Where(v => !(v.Value is null && v.StatusCode.Code == StatusCodes.BadBoundNotFound))
|
||||
.GroupBy(v => v.SourceTimestamp.Ticks).Count(g => g.Count() >= Math.Max(2, cap - 1))
|
||||
? truth.Where(v => !(SdkValueBoundary.ToClr(v.WrappedValue)! is null && v.StatusCode.Code == StatusCodes.BadBoundNotFound))
|
||||
.GroupBy(v => v.SourceTimestamp.ToDateTime().Ticks).Count(g => g.Count() >= Math.Max(2, cap - 1))
|
||||
: 0;
|
||||
|
||||
// The read under test.
|
||||
@@ -163,7 +163,7 @@ public sealed class NodeManagerHistoryReadRawCapFuzzTests(BoundsServerFixture fx
|
||||
continue;
|
||||
}
|
||||
|
||||
status.Code.ShouldBe(StatusCodes.Good, $"{tag}");
|
||||
status.Code.ShouldBe(StatusCodes.Good.Code, $"{tag}");
|
||||
Render(collected).ShouldBe(Render(truth), $"paged ≠ unpaged: {tag} pages {Sizes(pages)}");
|
||||
if (cap > 0)
|
||||
{
|
||||
@@ -179,8 +179,8 @@ public sealed class NodeManagerHistoryReadRawCapFuzzTests(BoundsServerFixture fx
|
||||
{
|
||||
if (pages[i].Count > 0) continue;
|
||||
// Allowed only as the step past a boundary tie cluster the cap cannot page within.
|
||||
var previousLast = i > 0 && pages[i - 1].Count > 0 ? pages[i - 1][^1].SourceTimestamp.Ticks : (long?)null;
|
||||
var clusterAtBoundary = previousLast is { } t ? truth.Count(v => v.SourceTimestamp.Ticks == t) : 0;
|
||||
var previousLast = i > 0 && pages[i - 1].Count > 0 ? pages[i - 1][^1].SourceTimestamp.ToDateTime().Ticks : (long?)null;
|
||||
var clusterAtBoundary = previousLast is { } t ? truth.Count(v => v.SourceTimestamp.ToDateTime().Ticks == t) : 0;
|
||||
(cap > 0 && clusterAtBoundary >= Math.Max(2, cap - 1)).ShouldBeTrue(
|
||||
$"an empty page with a continuation point (page {i}, boundary cluster {clusterAtBoundary}): {tag} pages {Sizes(pages)}");
|
||||
Count("empty step over an oversized tie cluster");
|
||||
@@ -259,7 +259,7 @@ public sealed class NodeManagerHistoryReadRawCapFuzzTests(BoundsServerFixture fx
|
||||
{
|
||||
var ts = Trunc(new DateTime(t, DateTimeKind.Utc), resolution);
|
||||
var status = rng.Next(12) == 0 ? StatusCodes.Bad : StatusCodes.Good;
|
||||
list.Add(new DataValueSnapshot(status == StatusCodes.Bad ? null : (double)v, status, ts, ts));
|
||||
list.Add(new DataValueSnapshot(status == StatusCodes.Bad.Code ? null : (double)v, status.Code, ts, ts));
|
||||
v++;
|
||||
}
|
||||
}
|
||||
@@ -302,13 +302,13 @@ public sealed class NodeManagerHistoryReadRawCapFuzzTests(BoundsServerFixture fx
|
||||
Direction.Reverse => series.Where(x => Ts(x) > e && Ts(x) <= s).Reverse(),
|
||||
_ => series.Where(x => Ts(x) <= e).Reverse(),
|
||||
};
|
||||
return window.Select(x => new DataValue(new Variant(x.Value), new StatusCode(x.StatusCode), Ts(x), Ts(x))).ToList();
|
||||
return window.Select(x => new DataValue(SdkValueBoundary.FromClr(x.Value), new StatusCode(x.StatusCode), Ts(x), Ts(x))).ToList();
|
||||
}
|
||||
|
||||
/// <summary>The largest tie cluster among the returned stored values (a bound placeholder is not a stored value).</summary>
|
||||
private static int LargestTieCluster(IReadOnlyList<DataValue> truth) =>
|
||||
truth.Where(v => !(v.Value is null && v.StatusCode.Code == StatusCodes.BadBoundNotFound))
|
||||
.GroupBy(v => v.SourceTimestamp.Ticks).Select(g => g.Count()).DefaultIfEmpty(0).Max();
|
||||
truth.Where(v => !(SdkValueBoundary.ToClr(v.WrappedValue)! is null && v.StatusCode.Code == StatusCodes.BadBoundNotFound))
|
||||
.GroupBy(v => v.SourceTimestamp.ToDateTime().Ticks).Select(g => g.Count()).DefaultIfEmpty(0).Max();
|
||||
|
||||
/// <summary>The most samples within one millisecond anywhere in the series (the walk-back's refusal rule).</summary>
|
||||
private static int MillisecondMax(List<DataValueSnapshot> series)
|
||||
@@ -334,24 +334,24 @@ public sealed class NodeManagerHistoryReadRawCapFuzzTests(BoundsServerFixture fx
|
||||
private static string Sizes(IEnumerable<IReadOnlyList<DataValue>> pages) => "[" + string.Join(",", pages.Select(p => p.Count)) + "]";
|
||||
|
||||
private static string Render(IEnumerable<DataValue> values) =>
|
||||
string.Join(" | ", values.Select(v => $"{v.SourceTimestamp.Ticks}={v.Value ?? "null"}/{v.StatusCode.Code:X8}"));
|
||||
string.Join(" | ", values.Select(v => $"{v.SourceTimestamp.ToDateTime().Ticks}={SdkValueBoundary.ToClr(v.WrappedValue)! ?? "null"}/{v.StatusCode.Code:X8}"));
|
||||
|
||||
private (IReadOnlyList<DataValue> Values, ServiceResult Error, byte[]? Cp) Read(DateTime start, DateTime end, uint max, bool bounds, byte[]? cp)
|
||||
{
|
||||
var context = new OperationContext(new RequestHeader(), secureChannelContext: null, RequestType.HistoryRead, identity: null);
|
||||
var context = new OperationContext(new RequestHeader(), secureChannelContext: null, RequestType.HistoryRead, requestLifetime: RequestLifetime.None, identity: null);
|
||||
var details = new ReadRawModifiedDetails
|
||||
{
|
||||
StartTime = start, EndTime = end, NumValuesPerNode = max, ReturnBounds = bounds, IsReadModified = false,
|
||||
};
|
||||
var nodesToRead = new List<HistoryReadValueId> { new() { NodeId = fx.NodeId, ContinuationPoint = cp } };
|
||||
var nodesToRead = new List<HistoryReadValueId> { new() { NodeId = fx.NodeId, ContinuationPoint = ByteString.From(cp) } };
|
||||
var results = new List<SdkHistoryReadResult> { null! };
|
||||
var errors = new List<ServiceResult> { null! };
|
||||
fx.Nm.HistoryRead(context, details, TimestampsToReturn.Both, releaseContinuationPoints: false, nodesToRead, results, errors);
|
||||
|
||||
var values = new List<DataValue>();
|
||||
if (results[0]?.HistoryData is { } ho && !ExtensionObject.IsNull(ho))
|
||||
values.AddRange(((HistoryData)ExtensionObject.ToEncodeable(ho)).DataValues);
|
||||
return (values, errors[0], results[0]?.ContinuationPoint);
|
||||
if (results[0]?.HistoryData is { } ho && !ho.IsNull)
|
||||
values.AddRange(((HistoryData)ExtensionObject.ToEncodeable(ho)!).DataValues);
|
||||
return (values, errors[0], results[0]?.ContinuationPoint.ToNullableArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user