feat(sphistorianclient): port SDK source + tests, rebrand namespace to ZB.MOM.WW.SPHistorianClient
This commit is contained in:
+233
@@ -0,0 +1,233 @@
|
||||
using System.Runtime.Versioning;
|
||||
using ZB.MOM.WW.SPHistorianClient.Models;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.SPHistorianClient.Tests;
|
||||
|
||||
/// <remarks>
|
||||
/// Live verification of the RemoteTcpIntegrated and RemoteTcpCertificate transports
|
||||
/// per <c>docs/plans/tcp-connection-validation.md</c>. Gated by env vars:
|
||||
/// <list type="bullet">
|
||||
/// <item><c>HISTORIAN_REMOTE_TCP_HOST</c> — hostname or IP of a reachable remote Historian.</item>
|
||||
/// <item><c>HISTORIAN_REMOTE_TCP_TAG</c> — tag with non-zero history rows.</item>
|
||||
/// <item><c>HISTORIAN_REMOTE_TCP_SPN</c> — optional Kerberos SPN override (default per <c>HistorianClientOptions.TargetSpn</c>).</item>
|
||||
/// <item><c>HISTORIAN_REMOTE_TCPCERT_HOST</c> + <c>HISTORIAN_REMOTE_TCPCERT_DNS</c> — for the certificate transport variant.</item>
|
||||
/// </list>
|
||||
/// All tests skip cleanly if the gating env var isn't set.
|
||||
/// </remarks>
|
||||
[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<HistorianSample> 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<HistorianAggregateSample> 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<HistorianSample> 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<string> 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<HistorianEvent> 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user