using NATS.Server.Raft; namespace NATS.Server.Raft.Tests.Raft; public class RaftNodeParityBatch2Tests { private static RaftNode ElectSingleNodeLeader() { var node = new RaftNode("n1"); node.ConfigureCluster([node]); node.StartElection(1); node.IsLeader.ShouldBeTrue(); return node; } [Fact] public void Leader_tracking_flags_update_on_election_and_heartbeat() { var node1 = new RaftNode("n1"); var node2 = new RaftNode("n2"); var node3 = new RaftNode("n3"); node1.ConfigureCluster([node1, node2, node3]); node2.ConfigureCluster([node1, node2, node3]); node3.ConfigureCluster([node1, node2, node3]); node1.StartElection(3); node1.ReceiveVote(node2.GrantVote(node1.Term, node1.Id), 3); node1.IsLeader.ShouldBeTrue(); node1.GroupLeader.ShouldBe("n1"); node1.Leaderless.ShouldBeFalse(); node1.HadPreviousLeader.ShouldBeTrue(); node1.LeaderSince.ShouldNotBeNull(); node2.ReceiveHeartbeat(node1.Term, fromPeerId: "n1"); node2.IsLeader.ShouldBeFalse(); node2.GroupLeader.ShouldBe("n1"); node2.Leaderless.ShouldBeFalse(); node2.HadPreviousLeader.ShouldBeTrue(); node2.LeaderSince.ShouldBeNull(); } [Fact] public void Stepdown_clears_group_leader_and_leader_since() { using var leader = ElectSingleNodeLeader(); leader.GroupLeader.ShouldBe("n1"); leader.LeaderSince.ShouldNotBeNull(); leader.RequestStepDown(); leader.Leaderless.ShouldBeTrue(); leader.GroupLeader.ShouldBe(RaftNode.NoLeader); leader.LeaderSince.ShouldBeNull(); } [Fact] public void Observer_mode_can_be_toggled() { using var node = new RaftNode("n1"); node.IsObserver.ShouldBeFalse(); node.SetObserver(true); node.IsObserver.ShouldBeTrue(); node.SetObserver(false); node.IsObserver.ShouldBeFalse(); } [Fact] public void Cluster_size_adjustments_enforce_boot_and_leader_rules() { using var node = new RaftNode("n1"); node.ClusterSize().ShouldBe(1); node.AdjustBootClusterSize(1).ShouldBeTrue(); node.ClusterSize().ShouldBe(2); // floor is 2 node.ConfigureCluster([node]); node.StartElection(1); node.IsLeader.ShouldBeTrue(); node.AdjustClusterSize(5).ShouldBeTrue(); node.ClusterSize().ShouldBe(5); node.AdjustBootClusterSize(7).ShouldBeFalse(); } [Fact] public async Task Progress_size_and_applied_accessors_report_expected_values() { using var leader = ElectSingleNodeLeader(); await leader.ProposeAsync("abc", CancellationToken.None); await leader.ProposeAsync("de", CancellationToken.None); var progress = leader.Progress(); progress.Index.ShouldBe(2); progress.Commit.ShouldBe(2); progress.Applied.ShouldBe(2); var size = leader.Size(); size.Entries.ShouldBe(2); size.Bytes.ShouldBe(5); var applied = leader.Applied(1); applied.Entries.ShouldBe(1); applied.Bytes.ShouldBe(3); leader.ProcessedIndex.ShouldBe(1); } [Fact] public void Campaign_timeout_randomization_and_defaults_match_go_constants() { using var node = new RaftNode("n1"); for (var i = 0; i < 20; i++) { var timeout = node.RandomizedCampaignTimeout(); timeout.ShouldBeGreaterThanOrEqualTo(RaftNode.MinCampaignTimeoutDefault); timeout.ShouldBeLessThan(RaftNode.MaxCampaignTimeoutDefault); } RaftNode.HbIntervalDefault.ShouldBe(TimeSpan.FromSeconds(1)); RaftNode.LostQuorumIntervalDefault.ShouldBe(TimeSpan.FromSeconds(10)); RaftNode.ObserverModeIntervalDefault.ShouldBe(TimeSpan.FromHours(48)); RaftNode.PeerRemoveTimeoutDefault.ShouldBe(TimeSpan.FromMinutes(5)); RaftNode.NoLeader.ShouldBe(string.Empty); RaftNode.NoVote.ShouldBe(string.Empty); } [Fact] public void Stop_wait_for_stop_and_delete_set_lifecycle_state() { var path = Path.Combine(Path.GetTempPath(), $"raft-node-delete-{Guid.NewGuid():N}"); Directory.CreateDirectory(path); File.WriteAllText(Path.Combine(path, "marker.txt"), "x"); using var node = new RaftNode("n1", persistDirectory: path); node.IsDeleted.ShouldBeFalse(); node.Stop(); node.WaitForStop(); node.IsDeleted.ShouldBeFalse(); Directory.Exists(path).ShouldBeTrue(); node.Delete(); node.IsDeleted.ShouldBeTrue(); Directory.Exists(path).ShouldBeFalse(); } }