using ZB.MOM.WW.Health.Akka; namespace ZB.MOM.WW.Health.Akka.Tests; /// /// Covers — which role a node competes for. Pure, /// so no cluster is needed; the age-ordering half of the rule is pinned in /// against a real two-node cluster. /// public sealed class ActiveRoleResolutionTests { [Fact] public void EmptyPreference_IsUnscoped() { // Unscoped: every Up member competes for one active slot (the ScadaBridge central pattern). Assert.Null(ClusterActiveNode.ResolveActiveRole(["admin", "driver"], [])); } [Fact] public void FusedNode_AnswersForTheHighestPrecedenceRoleItCarries() { Assert.Equal( "admin", ClusterActiveNode.ResolveActiveRole(["admin", "driver", "cluster-MAIN"], ["admin", "driver"])); } [Fact] public void SingleRoleNode_AnswersForThatRole() { // The regression the OtOpcUa fix existed for: under the old role-filtered check every // driver-only node reported Healthy — "or not a role member" — so all of them called // themselves active and no consumer could find the active node of a site cluster. lmxopcua#494. Assert.Equal( "driver", ClusterActiveNode.ResolveActiveRole(["driver", "cluster-SITE-A"], ["admin", "driver"])); } [Fact] public void PreferenceOrderWins_NotTheOrderTheNodeListsItsRoles() { // Self-role order is incidental (it comes from config parsing); precedence must come from the // caller's list alone, or a fused node's answer would depend on how its roles were spelled. Assert.Equal( "admin", ClusterActiveNode.ResolveActiveRole(["driver", "admin"], ["admin", "driver"])); } [Fact] public void NodeCarryingNoneOfThePreferredRoles_ResolvesToNull() { // Reachable, not merely defensive: OtOpcUa's RoleParser admits a dev-only or cluster-role-only // node. Such a node owns no active work — the health check maps this to Unhealthy so it stays // out of the active pool. Assert.Null(ClusterActiveNode.ResolveActiveRole(["dev", "cluster-MAIN"], ["admin", "driver"])); } }