using Microsoft.Extensions.Diagnostics.HealthChecks;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Server.Diagnostics;
using ZB.MOM.WW.MxGateway.Server.Sessions;
namespace ZB.MOM.WW.MxGateway.Tests.Diagnostics;
public sealed class SessionHealthCheckTests
{
///
/// An idle gateway is healthy. This is the load-bearing case: a gateway holding no sessions is
/// the normal steady state on a host nothing dials yet, and a probe that reports red there is
/// one operators learn to ignore.
///
/// A task that represents the asynchronous operation.
[Fact]
public async Task Healthy_WhenNoSessionsAreOpen()
{
var check = new SessionHealthCheck(new SessionRegistry());
HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());
Assert.Equal(HealthStatus.Healthy, result.Status);
Assert.Equal(0, result.Data["total"]);
Assert.Equal("No MXAccess sessions are open.", result.Description);
}
/// Every session ready reports healthy, with the counts carried as entry data.
/// A task that represents the asynchronous operation.
[Fact]
public async Task Healthy_WhenAllSessionsReady()
{
var check = new SessionHealthCheck(RegistryWith(SessionState.Ready, SessionState.Ready));
HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());
Assert.Equal(HealthStatus.Healthy, result.Status);
Assert.Equal(2, result.Data["total"]);
Assert.Equal(2, result.Data["ready"]);
Assert.Equal(0, result.Data["faulted"]);
}
///
/// A faulted session alongside a usable one is degraded, not unhealthy — the gateway is still
/// serving the sessions that work.
///
/// A task that represents the asynchronous operation.
[Fact]
public async Task Degraded_WhenSomeFaultedAndSomeReady()
{
var check = new SessionHealthCheck(RegistryWith(SessionState.Ready, SessionState.Faulted));
HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());
Assert.Equal(HealthStatus.Degraded, result.Status);
Assert.Equal(1, result.Data["ready"]);
Assert.Equal(1, result.Data["faulted"]);
Assert.Contains("1 faulted", result.Description, StringComparison.Ordinal);
}
///
/// A session still starting counts as usable for grading, so a fault beside it is degraded
/// rather than unhealthy — the startup has not failed yet.
///
/// A task that represents the asynchronous operation.
[Fact]
public async Task Degraded_WhenFaultedBesideAStartingSession()
{
var check = new SessionHealthCheck(
RegistryWith(SessionState.Faulted, SessionState.StartingWorker));
HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());
Assert.Equal(HealthStatus.Degraded, result.Status);
Assert.Equal(1, result.Data["starting"]);
}
/// Every session faulted is the genuinely bad condition, and the only unhealthy one.
/// A task that represents the asynchronous operation.
[Fact]
public async Task Unhealthy_WhenEverySessionIsFaulted()
{
var check = new SessionHealthCheck(RegistryWith(SessionState.Faulted, SessionState.Faulted));
HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());
Assert.Equal(HealthStatus.Unhealthy, result.Status);
Assert.Equal(2, result.Data["faulted"]);
Assert.Equal(0, result.Data["ready"]);
}
///
/// Closed sessions linger in the registry until they are removed. They are counted separately
/// and excluded from the verdict, so a gateway whose sessions all closed cleanly is healthy —
/// not unhealthy for having zero ready ones.
///
/// A task that represents the asynchronous operation.
[Fact]
public async Task Healthy_WhenOnlyClosedSessionsRemain()
{
var check = new SessionHealthCheck(RegistryWith(SessionState.Closed, SessionState.Closed));
HealthCheckResult result = await check.CheckHealthAsync(new HealthCheckContext());
Assert.Equal(HealthStatus.Healthy, result.Status);
Assert.Equal(2, result.Data["closing"]);
Assert.Equal(0, result.Data["ready"]);
}
private static SessionRegistry RegistryWith(params SessionState[] states)
{
var registry = new SessionRegistry();
for (int i = 0; i < states.Length; i++)
{
GatewaySession session = CreateSession($"session-{i}");
session.TransitionTo(states[i]);
Assert.True(registry.TryAdd(session));
}
return registry;
}
private static GatewaySession CreateSession(string sessionId)
{
return new GatewaySession(
sessionId,
"mxaccess",
$"mxaccess-gateway-1-{sessionId}",
"nonce",
clientIdentity: null,
clientSessionName: "test-session",
clientCorrelationId: "client-correlation",
TimeSpan.FromSeconds(30),
TimeSpan.FromSeconds(5),
TimeSpan.FromSeconds(5),
DateTimeOffset.UnixEpoch);
}
}