test: HistorianSession end-to-end round-trip (write+read+status+ping on one session, env-gated)

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
Joseph Doherty
2026-06-25 03:09:18 -04:00
parent 15da8516ba
commit be60d0b8d9
@@ -0,0 +1,88 @@
using AVEVA.Historian.Client.Models;
using Xunit.Abstractions;
namespace AVEVA.Historian.Client.Tests;
/// <summary>
/// Live end-to-end round-trip for <see cref="HistorianSession"/>: open → write → read → status →
/// ping → dispose — ALL on ONE reused session (no re-handshake). Env-gated exactly like
/// <see cref="HandshakeReuseSpikeTests"/> (silent skip without HISTORIAN_GRPC_HOST +
/// HISTORIAN_WRITE_SANDBOX_TAG + HISTORIAN_USER). Bounded write to the sandbox tag only.
/// </summary>
public sealed class HistorianSessionRoundTripTests
{
private readonly ITestOutputHelper _output;
public HistorianSessionRoundTripTests(ITestOutputHelper output) => _output = output;
[Fact]
public async Task Session_WriteReadStatusPing_AllOnOneSession()
{
string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST");
string? sandboxTag = Environment.GetEnvironmentVariable("HISTORIAN_WRITE_SANDBOX_TAG");
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(sandboxTag)
|| string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER")))
{
return; // skip — env not configured
}
HistorianClientOptions options = BuildOptions(host);
await using var client = new HistorianClient(options);
await using HistorianSession session = await client.OpenSessionAsync(HistorianSessionKind.WriteEnabled, CancellationToken.None);
// 1) write on the session
bool wrote = await session.AddHistoricalValuesAsync(sandboxTag,
new[] { new HistorianHistoricalValue(DateTime.UtcNow, 42.0, OpcQuality: 192) },
CancellationToken.None);
Assert.True(wrote);
_output.WriteLine("1) write -> ok");
// 2) read on the SAME session
DateTime end = DateTime.UtcNow, start = end - TimeSpan.FromDays(7);
var rows = new List<HistorianSample>();
await foreach (HistorianSample s in session.ReadRawAsync(sandboxTag, start, end, 8, CancellationToken.None))
rows.Add(s);
_output.WriteLine($"2) session read rows={rows.Count}");
Assert.NotEmpty(rows);
// 3) status + ping on the SAME session
string? version = await session.GetSystemParameterAsync("HistorianVersion", CancellationToken.None);
Assert.False(string.IsNullOrWhiteSpace(version));
_output.WriteLine($"3) HistorianVersion={version}");
await session.PingAsync(CancellationToken.None); // must not throw
_output.WriteLine("4) ping -> ok");
_output.WriteLine("session round-trip OK (write+read+status+ping on one session)");
}
// verbatim copy of BuildOptions from HandshakeReuseSpikeTests
private static HistorianClientOptions BuildOptions(string host)
{
string? user = Environment.GetEnvironmentVariable("HISTORIAN_USER");
string? password = Environment.GetEnvironmentVariable("HISTORIAN_PASSWORD");
bool explicitCreds = !string.IsNullOrEmpty(user);
int port = int.TryParse(Environment.GetEnvironmentVariable("HISTORIAN_GRPC_PORT"), out int parsed)
? parsed
: HistorianClientOptions.DefaultGrpcPort;
bool tls = string.Equals(Environment.GetEnvironmentVariable("HISTORIAN_GRPC_TLS"), "true", StringComparison.OrdinalIgnoreCase);
TimeSpan timeout = int.TryParse(Environment.GetEnvironmentVariable("HISTORIAN_GRPC_TIMEOUT"), out int secs) && secs > 0
? TimeSpan.FromSeconds(secs)
: new HistorianClientOptions { Host = host }.RequestTimeout;
return new HistorianClientOptions
{
Host = host,
Port = port,
Transport = HistorianTransport.RemoteGrpc,
GrpcUseTls = tls,
AllowUntrustedServerCertificate = tls,
ServerDnsIdentity = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_DNSID"),
IntegratedSecurity = !explicitCreds,
UserName = user ?? string.Empty,
Password = password ?? string.Empty,
RequestTimeout = timeout,
Compression = true
};
}
}