using System; using System.Threading; using Shouldly; using Xunit; using ZB.MOM.WW.LmxOpcUa.Host.Configuration; using ZB.MOM.WW.LmxOpcUa.Host.Historian; namespace ZB.MOM.WW.LmxOpcUa.Tests.Historian { /// /// Verifies Historian data source lifecycle behavior: dispose safety, /// post-dispose rejection, and double-dispose idempotency. /// public class HistorianDataSourceLifecycleTests { private static HistorianConfiguration DefaultConfig => new() { Enabled = true, ServerName = "test-historian", Port = 32568, IntegratedSecurity = true, CommandTimeoutSeconds = 5 }; [Fact] public void ReadRawAsync_AfterDispose_ThrowsObjectDisposedException() { var ds = new HistorianDataSource(DefaultConfig); ds.Dispose(); Should.Throw(() => ds.ReadRawAsync("Tag1", DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, 100) .GetAwaiter().GetResult()); } [Fact] public void ReadAggregateAsync_AfterDispose_ThrowsObjectDisposedException() { var ds = new HistorianDataSource(DefaultConfig); ds.Dispose(); Should.Throw(() => ds.ReadAggregateAsync("Tag1", DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, 60000, "Average") .GetAwaiter().GetResult()); } [Fact] public void ReadAtTimeAsync_AfterDispose_ThrowsObjectDisposedException() { var ds = new HistorianDataSource(DefaultConfig); ds.Dispose(); Should.Throw(() => ds.ReadAtTimeAsync("Tag1", new[] { DateTime.UtcNow }) .GetAwaiter().GetResult()); } [Fact] public void ReadEventsAsync_AfterDispose_ThrowsObjectDisposedException() { var ds = new HistorianDataSource(DefaultConfig); ds.Dispose(); Should.Throw(() => ds.ReadEventsAsync(null, DateTime.UtcNow.AddHours(-1), DateTime.UtcNow, 100) .GetAwaiter().GetResult()); } [Fact] public void Dispose_CalledTwice_DoesNotThrow() { var ds = new HistorianDataSource(DefaultConfig); ds.Dispose(); Should.NotThrow(() => ds.Dispose()); } [Fact] public void ExtractAggregateValue_UnknownColumn_ReturnsNull() { HistorianDataSource.MapAggregateToColumn(new Opc.Ua.NodeId(99999)).ShouldBeNull(); } } }