using System.Runtime.Versioning; using AVEVA.Historian.Client.Models; using Xunit.Abstractions; namespace AVEVA.Historian.Client.Tests; /// /// Live verification of the RemoteTcpIntegrated and RemoteTcpCertificate transports /// per docs/plans/tcp-connection-validation.md. Gated by env vars: /// /// HISTORIAN_REMOTE_TCP_HOST — hostname or IP of a reachable remote Historian. /// HISTORIAN_REMOTE_TCP_TAG — tag with non-zero history rows. /// HISTORIAN_REMOTE_TCP_SPN — optional Kerberos SPN override (default per HistorianClientOptions.TargetSpn). /// HISTORIAN_REMOTE_TCPCERT_HOST + HISTORIAN_REMOTE_TCPCERT_DNS — for the certificate transport variant. /// /// All tests skip cleanly if the gating env var isn't set. /// [SupportedOSPlatform("windows")] public sealed class RemoteTcpIntegrationTests { private readonly ITestOutputHelper _output; public RemoteTcpIntegrationTests(ITestOutputHelper output) { _output = output; } [Fact] public async Task ProbeAsync_RemoteTcpIntegrated_ReturnsTrue() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); if (string.IsNullOrWhiteSpace(host) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); bool reachable = await client.ProbeAsync(CancellationToken.None); Assert.True(reachable, "ProbeAsync against remote-TCP host returned false"); } [Fact] public async Task ReadRawAsync_RemoteTcpIntegrated_ReturnsAtLeastOneRow() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); string? testTag = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_TAG"); if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(testTag) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); DateTime endUtc = DateTime.UtcNow; DateTime startUtc = endUtc - TimeSpan.FromDays(7); List samples = []; await foreach (HistorianSample sample in client.ReadRawAsync(testTag, startUtc, endUtc, maxValues: 8, CancellationToken.None)) { samples.Add(sample); } _output.WriteLine($"Returned {samples.Count} samples for {testTag}"); Assert.NotEmpty(samples); Assert.All(samples, s => Assert.Equal(testTag, s.TagName)); } [Fact] public async Task GetTagMetadataAsync_RemoteTcpIntegrated_PopulatesFields() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); string? testTag = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_TAG"); if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(testTag) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); HistorianTagMetadata? metadata = await client.GetTagMetadataAsync(testTag, CancellationToken.None); Assert.NotNull(metadata); Assert.Equal(testTag, metadata.Name); } [Fact] public async Task GetSystemParameterAsync_RemoteTcpIntegrated_ReturnsHistorianVersion() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); if (string.IsNullOrWhiteSpace(host) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); string? value = await client.GetSystemParameterAsync("HistorianVersion", CancellationToken.None); _output.WriteLine($"HistorianVersion: {value}"); Assert.False(string.IsNullOrWhiteSpace(value)); } [Fact] public async Task ReadAggregateAsync_RemoteTcpIntegrated_ReturnsTimeWeightedRows() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); string? testTag = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_TAG"); if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(testTag) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); DateTime endUtc = DateTime.UtcNow; DateTime startUtc = endUtc - TimeSpan.FromMinutes(10); List samples = []; await foreach (HistorianAggregateSample sample in client.ReadAggregateAsync( testTag, startUtc, endUtc, RetrievalMode.TimeWeightedAverage, TimeSpan.FromMinutes(1), CancellationToken.None)) { samples.Add(sample); } Assert.NotEmpty(samples); Assert.All(samples, s => Assert.Equal(testTag, s.TagName)); } [Fact] public async Task ReadAtTimeAsync_RemoteTcpIntegrated_ReturnsTimestamps() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); string? testTag = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_TAG"); if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(testTag) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); DateTime now = DateTime.UtcNow; DateTime[] timestamps = [now - TimeSpan.FromMinutes(5), now - TimeSpan.FromMinutes(2), now - TimeSpan.FromMinutes(1)]; IReadOnlyList samples = await client.ReadAtTimeAsync(testTag, timestamps, CancellationToken.None); Assert.NotEmpty(samples); Assert.All(samples, s => Assert.Equal(testTag, s.TagName)); } [Fact] public async Task BrowseTagNamesAsync_RemoteTcpIntegrated_FindsTestTag() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); string? testTag = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_TAG"); if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(testTag) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); List names = []; await foreach (string name in client.BrowseTagNamesAsync(testTag, CancellationToken.None)) { names.Add(name); } Assert.Contains(testTag, names); } [Fact] public async Task ReadEventsAsync_RemoteTcpIntegrated_DoesNotThrow() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); if (string.IsNullOrWhiteSpace(host) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); DateTime endUtc = DateTime.UtcNow; DateTime startUtc = endUtc - TimeSpan.FromDays(1); // Empty result is acceptable — we're just verifying the chain doesn't throw over TCP. List events = []; await foreach (HistorianEvent evt in client.ReadEventsAsync(startUtc, endUtc, CancellationToken.None)) { events.Add(evt); } Assert.NotNull(events); } [Fact] public async Task GetConnectionStatusAsync_RemoteTcpIntegrated_ReportsConnectedToServer() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_HOST"); if (string.IsNullOrWhiteSpace(host) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(BuildIntegratedOptions(host)); HistorianConnectionStatus status = await client.GetConnectionStatusAsync(CancellationToken.None); Assert.True(status.ConnectedToServer); Assert.False(status.ErrorOccurred); Assert.Equal(host, status.ServerName); } [Fact] public async Task ProbeAsync_RemoteTcpCertificate_ReturnsTrue() { string? host = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCPCERT_HOST"); if (string.IsNullOrWhiteSpace(host) || !OperatingSystem.IsWindows()) { return; } HistorianClient client = new(new HistorianClientOptions { Host = host, Port = HistorianClientOptions.DefaultPort, IntegratedSecurity = false, Transport = HistorianTransport.RemoteTcpCertificate, }); bool reachable = await client.ProbeAsync(CancellationToken.None); Assert.True(reachable, "ProbeAsync over RemoteTcpCertificate returned false"); } private static HistorianClientOptions BuildIntegratedOptions(string host) { string? spn = Environment.GetEnvironmentVariable("HISTORIAN_REMOTE_TCP_SPN"); return new HistorianClientOptions { Host = host, Port = HistorianClientOptions.DefaultPort, IntegratedSecurity = true, Transport = HistorianTransport.RemoteTcpIntegrated, // SPN default in HistorianClientOptions is "NT SERVICE\aahClientAccessPoint" which is the // LocalPipe service identity; for remote TCP, override via env var if needed. TargetSpn = string.IsNullOrWhiteSpace(spn) ? "NT SERVICE\\aahClientAccessPoint" : spn, }; } }