using Akka.Cluster; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Cluster.Redundancy; namespace ZB.MOM.WW.OtOpcUa.Cluster.Tests; /// /// Unit tests for — the pure ServiceLevel tiering logic. /// Moved from ControlPlane.Tests to Cluster.Tests because the type lives in Core.Cluster. /// See code-reviews/Cluster/findings.md Cluster-005. /// public sealed class ServiceLevelCalculatorTests { /// Non-Up member statuses produce ServiceLevel 0 regardless of other inputs. [Theory] [InlineData(MemberStatus.Down)] [InlineData(MemberStatus.Removed)] [InlineData(MemberStatus.Exiting)] [InlineData(MemberStatus.Leaving)] public void NotUp_returns_zero(MemberStatus status) { var sl = ServiceLevelCalculator.Compute(new(status, DbReachable: true, OpcUaProbeOk: true, Stale: false, IsDriverRoleLeader: true)); sl.ShouldBe((byte)0); } /// Up + DB reachable + probe ok + not stale + not leader = tier 240 (healthy follower). [Fact] public void Fully_healthy_non_leader_returns_240() { var sl = ServiceLevelCalculator.Compute(new(MemberStatus.Up, DbReachable: true, OpcUaProbeOk: true, Stale: false, IsDriverRoleLeader: false)); sl.ShouldBe((byte)240); } /// Up + DB reachable + probe ok + not stale + is leader = tier 250 (healthy leader, +10 bonus). [Fact] public void Fully_healthy_role_leader_returns_250() { var sl = ServiceLevelCalculator.Compute(new(MemberStatus.Up, DbReachable: true, OpcUaProbeOk: true, Stale: false, IsDriverRoleLeader: true)); sl.ShouldBe((byte)250); } /// DB reachable but data stale = tier 200 (stale). [Fact] public void Db_reachable_but_stale_returns_200() { var sl = ServiceLevelCalculator.Compute(new(MemberStatus.Up, DbReachable: true, OpcUaProbeOk: true, Stale: true, IsDriverRoleLeader: false)); sl.ShouldBe((byte)200); } /// DB unreachable AND stale = tier 100 (critically degraded). [Fact] public void Db_unreachable_and_stale_returns_100() { var sl = ServiceLevelCalculator.Compute(new(MemberStatus.Up, DbReachable: false, OpcUaProbeOk: false, Stale: true, IsDriverRoleLeader: false)); sl.ShouldBe((byte)100); } /// OPC UA probe failed while not stale falls through to the catch-all 0. [Fact] public void Opcua_probe_fail_when_not_stale_returns_zero() { var sl = ServiceLevelCalculator.Compute(new(MemberStatus.Up, DbReachable: true, OpcUaProbeOk: false, Stale: false, IsDriverRoleLeader: false)); sl.ShouldBe((byte)0); } /// Joining is treated identically to Up for grading (node is joining the cluster). [Fact] public void Joining_member_is_treated_like_Up() { var sl = ServiceLevelCalculator.Compute(new(MemberStatus.Joining, DbReachable: true, OpcUaProbeOk: true, Stale: false, IsDriverRoleLeader: false)); sl.ShouldBe((byte)240); } /// Leader bonus is clamped to 255 even if a hypothetical basis would overflow. [Fact] public void Result_is_clamped_to_255() { // basis 240 + 10 = 250, already within byte range; confirms Clamp is in path var sl = ServiceLevelCalculator.Compute(new(MemberStatus.Up, DbReachable: true, OpcUaProbeOk: true, Stale: false, IsDriverRoleLeader: true)); ((int)sl).ShouldBeLessThanOrEqualTo(255); } }