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!));
}
}