feat(batch30): implement raft group-c run loop behaviors

This commit is contained in:
Joseph Doherty
2026-02-28 20:17:45 -05:00
parent ed1b62d6a3
commit 8bc4dfa58c
6 changed files with 731 additions and 6 deletions

View File

@@ -108,4 +108,98 @@ public sealed class RaftNodeCoreTests
raft.Peers_.Count.ShouldBe(2);
raft.RandCampaignTimeout().ShouldBeGreaterThan(TimeSpan.Zero);
}
[Fact]
public void RunLoopHelpers_WhenInvoked_ShouldManageSubscriptionAndTimers()
{
var raft = new Raft
{
Id = "N1",
GroupName = "RG",
Active = DateTime.UtcNow,
};
var inbox = raft.NewInbox();
var catchupInbox = raft.NewCatchupInbox();
inbox.ShouldContain("_INBOX.");
catchupInbox.ShouldContain("_INBOX.CATCHUP.");
var sub = raft.Subscribe("raft.append");
raft.AeSub.ShouldNotBeNull();
raft.Unsubscribe(sub);
raft.AeSub.ShouldBeNull();
raft.CreateInternalSubs().ShouldBeNull();
raft.ResetElectionTimeout();
raft.Elect.ShouldNotBeNull();
raft.ResetElect(TimeSpan.FromMilliseconds(10));
raft.ElectTimer().ShouldBeGreaterThan(DateTime.MinValue);
}
[Fact]
public void CodecHelpers_WhenRoundTrippingEntriesAndVotes_ShouldPreserveFields()
{
var raft = new Raft
{
GroupName = "RG",
};
var entry = raft.NewEntry(EntryType.EntryNormal, [1, 2, 3]);
var proposed = raft.NewProposedEntry(entry, "reply");
proposed.Reply.ShouldBe("reply");
var appendEntry = raft.NewAppendEntry("L1", 2, 1, 1, 0, [entry]);
appendEntry.String().ShouldContain("leader=L1");
appendEntry.ShouldStore().ShouldBeTrue();
var decodedAppend = raft.DecodeAppendEntry(appendEntry.Encode());
decodedAppend.Leader.ShouldBe("L1");
var appendResponse = raft.NewAppendEntryResponse(2, 1, "N2", "_R_", success: true);
var decodedResponse = raft.DecodeAppendEntryResponse(appendResponse.Encode());
decodedResponse.Success.ShouldBeTrue();
decodedResponse.Peer.ShouldBe("N2");
var voteRequest = new VoteRequest { TermV = 4, Candidate = "N3", LastIndex = 9, LastTerm = 3, Reply = "_R_" };
var decodedVoteRequest = raft.DecodeVoteRequest(voteRequest.Encode());
decodedVoteRequest.Candidate.ShouldBe("N3");
var voteResponse = new VoteResponse { TermV = 4, Peer = "N2", Granted = true, Empty = false };
var decodedVoteResponse = raft.DecodeVoteResponse(voteResponse.Encode());
decodedVoteResponse.Granted.ShouldBeTrue();
decodedVoteResponse.Peer.ShouldBe("N2");
}
[Fact]
public void PeerStatePersistence_WhenWrittenAndRead_ShouldRoundTrip()
{
var raft = new Raft();
var storeDir = Path.Combine(Path.GetTempPath(), $"raft-peer-state-{Guid.NewGuid():N}");
try
{
var state = new PeerState
{
KnownPeers = ["A1", "A2"],
ClusterSize = 2,
DomainExt = 7,
};
raft.PeerStateBufSize(state).ShouldBeGreaterThan(0);
raft.WritePeerStateStatic(storeDir, state).ShouldBeNull();
var (readState, readError) = raft.ReadPeerState(storeDir);
readError.ShouldBeNull();
readState.ShouldNotBeNull();
readState!.KnownPeers.Count.ShouldBe(2);
raft.WriteTermVoteStatic(storeDir, 6, "A1").ShouldBeNull();
raft.Term_.ShouldBe(6UL);
raft.Vote.ShouldBe("A1");
}
finally
{
if (Directory.Exists(storeDir))
{
Directory.Delete(storeDir, recursive: true);
}
}
}
}