a74a18e49a
R08 2.3: AggregateType drops StandardDeviation (no backend serves it; always BadAggregateNotSupported) and gains Range, Delta, MinimumActualTime, MaximumActualTime, PercentGood and PercentBad. A parity test pins the enum to AggregateFamilies.Served and each member to its same-named Part 13 ObjectId. The CLI parses every enum name (plus aliases) and the UI picker lists them all. Existing members keep their numeric values; 6 is retired. Refs #747 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
88 lines
3.4 KiB
C#
88 lines
3.4 KiB
C#
using CliFx.Exceptions;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Client.CLI.Commands;
|
|
using ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes;
|
|
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
|
|
|
|
/// <summary>
|
|
/// <c>historyread --aggregate</c> accepts every aggregate the client offers (which is exactly the served set), so the
|
|
/// gate recipes in docs/Historian.md that need Range / Delta / PercentGood / ... can run from this CLI.
|
|
/// </summary>
|
|
public class HistoryReadAggregateParsingTests
|
|
{
|
|
/// <summary>Every client aggregate, by its name.</summary>
|
|
/// <returns>Each <see cref="AggregateType"/> value.</returns>
|
|
public static TheoryData<AggregateType> AllAggregates() => new(Enum.GetValues<AggregateType>());
|
|
|
|
/// <summary>Every aggregate name (in any case) reaches the service as that aggregate.</summary>
|
|
/// <param name="aggregate">The aggregate.</param>
|
|
[Theory]
|
|
[MemberData(nameof(AllAggregates))]
|
|
public async Task Every_aggregate_name_parses(AggregateType aggregate)
|
|
{
|
|
var fakeService = new FakeOpcUaClientService();
|
|
var command = new HistoryReadCommand(new FakeOpcUaClientServiceFactory(fakeService))
|
|
{
|
|
Url = "opc.tcp://localhost:4840",
|
|
NodeId = "ns=2;s=HistNode",
|
|
Aggregate = aggregate.ToString().ToUpperInvariant()
|
|
};
|
|
|
|
using var console = TestConsoleHelper.CreateConsole();
|
|
await command.ExecuteAsync(console);
|
|
|
|
fakeService.HistoryReadAggregateCalls.ShouldHaveSingleItem().Aggregate.ShouldBe(aggregate);
|
|
}
|
|
|
|
/// <summary>The short aliases still parse.</summary>
|
|
/// <param name="alias">The alias.</param>
|
|
/// <param name="expected">The aggregate it names.</param>
|
|
[Theory]
|
|
[InlineData("avg", AggregateType.Average)]
|
|
[InlineData("min", AggregateType.Minimum)]
|
|
[InlineData("last", AggregateType.End)]
|
|
[InlineData("interp", AggregateType.Interpolative)]
|
|
[InlineData("mintime", AggregateType.MinimumActualTime)]
|
|
[InlineData("maxtime", AggregateType.MaximumActualTime)]
|
|
public async Task Aliases_parse(string alias, AggregateType expected)
|
|
{
|
|
var fakeService = new FakeOpcUaClientService();
|
|
var command = new HistoryReadCommand(new FakeOpcUaClientServiceFactory(fakeService))
|
|
{
|
|
Url = "opc.tcp://localhost:4840",
|
|
NodeId = "ns=2;s=HistNode",
|
|
Aggregate = alias
|
|
};
|
|
|
|
using var console = TestConsoleHelper.CreateConsole();
|
|
await command.ExecuteAsync(console);
|
|
|
|
fakeService.HistoryReadAggregateCalls.ShouldHaveSingleItem().Aggregate.ShouldBe(expected);
|
|
}
|
|
|
|
/// <summary>
|
|
/// StandardDeviation is no longer offered (no backend serves it): it is refused before connecting, naming the
|
|
/// supported set.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task StandardDeviation_is_refused_before_connecting()
|
|
{
|
|
var fakeService = new FakeOpcUaClientService();
|
|
var command = new HistoryReadCommand(new FakeOpcUaClientServiceFactory(fakeService))
|
|
{
|
|
Url = "opc.tcp://localhost:4840",
|
|
NodeId = "ns=2;s=HistNode",
|
|
Aggregate = "stddev"
|
|
};
|
|
|
|
using var console = TestConsoleHelper.CreateConsole();
|
|
var ex = await Should.ThrowAsync<CommandException>(async () => await command.ExecuteAsync(console));
|
|
|
|
ex.Message.ShouldContain("PercentGood");
|
|
fakeService.HistoryReadAggregateCalls.ShouldBeEmpty();
|
|
}
|
|
}
|