using Opc.Ua; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests; [Trait("Category", "Unit")] public sealed class OpcUaClientHistoryTests { [Theory] [InlineData(HistoryAggregateType.Average)] [InlineData(HistoryAggregateType.Minimum)] [InlineData(HistoryAggregateType.Maximum)] [InlineData(HistoryAggregateType.Total)] [InlineData(HistoryAggregateType.Count)] public void MapAggregateToNodeId_returns_standard_Part13_aggregate_for_every_enum(HistoryAggregateType agg) { var nodeId = OpcUaClientDriver.MapAggregateToNodeId(agg); NodeId.IsNull(nodeId).ShouldBeFalse(); // Every mapping should resolve to an AggregateFunction_* NodeId (namespace 0, numeric id). nodeId.NamespaceIndex.ShouldBe((ushort)0); } [Fact] public void MapAggregateToNodeId_rejects_invalid_enum_value() { // Defense-in-depth: a future HistoryAggregateType addition mustn't silently fall through. Should.Throw(() => OpcUaClientDriver.MapAggregateToNodeId((HistoryAggregateType)99)); } [Fact] public async Task ReadRawAsync_without_initialize_throws_InvalidOperationException() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-hist-uninit"); await Should.ThrowAsync(async () => await drv.ReadRawAsync("ns=2;s=Counter", DateTime.UtcNow.AddMinutes(-5), DateTime.UtcNow, 1000, TestContext.Current.CancellationToken)); } [Fact] public async Task ReadRawAsync_with_malformed_NodeId_returns_empty_result_not_throw() { // Same defensive pattern as ReadAsync / WriteAsync — malformed NodeId short-circuits // to an empty result rather than crashing a batch history call. Needs init via the // throw path first, then we pass "" to trigger the parse-fail branch inside // ExecuteHistoryReadAsync. The init itself fails against 127.0.0.1:1 so we stop there. // Not runnable without init — keep as placeholder for when the in-process fixture // PR lands. await Task.CompletedTask; } [Fact] public async Task ReadProcessedAsync_without_initialize_throws_InvalidOperationException() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-hist-uninit"); await Should.ThrowAsync(async () => await drv.ReadProcessedAsync("ns=2;s=Counter", DateTime.UtcNow.AddMinutes(-5), DateTime.UtcNow, TimeSpan.FromSeconds(10), HistoryAggregateType.Average, TestContext.Current.CancellationToken)); } [Fact] public async Task ReadAtTimeAsync_without_initialize_throws_InvalidOperationException() { using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-hist-uninit"); await Should.ThrowAsync(async () => await drv.ReadAtTimeAsync("ns=2;s=Counter", [DateTime.UtcNow.AddMinutes(-5), DateTime.UtcNow], TestContext.Current.CancellationToken)); } [Fact] public async Task ReadEventsAsync_throws_NotSupportedException_as_documented() { // The IHistoryProvider default implementation throws; the OPC UA Client driver // deliberately inherits that default (see PR 76 commit body) because the OPC UA // client call path needs an EventFilter SelectClauses spec the interface doesn't carry. using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-events-default"); await Should.ThrowAsync(async () => await ((IHistoryProvider)drv).ReadEventsAsync( sourceName: null, startUtc: DateTime.UtcNow.AddMinutes(-5), endUtc: DateTime.UtcNow, maxEvents: 100, cancellationToken: TestContext.Current.CancellationToken)); } }