using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Host.Health;
namespace ZB.MOM.WW.OtOpcUa.Host.Tests.Health;
///
/// Pins the rule the active tier now uses to decide which node is in charge, and the startup path
/// that must not report a booting node as a failed standby.
///
///
/// The cluster-membership half of the rule — oldest Up member, never RoleLeader — is pinned
/// against a real two-node cluster in RedundancyPrimaryElectionTests, including parity with
/// the redundancy snapshot's own election. What is left to cover here is the role-selection rule and
/// the startup guard.
///
public sealed class ClusterPrimaryHealthCheckTests
{
[Fact]
public void Fused_node_answers_for_the_admin_role()
{
// Admin wins on a node carrying both, because the cluster singletons and the AdminUI that
// Traefik routes to are pinned to that role. Answering for 'driver' here could name a
// different node the moment an admin-only or driver-only member joins the mesh.
ClusterPrimaryHealthCheck.ActiveRoleFor(["admin", "driver", "cluster-MAIN"])
.ShouldBe("admin");
}
[Fact]
public void Driver_only_node_answers_for_the_driver_role()
{
// The regression this whole change exists for. Under the previous admin-scoped check every
// site node returned Healthy — "or not a role member" — so all four called themselves
// active and no consumer could find the Primary of a site Cluster. lmxopcua#494.
ClusterPrimaryHealthCheck.ActiveRoleFor(["driver", "cluster-SITE-A"])
.ShouldBe("driver");
}
[Fact]
public void Admin_only_node_answers_for_the_admin_role()
{
ClusterPrimaryHealthCheck.ActiveRoleFor(["admin"]).ShouldBe("admin");
}
[Fact]
public void Node_in_neither_role_has_no_active_role()
{
ClusterPrimaryHealthCheck.ActiveRoleFor(["cluster-MAIN"]).ShouldBeNull();
}
[Fact]
public async Task No_actor_system_is_degraded_not_unhealthy()
{
// A node that is still booting has not lost an election. Reporting Unhealthy here would make
// every start look like a failed standby, and — now that the tier is a real 503 — would pull
// the node out of Traefik's pool on the way up.
var check = new ClusterPrimaryHealthCheck(new ServiceCollection().BuildServiceProvider());
var result = await check.CheckHealthAsync(new HealthCheckContext(), CancellationToken.None);
result.Status.ShouldBe(HealthStatus.Degraded);
}
}