feat(redundancy): periodic HealthTick refreshes DB reachability via Ask/PipeTo

This commit is contained in:
Joseph Doherty
2026-06-15 13:15:26 -04:00
parent 5382eea9b5
commit 37b32a5623
2 changed files with 81 additions and 6 deletions
@@ -505,6 +505,30 @@ public sealed class OpcUaPublishActorTests : RuntimeActorTestBase
duration: TimeSpan.FromMilliseconds(500));
}
/// <summary>Verifies that the periodic <c>HealthTick</c> Asks the local <see cref="DbHealthProbeActor"/>
/// for status and pipes the reply back to itself — populating <c>_lastDbHealth</c> in production WITHOUT
/// any external pump (no direct <see cref="DbHealthProbeActor.DbHealthStatus"/> Tell). With a healthy DB
/// reply and a primary-leader snapshot, the calculator path therefore reaches 250.</summary>
[Fact]
public void HealthTick_asks_db_probe_and_publishes_calculator_byte()
{
var publisher = new RecordingPublisher();
var local = NodeId.Parse("primary-node");
var db = Sys.ActorOf(Akka.Actor.Props.Create(() => new StubDbHealth(
new DbHealthProbeActor.DbHealthStatus(true, DateTime.UtcNow, null))));
var actor = Sys.ActorOf(OpcUaPublishActor.PropsForTests(
serviceLevel: publisher, localNode: local, dbHealthProbe: db,
staleWindow: TimeSpan.FromSeconds(30), probeFreshnessWindow: TimeSpan.FromSeconds(30),
healthTickInterval: TimeSpan.FromMilliseconds(100)));
actor.Tell(new RedundancyStateChanged(new[]
{
new NodeRedundancyState(local, RedundancyRole.Primary,
IsClusterLeader: true, IsRoleLeaderForDriver: true, DateTime.UtcNow),
}, CorrelationId.NewId()));
// No direct DbHealthStatus Tell — the periodic HealthTick Ask must populate it → 250.
AwaitAssert(() => publisher.Levels.ShouldContain((byte)250), TimeSpan.FromSeconds(2));
}
/// <summary>Stub DB-health probe actor that answers <see cref="DbHealthProbeActor.GetStatus"/>
/// with a fixed status, so the calculator path is deterministic without a real timer.</summary>
private sealed class StubDbHealth : Akka.Actor.ReceiveActor