using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Cluster.Tests; /// /// A split-topology node (one carrying a cluster-{ClusterId} role) left on a DPS transport /// mode does not error — the transport simply delivers nothing across the mesh boundary, and the /// symptom is a deployment that never applies or telemetry that never arrives. These tests make /// that misconfiguration a loud host-start failure instead. A node with no cluster-role is exempt /// (legacy / single-mesh / test), and the validator is a no-op for it. /// public class SplitTopologyTransportValidatorTests { private static ValidateOptionsResult Validate( string? meshMode, string? telemetryMode, string? telemetryDialMode, params string[] roles) { var pairs = new Dictionary(); for (var i = 0; i < roles.Length; i++) { pairs[$"Cluster:Roles:{i}"] = roles[i]; } if (meshMode is not null) pairs["MeshTransport:Mode"] = meshMode; if (telemetryMode is not null) pairs["Telemetry:Mode"] = telemetryMode; if (telemetryDialMode is not null) pairs["TelemetryDial:Mode"] = telemetryDialMode; var configuration = new ConfigurationBuilder().AddInMemoryCollection(pairs).Build(); // 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) .Validate(MeshTransportOptions.SectionName, new MeshTransportOptions()); } [Fact] public void Cluster_role_driver_node_on_Dps_mesh_transport_fails() { // Red-before-green anchor: a split-topology node on DistributedPubSub cannot deliver commands. var result = Validate( meshMode: MeshTransportOptions.ModeDps, telemetryMode: TelemetryOptions.ModeGrpc, telemetryDialMode: null, "driver", "cluster-SITE-A"); result.Failed.ShouldBeTrue(); result.FailureMessage.ShouldContain("MeshTransport:Mode"); result.FailureMessage.ShouldContain(MeshTransportOptions.ModeClusterClient); result.FailureMessage.ShouldContain("SITE-A"); } [Fact] public void Cluster_role_driver_node_fully_configured_passes() { Validate( meshMode: MeshTransportOptions.ModeClusterClient, telemetryMode: TelemetryOptions.ModeGrpc, telemetryDialMode: null, "driver", "cluster-SITE-A").Succeeded.ShouldBeTrue(); } [Fact] public void Cluster_role_admin_node_fully_configured_passes() { Validate( meshMode: MeshTransportOptions.ModeClusterClient, telemetryMode: null, telemetryDialMode: TelemetryDialOptions.ModeGrpc, "admin", "cluster-SITE-A").Succeeded.ShouldBeTrue(); } [Fact] public void Cluster_role_driver_node_on_Dps_telemetry_fails() { var result = Validate( meshMode: MeshTransportOptions.ModeClusterClient, telemetryMode: TelemetryOptions.ModeDps, telemetryDialMode: null, "driver", "cluster-SITE-A"); result.Failed.ShouldBeTrue(); result.FailureMessage.ShouldContain("Telemetry:Mode"); result.FailureMessage.ShouldContain(TelemetryOptions.ModeGrpc); } [Fact] public void Cluster_role_admin_node_on_Dps_telemetry_dial_fails() { var result = Validate( meshMode: MeshTransportOptions.ModeClusterClient, telemetryMode: null, telemetryDialMode: TelemetryDialOptions.ModeDps, "admin", "cluster-SITE-A"); result.Failed.ShouldBeTrue(); result.FailureMessage.ShouldContain("TelemetryDial:Mode"); result.FailureMessage.ShouldContain(TelemetryDialOptions.ModeGrpc); } [Fact] public void Fused_cluster_role_node_needs_all_three_to_pass() { Validate( meshMode: MeshTransportOptions.ModeClusterClient, telemetryMode: TelemetryOptions.ModeGrpc, telemetryDialMode: TelemetryDialOptions.ModeGrpc, "admin", "driver", "cluster-MAIN").Succeeded.ShouldBeTrue(); } [Fact] public void Fused_cluster_role_node_missing_telemetry_grpc_fails() { var result = Validate( meshMode: MeshTransportOptions.ModeClusterClient, telemetryMode: TelemetryOptions.ModeDps, telemetryDialMode: TelemetryDialOptions.ModeGrpc, "admin", "driver", "cluster-MAIN"); result.Failed.ShouldBeTrue(); result.FailureMessage.ShouldContain("Telemetry:Mode"); } [Fact] public void Fused_cluster_role_node_missing_telemetry_dial_grpc_fails() { var result = Validate( meshMode: MeshTransportOptions.ModeClusterClient, telemetryMode: TelemetryOptions.ModeGrpc, telemetryDialMode: TelemetryDialOptions.ModeDps, "admin", "driver", "cluster-MAIN"); result.Failed.ShouldBeTrue(); result.FailureMessage.ShouldContain("TelemetryDial:Mode"); } [Fact] public void Non_cluster_role_node_on_all_Dps_passes() { // The exemption that keeps the validator a no-op for every legacy / single-mesh / test node: // no cluster-{ClusterId} role means the split-topology rule does not apply at all. Validate( meshMode: MeshTransportOptions.ModeDps, telemetryMode: TelemetryOptions.ModeDps, telemetryDialMode: TelemetryDialOptions.ModeDps, "admin", "driver").Succeeded.ShouldBeTrue(); } [Fact] public void Node_with_no_roles_on_all_Dps_passes() { // Absolute default: nothing declared, nothing to enforce. Validate( meshMode: MeshTransportOptions.ModeDps, telemetryMode: TelemetryOptions.ModeDps, telemetryDialMode: TelemetryDialOptions.ModeDps).Succeeded.ShouldBeTrue(); } [Fact] public void Unset_modes_on_a_cluster_role_node_fail_as_the_Dps_default() { // A split node that never sets the mode keys inherits the Dps defaults — which the split // topology cannot use. Leaving them unset must fail exactly as setting them to Dps would. var result = Validate( meshMode: null, telemetryMode: null, telemetryDialMode: null, "admin", "driver", "cluster-MAIN"); result.Failed.ShouldBeTrue(); result.FailureMessage.ShouldContain("MeshTransport:Mode"); } [Fact] public void Mode_matching_is_case_insensitive() { Validate( meshMode: "clusterclient", telemetryMode: "grpc", telemetryDialMode: "grpc", "admin", "driver", "cluster-MAIN").Succeeded.ShouldBeTrue(); } }