From 9909921e87d8f3aa53ed2188f289ecf9b0db49a4 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 25 Jun 2026 02:08:23 -0400 Subject: [PATCH] test(grpc): write-reuse + read-on-write-session probe (one-vs-two-kind decider) --- .../HandshakeReuseSpikeTests.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/AVEVA.Historian.Client.Tests/HandshakeReuseSpikeTests.cs b/tests/AVEVA.Historian.Client.Tests/HandshakeReuseSpikeTests.cs index 0aba687..c34de08 100644 --- a/tests/AVEVA.Historian.Client.Tests/HandshakeReuseSpikeTests.cs +++ b/tests/AVEVA.Historian.Client.Tests/HandshakeReuseSpikeTests.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using AVEVA.Historian.Client.Grpc; using AVEVA.Historian.Client.Models; +using AVEVA.Historian.Client.Wcf; using Xunit.Abstractions; namespace AVEVA.Historian.Client.Tests; @@ -110,8 +111,70 @@ public sealed class HandshakeReuseSpikeTests } } + // (A) WRITE REUSE VALIDITY: one externally-opened 0x401 (write-enabled) session, N writes on it via + // RunWriteOnSession — NO Create()/handshake per write. If the server rejects reusing a write session, + // write #2 throws -> RED finding. Both succeed -> GREEN (write-reuse is sound). Bounded writes to the + // sandbox tag ONLY; latency LOGGED, success ASSERTED. + [Fact] + public void WriteEnabledSession_RunsTwoWrites_AllSucceed() + { + if (!TryGetWriteEnv(out string host, out string sandboxTag)) return; + HistorianClientOptions options = BuildOptions(host); + using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options); + HistorianGrpcHandshake.Session session = OpenWriteSession(options, connection); + var writer = new HistorianGrpcHistoricalWriteOrchestrator(options); + + for (int i = 0; i < 2; i++) + { + var sw = Stopwatch.StartNew(); + bool ok = writer.RunWriteOnSession(connection, session, sandboxTag, + new[] { new HistorianHistoricalValue(DateTime.UtcNow.AddSeconds(-i), 1.0 + i, OpcQuality: 192) }, + CancellationToken.None); + sw.Stop(); + _output.WriteLine($"reused-write[{i}] = {sw.ElapsedMilliseconds} ms, ok={ok}"); + Assert.True(ok); + } + } + + // (B) READ-ON-WRITE-SESSION PROBE: can a 0x401 (write-enabled) session ALSO serve a raw read? Decides + // the pool shape — ONE-KIND (a single write-enabled session serves both reads and writes) vs TWO-KIND + // (separate read 0x402 / write 0x401 sessions). The kind is LOGGED, never asserted; any failure is + // swallowed (a rejection is itself the finding, not a test failure). READ-ONLY here. + [Fact] + public void WriteEnabledSession_AlsoServesRead_RecordsKind() + { + if (!TryGetWriteEnv(out string host, out string sandboxTag)) return; + HistorianClientOptions options = BuildOptions(host); + using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(options); + HistorianGrpcHandshake.Session session = OpenWriteSession(options, connection); + (DateTime startUtc, DateTime endUtc) = LastSevenDays(); + try + { + List rows = new HistorianGrpcReadOrchestrator(options) + .RunRawQueryOnSession(connection, session.ClientHandle, sandboxTag, startUtc, endUtc, 8, CancellationToken.None); + _output.WriteLine($"read-on-0x401 -> OK (rows={rows.Count}) => ONE-KIND pool (write-enabled serves reads)"); + } + catch (Exception ex) + { + _output.WriteLine($"read-on-0x401 -> FAILED ({ex.GetType().Name}) => TWO-KIND pool (separate read/write)"); + } + } + // --- helpers --- + private static bool TryGetWriteEnv(out string host, out string sandboxTag) + { + host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST") ?? ""; + sandboxTag = Environment.GetEnvironmentVariable("HISTORIAN_WRITE_SANDBOX_TAG") ?? ""; + return !string.IsNullOrWhiteSpace(host) + && !string.IsNullOrWhiteSpace(sandboxTag) + && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER")); + } + + private static HistorianGrpcHandshake.Session OpenWriteSession(HistorianClientOptions o, HistorianGrpcConnection c) + => HistorianGrpcHandshake.OpenSession(c, o, CancellationToken.None, + connectionMode: HistorianWcfAuthChainHelper.NativeIntegratedWriteEnabledConnectionMode); + private static bool TryGetEnv(out string host, out string tag) { host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST") ?? "";