Skip to content

Commit 7f8476d

Browse files
auricomclaude
andcommitted
fix(raft): add LeaderID doc comment; consolidate raft-status tests
- Add Go doc comment to Node.LeaderID() describing return value, nil-safety, and staleness semantics, consistent with IsLeader/HasQuorum style. - Consolidate three near-duplicate TestRegisterCustomHTTPEndpoints_RaftNodeStatus* tests into a single table-driven test covering: leaderID==nodeID (is_leader true), leaderID!=nodeID (is_leader false), empty leaderID fallback, and non-GET method (405). Clarifies that is_leader is derived from LeaderID(), not the IsLeader() field on testRaftNodeSource. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4677b5b commit 7f8476d

2 files changed

Lines changed: 73 additions & 72 deletions

File tree

pkg/raft/node.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ func (n *Node) NodeID() string {
257257
return n.config.NodeID
258258
}
259259

260+
// LeaderID returns the server ID of the current cluster leader.
261+
// Returns an empty string if the receiver is nil, raft is uninitialized, or no
262+
// leader has been elected yet. The value may be momentarily stale between raft
263+
// leadership changes; callers that need a strong guarantee should cross-check
264+
// with HasQuorum.
260265
func (n *Node) LeaderID() string {
261266
if n == nil || n.raft == nil {
262267
return ""

pkg/rpc/server/http_test.go

Lines changed: 68 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ import (
88
"testing"
99
"time"
1010

11+
"github.com/evstack/ev-node/pkg/config"
12+
"github.com/evstack/ev-node/test/mocks"
13+
"github.com/evstack/ev-node/types"
1114
"github.com/rs/zerolog"
1215
"github.com/stretchr/testify/assert"
1316
"github.com/stretchr/testify/mock"
1417
"github.com/stretchr/testify/require"
15-
16-
"github.com/evstack/ev-node/pkg/config"
17-
"github.com/evstack/ev-node/test/mocks"
18-
"github.com/evstack/ev-node/types"
1918
)
2019

2120
func TestRegisterCustomHTTPEndpoints(t *testing.T) {
@@ -65,85 +64,82 @@ func (t testRaftNodeSource) NodeID() string {
6564
}
6665

6766
func TestRegisterCustomHTTPEndpoints_RaftNodeStatus(t *testing.T) {
68-
mux := http.NewServeMux()
69-
logger := zerolog.Nop()
70-
71-
raftNode := testRaftNodeSource{
72-
isLeader: false,
73-
leaderID: "node-a",
74-
nodeID: "node-a",
75-
}
76-
77-
RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil, logger, raftNode)
78-
79-
ts := httptest.NewServer(mux)
80-
t.Cleanup(ts.Close)
81-
82-
req, err := http.NewRequest(http.MethodGet, ts.URL+"/raft/node", nil)
83-
require.NoError(t, err)
84-
resp, err := http.DefaultClient.Do(req) //nolint:gosec // test-only request to httptest server
85-
require.NoError(t, err)
86-
t.Cleanup(func() { _ = resp.Body.Close() })
87-
88-
require.Equal(t, http.StatusOK, resp.StatusCode)
89-
90-
var body struct {
67+
type bodyShape struct {
9168
IsLeader bool `json:"is_leader"`
9269
NodeID string `json:"node_id"`
9370
}
94-
require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))
95-
assert.True(t, body.IsLeader)
96-
assert.Equal(t, "node-a", body.NodeID)
97-
}
9871

99-
func TestRegisterCustomHTTPEndpoints_RaftNodeStatusFallsBackWithoutLeaderID(t *testing.T) {
100-
mux := http.NewServeMux()
101-
logger := zerolog.Nop()
102-
103-
raftNode := testRaftNodeSource{
104-
isLeader: false,
105-
leaderID: "",
106-
nodeID: "node-a",
72+
cases := []struct {
73+
name string
74+
node testRaftNodeSource
75+
method string
76+
wantStatus int
77+
wantIsLeader bool
78+
wantNodeID string
79+
skipBodyDecode bool
80+
}{
81+
{
82+
// leaderID == nodeID: handler derives is_leader=true from LeaderID(),
83+
// regardless of the IsLeader() field on testRaftNodeSource.
84+
name: "leader matches — is_leader true",
85+
node: testRaftNodeSource{leaderID: "node-a", nodeID: "node-a"},
86+
method: http.MethodGet,
87+
wantStatus: http.StatusOK,
88+
wantIsLeader: true,
89+
wantNodeID: "node-a",
90+
},
91+
{
92+
// leaderID != nodeID: handler derives is_leader=false.
93+
name: "leader differs — is_leader false",
94+
node: testRaftNodeSource{leaderID: "node-b", nodeID: "node-a"},
95+
method: http.MethodGet,
96+
wantStatus: http.StatusOK,
97+
wantIsLeader: false,
98+
wantNodeID: "node-a",
99+
},
100+
{
101+
// empty leaderID: fallback — is_leader=false (no elected leader known).
102+
name: "empty leaderID fallback — is_leader false",
103+
node: testRaftNodeSource{leaderID: "", nodeID: "node-a"},
104+
method: http.MethodGet,
105+
wantStatus: http.StatusOK,
106+
wantIsLeader: false,
107+
wantNodeID: "node-a",
108+
},
109+
{
110+
name: "non-GET method — 405",
111+
node: testRaftNodeSource{},
112+
method: http.MethodPost,
113+
wantStatus: http.StatusMethodNotAllowed,
114+
skipBodyDecode: true,
115+
},
107116
}
108117

109-
RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil, logger, raftNode)
118+
for _, tc := range cases {
119+
t.Run(tc.name, func(t *testing.T) {
120+
mux := http.NewServeMux()
121+
RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil, zerolog.Nop(), tc.node)
110122

111-
ts := httptest.NewServer(mux)
112-
t.Cleanup(ts.Close)
123+
ts := httptest.NewServer(mux)
124+
t.Cleanup(ts.Close)
113125

114-
req, err := http.NewRequest(http.MethodGet, ts.URL+"/raft/node", nil)
115-
require.NoError(t, err)
116-
resp, err := http.DefaultClient.Do(req) //nolint:gosec // test-only request to httptest server
117-
require.NoError(t, err)
118-
t.Cleanup(func() { _ = resp.Body.Close() })
126+
req, err := http.NewRequest(tc.method, ts.URL+"/raft/node", nil)
127+
require.NoError(t, err)
128+
resp, err := http.DefaultClient.Do(req) //nolint:gosec // test-only request to httptest server
129+
require.NoError(t, err)
130+
t.Cleanup(func() { _ = resp.Body.Close() })
119131

120-
require.Equal(t, http.StatusOK, resp.StatusCode)
132+
require.Equal(t, tc.wantStatus, resp.StatusCode)
133+
if tc.skipBodyDecode {
134+
return
135+
}
121136

122-
var body struct {
123-
IsLeader bool `json:"is_leader"`
124-
NodeID string `json:"node_id"`
137+
var body bodyShape
138+
require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))
139+
assert.Equal(t, tc.wantIsLeader, body.IsLeader)
140+
assert.Equal(t, tc.wantNodeID, body.NodeID)
141+
})
125142
}
126-
require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))
127-
assert.False(t, body.IsLeader)
128-
assert.Equal(t, "node-a", body.NodeID)
129-
}
130-
131-
func TestRegisterCustomHTTPEndpoints_RaftNodeStatusMethodNotAllowed(t *testing.T) {
132-
mux := http.NewServeMux()
133-
logger := zerolog.Nop()
134-
135-
RegisterCustomHTTPEndpoints(mux, nil, nil, config.DefaultConfig(), nil, logger, testRaftNodeSource{})
136-
137-
ts := httptest.NewServer(mux)
138-
t.Cleanup(ts.Close)
139-
140-
req, err := http.NewRequest(http.MethodPost, ts.URL+"/raft/node", nil)
141-
require.NoError(t, err)
142-
resp, err := http.DefaultClient.Do(req) //nolint:gosec // test-only request to httptest server
143-
require.NoError(t, err)
144-
t.Cleanup(func() { _ = resp.Body.Close() })
145-
146-
assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
147143
}
148144

149145
func TestHealthReady_aggregatorBlockDelay(t *testing.T) {

0 commit comments

Comments
 (0)