using NATS.Server.Raft; namespace NATS.Server.Tests; public class RaftElectionTests { [Fact] public async Task Candidate_becomes_leader_after_majority_votes() { var cluster = RaftTestCluster.Create(3); var leader = await cluster.ElectLeaderAsync(); leader.Role.ShouldBe(RaftRole.Leader); leader.Term.ShouldBe(1); } } internal sealed class RaftTestCluster { public List Nodes { get; } private RaftTestCluster(List nodes) { Nodes = nodes; } public static RaftTestCluster Create(int nodes) { var created = Enumerable.Range(1, nodes).Select(i => new RaftNode($"n{i}")).ToList(); foreach (var node in created) node.ConfigureCluster(created); return new RaftTestCluster(created); } public Task ElectLeaderAsync() { var candidate = Nodes[0]; candidate.StartElection(Nodes.Count); foreach (var voter in Nodes.Skip(1)) candidate.ReceiveVote(voter.GrantVote(candidate.Term)); return Task.FromResult(candidate); } public async Task WaitForAppliedAsync(long index) { using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2)); while (!timeout.IsCancellationRequested) { if (Nodes.All(n => n.AppliedIndex >= index)) return; await Task.Delay(20, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default); } } }