feat(historian): backend registry, ServerHistorian:Backend, Host wiring via provider

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 13:05:53 -04:00
parent 65e6352dad
commit 31d342eff3
7 changed files with 186 additions and 9 deletions
@@ -33,6 +33,42 @@ public sealed class ServerHistorianOptionsValidatorTests
}
/// <summary>Enabled historian with an empty endpoint fails (would crash-loop in the factory).</summary>
/// <summary>An unknown backend always fails, even when nothing consumes the section.</summary>
[Fact]
public void Unknown_backend_fails_even_when_not_consumed()
{
var result = Sut().Validate(null, new ServerHistorianOptions { Backend = "Nope" });
result.Failed.ShouldBeTrue();
result.Failures.ShouldContain(f => f.Contains("ServerHistorian:Backend") && f.Contains("Nope") && f.Contains("Gateway"));
}
/// <summary>Backend names are case-insensitive; the gateway checks still apply (control: no ApiKey fails).</summary>
[Fact]
public void Lowercase_gateway_backend_still_enforces_gateway_checks()
{
var result = Sut().Validate(null, new ServerHistorianOptions
{
Backend = "gateway", Enabled = true, Endpoint = "https://host:5222", ApiKey = "",
});
result.Failed.ShouldBeTrue();
result.Failures.ShouldContain(f => f.Contains("ServerHistorian:ApiKey"));
}
/// <summary>
/// The gateway-only checks (Endpoint, ApiKey, UseTls) do not apply to a non-gateway backend. Needs a
/// registered non-gateway backend, which does not exist yet, so this asserts the gate through
/// <see cref="ServerHistorianOptions.IsGatewayBackend"/> and the registry's unknown-name failure only.
/// </summary>
[Fact]
public void Gateway_checks_are_gated_on_backend_being_gateway()
{
new ServerHistorianOptions { Backend = "SqlServer" }.IsGatewayBackend.ShouldBeFalse();
new ServerHistorianOptions { Backend = "gateway" }.IsGatewayBackend.ShouldBeTrue();
new ServerHistorianOptions { Backend = "SqlServer", Enabled = true }.Validate().ShouldBeEmpty();
}
[Fact]
public void Enabled_with_empty_endpoint_fails()
{