using Akka.Actor;
using Akka.Cluster;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using ZB.MOM.WW.Health.Akka;
namespace ZB.MOM.WW.Health.Akka.Tests;
///
/// Table-driven tests for the pure status-mapping function inside .
/// The two presets ( and
/// ) are the convergence targets for ScadaBridge
/// and OtOpcUa respectively; every is exercised so a drift in either
/// preset fails loudly. Also covers the startup-safety null-guard on .
///
public sealed class AkkaClusterStatusPolicyTests
{
public static IEnumerable DefaultCases() => new[]
{
new object[] { MemberStatus.Up, HealthStatus.Healthy },
new object[] { MemberStatus.Joining, HealthStatus.Healthy },
new object[] { MemberStatus.Leaving, HealthStatus.Degraded },
new object[] { MemberStatus.Exiting, HealthStatus.Degraded },
new object[] { MemberStatus.WeaklyUp, HealthStatus.Unhealthy },
new object[] { MemberStatus.Down, HealthStatus.Unhealthy },
new object[] { MemberStatus.Removed, HealthStatus.Unhealthy },
new object[] { (MemberStatus)99, HealthStatus.Unhealthy }, // unknown / future status
};
[Theory]
[MemberData(nameof(DefaultCases))]
public void Default_MapsEveryStatus(MemberStatus status, HealthStatus expected)
{
Assert.Equal(expected, AkkaClusterStatusPolicy.Default.Evaluate(status));
}
public static IEnumerable OtOpcUaCompatCases() => new[]
{
new object[] { MemberStatus.Up, HealthStatus.Healthy },
new object[] { MemberStatus.Joining, HealthStatus.Degraded },
new object[] { MemberStatus.Leaving, HealthStatus.Degraded },
new object[] { MemberStatus.Exiting, HealthStatus.Degraded },
new object[] { MemberStatus.WeaklyUp, HealthStatus.Degraded },
new object[] { MemberStatus.Down, HealthStatus.Degraded },
new object[] { MemberStatus.Removed, HealthStatus.Degraded },
new object[] { (MemberStatus)99, HealthStatus.Degraded }, // unknown / future status
};
[Theory]
[MemberData(nameof(OtOpcUaCompatCases))]
public void OtOpcUaCompat_OnlyUpIsHealthy(MemberStatus status, HealthStatus expected)
{
Assert.Equal(expected, AkkaClusterStatusPolicy.OtOpcUaCompat.Evaluate(status));
}
[Fact]
public void CustomPolicy_UsesSuppliedFunc()
{
var policy = new AkkaClusterStatusPolicy(_ => HealthStatus.Unhealthy);
Assert.Equal(HealthStatus.Unhealthy, policy.Evaluate(MemberStatus.Up));
}
[Fact]
public async Task HealthCheck_NoActorSystem_ReturnsDegraded()
{
var provider = new ServiceCollection().BuildServiceProvider();
var check = new AkkaClusterHealthCheck(provider, AkkaClusterStatusPolicy.Default);
var result = await check.CheckHealthAsync(NewContext(check));
Assert.Equal(HealthStatus.Degraded, result.Status);
}
[Fact]
public async Task HealthCheck_ActorSystemPresentButClusterInaccessible_ReturnsDegraded()
{
// A plain (non-clustered) ActorSystem exists in DI, but Akka.Cluster is not configured,
// so Cluster.Get(system) throws a ConfigurationException — the startup race the spec calls
// out. The check must return Degraded, not let the exception escape (→ Unhealthy via the host).
using var system = ActorSystem.Create("plain-no-cluster");
try
{
var provider = new ServiceCollection()
.AddSingleton(system)
.BuildServiceProvider();
var check = new AkkaClusterHealthCheck(provider, AkkaClusterStatusPolicy.Default);
var result = await check.CheckHealthAsync(NewContext(check));
Assert.Equal(HealthStatus.Degraded, result.Status);
}
finally
{
await system.Terminate();
}
}
private static HealthCheckContext NewContext(IHealthCheck check) => new()
{
Registration = new HealthCheckRegistration("akka-cluster", check, HealthStatus.Unhealthy, tags: null),
};
}