0b85c499e3
AVEVA's native TimeAverage / Total are answered with Good-status wrong values while the gateway sends the interval in the wrong unit (historiangw#27): one whole-span value on the earliest interval, the rest absent. Nothing on the wire marks the answer as degraded. Both are now refused BadAggregateNotSupported (unsent, not counted as a backend failure, not advertised in AggregateFunctions) unless ServerHistorian:Gateway:EnableNativeTimeWeighted is true. The default Gateway set is the ten raw-evaluated aggregates (GatewayHistorianDataSource.RawEvaluatedAggregates); ServedAggregates stays the 12 the backend can serve with the flag on. The native-path tests opt in. Also documents the ServerHistorian:Gateway sub-section (EventDrainBudget from the previous commit) and the events drain in docs/Historian.md. Rebased from the unpushed remed/lane-H2 branch (originally authored 2026-10-22 in session_01Ub9hGmYLQvn9XETzw7CMsT) onto current master. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
65 lines
3.6 KiB
C#
65 lines
3.6 KiB
C#
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions; // HistoryAggregateType
|
|
using ZB.MOM.WW.HistorianGateway.Contracts.Grpc; // RetrievalMode
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Mapping;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests.Mapping;
|
|
|
|
public sealed class AggregateModeMapperTests
|
|
{
|
|
[Theory]
|
|
[InlineData(HistoryAggregateType.TimeAverage, RetrievalMode.TimeWeightedAverage)]
|
|
[InlineData(HistoryAggregateType.Total, RetrievalMode.Integral)]
|
|
public void Maps_each_aggregate(HistoryAggregateType a, RetrievalMode expected)
|
|
=> Assert.Equal(expected, AggregateModeMapper.ToRetrievalMode(a));
|
|
|
|
[Fact] // matrix guard: every member is either mapped or explicitly reported as not supported
|
|
public void Every_aggregate_member_is_mapped_or_reports_not_supported()
|
|
{
|
|
var original = new[] { HistoryAggregateType.Total, HistoryAggregateType.TimeAverage };
|
|
Assert.Equal(original.OrderBy(a => a), AggregateModeMapper.NativeAggregates.OrderBy(a => a));
|
|
foreach (var a in Enum.GetValues<HistoryAggregateType>())
|
|
{
|
|
if (original.Contains(a))
|
|
_ = AggregateModeMapper.ToRetrievalMode(a);
|
|
else
|
|
Assert.Throws<HistoryAggregateNotSupportedException>(() => AggregateModeMapper.ToRetrievalMode(a));
|
|
}
|
|
}
|
|
|
|
[Theory] // #679: AVEVA has no arithmetic mean and no sample count; #681: the per-sample aggregates are raw-evaluated, not native
|
|
[InlineData(HistoryAggregateType.Average)]
|
|
[InlineData(HistoryAggregateType.Count)]
|
|
[InlineData(HistoryAggregateType.Minimum)]
|
|
[InlineData(HistoryAggregateType.Maximum)]
|
|
public void Per_sample_aggregates_have_no_native_mode(HistoryAggregateType a)
|
|
=> Assert.Throws<HistoryAggregateNotSupportedException>(() => AggregateModeMapper.ToRetrievalMode(a));
|
|
|
|
[Fact] // #682/#681: the served (= advertised) set is the native members plus the raw-evaluated per-sample families,
|
|
// and the data source returns exactly that static
|
|
public void Served_is_exactly_the_mapped_members_and_the_data_source_declares_it()
|
|
{
|
|
foreach (var a in Enum.GetValues<HistoryAggregateType>())
|
|
{
|
|
var native = AggregateModeMapper.NativeAggregates.Contains(a);
|
|
var perSample = ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.AggregateFamilies.FamilyOf(a) is
|
|
ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.AggregateFamily.Simple
|
|
or ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.AggregateFamily.StartEnd
|
|
or ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.AggregateFamily.ActualTime;
|
|
Assert.Equal(native || perSample, GatewayHistorianDataSource.ServedAggregates.Contains(a));
|
|
if (!native) Assert.Throws<HistoryAggregateNotSupportedException>(() => AggregateModeMapper.ToRetrievalMode(a));
|
|
}
|
|
Assert.Equal(12, GatewayHistorianDataSource.ServedAggregates.Count);
|
|
|
|
// The native pair is opt-in (ServerHistorian:Gateway:EnableNativeTimeWeighted) — see GatewayNativeTimeWeightedGateTests.
|
|
var source = new GatewayHistorianDataSource(
|
|
new FakeHistorianGatewayClient(), Microsoft.Extensions.Logging.Abstractions.NullLogger<GatewayHistorianDataSource>.Instance,
|
|
enableNativeTimeWeighted: true);
|
|
Assert.Same(GatewayHistorianDataSource.ServedAggregates, source.SupportedAggregates);
|
|
}
|
|
|
|
[Fact]
|
|
public void Undefined_value_is_a_programming_error_not_a_not_supported()
|
|
=> Assert.Throws<ArgumentOutOfRangeException>(() => AggregateModeMapper.ToRetrievalMode((HistoryAggregateType)9999));
|
|
}
|