From ac9934480fa18a9c6b12a7c7c244b46fd3ef5221 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 20:23:42 -0500 Subject: [PATCH] test(batch30): add direct raft node impl backlog tests --- .../ImplBacklog/RaftNodeTests.Impltests.cs | 181 ++++++++++++++++++ porting.db | Bin 6692864 -> 6696960 bytes 2 files changed, 181 insertions(+) create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RaftNodeTests.Impltests.cs diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RaftNodeTests.Impltests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RaftNodeTests.Impltests.cs new file mode 100644 index 0000000..b38726b --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RaftNodeTests.Impltests.cs @@ -0,0 +1,181 @@ +// 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); + } +} diff --git a/porting.db b/porting.db index b7dfa50b5871cf4b20f1790eff7c234c3be6d09b..e558601d533ea0c210b20a7c4f84863c074f2339 100644 GIT binary patch delta 5944 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 A^#A|> delta 2391 zcmZ9Oe^3NeMPejT?+yuR1Cm97OeVG!g?7?* z6olTHGD!gs9;!y6tuvYYk{L)5bc9RBDZ(QHVnM8k4G|GLVytl@E><$`zWQ?_fvZ(Yw@M$% zNpcd))JRx@^KVK?isihUUrfK-aNw23R~qWY+wkW%C9T-Qxr}suLt_JuaBo&0IM7f} z_v1|Re3SFraxEv#Wmv&aq~u>5Hq3^Byf3`WWyGWQgl08-?_7;m8@0XP`PTk@4b2VC zocSxsVg65?H{Rt?LM@6lDPH@XSvx$t{SR>a{qf@W6O4ZlGUpjTN~nE^zM{DAB1UuD zwkOaxmB<%;aM|Eh3n?m@{AMsovSVZSMq%ZG$9KFUF0fM&DYfMO#c6D7hf z6Xh!lpF+y3bVXj@{QVuh2~i{=I%Y&r=}=0O67p{t5qsOAR1@A% zR+lnlD!A!ZvIW!iU$+uSbd{TVT~cbbgl?M?qD|(i2}yY@Hz8k~5N$|^)*BJ?$VzPT zuru3I$`u@JZrNkMfNHs+Y}ZjOF;x^*imCRYa+vB>JHL<`idKjdT)`rkAdbdy~djs1xZ+=^J(=S9Pd`^py`C_gWMkV zDKp>Fqi#30^TE9yHP1|~KBhLqrL?pxP^we~Mvkdlj67e{n*z#7H3c?B)E#hOX?hl% zji}E-^mD75iM{G60zG%L%w#6~txsKrE}8ZwLEA1h15R8H;y9qLi#s#n2B@n{d*6T{ zgoObWNktw*(vWmy5t4x{MwTE;nJ}O|%*)|(If-;~v`*}>U9;Y|YNS&ZEfvC#`Cqw< zoMVn@`xoRsx!=|t*#NIs-~b(?+NV}uzVKVF4)ihYMK@lHwm@Wk5=XdVH`I)2wKHe) z_c3ke%nI*lR|R_}*9i}gsOhkEM3Z6Vh_(`ztxh%%M}~*DwCZ@MK<9|I);KG6ZA8-u z(5Kytq3b18g=M4K8uM^{Tr-YIV)nmiUYd$Oo>z={cDsG<-IOfe@eDsPQ+|bs**^3J#miwtTO!)cFJQ9j&p;%@p zw(PxFmSsTZtEkY(SJ^)kKelyQPqCC%k3b>!i02)k-P)su&UVknzU!IN!js&(WRB3+ zaGux9dD`5Zk5#pMN-1@G#E$>SBM=x5dzxmRsr9I5v++VDNIT;B;Qtz$h48hb9^`Q( z8+ig*j;uhuNDi_RS%u^xc}PC88Yw`0NFlNYS&KZ06d_L`>yY)x2Ba9-h?F3kkj+Rb zvIQwao<_DJ&mh~7a^zX$Ib=Js1KG*09rYJHBiqY3n{68v{kCng-Tfb?4Esw&p{*(6 zhn-h~8BjRk-!*fEem&tYg|63AWlAi-w|Ty%Wi70H)PJ6b4Y&R6P?0oqXBNPhxBcbD zd*_=XL95?H=Q&JvhtRNU<0q|Q4;hb_YW zi&k=YI(iC$;d9gqt%LP0sJX89NRC?D1xLV9>L_$%i_?&=(B<&%XL_yC!Nv4X^g@d< zxECr%bq~~dt0n3Tva7fB0s?PO>Jr?2r(R-Rf7R0nut~j$1lYb&Jz@z>>7l8*(A1vL x)ZWlk{d-dlf~HuVWt_ZL+UNe6Ys4iv%Z$G-`A(s2>u~|D3ysOFdQsyU;eYD>Gd2JK