Merge feat/677-aggregates: reverse processed reads SDK-anchored, start-of-data Partial (forward), AggregateConfiguration accept-defaults, IntegralDivisor=1, SDK parity tests (#677)
This commit is contained in:
@@ -278,6 +278,94 @@ public sealed class NodeManagerHistoryReadTests : IDisposable
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #677: a client <c>AggregateConfiguration</c> that differs from the server defaults is refused per node with
|
||||
/// <c>BadAggregateConfigurationRejected</c> (it used to be silently ignored); the source is never invoked.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(true, (byte)100, (byte)100, false)] // TreatUncertainAsBad
|
||||
[InlineData(false, (byte)50, (byte)100, false)] // PercentDataBad
|
||||
[InlineData(false, (byte)100, (byte)80, false)] // PercentDataGood
|
||||
[InlineData(false, (byte)100, (byte)100, true)] // UseSlopedExtrapolation
|
||||
[InlineData(false, (byte)0, (byte)0, false)] // an all-zero structure without UseServerCapabilitiesDefaults
|
||||
public async Task Processed_with_a_non_default_aggregate_configuration_is_rejected(
|
||||
bool treatUncertainAsBad, byte percentBad, byte percentGood, bool sloped)
|
||||
{
|
||||
var (host, server) = await BootAsync();
|
||||
var nm = server.NodeManager!;
|
||||
var fake = new RecordingHistorianDataSource { ProcessedResult = Empty() };
|
||||
nm.HistorianDataSource = fake;
|
||||
nm.EnsureVariable("eq-1/cfg", parentFolderNodeId: null, displayName: "Cfg", dataType: "Float",
|
||||
writable: false, historianTagname: "WW.Cfg", realm: AddressSpaceRealm.Uns);
|
||||
var nodeId = nm.TryGetVariable("eq-1/cfg")!.NodeId;
|
||||
|
||||
var (_, errors) = InvokeHistoryRead(server, nm, new ReadProcessedDetails
|
||||
{
|
||||
StartTime = DateTime.UtcNow.AddHours(-1), EndTime = DateTime.UtcNow, ProcessingInterval = 10_000.0,
|
||||
AggregateType = new NodeIdCollection { ObjectIds.AggregateFunction_TimeAverage },
|
||||
AggregateConfiguration = new AggregateConfiguration
|
||||
{
|
||||
UseServerCapabilitiesDefaults = false, TreatUncertainAsBad = treatUncertainAsBad,
|
||||
PercentDataBad = percentBad, PercentDataGood = percentGood, UseSlopedExtrapolation = sloped,
|
||||
},
|
||||
}, nodeId);
|
||||
|
||||
errors[0].StatusCode.Code.ShouldBe(StatusCodes.BadAggregateConfigurationRejected);
|
||||
fake.LastCall.ShouldBeNull();
|
||||
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #677: <c>UseServerCapabilitiesDefaults</c> (whatever the other fields say), or a configuration spelling out exactly
|
||||
/// the server defaults, is served.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(true, true, (byte)0, (byte)0, true)]
|
||||
[InlineData(false, false, (byte)100, (byte)100, false)]
|
||||
public async Task Processed_with_the_server_default_aggregate_configuration_is_served(
|
||||
bool useServerDefaults, bool treatUncertainAsBad, byte percentBad, byte percentGood, bool sloped)
|
||||
{
|
||||
var (host, server) = await BootAsync();
|
||||
var nm = server.NodeManager!;
|
||||
var fake = new RecordingHistorianDataSource { ProcessedResult = Empty() };
|
||||
nm.HistorianDataSource = fake;
|
||||
nm.EnsureVariable("eq-1/cfg-ok", parentFolderNodeId: null, displayName: "CfgOk", dataType: "Float",
|
||||
writable: false, historianTagname: "WW.CfgOk", realm: AddressSpaceRealm.Uns);
|
||||
var nodeId = nm.TryGetVariable("eq-1/cfg-ok")!.NodeId;
|
||||
|
||||
var (_, errors) = InvokeHistoryRead(server, nm, new ReadProcessedDetails
|
||||
{
|
||||
StartTime = DateTime.UtcNow.AddHours(-1), EndTime = DateTime.UtcNow, ProcessingInterval = 10_000.0,
|
||||
AggregateType = new NodeIdCollection { ObjectIds.AggregateFunction_TimeAverage },
|
||||
AggregateConfiguration = new AggregateConfiguration
|
||||
{
|
||||
UseServerCapabilitiesDefaults = useServerDefaults, TreatUncertainAsBad = treatUncertainAsBad,
|
||||
PercentDataBad = percentBad, PercentDataGood = percentGood, UseSlopedExtrapolation = sloped,
|
||||
},
|
||||
}, nodeId);
|
||||
|
||||
StatusCode.IsGood(errors[0].StatusCode).ShouldBeTrue();
|
||||
fake.LastCall.ShouldBe("Processed");
|
||||
fake.LastAggregate.ShouldBe(HistoryAggregateType.TimeAverage);
|
||||
|
||||
await host.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <summary>#677: the server's declared aggregate defaults are the SDK's own server defaults.</summary>
|
||||
[Fact]
|
||||
public void The_server_aggregate_defaults_agree_with_the_SDK_AggregateManager()
|
||||
{
|
||||
var sdk = new Opc.Ua.Server.AggregateManager(null!).GetDefaultConfiguration(null);
|
||||
var ours = ServerAggregateConfiguration.Defaults;
|
||||
ours.TreatUncertainAsBad.ShouldBe(sdk.TreatUncertainAsBad);
|
||||
ours.PercentDataBad.ShouldBe(sdk.PercentDataBad);
|
||||
ours.PercentDataGood.ShouldBe(sdk.PercentDataGood);
|
||||
ours.UseSlopedExtrapolation.ShouldBe(sdk.UseSlopedExtrapolation);
|
||||
ServerAggregateConfiguration.IsServable(sdk).ShouldBeTrue();
|
||||
ServerAggregateConfiguration.IsServable(null).ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>#285: a Part 13 aggregate beyond the original five (Interpolative) is forwarded to the
|
||||
/// backend as its own <see cref="HistoryAggregateType"/> member.</summary>
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user