namespace AVEVA.Historian.Client.Tests; /// /// Unit coverage for the R0.6 connect-time interface-version gate. The supported versions /// (History=11, Retrieval=4, Transaction=2) are evidence-based, captured from a live AVEVA /// Historian 2020 server via the reverse-engineering wcf-probe command. /// public sealed class HistorianServerVersionGateTests { private static HistorianClientOptions Options(bool verify = true) => new() { Host = "histserver", IntegratedSecurity = true, VerifyServerInterfaceVersion = verify }; [Fact] public void VerifyServerInterfaceVersion_DefaultsToTrue() { Assert.True(new HistorianClientOptions { Host = "h" }.VerifyServerInterfaceVersion); } [Fact] public void Validate_MatchingVersion_DoesNotThrow() { // Each value-gated service accepts exactly the version this SDK targets. HistorianServerVersionGate.Validate(HistorianServiceInterface.History, HistorianServerVersionGate.HistoryInterfaceVersion, Options()); HistorianServerVersionGate.Validate(HistorianServiceInterface.Retrieval, HistorianServerVersionGate.RetrievalInterfaceVersion, Options()); HistorianServerVersionGate.Validate(HistorianServiceInterface.Transaction, HistorianServerVersionGate.TransactionInterfaceVersion, Options()); } [Fact] public void Validate_MismatchedVersion_ThrowsProtocolEvidenceMissing() { // (service, wrong version) cases — one below and one above each expected value. (HistorianServiceInterface Service, uint Version)[] cases = [ (HistorianServiceInterface.History, 10u), (HistorianServiceInterface.History, 12u), (HistorianServiceInterface.Retrieval, 3u), (HistorianServiceInterface.Retrieval, 5u), (HistorianServiceInterface.Transaction, 1u), ]; foreach ((HistorianServiceInterface service, uint version) in cases) { ProtocolEvidenceMissingException ex = Assert.Throws( () => HistorianServerVersionGate.Validate(service, version, Options())); // The message must name the reported version, the expected version, and the bypass knob. Assert.Contains(version.ToString(System.Globalization.CultureInfo.InvariantCulture), ex.Operation); Assert.Contains(HistorianServerVersionGate.ExpectedVersion(service).ToString(System.Globalization.CultureInfo.InvariantCulture), ex.Operation); Assert.Contains(nameof(HistorianClientOptions.VerifyServerInterfaceVersion), ex.Operation); } } [Fact] public void Validate_VerificationDisabled_NeverThrows() { // A wildly wrong version is tolerated when the operator opts out (e.g. bringing up a // 2023 R2 gRPC server whose reported integers have not yet been captured). HistorianServerVersionGate.Validate(HistorianServiceInterface.History, 999u, Options(verify: false)); HistorianServerVersionGate.Validate(HistorianServiceInterface.Retrieval, 0u, Options(verify: false)); } [Theory] [InlineData(0u)] [InlineData(7u)] [InlineData(999u)] public void Validate_StatusService_IsReachabilityOnly_NeverThrows(uint anyVersion) { // Status' GetInterfaceVersion returns 0 on the live server; it is not value-gated. HistorianServerVersionGate.Validate(HistorianServiceInterface.Status, anyVersion, Options()); Assert.False(HistorianServerVersionGate.IsValueGated(HistorianServiceInterface.Status)); } [Fact] public void IsValueGated_HistoryRetrievalTransaction_AreGated() { Assert.True(HistorianServerVersionGate.IsValueGated(HistorianServiceInterface.History)); Assert.True(HistorianServerVersionGate.IsValueGated(HistorianServiceInterface.Retrieval)); Assert.True(HistorianServerVersionGate.IsValueGated(HistorianServiceInterface.Transaction)); } [Fact] public void ExpectedVersion_Status_Throws() { Assert.Throws( () => HistorianServerVersionGate.ExpectedVersion(HistorianServiceInterface.Status)); } [Fact] public void Validate_NullOptions_Throws() { Assert.Throws( () => HistorianServerVersionGate.Validate(HistorianServiceInterface.History, 11u, null!)); } }