// Copyright 2012-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 using Shouldly; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed class RaftNodeTests { [Fact] public void NRGAppendEntryEncode_ShouldSucceed() { var raft = new Raft(); var ae = raft.NewAppendEntry("N1", 2, 1, 1, 0, [raft.NewEntry(EntryType.EntryNormal, [1])]); var enc = ae.Encode(); enc.Length.ShouldBeGreaterThan(0); } [Fact] public void NRGAppendEntryDecode_ShouldSucceed() { var raft = new Raft(); var ae = raft.NewAppendEntry("N1", 2, 1, 1, 0, [raft.NewEntry(EntryType.EntryNormal, [1])]); var dec = raft.DecodeAppendEntry(ae.Encode()); dec.Leader.ShouldBe("N1"); dec.TermV.ShouldBe(2UL); } [Fact] public void NRGInlineStepdown_ShouldSucceed() { var raft = new Raft { StateValue = (int)RaftState.Leader }; raft.StepdownLocked("N2"); raft.State().ShouldBe(RaftState.Follower); raft.LeaderId.ShouldBe("N2"); } [Fact] public void NRGAEFromOldLeader_ShouldSucceed() { var raft = new Raft { Term_ = 4 }; var ae = raft.NewAppendEntry("L1", 3, 1, 2, 0, []); raft.ProcessAppendEntries(ae); raft.Term_.ShouldBe(4UL); } [Fact] public void NRGLeaderTransfer_ShouldSucceed() { var raft = new Raft { StateValue = (int)RaftState.Follower }; raft.XferCampaign().ShouldBeNull(); raft.State().ShouldBe(RaftState.Candidate); } [Fact] public void NRGHeartbeatOnLeaderChange_ShouldSucceed() { var raft = new Raft { StateValue = (int)RaftState.Follower }; raft.RunAsLeader(); raft.Leader().ShouldBeTrue(); raft.LeadChangeC().ShouldNotBeNull(); } [Fact] public void NRGElectionTimerAfterObserver_ShouldSucceed() { var raft = new Raft { StateValue = (int)RaftState.Follower }; raft.SetObserverInternal(true); raft.ResetElectionTimeout(); raft.Elect.ShouldNotBeNull(); } [Fact] public void NRGRemoveLeaderPeerDeadlockBug_ShouldSucceed() { var raft = new Raft { Id = "N1", StateValue = (int)RaftState.Leader }; raft.ProposeRemovePeer("N2"); raft.MembershipChangeInProgress().ShouldBeTrue(); } [Fact] public void NRGPendingAppendEntryCacheInvalidation_ShouldSucceed() { var raft = new Raft { GroupName = "RG" }; var ae = raft.NewAppendEntry("N1", 1, 1, 0, 0, [raft.NewEntry(EntryType.EntryNormal, [1])]); raft.ProcessAppendEntries(ae); raft.LoadFirstEntry().ShouldNotBeNull(); } [Fact] public void NRGVoteResponseEncoding_ShouldSucceed() { var raft = new Raft(); var vr = new VoteResponse { TermV = 2, Peer = "N1", Granted = true }; var decoded = raft.DecodeVoteResponse(vr.Encode()); decoded.Peer.ShouldBe("N1"); decoded.Granted.ShouldBeTrue(); } [Fact] public void NRGProposeRemovePeer_ShouldSucceed() { var raft = new Raft { PIndex = 5 }; raft.ProposeRemovePeer("N2"); raft.Removed.ContainsKey("N2").ShouldBeTrue(); } [Fact] public void NRGProposeRemovePeerConcurrent_ShouldSucceed() { var raft = new Raft { PIndex = 10 }; Parallel.For(0, 4, i => raft.ProposeRemovePeer($"N{i}")); raft.Removed.Count.ShouldBeGreaterThan(0); } [Fact] public void NRGProposeRemovePeerQuorum_ShouldSucceed() { var raft = new Raft { Qn = 2, Csz = 3 }; raft.ProposeRemovePeer("N2"); raft.ClusterSize().ShouldBe(1); } [Fact] public void NRGProposeRemovePeerLeader_ShouldSucceed() { var raft = new Raft { Id = "N1", StateValue = (int)RaftState.Leader }; raft.ProposeRemovePeer("N2"); raft.State().ShouldBe(RaftState.Leader); } [Fact] public void NRGProposeRemovePeerAll_ShouldSucceed() { var raft = new Raft(); raft.ProposeRemovePeer("N2"); raft.ProposeRemovePeer("N3"); raft.Removed.Count.ShouldBe(2); } [Fact] public void NRGLeaderResurrectsRemovedPeers_ShouldSucceed() { var raft = new Raft(); raft.ProposeRemovePeer("N2"); raft.ProposeAddPeer("N2"); raft.Peers_.ContainsKey("N2").ShouldBeTrue(); } [Fact] public void NRGAddPeers_ShouldSucceed() { var raft = new Raft(); raft.AddPeer("N2"); raft.AddPeer("N3"); raft.Peers_.Count.ShouldBe(2); } [Fact] public void NRGDisjointMajorities_ShouldSucceed() { var raft = new Raft { Qn = 3, Peers_ = new Dictionary { ["N2"] = new() { Ts = DateTime.UtcNow }, ["N3"] = new() { Ts = DateTime.UtcNow }, }, }; raft.LostQuorum().ShouldBeFalse(); } [Fact] public void NRGSingleNodeElection_ShouldSucceed() { var raft = new Raft { Csz = 1, Qn = 1, StateValue = (int)RaftState.Follower }; raft.CampaignInternal(TimeSpan.FromMilliseconds(10)).ShouldBeNull(); raft.State().ShouldBe(RaftState.Candidate); } }