108 lines
4.8 KiB
C#
108 lines
4.8 KiB
C#
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));
|
|
}
|
|
}
|