R1.3 GetServerTimeZoneAsync over gRPC (live-verified); R1.4 bounded out on gRPC
Live-probed both R1.3 and R1.4 against a real 2023 R2 server over the gRPC StatusService; implemented the one that carries an evidence-backed value. R1.3 GetServerTimeZoneAsync — SHIPPED: - StatusService.GetSystemTimeZoneName(uiHandle) returns the real server zone over RemoteGrpc (the 2020 WCF op is a client-side stub returning empty). - HistorianGrpcStatusClient.GetSystemTimeZoneNameAsync -> dialect routing -> public HistorianClient.GetServerTimeZoneAsync. Non-gRPC transports fail closed with ProtocolEvidenceMissingException (no empty-string lie). - Golden message-shape unit test + non-gRPC guardrail unit test + gated live test. 271 unit tests pass. R1.4 GetHistorianInfoAsync (EventStorageMode) — bounded out on gRPC too: - gRPC GetHistorianInfo is the same named-value query as 2020 WCF (only HistorianVersion resolves); EventStorageMode + 7 variants fail on both GetHistorianInfo and GetSystemParameter. The 518-byte struct is filled by a native vtable+648 HCAL call, not the gRPC op (per the 2023 R2 decompile), so the field is never on the wire. Not shipped on any transport. Closes the roadmap's open "build against a live 2023 R2 server" caveat. Also correct the stale M3 roadmap section: D2 already proved Transaction.AddNonStreamValues* rides the storage-engine pipe (STransactPipeClient2 -> aaStorageEngine), not WCF — same wall as R4.2 — so M3-over-WCF is blocked, not "the path that is NOT the gated cache push". Docs: hcal-roadmap.md, wcf-historian-info.md, wcf-status-localhost.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
@@ -66,6 +66,22 @@ public sealed class HistorianGrpcIntegrationTests
|
||||
Assert.False(string.IsNullOrWhiteSpace(version));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetServerTimeZoneAsync_OverGrpc_ReturnsZone()
|
||||
{
|
||||
string? host = Environment.GetEnvironmentVariable("HISTORIAN_GRPC_HOST");
|
||||
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HISTORIAN_USER")))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// R1.3: gRPC StatusService.GetSystemTimeZoneName returns the real server zone (the 2020 WCF
|
||||
// op is a stub). Live-verified value: "Eastern Daylight Time".
|
||||
HistorianClient client = new(BuildOptions(host));
|
||||
string? zone = await client.GetServerTimeZoneAsync(CancellationToken.None);
|
||||
Assert.False(string.IsNullOrWhiteSpace(zone));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetTagMetadataAsync_OverGrpc_ReturnsRequestedTag()
|
||||
{
|
||||
|
||||
@@ -135,6 +135,39 @@ public sealed class HistorianGrpcTransportTests
|
||||
Assert.Equal("20.0.000", response.StrParameterValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetSystemTimeZoneNameMessages_CarryHandleAndValue_AsStatusClientExpects()
|
||||
{
|
||||
// R1.3 sends {uiHandle} and reads strSystemTimeZoneName when status succeeds — no buffer.
|
||||
var request = ArchestrA.Grpc.Contract.Status.GetSystemTimeZoneNameRequest.Parser.ParseFrom(
|
||||
new ArchestrA.Grpc.Contract.Status.GetSystemTimeZoneNameRequest { UiHandle = 11 }.ToByteArray());
|
||||
Assert.Equal(11u, request.UiHandle);
|
||||
|
||||
var response = ArchestrA.Grpc.Contract.Status.GetSystemTimeZoneNameResponse.Parser.ParseFrom(
|
||||
new ArchestrA.Grpc.Contract.Status.GetSystemTimeZoneNameResponse
|
||||
{
|
||||
Status = new ArchestrA.Grpc.Contract.RequestStatus.Status { BSuccess = true },
|
||||
StrSystemTimeZoneName = "Eastern Daylight Time"
|
||||
}.ToByteArray());
|
||||
Assert.True(response.Status.BSuccess);
|
||||
Assert.Equal("Eastern Daylight Time", response.StrSystemTimeZoneName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetServerTimeZoneAsync_OnNonGrpcTransport_ThrowsEvidenceMissing()
|
||||
{
|
||||
// The 2020 WCF GetSystemTimeZoneName is a client-side stub (empty value); R1.3 only has an
|
||||
// evidence-backed value on the gRPC front door, so the non-gRPC path must fail closed.
|
||||
await using var client = new HistorianClient(new HistorianClientOptions
|
||||
{
|
||||
Host = "histserver",
|
||||
Transport = HistorianTransport.LocalPipe,
|
||||
IntegratedSecurity = true
|
||||
});
|
||||
|
||||
await Assert.ThrowsAsync<ProtocolEvidenceMissingException>(() => client.GetServerTimeZoneAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildTagNamesBuffer_EncodesCountThenLengthPrefixedUtf16Names()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user