1+ /* *
2+ * @file raft_group_tests.cpp
3+ * @brief Unit tests for RaftGroup consensus implementation
4+ */
5+
6+ #include < gtest/gtest.h>
7+
8+ #include < atomic>
9+ #include < cstdio>
10+ #include < memory>
11+ #include < vector>
12+
13+ #include " common/cluster_manager.hpp"
14+ #include " common/config.hpp"
15+ #include " distributed/raft_group.hpp"
16+ #include " distributed/raft_manager.hpp"
17+ #include " network/rpc_server.hpp"
18+
19+ using namespace cloudsql ;
20+ using namespace cloudsql ::raft;
21+ using namespace cloudsql ::cluster;
22+ using namespace cloudsql ::network;
23+
24+ namespace {
25+
26+ class RaftGroupTests : public ::testing::Test {
27+ protected:
28+ void SetUp () override {
29+ config_.mode = config::RunMode::Coordinator;
30+ constexpr uint16_t TEST_PORT = 6200 ;
31+ config_.cluster_port = TEST_PORT;
32+ cm_ = std::make_unique<ClusterManager>(&config_);
33+ rpc_ = std::make_unique<RpcServer>(TEST_PORT);
34+ ASSERT_TRUE (rpc_->start ()) << " RpcServer failed to start - port may be in use" ;
35+ manager_ = std::make_unique<RaftManager>(" node1" , *cm_, *rpc_);
36+ group_ = manager_->get_or_create_group (1 );
37+ }
38+
39+ void TearDown () override {
40+ if (group_) {
41+ group_->stop ();
42+ }
43+ if (manager_) {
44+ manager_->stop ();
45+ }
46+ if (rpc_) {
47+ rpc_->stop ();
48+ }
49+ // Cleanup state files
50+ std::remove (" raft_group_1.state" );
51+ }
52+
53+ config::Config config_;
54+ std::unique_ptr<ClusterManager> cm_;
55+ std::unique_ptr<RpcServer> rpc_;
56+ std::unique_ptr<RaftManager> manager_;
57+ std::shared_ptr<RaftGroup> group_;
58+ };
59+
60+ // ============= Constructor and Basic Tests =============
61+
62+ TEST_F (RaftGroupTests, ConstructorInitialState) {
63+ EXPECT_NE (group_, nullptr );
64+ EXPECT_FALSE (group_->is_leader ());
65+ EXPECT_EQ (group_->group_id (), 1 );
66+ }
67+
68+ TEST_F (RaftGroupTests, StartStopLifecycle) {
69+ group_->start ();
70+ group_->stop ();
71+ group_->start ();
72+ group_->stop ();
73+ SUCCEED (); // No crash
74+ }
75+
76+ // ============= State Machine Tests =============
77+
78+ TEST_F (RaftGroupTests, SetStateMachine) {
79+ // Should not crash when setting state machine
80+ group_->set_state_machine (nullptr );
81+ SUCCEED ();
82+ }
83+
84+ // ============= Log Replication Tests =============
85+
86+ TEST_F (RaftGroupTests, ReplicateNotLeader) {
87+ // Before becoming leader, replicate should fail
88+ std::vector<uint8_t > data = {1 , 2 , 3 };
89+ EXPECT_FALSE (group_->replicate (data));
90+ }
91+
92+ TEST_F (RaftGroupTests, ReplicateDataSize) {
93+ // Test with various data sizes
94+ std::vector<uint8_t > small_data = {1 };
95+ std::vector<uint8_t > large_data (1024 , 42 );
96+
97+ // Both should fail when not leader
98+ EXPECT_FALSE (group_->replicate (small_data));
99+ EXPECT_FALSE (group_->replicate (large_data));
100+ }
101+
102+ // ============= Timeout Tests =============
103+
104+ // Note: get_random_timeout() is private and tested indirectly through
105+ // election timing behavior in integration tests
106+
107+ // ============= RPC Handler Serialization Tests =============
108+
109+ TEST_F (RaftGroupTests, RequestVoteArgsSerialization) {
110+ RequestVoteArgs args;
111+ args.term = 5 ;
112+ args.candidate_id = " node2" ;
113+ args.last_log_index = 10 ;
114+ args.last_log_term = 3 ;
115+
116+ auto serialized = args.serialize ();
117+
118+ // Should have: 8 (term) + 8 (id_len) + id + 8 (last_log_index) + 8 (last_log_term)
119+ EXPECT_EQ (serialized.size (), 8 + 8 + 5 + 8 + 8 );
120+ }
121+
122+ TEST_F (RaftGroupTests, AppendEntriesArgsStructure) {
123+ AppendEntriesArgs args;
124+ args.term = 1 ;
125+ args.leader_id = " leader1" ;
126+ args.prev_log_index = 5 ;
127+ args.prev_log_term = 1 ;
128+ args.leader_commit = 3 ;
129+
130+ // Empty entries vector should be valid
131+ EXPECT_TRUE (true ); // Structure is valid
132+ }
133+
134+ // ============= Log Entry Tests =============
135+
136+ TEST_F (RaftGroupTests, LogEntryDefaultValues) {
137+ LogEntry entry;
138+ EXPECT_EQ (entry.term , 0 );
139+ EXPECT_EQ (entry.index , 0 );
140+ EXPECT_TRUE (entry.data .empty ());
141+ }
142+
143+ TEST_F (RaftGroupTests, LogEntryWithData) {
144+ LogEntry entry;
145+ entry.term = 1 ;
146+ entry.index = 5 ;
147+ entry.data = {1 , 2 , 3 , 4 , 5 };
148+
149+ EXPECT_EQ (entry.term , 1 );
150+ EXPECT_EQ (entry.index , 5 );
151+ EXPECT_EQ (entry.data .size (), 5 );
152+ }
153+
154+ // ============= Persistent State Tests =============
155+
156+ TEST_F (RaftGroupTests, PersistentStateDefaultValues) {
157+ RaftPersistentState state;
158+ EXPECT_EQ (state.current_term , 0 );
159+ EXPECT_TRUE (state.voted_for .empty ());
160+ EXPECT_TRUE (state.log .empty ());
161+ }
162+
163+ TEST_F (RaftGroupTests, VolatileStateDefaultValues) {
164+ RaftVolatileState state;
165+ EXPECT_EQ (state.commit_index , 0 );
166+ EXPECT_EQ (state.last_applied , 0 );
167+ }
168+
169+ // ============= Leader State Tests =============
170+
171+ TEST_F (RaftGroupTests, LeaderStateEmpty) {
172+ LeaderState state;
173+ EXPECT_TRUE (state.next_index .empty ());
174+ EXPECT_TRUE (state.match_index .empty ());
175+ }
176+
177+ TEST_F (RaftGroupTests, LeaderStateWithPeers) {
178+ LeaderState state;
179+ state.next_index [" node2" ] = 1 ;
180+ state.next_index [" node3" ] = 1 ;
181+ state.match_index [" node2" ] = 0 ;
182+ state.match_index [" node3" ] = 0 ;
183+
184+ EXPECT_EQ (state.next_index .size (), 2 );
185+ EXPECT_EQ (state.match_index .size (), 2 );
186+ EXPECT_EQ (state.next_index [" node2" ], 1 );
187+ EXPECT_EQ (state.match_index [" node3" ], 0 );
188+ }
189+
190+ // ============= Vote Reply Tests =============
191+
192+ TEST_F (RaftGroupTests, RequestVoteReplyStructure) {
193+ RequestVoteReply reply;
194+ reply.term = 5 ;
195+ reply.vote_granted = true ;
196+
197+ EXPECT_EQ (reply.term , 5 );
198+ EXPECT_TRUE (reply.vote_granted );
199+ }
200+
201+ // ============= AppendEntries Reply Tests =============
202+
203+ TEST_F (RaftGroupTests, AppendEntriesReplyStructure) {
204+ AppendEntriesReply reply;
205+ reply.term = 3 ;
206+ reply.success = true ;
207+
208+ EXPECT_EQ (reply.term , 3 );
209+ EXPECT_TRUE (reply.success );
210+ }
211+
212+ // ============= NodeState Tests =============
213+
214+ TEST_F (RaftGroupTests, NodeStateEnum) {
215+ EXPECT_EQ (static_cast <uint8_t >(NodeState::Follower), 0 );
216+ EXPECT_EQ (static_cast <uint8_t >(NodeState::Candidate), 1 );
217+ EXPECT_EQ (static_cast <uint8_t >(NodeState::Leader), 2 );
218+ EXPECT_EQ (static_cast <uint8_t >(NodeState::Shutdown), 3 );
219+ }
220+
221+ // ============= Group ID Tests =============
222+
223+ TEST_F (RaftGroupTests, DifferentGroupIds) {
224+ auto group1 = manager_->get_or_create_group (1 );
225+ auto group2 = manager_->get_or_create_group (2 );
226+
227+ EXPECT_NE (group1, group2);
228+ EXPECT_EQ (group1->group_id (), 1 );
229+ EXPECT_EQ (group2->group_id (), 2 );
230+ }
231+
232+ } // namespace
0 commit comments