diff --git a/tests/AVEVA.Historian.Client.Tests/HistorianGrpcIntegrationTests.cs b/tests/AVEVA.Historian.Client.Tests/HistorianGrpcIntegrationTests.cs index 7cfd9cd..7cf7e47 100644 --- a/tests/AVEVA.Historian.Client.Tests/HistorianGrpcIntegrationTests.cs +++ b/tests/AVEVA.Historian.Client.Tests/HistorianGrpcIntegrationTests.cs @@ -351,6 +351,104 @@ public sealed class HistorianGrpcIntegrationTests Assert.All(samples, s => Assert.Equal(testTag, s.TagName)); } + [Fact] + public async Task GetRuntimeParameterAsync_OverGrpc_ReturnsValue() + { + string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST"); + if (string.IsNullOrWhiteSpace(host) || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER"))) + { + return; + } + + // Config op tooled over gRPC: StatusService.GetRuntimeParameter carries the proven 2020 GETRP + // request/response buffers unchanged inside the protobuf bytes fields. + HistorianClient client = new(BuildOptions(host)); + string? value = await client.GetRuntimeParameterAsync("HistorianVersion", CancellationToken.None); + Assert.False(string.IsNullOrWhiteSpace(value)); + } + + [Fact] + public async Task GetTagExtendedPropertiesAsync_OverGrpc_DoesNotThrow() + { + string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST"); + string? tag = Environment.GetEnvironmentVariable("HISTORIAN_TEST_TAG"); + if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(tag) + || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER"))) + { + return; + } + + // Config op tooled over gRPC: RetrievalService.GetTagExtendedPropertiesFromName carries the + // proven 2020 GetTepByNm buffers. A system tag may have no user-defined properties, so this + // asserts the call completes and returns a well-formed (possibly empty) list. + HistorianClient client = new(BuildOptions(host)); + IReadOnlyList props = await client.GetTagExtendedPropertiesAsync(tag!, CancellationToken.None); + Assert.NotNull(props); + } + + [Fact] + public async Task ExecuteSqlCommandAsync_OverGrpc_IsServerWalled() + { + string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST"); + if (string.IsNullOrWhiteSpace(host) || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER"))) + { + return; + } + + // ExecuteSqlCommand request rides the gRPC front door, but the server-side + // CSrvDbConnection.ExecuteSqlCommand faults (IndexOutOfRange / native error 38) — an unmet + // DB-connection precondition the pure managed gRPC session doesn't establish (captured + // 2026-06-22). The SDK surfaces this as ProtocolEvidenceMissingException. This test pins the + // wall so a future server/registration change that lifts it is noticed. + HistorianClient client = new(BuildOptions(host)); + await Assert.ThrowsAsync(() => client.ExecuteSqlCommandAsync( + "SELECT 10 AS Num, 'alpha' AS Word UNION ALL SELECT 20, NULL", + cancellationToken: CancellationToken.None)); + } + + [Fact] + public async Task TagWriteLifecycle_OverGrpc_CreatesAddsPropRenamesDeletes() + { + string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST"); + // DESTRUCTIVE: gated on a dedicated sandbox-tag name so it never mutates a server by accident. + // Set HISTORIAN_GRPC_WRITE_SANDBOX_TAG to a throwaway tag name the test may create/rename/delete. + string? sandbox = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_WRITE_SANDBOX_TAG"); + if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(sandbox) + || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER"))) + { + return; + } + + // Exercises the full gRPC tag-config write surface end-to-end against a write-enabled (0x401) + // session, then cleans up after itself: EnsureTags -> AddTagExtendedProperties -> + // (read-back verify) -> StartJob rename -> DeleteTags. + HistorianClient client = new(BuildOptions(host)); + string renamed = sandbox + "_R"; + + try + { + bool created = await client.EnsureTagAsync( + new HistorianTagDefinition { TagName = sandbox!, DataType = HistorianDataType.Float, EngineeringUnit = "u", MaxEU = 100 }, + CancellationToken.None); + Assert.True(created, "EnsureTags over gRPC should create the sandbox tag."); + + bool propAdded = await client.AddTagExtendedPropertyAsync(sandbox!, "GrpcToolingTest", "ok", CancellationToken.None); + Assert.True(propAdded, "AddTagExtendedProperties over gRPC should succeed."); + + IReadOnlyList props = await client.GetTagExtendedPropertiesAsync(sandbox!, CancellationToken.None); + Assert.Contains(props, p => string.Equals(p.Name, "GrpcToolingTest", StringComparison.OrdinalIgnoreCase)); + + HistorianTagRenameResult rename = await client.RenameTagsAsync([(sandbox!, renamed)], CancellationToken.None); + Assert.True(rename.Accepted, $"StartJob rename over gRPC should be accepted: {rename.Error}"); + } + finally + { + // Best-effort cleanup of whichever name survives (rename is an async server job). + try { await client.DeleteTagAsync(sandbox!, CancellationToken.None); } catch { /* ignore */ } + try { await client.DeleteTagAsync(renamed, CancellationToken.None); } catch { /* ignore */ } + } + } + private static HistorianClientOptions BuildOptions(string host) { string? user = Environment.GetEnvironmentVariable("HISTORIAN_USER");