From 42eb96af9e3f478d85b419dbbbc8f1a7e5404f75 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sun, 11 Oct 2026 10:43:15 -0400 Subject: [PATCH] test(runtime): #678.2 #672 initial-state unreachable peer + strengthen Self_unreachable_is_ignored --- ...iverHostActorHistorizationRoleViewTests.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistorizationRoleViewTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistorizationRoleViewTests.cs index 0ca0dcac..dee3ccec 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistorizationRoleViewTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverHostActorHistorizationRoleViewTests.cs @@ -223,10 +223,30 @@ public sealed class DriverHostActorHistorizationRoleViewTests : RuntimeActorTest host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Peer672))); AwaitAssert(() => hist.Published.ShouldBe([false, true]), duration: Timeout); - // And self "returning" does not close what the peer's loss opened. + // Self "returning" is ignored too, and it does not close what the peer's loss opened. Events are + // processed in order, so the peer's return (which MUST close the gate) is the only thing that may + // appear next: had the self event published anything it would sit between `true` and `false`. host.Tell(new ClusterEvent.ReachableMember(MemberAt(Self672))); + host.Tell(new ClusterEvent.ReachableMember(MemberAt(Peer672))); + AwaitAssert(() => hist.Published.ShouldBe([false, true, false]), duration: Timeout); + } + + /// + /// A host that starts while its peer is ALREADY unreachable (the InitialStateAsEvents replay + /// delivers the UnreachableMember before the first redundancy snapshot) must still treat that + /// peer as unreachable when the snapshot naming it Primary finally arrives. + /// + [Fact] + public void A_peer_unreachable_before_the_first_snapshot_still_opens_capture() + { + var hist = new RecordingHistorizationView(); + IActorRef host = SpawnHost672(hist, others: null, captureWhilePrimaryUnreachable: true); + + host.Tell(new ClusterEvent.UnreachableMember(MemberAt(Peer672))); host.Tell(Snapshot((Self672, RedundancyRole.Secondary), (Peer672, RedundancyRole.Primary))); - AwaitAssert(() => hist.Published.ShouldBe([false, true, true]), duration: Timeout); + + // Exactly one publication, and it is `true`: no spurious `false` from treating the peer as reachable. + AwaitAssert(() => hist.Published.ShouldBe([true]), duration: Timeout); } /// CaptureWhilePrimaryUnreachable = false restores the pre-#672 behaviour.