fix(cluster): all four role-scoped validators read the authority — 'Driver' no longer skips every driver rule (archreview 01/C-9)
This commit is contained in:
@@ -23,7 +23,8 @@ public class ConfigSourceOptionsValidatorTests
|
||||
|
||||
var configuration = new ConfigurationBuilder().AddInMemoryCollection(pairs).Build();
|
||||
|
||||
return new ConfigSourceOptionsValidator(configuration).Validate(ConfigSourceOptions.SectionName, o);
|
||||
return new ConfigSourceOptionsValidator(EffectiveRoles.Resolve(configuration))
|
||||
.Validate(ConfigSourceOptions.SectionName, o);
|
||||
}
|
||||
|
||||
private static ConfigSourceOptions ValidFetch() => new()
|
||||
|
||||
@@ -39,7 +39,7 @@ public class MeshTransportOptionsValidatorTests
|
||||
|
||||
var configuration = new ConfigurationBuilder().AddInMemoryCollection(pairs).Build();
|
||||
|
||||
return new MeshTransportOptionsValidator(configuration)
|
||||
return new MeshTransportOptionsValidator(configuration, EffectiveRoles.Resolve(configuration))
|
||||
.Validate(MeshTransportOptions.SectionName, o);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Cluster.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// R3-04 (01/C-9): a mixed-case role must fire the SAME fail-fast rule the canonical spelling
|
||||
/// fires, on ALL FOUR role-scoped validators. Before the fix, three of the four
|
||||
/// (<see cref="MeshTransportOptionsValidator"/>, <see cref="ConfigSourceOptionsValidator"/>,
|
||||
/// <see cref="TelemetryOptionsValidator"/>) compared roles ordinally and silently PASSED a
|
||||
/// misconfigured node spelled <c>Driver</c>/<c>Admin</c> — the load-bearing assertion in every
|
||||
/// case below is the FAILURE, not the pass, because a validator whose driver/admin branch never
|
||||
/// runs also "passes" vacuously.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="SplitTopologyTransportValidator"/> already normalized before this fix (its own
|
||||
/// comment names the hazard) — its case here is the BUILT-IN falsification control: it must have
|
||||
/// been passing (i.e. correctly failing the misconfigured node) even before T4 landed, proving
|
||||
/// the Theory shape itself is capable of catching a real regression.
|
||||
/// </remarks>
|
||||
[Collection(RoleEnvironmentCollection.Name)]
|
||||
public sealed class RoleCaseVariantValidatorTests
|
||||
{
|
||||
public static readonly TheoryData<string> DriverSpellings = new()
|
||||
{
|
||||
"driver", "Driver", " driver ", "DRIVER",
|
||||
};
|
||||
|
||||
private static IConfiguration Config(params (string Key, string Value)[] pairs) =>
|
||||
new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(pairs.ToDictionary(p => p.Key, p => (string?)p.Value))
|
||||
.Build();
|
||||
|
||||
// ---- SplitTopologyTransportValidator: the built-in control (already normalized pre-fix) -------
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DriverSpellings))]
|
||||
public void SplitTopology_fails_on_Dps_mesh_transport_for_every_driver_spelling(string driverSpelling)
|
||||
{
|
||||
var configuration = Config(
|
||||
("Cluster:Roles:0", driverSpelling),
|
||||
("Cluster:Roles:1", "cluster-SITE-A"),
|
||||
("MeshTransport:Mode", MeshTransportOptions.ModeDps),
|
||||
("Telemetry:Mode", TelemetryOptions.ModeGrpc));
|
||||
|
||||
var result = new SplitTopologyTransportValidator(configuration, EffectiveRoles.Resolve(configuration))
|
||||
.Validate(MeshTransportOptions.SectionName, new MeshTransportOptions());
|
||||
|
||||
result.Failed.ShouldBeTrue();
|
||||
result.FailureMessage.ShouldContain("MeshTransport:Mode");
|
||||
}
|
||||
|
||||
// ---- MeshTransportOptionsValidator: GrpcPsk required for a driver node under Grpc mode --------
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DriverSpellings))]
|
||||
public void MeshTransport_Grpc_driver_node_without_a_psk_fails_for_every_driver_spelling(string driverSpelling)
|
||||
{
|
||||
var configuration = Config(("Cluster:Roles:0", driverSpelling), ("Cluster:Roles:1", "cluster-SITE-A"));
|
||||
var options = new MeshTransportOptions
|
||||
{
|
||||
Mode = MeshTransportOptions.ModeGrpc,
|
||||
GrpcPsk = null,
|
||||
CentralGrpcEndpoints = ["http://central-1:4055"],
|
||||
};
|
||||
|
||||
var result = new MeshTransportOptionsValidator(configuration, EffectiveRoles.Resolve(configuration))
|
||||
.Validate(MeshTransportOptions.SectionName, options);
|
||||
|
||||
result.Failed.ShouldBeTrue();
|
||||
result.FailureMessage.ShouldContain("MeshTransport:GrpcPsk");
|
||||
}
|
||||
|
||||
// ---- ConfigSourceOptionsValidator: a driver-only node on Direct fails ---------------------------
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DriverSpellings))]
|
||||
public void ConfigSource_driver_only_node_on_Direct_fails_for_every_driver_spelling(string driverSpelling)
|
||||
{
|
||||
var configuration = Config(("Cluster:Roles:0", driverSpelling));
|
||||
var options = new ConfigSourceOptions { Mode = ConfigSourceOptions.ModeDirect };
|
||||
|
||||
var result = new ConfigSourceOptionsValidator(EffectiveRoles.Resolve(configuration))
|
||||
.Validate(ConfigSourceOptions.SectionName, options);
|
||||
|
||||
result.Failed.ShouldBeTrue();
|
||||
result.FailureMessage.ShouldContain(ConfigSourceOptions.ModeFetchAndCache);
|
||||
}
|
||||
|
||||
// ---- TelemetryOptionsValidator: a driver node under Grpc mode needs a listen port ---------------
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DriverSpellings))]
|
||||
public void Telemetry_Grpc_driver_node_with_no_listen_port_fails_for_every_driver_spelling(string driverSpelling)
|
||||
{
|
||||
var configuration = Config(("Cluster:Roles:0", driverSpelling));
|
||||
var options = new TelemetryOptions { Mode = TelemetryOptions.ModeGrpc, ApiKey = "k" };
|
||||
|
||||
var result = new TelemetryOptionsValidator(EffectiveRoles.Resolve(configuration))
|
||||
.Validate(TelemetryOptions.SectionName, options);
|
||||
|
||||
result.Failed.ShouldBeTrue();
|
||||
result.FailureMessage.ShouldContain(nameof(TelemetryOptions.GrpcListenPort));
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ public class SplitTopologyTransportValidatorTests
|
||||
|
||||
// The validated options instance is never read (all four values come from IConfiguration), so a
|
||||
// default MeshTransportOptions is a fine stand-in.
|
||||
return new SplitTopologyTransportValidator(configuration)
|
||||
return new SplitTopologyTransportValidator(configuration, EffectiveRoles.Resolve(configuration))
|
||||
.Validate(MeshTransportOptions.SectionName, new MeshTransportOptions());
|
||||
}
|
||||
|
||||
@@ -246,7 +246,7 @@ public class SplitTopologyTransportValidatorTests
|
||||
var pairs = new Dictionary<string, string?> { ["MeshTransport:Mode"] = MeshTransportOptions.ModeDps };
|
||||
var configuration = new ConfigurationBuilder().AddInMemoryCollection(pairs).Build();
|
||||
|
||||
var result = new SplitTopologyTransportValidator(configuration)
|
||||
var result = new SplitTopologyTransportValidator(configuration, EffectiveRoles.Resolve(configuration))
|
||||
.Validate(MeshTransportOptions.SectionName, new MeshTransportOptions());
|
||||
|
||||
result.Failed.ShouldBeTrue();
|
||||
|
||||
@@ -23,7 +23,8 @@ public class TelemetryOptionsValidatorTests
|
||||
|
||||
var configuration = new ConfigurationBuilder().AddInMemoryCollection(pairs).Build();
|
||||
|
||||
return new TelemetryOptionsValidator(configuration).Validate(TelemetryOptions.SectionName, o);
|
||||
return new TelemetryOptionsValidator(EffectiveRoles.Resolve(configuration))
|
||||
.Validate(TelemetryOptions.SectionName, o);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user