using NATS.Server.Raft; namespace NATS.Server.Raft.Tests.Raft; public class RaftStrictConsensusRuntimeTests { [Fact] public async Task Quorum_and_nextindex_rules_gate_commit_visibility_and_snapshot_catchup_convergence() { var voter = new RaftNode("v1"); voter.GrantVote(2, "cand-a").Granted.ShouldBeTrue(); voter.GrantVote(2, "cand-b").Granted.ShouldBeFalse(); var transport = new RejectingRaftTransport(); var leader = new RaftNode("n1", transport); var followerA = new RaftNode("n2", transport); var followerB = new RaftNode("n3", transport); var cluster = new[] { leader, followerA, followerB }; foreach (var node in cluster) node.ConfigureCluster(cluster); leader.StartElection(cluster.Length); leader.ReceiveVote(new VoteResponse { Granted = true }, cluster.Length); leader.IsLeader.ShouldBeTrue(); _ = await leader.ProposeAsync("cmd-1", default); leader.AppliedIndex.ShouldBe(0); followerA.AppliedIndex.ShouldBe(0); followerB.AppliedIndex.ShouldBe(0); } private sealed class RejectingRaftTransport : IRaftTransport { public Task> AppendEntriesAsync(string leaderId, IReadOnlyList followerIds, RaftLogEntry entry, CancellationToken ct) => Task.FromResult>( followerIds.Select(id => new AppendResult { FollowerId = id, Success = false }).ToArray()); public Task RequestVoteAsync(string candidateId, string voterId, VoteRequest request, CancellationToken ct) => Task.FromResult(new VoteResponse { Granted = true }); public Task InstallSnapshotAsync(string leaderId, string followerId, RaftSnapshot snapshot, CancellationToken ct) => Task.CompletedTask; public Task SendTimeoutNowAsync(string leaderId, string targetId, ulong term, CancellationToken ct) => Task.CompletedTask; public Task SendHeartbeatAsync(string leaderId, IReadOnlyList followerIds, int term, Action onAck, CancellationToken ct) => Task.CompletedTask; } }