9d1e60c00c
ConfigSource:Mode = Direct (default, read central SQL) | FetchAndCache (fetch the artifact from central over gRPC, read LocalDb). The validator fails host start on a FetchAndCache shape that cannot fetch — no endpoints, a non-http(s) endpoint, no shared key, or a non-positive timeout — because each otherwise surfaces as a silent absence (a deploy that never applies). ConfigServe (the central serve surface) takes a plain Configure: a 0 port is a valid "disabled". Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
121 lines
3.6 KiB
C#
121 lines
3.6 KiB
C#
using Microsoft.Extensions.Options;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Cluster.Tests;
|
|
|
|
/// <summary>
|
|
/// A <c>FetchAndCache</c> node with no endpoint or no key does not error — it simply never
|
|
/// applies a deployment, and the deploy times out naming a node that is otherwise healthy. These
|
|
/// tests make that misconfiguration a loud host-start failure instead.
|
|
/// </summary>
|
|
public class ConfigSourceOptionsValidatorTests
|
|
{
|
|
private static ValidateOptionsResult Validate(ConfigSourceOptions o) =>
|
|
new ConfigSourceOptionsValidator().Validate(ConfigSourceOptions.SectionName, o);
|
|
|
|
private static ConfigSourceOptions ValidFetch() => new()
|
|
{
|
|
Mode = ConfigSourceOptions.ModeFetchAndCache,
|
|
CentralFetchEndpoints = ["http://central-1:4055", "http://central-2:4055"],
|
|
ApiKey = "shared-key",
|
|
FetchTimeoutSeconds = 30,
|
|
};
|
|
|
|
[Fact]
|
|
public void Default_options_are_valid()
|
|
{
|
|
// The default is Direct with everything empty — the pre-Phase-3 behaviour. A node that never
|
|
// sets this section must validate, which is the premise of the dark switch.
|
|
Validate(new ConfigSourceOptions()).Succeeded.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Unknown_mode_fails()
|
|
{
|
|
var result = Validate(new ConfigSourceOptions { Mode = "cache" });
|
|
|
|
result.Failed.ShouldBeTrue();
|
|
result.FailureMessage.ShouldContain("cache");
|
|
}
|
|
|
|
[Fact]
|
|
public void Mode_matching_is_case_insensitive()
|
|
{
|
|
Validate(new ConfigSourceOptions
|
|
{
|
|
Mode = "fetchandcache",
|
|
CentralFetchEndpoints = ["http://central-1:4055"],
|
|
ApiKey = "k",
|
|
}).Succeeded.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void FetchAndCache_with_no_endpoints_fails()
|
|
{
|
|
var o = ValidFetch();
|
|
o.CentralFetchEndpoints = [];
|
|
|
|
var result = Validate(o);
|
|
|
|
result.Failed.ShouldBeTrue();
|
|
result.FailureMessage.ShouldContain(nameof(ConfigSourceOptions.CentralFetchEndpoints));
|
|
}
|
|
|
|
[Fact]
|
|
public void FetchAndCache_with_a_non_http_endpoint_fails()
|
|
{
|
|
var o = ValidFetch();
|
|
o.CentralFetchEndpoints = ["central-1:4055"];
|
|
|
|
var result = Validate(o);
|
|
|
|
result.Failed.ShouldBeTrue();
|
|
result.FailureMessage.ShouldContain("central-1:4055");
|
|
}
|
|
|
|
[Fact]
|
|
public void FetchAndCache_with_an_empty_key_fails()
|
|
{
|
|
var o = ValidFetch();
|
|
o.ApiKey = "";
|
|
|
|
var result = Validate(o);
|
|
|
|
result.Failed.ShouldBeTrue();
|
|
result.FailureMessage.ShouldContain(nameof(ConfigSourceOptions.ApiKey));
|
|
}
|
|
|
|
[Fact]
|
|
public void FetchAndCache_with_a_non_positive_timeout_fails()
|
|
{
|
|
var o = ValidFetch();
|
|
o.FetchTimeoutSeconds = 0;
|
|
|
|
var result = Validate(o);
|
|
|
|
result.Failed.ShouldBeTrue();
|
|
result.FailureMessage.ShouldContain(nameof(ConfigSourceOptions.FetchTimeoutSeconds));
|
|
}
|
|
|
|
[Fact]
|
|
public void A_fully_populated_FetchAndCache_config_is_valid()
|
|
{
|
|
Validate(ValidFetch()).Succeeded.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Direct_ignores_the_empty_fetch_surface()
|
|
{
|
|
// Under Direct the endpoints/key are never read, so their emptiness must not fail validation —
|
|
// otherwise every admin-only and every Direct node would be forced to carry fetch config.
|
|
Validate(new ConfigSourceOptions
|
|
{
|
|
Mode = ConfigSourceOptions.ModeDirect,
|
|
CentralFetchEndpoints = [],
|
|
ApiKey = "",
|
|
FetchTimeoutSeconds = 0,
|
|
}).Succeeded.ShouldBeTrue();
|
|
}
|
|
}
|